Repository: ImperialCollegeLondon/sharpy Branch: main Commit: 2c9d92b1187e Files: 895 Total size: 13.9 MB Directory structure: gitextract__e84dmdr/ ├── .codecov.yml ├── .coveragerc ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ └── bug_report.md │ └── workflows/ │ ├── docker_build.yaml │ ├── docker_build_test.yaml │ ├── pypi_build.yaml │ ├── readme.md │ ├── sharpy_no_test_needed.yaml │ └── sharpy_tests.yaml ├── .github_changelog_generator ├── .gitignore ├── .gitmodules ├── .readthedocs.yaml ├── .version.json ├── .zenodo.json ├── CHANGELOG.md ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── README.md ├── docs/ │ ├── .nojekyll │ ├── JOSS/ │ │ ├── codemeta.json │ │ ├── generate.rb │ │ ├── paper.bib │ │ └── paper.md │ ├── Makefile │ ├── docignore.yml │ ├── requirements_rtd │ └── source/ │ ├── _static/ │ │ ├── .placeholder │ │ └── sharpy_guide/ │ │ └── sharpy_intro.html │ ├── conf.py │ ├── content/ │ │ ├── capabilities.md │ │ ├── casefiles.rst │ │ ├── contributing.md │ │ ├── debug.rst │ │ ├── example_notebooks/ │ │ │ ├── UDP_control/ │ │ │ │ ├── control_design_script.m │ │ │ │ ├── get_settings_udp.py │ │ │ │ ├── matlab_functions/ │ │ │ │ │ ├── PID_linear_model.slx │ │ │ │ │ ├── adjust_state_space_system.m │ │ │ │ │ ├── get_1minuscosine_gust_input.m │ │ │ │ │ ├── read_SHARPy_state_space_system.m │ │ │ │ │ └── set_input_parameters.m │ │ │ │ ├── parameter_UDP_control_pazy_udp_closed_loop_gust_response.json │ │ │ │ ├── pazy_PID_controller_UDP.py │ │ │ │ ├── pazy_network_info.yml │ │ │ │ ├── pid_controller.py │ │ │ │ └── tutorial_udp_control.ipynb │ │ │ ├── cantilever/ │ │ │ │ ├── model_static_cantilever.py │ │ │ │ └── static_cantilever.ipynb │ │ │ ├── cantilever_wing.ipynb │ │ │ ├── linear_goland_flutter.ipynb │ │ │ ├── linear_horten.ipynb │ │ │ ├── nonlinear_t-tail_HALE.ipynb │ │ │ ├── source/ │ │ │ │ └── type04_db_nrel5mw_oc3_v06.xlsx │ │ │ └── wind_turbine.ipynb │ │ ├── examples.rst │ │ ├── faqs.md │ │ ├── installation.md │ │ ├── postproc.rst │ │ ├── publications.md │ │ ├── solvers.rst │ │ └── test_cases.rst │ ├── includes/ │ │ ├── aero/ │ │ │ ├── index.rst │ │ │ ├── models/ │ │ │ │ ├── aerogrid/ │ │ │ │ │ ├── AeroTimeStepInfo.rst │ │ │ │ │ ├── Aerogrid.rst │ │ │ │ │ ├── generate_strip.rst │ │ │ │ │ └── index.rst │ │ │ │ └── index.rst │ │ │ └── utils/ │ │ │ ├── airfoilpolars/ │ │ │ │ ├── Polar.rst │ │ │ │ └── index.rst │ │ │ ├── index.rst │ │ │ └── mapping/ │ │ │ ├── aero2struct_force_mapping.rst │ │ │ ├── index.rst │ │ │ └── total_forces_moments.rst │ │ ├── cases/ │ │ │ ├── coupled/ │ │ │ │ ├── X-HALE/ │ │ │ │ │ ├── generate_xhale/ │ │ │ │ │ │ ├── generate_naca_camber.rst │ │ │ │ │ │ ├── generate_solver_file.rst │ │ │ │ │ │ ├── index.rst │ │ │ │ │ │ └── read_beam_data.rst │ │ │ │ │ └── index.rst │ │ │ │ └── index.rst │ │ │ ├── hangar/ │ │ │ │ ├── horten_wing/ │ │ │ │ │ ├── HortenWing.rst │ │ │ │ │ └── index.rst │ │ │ │ ├── index.rst │ │ │ │ ├── richards_wing/ │ │ │ │ │ ├── HortenWing.rst │ │ │ │ │ └── index.rst │ │ │ │ └── swept_flying_wing/ │ │ │ │ ├── SweptWing.rst │ │ │ │ └── index.rst │ │ │ ├── index.rst │ │ │ └── templates/ │ │ │ ├── Ttail/ │ │ │ │ ├── Ttail_3beams.rst │ │ │ │ ├── Ttail_canonical.rst │ │ │ │ └── index.rst │ │ │ ├── flying_wings/ │ │ │ │ ├── FlyingWing.rst │ │ │ │ ├── Goland.rst │ │ │ │ ├── Pazy.rst │ │ │ │ ├── QuasiInfinite.rst │ │ │ │ ├── Smith.rst │ │ │ │ └── index.rst │ │ │ ├── index.rst │ │ │ └── template_wt/ │ │ │ ├── create_blade_coordinates.rst │ │ │ ├── create_node_radial_pos_from_elem_centres.rst │ │ │ ├── generate_from_excel_type03.rst │ │ │ ├── index.rst │ │ │ ├── rotor_from_excel_type03.rst │ │ │ └── spar_from_excel_type04.rst │ │ ├── controllers/ │ │ │ ├── controlsurfacepidcontroller/ │ │ │ │ ├── ControlSurfacePidController.rst │ │ │ │ └── index.rst │ │ │ ├── index.rst │ │ │ └── takeofftrajectorycontroller/ │ │ │ ├── TakeOffTrajectoryController.rst │ │ │ └── index.rst │ │ ├── generators/ │ │ │ ├── bumpvelocityfield/ │ │ │ │ ├── BumpVelocityField.rst │ │ │ │ └── index.rst │ │ │ ├── dynamiccontrolsurface/ │ │ │ │ ├── DynamicControlSurface.rst │ │ │ │ └── index.rst │ │ │ ├── floatingforces/ │ │ │ │ ├── FloatingForces.rst │ │ │ │ ├── change_of_to_sharpy.rst │ │ │ │ ├── compute_equiv_hd_added_mass.rst │ │ │ │ ├── compute_jacobian.rst │ │ │ │ ├── compute_xf_zf.rst │ │ │ │ ├── index.rst │ │ │ │ ├── jonswap_spectrum.rst │ │ │ │ ├── matrix_from_rf.rst │ │ │ │ ├── noise_freq_1s.rst │ │ │ │ ├── quasisteady_mooring.rst │ │ │ │ ├── rename_terms.rst │ │ │ │ ├── response_freq_dep_matrix.rst │ │ │ │ ├── rfval.rst │ │ │ │ ├── time_wave_forces.rst │ │ │ │ └── wave_radiation_damping.rst │ │ │ ├── gridbox/ │ │ │ │ ├── GridBox.rst │ │ │ │ └── index.rst │ │ │ ├── gustvelocityfield/ │ │ │ │ ├── DARPA.rst │ │ │ │ ├── GustVelocityField.rst │ │ │ │ ├── continuous_sin.rst │ │ │ │ ├── index.rst │ │ │ │ ├── lateral_one_minus_cos.rst │ │ │ │ ├── one_minus_cos.rst │ │ │ │ ├── span_sine.rst │ │ │ │ ├── time_varying.rst │ │ │ │ └── time_varying_global.rst │ │ │ ├── helicoidalwake/ │ │ │ │ ├── HelicoidalWake.rst │ │ │ │ └── index.rst │ │ │ ├── index.rst │ │ │ ├── modifystructure/ │ │ │ │ ├── ChangeLumpedMass.rst │ │ │ │ ├── ChangedVariable.rst │ │ │ │ ├── LumpedMassControl.rst │ │ │ │ ├── ModifyStructure.rst │ │ │ │ └── index.rst │ │ │ ├── polaraeroforces/ │ │ │ │ ├── EfficiencyCorrection.rst │ │ │ │ ├── PolarCorrection.rst │ │ │ │ ├── get_aoacl0_from_camber.rst │ │ │ │ ├── index.rst │ │ │ │ ├── local_stability_axes.rst │ │ │ │ ├── magnitude_and_direction_of_relative_velocity.rst │ │ │ │ └── span_chord.rst │ │ │ ├── shearvelocityfield/ │ │ │ │ ├── ShearVelocityField.rst │ │ │ │ └── index.rst │ │ │ ├── steadyvelocityfield/ │ │ │ │ ├── SteadyVelocityField.rst │ │ │ │ └── index.rst │ │ │ ├── straightwake/ │ │ │ │ ├── StraightWake.rst │ │ │ │ └── index.rst │ │ │ ├── trajectorygenerator/ │ │ │ │ ├── TrajectoryGenerator.rst │ │ │ │ └── index.rst │ │ │ ├── turbvelocityfield/ │ │ │ │ ├── TurbVelocityField.rst │ │ │ │ └── index.rst │ │ │ └── turbvelocityfieldbts/ │ │ │ ├── TurbVelocityFieldBts.rst │ │ │ └── index.rst │ │ ├── index.rst │ │ ├── io/ │ │ │ ├── index.rst │ │ │ ├── inout_variables/ │ │ │ │ ├── SetOfVariables.rst │ │ │ │ └── index.rst │ │ │ └── network_interface/ │ │ │ ├── InNetwork.rst │ │ │ ├── Network.rst │ │ │ ├── NetworkLoader.rst │ │ │ ├── OutNetwork.rst │ │ │ └── index.rst │ │ ├── linear/ │ │ │ ├── assembler/ │ │ │ │ ├── index.rst │ │ │ │ ├── lincontrolsurfacedeflector/ │ │ │ │ │ ├── LinControlSurfaceDeflector.rst │ │ │ │ │ ├── der_R_arbitrary_axis_times_v.rst │ │ │ │ │ └── index.rst │ │ │ │ ├── linearaeroelastic/ │ │ │ │ │ ├── LinearAeroelastic.rst │ │ │ │ │ └── index.rst │ │ │ │ ├── linearbeam/ │ │ │ │ │ ├── LinearBeam.rst │ │ │ │ │ └── index.rst │ │ │ │ ├── lineargustassembler/ │ │ │ │ │ ├── LeadingEdge.rst │ │ │ │ │ ├── MultiLeadingEdge.rst │ │ │ │ │ ├── campbell.rst │ │ │ │ │ ├── gust_from_string.rst │ │ │ │ │ ├── index.rst │ │ │ │ │ └── spanwise_interpolation.rst │ │ │ │ └── linearuvlm/ │ │ │ │ ├── LinearUVLM.rst │ │ │ │ └── index.rst │ │ │ ├── index.rst │ │ │ └── src/ │ │ │ ├── assembly/ │ │ │ │ ├── AICs.rst │ │ │ │ ├── dfqsdgamma_vrel0.rst │ │ │ │ ├── dfqsduinput.rst │ │ │ │ ├── dfqsdvind_gamma.rst │ │ │ │ ├── dfqsdvind_zeta.rst │ │ │ │ ├── dfqsdzeta_omega.rst │ │ │ │ ├── dfqsdzeta_vrel0.rst │ │ │ │ ├── dfunstdgamma_dot.rst │ │ │ │ ├── dvinddzeta.rst │ │ │ │ ├── dvinddzeta_cpp.rst │ │ │ │ ├── eval_panel_cpp.rst │ │ │ │ ├── index.rst │ │ │ │ ├── nc_domegazetadzeta.rst │ │ │ │ ├── nc_dqcdzeta.rst │ │ │ │ ├── nc_dqcdzeta_Sin_to_Sout.rst │ │ │ │ ├── test_wake_prop_term.rst │ │ │ │ ├── uc_dncdzeta.rst │ │ │ │ ├── wake_prop.rst │ │ │ │ └── wake_prop_from_dimensions.rst │ │ │ ├── gridmapping/ │ │ │ │ ├── AeroGridMap.rst │ │ │ │ └── index.rst │ │ │ ├── index.rst │ │ │ ├── interp/ │ │ │ │ ├── get_Wnv_vector.rst │ │ │ │ ├── get_Wvc_scalar.rst │ │ │ │ ├── get_panel_wcv.rst │ │ │ │ └── index.rst │ │ │ ├── lib_dbiot/ │ │ │ │ ├── Dvcross_by_skew3d.rst │ │ │ │ ├── eval_panel_comp.rst │ │ │ │ ├── eval_panel_cpp.rst │ │ │ │ ├── eval_panel_exp.rst │ │ │ │ ├── eval_panel_fast.rst │ │ │ │ ├── eval_panel_fast_coll.rst │ │ │ │ ├── eval_seg_comp_loop.rst │ │ │ │ ├── eval_seg_exp.rst │ │ │ │ ├── eval_seg_exp_loop.rst │ │ │ │ └── index.rst │ │ │ ├── lib_ucdncdzeta/ │ │ │ │ ├── eval.rst │ │ │ │ └── index.rst │ │ │ ├── libfit/ │ │ │ │ ├── fitfrd.rst │ │ │ │ ├── get_rfa_res.rst │ │ │ │ ├── get_rfa_res_norm.rst │ │ │ │ ├── index.rst │ │ │ │ ├── poly_fit.rst │ │ │ │ ├── rfa.rst │ │ │ │ ├── rfa_fit_dev.rst │ │ │ │ ├── rfa_mimo.rst │ │ │ │ └── rfader.rst │ │ │ ├── libsparse/ │ │ │ │ ├── block_dot.rst │ │ │ │ ├── block_matrix_dot_vector.rst │ │ │ │ ├── block_sum.rst │ │ │ │ ├── csc_matrix.rst │ │ │ │ ├── dense.rst │ │ │ │ ├── dot.rst │ │ │ │ ├── eye_as.rst │ │ │ │ ├── index.rst │ │ │ │ ├── solve.rst │ │ │ │ └── zeros_as.rst │ │ │ ├── libss/ │ │ │ │ ├── Hnorm_from_freq_resp.rst │ │ │ │ ├── SSconv.rst │ │ │ │ ├── SSderivative.rst │ │ │ │ ├── SSintegr.rst │ │ │ │ ├── StateSpace.rst │ │ │ │ ├── addGain.rst │ │ │ │ ├── adjust_phase.rst │ │ │ │ ├── build_SS_poly.rst │ │ │ │ ├── butter.rst │ │ │ │ ├── compare_ss.rst │ │ │ │ ├── couple.rst │ │ │ │ ├── disc2cont.rst │ │ │ │ ├── eigvals.rst │ │ │ │ ├── freqresp.rst │ │ │ │ ├── get_freq_from_eigs.rst │ │ │ │ ├── index.rst │ │ │ │ ├── join.rst │ │ │ │ ├── join2.rst │ │ │ │ ├── parallel.rst │ │ │ │ ├── project.rst │ │ │ │ ├── random_ss.rst │ │ │ │ ├── retain_inout_channels.rst │ │ │ │ ├── scale_SS.rst │ │ │ │ ├── series.rst │ │ │ │ ├── simulate.rst │ │ │ │ ├── ss_block.rst │ │ │ │ ├── ss_to_scipy.rst │ │ │ │ └── sum_ss.rst │ │ │ ├── lin_aeroelastic/ │ │ │ │ ├── LinAeroEla.rst │ │ │ │ └── index.rst │ │ │ ├── lin_utils/ │ │ │ │ ├── Info.rst │ │ │ │ ├── comp_tot_force.rst │ │ │ │ ├── extract_from_data.rst │ │ │ │ ├── index.rst │ │ │ │ └── solve_linear.rst │ │ │ ├── lingebm/ │ │ │ │ ├── FlexDynamic.rst │ │ │ │ ├── index.rst │ │ │ │ ├── newmark_ss.rst │ │ │ │ └── sort_eigvals.rst │ │ │ ├── linuvlm/ │ │ │ │ ├── Dynamic.rst │ │ │ │ ├── DynamicBlock.rst │ │ │ │ ├── Frequency.rst │ │ │ │ ├── Static.rst │ │ │ │ ├── get_Cw_cpx.rst │ │ │ │ └── index.rst │ │ │ ├── multisurfaces/ │ │ │ │ ├── MultiAeroGridSurfaces.rst │ │ │ │ └── index.rst │ │ │ └── surface/ │ │ │ ├── AeroGridGeo.rst │ │ │ ├── AeroGridSurface.rst │ │ │ ├── get_aic3_cpp.rst │ │ │ └── index.rst │ │ ├── postprocs/ │ │ │ ├── AeroForcesCalculator.rst │ │ │ ├── AerogridPlot.rst │ │ │ ├── AsymptoticStability.rst │ │ │ ├── BeamLoads.rst │ │ │ ├── BeamPlot.rst │ │ │ ├── Cleanup.rst │ │ │ ├── FrequencyResponse.rst │ │ │ ├── LiftDistribution.rst │ │ │ ├── PickleData.rst │ │ │ ├── PlotFlowField.rst │ │ │ ├── SaveData.rst │ │ │ ├── SaveParametricCase.rst │ │ │ ├── StabilityDerivatives.rst │ │ │ ├── StallCheck.rst │ │ │ ├── UDPout.rst │ │ │ └── WriteVariablesTime.rst │ │ ├── rom/ │ │ │ ├── balanced/ │ │ │ │ ├── Balanced.rst │ │ │ │ ├── Direct.rst │ │ │ │ ├── FrequencyLimited.rst │ │ │ │ ├── Iterative.rst │ │ │ │ └── index.rst │ │ │ ├── index.rst │ │ │ ├── krylov/ │ │ │ │ ├── Krylov.rst │ │ │ │ └── index.rst │ │ │ └── utils/ │ │ │ ├── index.rst │ │ │ ├── krylovutils/ │ │ │ │ ├── check_eye.rst │ │ │ │ ├── construct_krylov.rst │ │ │ │ ├── evec.rst │ │ │ │ ├── index.rst │ │ │ │ ├── lu_factor.rst │ │ │ │ ├── lu_solve.rst │ │ │ │ ├── mgs_ortho.rst │ │ │ │ ├── remove_a12.rst │ │ │ │ └── schur_ordered.rst │ │ │ ├── librom/ │ │ │ │ ├── balfreq.rst │ │ │ │ ├── balreal_direct_py.rst │ │ │ │ ├── balreal_iter.rst │ │ │ │ ├── balreal_iter_old.rst │ │ │ │ ├── check_stability.rst │ │ │ │ ├── eigen_dec.rst │ │ │ │ ├── get_gauss_weights.rst │ │ │ │ ├── get_trapz_weights.rst │ │ │ │ ├── index.rst │ │ │ │ ├── low_rank_smith.rst │ │ │ │ ├── modred.rst │ │ │ │ ├── res_discrete_lyap.rst │ │ │ │ ├── smith_iter.rst │ │ │ │ └── tune_rom.rst │ │ │ └── librom_interp/ │ │ │ ├── FLB_transfer_function.rst │ │ │ ├── InterpROM.rst │ │ │ ├── index.rst │ │ │ └── transfer_function.rst │ │ ├── solvers/ │ │ │ ├── aero/ │ │ │ │ ├── DynamicUVLM.rst │ │ │ │ ├── NoAero.rst │ │ │ │ ├── PrescribedUvlm.rst │ │ │ │ ├── StaticUvlm.rst │ │ │ │ ├── StepLinearUVLM.rst │ │ │ │ └── StepUvlm.rst │ │ │ ├── aero_solvers.rst │ │ │ ├── coupled/ │ │ │ │ ├── DynamicCoupled.rst │ │ │ │ ├── LinDynamicSim.rst │ │ │ │ ├── StaticCoupled.rst │ │ │ │ └── StaticCoupledRBM.rst │ │ │ ├── coupled_solvers.rst │ │ │ ├── flight dynamics/ │ │ │ │ ├── StaticTrim.rst │ │ │ │ └── Trim.rst │ │ │ ├── flight dynamics_solvers.rst │ │ │ ├── linear/ │ │ │ │ ├── LinearAssembler.rst │ │ │ │ └── Modal.rst │ │ │ ├── linear_solvers.rst │ │ │ ├── loader/ │ │ │ │ ├── AerogridLoader.rst │ │ │ │ ├── BeamLoader.rst │ │ │ │ └── PreSharpy.rst │ │ │ ├── loader_solvers.rst │ │ │ ├── structural/ │ │ │ │ ├── NonLinearDynamic.rst │ │ │ │ ├── NonLinearDynamicCoupledStep.rst │ │ │ │ ├── NonLinearDynamicMultibody.rst │ │ │ │ ├── NonLinearDynamicPrescribedStep.rst │ │ │ │ ├── NonLinearStatic.rst │ │ │ │ ├── RigidDynamicCoupledStep.rst │ │ │ │ └── RigidDynamicPrescribedStep.rst │ │ │ ├── structural_solvers.rst │ │ │ ├── time_integrator/ │ │ │ │ ├── GeneralisedAlpha.rst │ │ │ │ └── NewmarkBeta.rst │ │ │ └── time_integrator_solvers.rst │ │ ├── structure/ │ │ │ ├── index.rst │ │ │ ├── models/ │ │ │ │ ├── beam/ │ │ │ │ │ ├── StructTimeStepInfo.rst │ │ │ │ │ └── index.rst │ │ │ │ ├── beamstructures/ │ │ │ │ │ ├── Element.rst │ │ │ │ │ └── index.rst │ │ │ │ └── index.rst │ │ │ └── utils/ │ │ │ ├── index.rst │ │ │ ├── lagrangeconstraints/ │ │ │ │ ├── BaseLagrangeConstraint.rst │ │ │ │ ├── constant_rot_vel_FoR.rst │ │ │ │ ├── constant_vel_FoR.rst │ │ │ │ ├── def_rot_axis_FoR_wrt_node_general.rst │ │ │ │ ├── def_rot_axis_FoR_wrt_node_xyz.rst │ │ │ │ ├── def_rot_vect_FoR_wrt_node.rst │ │ │ │ ├── def_rot_vel_FoR_wrt_node.rst │ │ │ │ ├── define_FoR_dof.rst │ │ │ │ ├── define_node_dof.rst │ │ │ │ ├── define_num_LM_eq.rst │ │ │ │ ├── equal_lin_vel_node_FoR.rst │ │ │ │ ├── equal_pos_node_FoR.rst │ │ │ │ ├── free.rst │ │ │ │ ├── fully_constrained_node_FoR.rst │ │ │ │ ├── generate_lagrange_matrix.rst │ │ │ │ ├── hinge_FoR.rst │ │ │ │ ├── hinge_FoR_wrtG.rst │ │ │ │ ├── hinge_node_FoR.rst │ │ │ │ ├── hinge_node_FoR_constant_vel.rst │ │ │ │ ├── index.rst │ │ │ │ ├── initialise_lc.rst │ │ │ │ ├── lagrangeconstraint.rst │ │ │ │ ├── lc_from_string.rst │ │ │ │ ├── lin_vel_node_wrtA.rst │ │ │ │ ├── lin_vel_node_wrtG.rst │ │ │ │ ├── postprocess.rst │ │ │ │ ├── print_available_lc.rst │ │ │ │ ├── remove_constraint.rst │ │ │ │ ├── spherical_FoR.rst │ │ │ │ └── spherical_node_FoR.rst │ │ │ ├── modalutils/ │ │ │ │ ├── assert_modes_mass_normalised.rst │ │ │ │ ├── assert_orthogonal_eigenvectors.rst │ │ │ │ ├── free_modes_principal_axes.rst │ │ │ │ ├── get_mode_zeta.rst │ │ │ │ ├── index.rst │ │ │ │ ├── mode_sign_convention.rst │ │ │ │ ├── modes_to_cg_ref.rst │ │ │ │ ├── principal_axes_inertia.rst │ │ │ │ ├── scale_mass_normalised_modes.rst │ │ │ │ ├── scale_mode.rst │ │ │ │ ├── write_modes_vtk.rst │ │ │ │ └── write_zeta_vtk.rst │ │ │ └── xbeamlib/ │ │ │ ├── Xbopts.rst │ │ │ ├── cbeam3_asbly_dynamic.rst │ │ │ ├── cbeam3_asbly_static.rst │ │ │ ├── cbeam3_correct_gravity_forces.rst │ │ │ ├── cbeam3_loads.rst │ │ │ ├── cbeam3_solv_modal.rst │ │ │ ├── cbeam3_solv_nlnstatic.rst │ │ │ ├── index.rst │ │ │ └── xbeam3_asbly_dynamic.rst │ │ └── utils/ │ │ ├── algebra/ │ │ │ ├── cross3.rst │ │ │ ├── crv2quat.rst │ │ │ ├── crv2rotation.rst │ │ │ ├── crv2tan.rst │ │ │ ├── crv_bounds.rst │ │ │ ├── der_CcrvT_by_v.rst │ │ │ ├── der_Ccrv_by_v.rst │ │ │ ├── der_Ceuler_by_v.rst │ │ │ ├── der_Ceuler_by_v_NED.rst │ │ │ ├── der_CquatT_by_v.rst │ │ │ ├── der_Cquat_by_v.rst │ │ │ ├── der_Peuler_by_v.rst │ │ │ ├── der_TanT_by_xv.rst │ │ │ ├── der_Tan_by_xv.rst │ │ │ ├── der_Teuler_by_w.rst │ │ │ ├── der_Teuler_by_w_NED.rst │ │ │ ├── der_quat_wrt_crv.rst │ │ │ ├── der_skewp_skewp_v.rst │ │ │ ├── deuler_dt.rst │ │ │ ├── deuler_dt_NED.rst │ │ │ ├── euler2quat.rst │ │ │ ├── euler2rot.rst │ │ │ ├── get_transformation_matrix.rst │ │ │ ├── get_triad.rst │ │ │ ├── index.rst │ │ │ ├── mat2quat.rst │ │ │ ├── multiply_matrices.rst │ │ │ ├── norm3d.rst │ │ │ ├── normsq3d.rst │ │ │ ├── panel_area.rst │ │ │ ├── quadskew.rst │ │ │ ├── quat2euler.rst │ │ │ ├── quat2rotation.rst │ │ │ ├── quat_bound.rst │ │ │ ├── rotation2crv.rst │ │ │ ├── rotation2quat.rst │ │ │ ├── rotation3d_x.rst │ │ │ ├── rotation3d_y.rst │ │ │ ├── rotation3d_z.rst │ │ │ ├── skew.rst │ │ │ ├── tangent_vector.rst │ │ │ ├── triad2rotation.rst │ │ │ └── unit_vector.rst │ │ ├── analytical/ │ │ │ ├── flat_plate_analytical.rst │ │ │ ├── garrick_drag_pitch.rst │ │ │ ├── garrick_drag_plunge.rst │ │ │ ├── index.rst │ │ │ ├── nc_derivs.rst │ │ │ ├── qs_derivs.rst │ │ │ ├── sears_CL_freq_resp.rst │ │ │ ├── sears_fun.rst │ │ │ ├── sears_lift_sin_gust.rst │ │ │ ├── theo_CL_freq_resp.rst │ │ │ ├── theo_CM_freq_resp.rst │ │ │ ├── theo_fun.rst │ │ │ ├── theo_lift.rst │ │ │ └── wagner_imp_start.rst │ │ ├── control_utils/ │ │ │ ├── PID.rst │ │ │ └── index.rst │ │ ├── datastructures/ │ │ │ ├── AeroTimeStepInfo.rst │ │ │ ├── Linear.rst │ │ │ ├── LinearTimeStepInfo.rst │ │ │ ├── StructTimeStepInfo.rst │ │ │ └── index.rst │ │ ├── docutils/ │ │ │ ├── check_folder_in_ignore.rst │ │ │ ├── generate_documentation.rst │ │ │ ├── index.rst │ │ │ ├── output_documentation_module_page.rst │ │ │ ├── write_file.rst │ │ │ └── write_folder.rst │ │ ├── exceptions/ │ │ │ ├── DocumentationError.rst │ │ │ ├── NotConvergedSolver.rst │ │ │ ├── NotRecognisedSetting.rst │ │ │ ├── NotValidSetting.rst │ │ │ └── index.rst │ │ ├── frequencyutils/ │ │ │ ├── find_limits.rst │ │ │ ├── find_target_system.rst │ │ │ ├── freqresp_relative_error.rst │ │ │ ├── frobenius_norm.rst │ │ │ ├── h_infinity_norm.rst │ │ │ ├── hamiltonian.rst │ │ │ ├── index.rst │ │ │ ├── l2norm.rst │ │ │ └── max_eigs.rst │ │ ├── generate_cases/ │ │ │ ├── AerodynamicInformation.rst │ │ │ ├── AeroelasticInformation.rst │ │ │ ├── SimulationInformation.rst │ │ │ ├── StructuralInformation.rst │ │ │ ├── clean_test_files.rst │ │ │ ├── from_node_array_to_elem_matrix.rst │ │ │ ├── from_node_list_to_elem_matrix.rst │ │ │ ├── get_airfoil_camber.rst │ │ │ ├── get_aoacl0_from_camber.rst │ │ │ ├── get_factor_geometric_progression.rst │ │ │ ├── get_mu0_from_camber.rst │ │ │ ├── index.rst │ │ │ └── read_column_sheet_type01.rst │ │ ├── generator_interface/ │ │ │ ├── index.rst │ │ │ └── output_documentation.rst │ │ ├── geo_utils/ │ │ │ ├── generate_naca_camber.rst │ │ │ ├── index.rst │ │ │ └── interpolate_naca_camber.rst │ │ ├── h5utils/ │ │ │ ├── add_array_to_grp.rst │ │ │ ├── add_as_grp.rst │ │ │ ├── check_file_exists.rst │ │ │ ├── index.rst │ │ │ ├── read_group.rst │ │ │ ├── readh5.rst │ │ │ ├── save_list_as_array.rst │ │ │ └── saveh5.rst │ │ ├── index.rst │ │ ├── model_utils/ │ │ │ ├── index.rst │ │ │ └── mass_matrix_generator.rst │ │ ├── multibody/ │ │ │ ├── disp_and_accel2state.rst │ │ │ ├── get_elems_nodes_list.rst │ │ │ ├── index.rst │ │ │ ├── merge_multibody.rst │ │ │ ├── split_multibody.rst │ │ │ ├── state2disp_and_accel.rst │ │ │ └── update_mb_dB_before_merge.rst │ │ ├── plotutils/ │ │ │ ├── index.rst │ │ │ ├── plot_timestep.rst │ │ │ └── set_axes_equal.rst │ │ └── settings/ │ │ ├── SettingsTable.rst │ │ ├── check_settings_in_options.rst │ │ ├── index.rst │ │ └── load_config_file.rst │ └── index.rst ├── environment.yml ├── environment_arm64.yml ├── lib/ │ ├── .placeholder │ └── CMakeLists.txt ├── pyproject.toml ├── scripts/ │ ├── __init__.py │ ├── optimiser/ │ │ ├── __init__.py │ │ ├── base_case/ │ │ │ └── generate.py │ │ ├── optimiser.py │ │ └── optimiser_input.yaml │ └── xplaneUDPout/ │ ├── HALE_varDIe.acf │ ├── __init__.py │ ├── variables.yaml │ └── xplaneUDP.py ├── setup.py ├── sharpy/ │ ├── __init__.py │ ├── aero/ │ │ ├── __init__.py │ │ ├── models/ │ │ │ ├── __init__.py │ │ │ ├── aerogrid.py │ │ │ ├── grid.py │ │ │ └── nonliftingbodygrid.py │ │ └── utils/ │ │ ├── __init__.py │ │ ├── airfoilpolars.py │ │ ├── mapping.py │ │ ├── utils.py │ │ └── uvlmlib.py │ ├── cases/ │ │ ├── __init__.py │ │ ├── coupled/ │ │ │ ├── WindTurbine/ │ │ │ │ ├── __init__.py │ │ │ │ └── generate_rotor.py │ │ │ ├── X-HALE/ │ │ │ │ ├── generate_xhale.py │ │ │ │ └── inputs/ │ │ │ │ ├── EMX-07_camber.txt │ │ │ │ ├── aero_properties.xlsx │ │ │ │ ├── beam_properties.xlsx │ │ │ │ └── lumped_mass.xlsx │ │ │ ├── __init__.py │ │ │ ├── hinged_controlled_wing/ │ │ │ │ ├── __init__.py │ │ │ │ ├── generate_hinged_controlled_wing.py │ │ │ │ └── generate_hinged_roll_controlled_wing.py │ │ │ ├── linear_horten/ │ │ │ │ └── __init__.py │ │ │ ├── multibody_takeoff/ │ │ │ │ ├── __init__.py │ │ │ │ └── generate_hale.py │ │ │ └── simple_HALE/ │ │ │ ├── __init__.py │ │ │ └── generate_hale.py │ │ ├── hangar/ │ │ │ ├── __init__.py │ │ │ ├── horten_wing.py │ │ │ ├── richards_wing.py │ │ │ └── swept_flying_wing.py │ │ └── templates/ │ │ ├── Ttail.py │ │ ├── __init__.py │ │ ├── flying_wings.py │ │ ├── fuselage_wing_configuration/ │ │ │ ├── __init__.py │ │ │ ├── fuselage_wing_configuration.py │ │ │ ├── fwc_aero.py │ │ │ ├── fwc_fuselage.py │ │ │ ├── fwc_get_settings.py │ │ │ └── fwc_structure.py │ │ └── template_wt.py │ ├── controllers/ │ │ ├── __init__.py │ │ ├── bladepitchpid.py │ │ ├── controlsurfacepidcontroller.py │ │ ├── multibodycontroller.py │ │ └── takeofftrajectorycontroller.py │ ├── generators/ │ │ ├── __init__.py │ │ ├── bumpvelocityfield.py │ │ ├── dynamiccontrolsurface.py │ │ ├── floatingforces.py │ │ ├── gridbox.py │ │ ├── gustvelocityfield.py │ │ ├── helicoidalwake.py │ │ ├── modifystructure.py │ │ ├── polaraeroforces.py │ │ ├── shearvelocityfield.py │ │ ├── steadyvelocityfield.py │ │ ├── straightwake.py │ │ ├── trajectorygenerator.py │ │ ├── turbvelocityfield.py │ │ └── turbvelocityfieldbts.py │ ├── io/ │ │ ├── __init__.py │ │ ├── inout_variables.py │ │ ├── logger_utils.py │ │ ├── message_interface.py │ │ └── network_interface.py │ ├── linear/ │ │ ├── __init__.py │ │ ├── assembler/ │ │ │ ├── __init__.py │ │ │ ├── lincontrolsurfacedeflector.py │ │ │ ├── linearaeroelastic.py │ │ │ ├── linearbeam.py │ │ │ ├── linearcustom.py │ │ │ ├── lineargustassembler.py │ │ │ └── linearuvlm.py │ │ ├── dev/ │ │ │ ├── __init__.py │ │ │ ├── linfunc.py │ │ │ ├── linsym_biot_segment.py │ │ │ ├── linsym_uc_dncdzeta.py │ │ │ └── sym_dev_dbiot.py │ │ ├── src/ │ │ │ ├── __init__.py │ │ │ ├── assembly.py │ │ │ ├── gridmapping.py │ │ │ ├── interp.py │ │ │ ├── lib_dbiot.py │ │ │ ├── lib_ucdncdzeta.py │ │ │ ├── libfit.py │ │ │ ├── libsparse.py │ │ │ ├── libss.py │ │ │ ├── lin_aeroelastic.py │ │ │ ├── lin_utils.py │ │ │ ├── lingebm.py │ │ │ ├── linuvlm.py │ │ │ ├── multisurfaces.py │ │ │ ├── surface.py │ │ │ └── uvlmutils.py │ │ └── utils/ │ │ ├── __init__.py │ │ ├── derivatives.py │ │ ├── ss_interface.py │ │ └── sselements.py │ ├── postproc/ │ │ ├── __init__.py │ │ ├── aeroforcescalculator.py │ │ ├── aerogridplot.py │ │ ├── asymptoticstability.py │ │ ├── beamloads.py │ │ ├── beamplot.py │ │ ├── cleanup.py │ │ ├── frequencyresponse.py │ │ ├── liftdistribution.py │ │ ├── pickledata.py │ │ ├── plotflowfield.py │ │ ├── savedata.py │ │ ├── saveparametriccase.py │ │ ├── stabilityderivatives.py │ │ ├── stallcheck.py │ │ ├── udpout.py │ │ └── writevariablestime.py │ ├── presharpy/ │ │ ├── __init__.py │ │ └── presharpy.py │ ├── rom/ │ │ ├── __init__.py │ │ ├── balanced.py │ │ ├── krylov.py │ │ └── utils/ │ │ ├── __init__.py │ │ ├── krylovutils.py │ │ ├── librom.py │ │ └── librom_interp.py │ ├── sharpy_main.py │ ├── solvers/ │ │ ├── __init__.py │ │ ├── _basestructural.py │ │ ├── aerogridloader.py │ │ ├── beamloader.py │ │ ├── dynamiccoupled.py │ │ ├── dynamicuvlm.py │ │ ├── gridloader.py │ │ ├── initialaeroelasticloader.py │ │ ├── lindynamicsim.py │ │ ├── linearassembler.py │ │ ├── modal.py │ │ ├── noaero.py │ │ ├── nonliftingbodygridloader.py │ │ ├── nonlineardynamic.py │ │ ├── nonlineardynamiccoupledstep.py │ │ ├── nonlineardynamicmultibody.py │ │ ├── nonlineardynamicmultibodyjax.py │ │ ├── nonlineardynamicprescribedstep.py │ │ ├── nonlinearstatic.py │ │ ├── nostructural.py │ │ ├── prescribeduvlm.py │ │ ├── rigiddynamiccoupledstep.py │ │ ├── rigiddynamicprescribedstep.py │ │ ├── staticcoupled.py │ │ ├── statictrim.py │ │ ├── staticuvlm.py │ │ ├── steplinearuvlm.py │ │ ├── stepuvlm.py │ │ ├── timeintegrators.py │ │ ├── timeintegratorsjax.py │ │ ├── trim.py │ │ └── updatepickle.py │ ├── structure/ │ │ ├── __init__.py │ │ ├── basestructure.py │ │ ├── models/ │ │ │ ├── __init__.py │ │ │ ├── beam.py │ │ │ └── beamstructures.py │ │ └── utils/ │ │ ├── __init__.py │ │ ├── lagrangeconstraints.py │ │ ├── lagrangeconstraintsjax.py │ │ ├── modalutils.py │ │ └── xbeamlib.py │ ├── utils/ │ │ ├── __init__.py │ │ ├── algebra.py │ │ ├── analytical.py │ │ ├── constants.py │ │ ├── control_utils.py │ │ ├── controller_interface.py │ │ ├── cout_utils.py │ │ ├── ctypes_utils.py │ │ ├── datastructures.py │ │ ├── docutils.py │ │ ├── exceptions.py │ │ ├── frequencyutils.py │ │ ├── generate_cases.py │ │ ├── generator_interface.py │ │ ├── geo_utils.py │ │ ├── h5utils.py │ │ ├── input_arg.py │ │ ├── linearutils.py │ │ ├── model_utils.py │ │ ├── multibody.py │ │ ├── multibodyjax.py │ │ ├── num_utils.py │ │ ├── plotutils.py │ │ ├── rom_interface.py │ │ ├── settings.py │ │ ├── sharpydir.py │ │ └── solver_interface.py │ └── version.py ├── tests/ │ ├── __init__.py │ ├── coupled/ │ │ ├── __init__.py │ │ ├── dynamic/ │ │ │ ├── __init__.py │ │ │ ├── hale/ │ │ │ │ └── generate_hale.py │ │ │ └── test_dynamic.py │ │ ├── multibody/ │ │ │ ├── __init__.py │ │ │ ├── double_pendulum/ │ │ │ │ ├── __init__.py │ │ │ │ └── test_double_pendulum_geradin.py │ │ │ ├── double_prescribed_pendulum/ │ │ │ │ └── test_double_prescribed_pendulum.py │ │ │ ├── double_slanted_pendulum/ │ │ │ │ ├── __init__.py │ │ │ │ └── test_double_pendulum_slanted.py │ │ │ ├── fix_node_velocity_wrtA/ │ │ │ │ ├── __init__.py │ │ │ │ └── test_fix_node_velocity_wrtA.py │ │ │ ├── fix_node_velocity_wrtG/ │ │ │ │ ├── __init__.py │ │ │ │ └── test_fix_node_velocity_wrtG.py │ │ │ ├── floating_forces/ │ │ │ │ └── test_floatingforces.py │ │ │ └── floating_wind_turbine/ │ │ │ ├── oc3_cs_v07.floating.h5 │ │ │ └── test_floating_wind_turbine.py │ │ ├── prescribed/ │ │ │ ├── WindTurbine/ │ │ │ │ ├── __init__.py │ │ │ │ └── test_rotor.py │ │ │ ├── __init__.py │ │ │ ├── gamma_dot_test/ │ │ │ │ └── test_gamma_dot.py │ │ │ ├── rotating_wing/ │ │ │ │ └── generate_rotating_wing.py │ │ │ └── test_prescribed.py │ │ └── static/ │ │ ├── __init__.py │ │ ├── pazy/ │ │ │ └── generate_pazy.py │ │ ├── smith_g_2deg/ │ │ │ ├── __init__.py │ │ │ └── generate_smith_g_2deg.py │ │ ├── smith_g_4deg/ │ │ │ ├── __init__.py │ │ │ └── generate_smith_g_4deg.py │ │ ├── smith_nog_2deg/ │ │ │ ├── __init__.py │ │ │ └── generate_smith_nog_2deg.py │ │ ├── smith_nog_4deg/ │ │ │ ├── __init__.py │ │ │ └── generate_smith_nog_4deg.py │ │ ├── test_pazy_static.py │ │ └── test_static.py │ ├── docs/ │ │ ├── __init__.py │ │ └── test_docs.py │ ├── io/ │ │ ├── Example_simple_hale/ │ │ │ ├── Client_HALE.py │ │ │ ├── __init__.py │ │ │ ├── generate_hale_io.py │ │ │ └── variables_hale.yaml │ │ ├── __init__.py │ │ ├── generate_pazy_udpout.py │ │ ├── sample_udp_inout/ │ │ │ ├── __init__.py │ │ │ ├── client.py │ │ │ ├── generate_pazy_test_io_local.py │ │ │ └── variables_coarse.yaml │ │ ├── test_pazy_udpout.py │ │ └── variables.yaml │ ├── linear/ │ │ ├── __init__.py │ │ ├── assembly/ │ │ │ ├── __init__.py │ │ │ └── test_assembly.py │ │ ├── control_surfaces/ │ │ │ ├── __init__.py │ │ │ └── test_control_surfaces.py │ │ ├── derivatives/ │ │ │ ├── __init__.py │ │ │ ├── test_ders.py │ │ │ └── test_stabilityderivatives.py │ │ ├── frequencyutils/ │ │ │ ├── __init__.py │ │ │ ├── src/ │ │ │ │ ├── a.npy │ │ │ │ ├── b.npy │ │ │ │ ├── c.npy │ │ │ │ └── d.npy │ │ │ └── test_frequency_utils.py │ │ ├── goland_wing/ │ │ │ ├── __init__.py │ │ │ └── test_goland_flutter.py │ │ ├── gusts/ │ │ │ ├── __init__.py │ │ │ ├── test_external_gust.py │ │ │ └── test_linear_gusts.py │ │ ├── horten/ │ │ │ ├── __init__.py │ │ │ └── test_horten.py │ │ ├── rom/ │ │ │ ├── __init__.py │ │ │ ├── src/ │ │ │ │ ├── A.mat │ │ │ │ ├── B.mat │ │ │ │ └── C.mat │ │ │ ├── test_balancing.py │ │ │ ├── test_krylov.py │ │ │ ├── test_rom_framework.py │ │ │ ├── test_schur.py │ │ │ └── test_springmasssystem.py │ │ ├── statespace/ │ │ │ ├── __init__.py │ │ │ ├── test_statespace.py │ │ │ └── test_variable_tracker.py │ │ └── uvlm/ │ │ ├── __init__.py │ │ └── test_infinite_span.py │ ├── sourcepanelmethod/ │ │ ├── __init__.py │ │ ├── geometry_parameter_models.json │ │ ├── test_data/ │ │ │ ├── results_low_wing_coupled_0.csv │ │ │ └── results_low_wing_coupled_1.csv │ │ ├── test_source_panel_method.py │ │ └── test_vlm_coupled_spm.py │ ├── utils/ │ │ ├── __init__.py │ │ ├── test_algebra.py │ │ ├── test_generate_cases.py │ │ └── test_settings.py │ ├── uvlm/ │ │ ├── __init__.py │ │ ├── dynamic/ │ │ │ └── test_wake_cfl_n1.py │ │ └── static/ │ │ ├── __init__.py │ │ ├── big_wake_planarwing/ │ │ │ └── generate_big_wake_planarwing.py │ │ ├── planarwing/ │ │ │ ├── __init__.py │ │ │ └── generate_planarwing.py │ │ └── polars/ │ │ ├── __init__.py │ │ ├── generate_wing.py │ │ ├── test_polars.py │ │ └── xf-naca0018-il-50000.txt │ └── xbeam/ │ ├── __init__.py │ ├── geradin/ │ │ ├── __init__.py │ │ └── generate_geradin.py │ ├── modal/ │ │ └── rotating_beam/ │ │ └── generate_bielawa_baromega2_1e3.py │ └── test_xbeam.py └── utils/ ├── docker/ │ └── bashrc └── environment.yml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .codecov.yml ================================================ ignore: - '*/tests/*' ================================================ FILE: .coveragerc ================================================ # this file controls the execution of coverage.py [run] omit= ./tests/* ./docs/* ./dev/* ./scripts/* [report] # Regexes for lines to exclude from consideration exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain about missing debug-only code: def __repr__ if self\.debug # Don't complain if tests don't hit defensive assertion code: raise AssertionError raise NotImplementedError # Don't complain if non-runnable code isn't run: if 0: if __name__ == .__main__.: ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: potential bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behaviour. If model related, please try and reproduce with one of the provided models (found in `/cases/`). Else, please provided the input files (a `.fem.h5`, `aero.h5` and `case.sharpy`). **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **System Info (please complete the following information):** - OS: [e.g. CentOS7/MacOS 13.1] - SHARPy Version [e.g. v1.2] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/workflows/docker_build.yaml ================================================ name: Create and publish Docker image on: push: branches: - develop - main tags: - 'v*' env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: build-and-push-image: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout repository uses: actions/checkout@v3 - name: Log in to the Container registry uses: docker/login-action@v2 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v4 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - name: Build and push Docker image uses: docker/build-push-action@v3.2.0 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} ================================================ FILE: .github/workflows/docker_build_test.yaml ================================================ name: Test Docker image build on: pull_request: branches: - main - develop - 'rc*' push: paths: - 'utils/*' - 'Dockerfile' - '.github/workflows/docker*' env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: test_image_build: runs-on: ubuntu-latest name: Test Docker image build permissions: contents: read packages: write steps: - name: Checkout repository uses: actions/checkout@v3 - name: Log in to the Container registry uses: docker/login-action@v2 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v4 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - name: Build Docker image uses: docker/build-push-action@v3.2.0 with: context: . push: false tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} ================================================ FILE: .github/workflows/pypi_build.yaml ================================================ name: Create and publish pypi image on: # only runs when there is a new published release # and for testing, pull requests to develop and main # and if there are changes to the build process and github action release: types: [published] push: branches: - develop - main paths: - 'setup.py' - '.github/workflows/pypi*' pull_request: branches: - main - develop - 'rc*' jobs: create-pypi-image: name: >- Create .whl 🛞 from SHARPy distribution runs-on: ubuntu-20.04 env: python-version-chosen: "3.10.8" permissions: contents: read packages: write steps: - uses: actions/checkout@v2 - name: Set up Python ${{ env.python-version-chosen }} uses: actions/setup-python@v2 with: python-version: ${{ env.python-version-chosen }} - name: Set up GCC uses: egor-tensin/setup-gcc@v1 with: version: 7 platform: x64 - name: Pre-Install dependencies run: | export QT_QPA_PLATFORM='offscreen' sudo apt install libeigen3-dev git submodule init git submodule update - name: Install pypa/build run: >- python3 -m pip install build --user - name: Install wheel run: python3 -m pip install wheel --user - name: Build a source tarball run: python setup.py sdist - name: Build a binary wheel run: python3 setup.py bdist_wheel - name: Find the wheel created during pip install run: python3 -m pip cache dir - name: Store the distribution packages uses: actions/upload-artifact@v4 with: name: python-package-distributions path: dist/ publish-to-pypi: name: >- Publish Python 🐍 distribution 📦 to PyPI if: github.event_name == 'release' && github.event.release.action == 'published' # only publish to PyPI on tag pushes needs: - create-pypi-image runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/ic_sharpy # Replace with your PyPI project name permissions: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - name: Download all the dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 # with: # path: dist/* ================================================ FILE: .github/workflows/readme.md ================================================ # SHARPy GitHub Workflows There are 4(+1 experimental) automated workflows for SHARPy's CI/CD. ## SHARPy Tests The related to the SHARPy tests that run the `SHARPy Tests` job are: * `sharpy_tests.yaml`: when Python or the submodules files are edited * `sharpy_no_test_needed.yaml`: otherwise This avoids running the full set of tests for changes in the documentation etc. Since the merge to `develop` and `main` is protected by the tests passing, the second workflow ensures a positive result for those PRs that did not change the Python code, hence allowing the merge. ## Docker Two nearly identical workflows, the only difference is that one pushes the Docker image to the SHARPy packages. Therefore: * `docker_build_test.yaml`: Builds the Docker image but does not push. Runs on changes to the `docker*` workflows, changes to the `utils/` directory (environments) and changes to the `Dockerfile`. Required test for PRs to merge to `develop` and `main`. * `docker_build.yaml`: Builds and pushes the Docker image. Runs on pushes to `develop`, `main` and annotated tags. ## Pypi (experimental!) One workflow with two jobs, the first creates and the second pushes the wheel artifact to ic-sharpy @ pypi. Therefore: * `pypi_build.yaml`: Builds and pushes the pypi wheel according to conditions. Runs on changes to the `pypi*` workflow, changes to the `setup.py`, and PRs and pushes to main and develop. Required test for PRs to merge to `develop` and `main`. Publishes on annotated tags. ================================================ FILE: .github/workflows/sharpy_no_test_needed.yaml ================================================ name: SHARPy Tests on: pull_request: branches: - main - develop - 'rc*' paths-ignore: - '*.py' - 'lib/**' - '.github/workflows/**' jobs: build: runs-on: ubuntu-latest steps: - run: 'echo "No changes to python files, submodules or workflows, no run required" ' ================================================ FILE: .github/workflows/sharpy_tests.yaml ================================================ name: SHARPy Tests on: push: paths: - '*.py' - 'lib/*' - '.github/workflows/sharpy*' pull_request: branches: - main - develop - 'rc*' jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: [3.10.8] steps: - uses: actions/checkout@v4 with: submodules: "recursive" fetch-tags: true - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Set up GCC uses: egor-tensin/setup-gcc@v1 with: version: 10 platform: x64 - name: Check that gfortran works run: gfortran --version - name: Install build package dependencies run: sudo apt install libblas-dev liblapack-dev libeigen3-dev - name: Install sharpy and coverage using pip run: | export QT_QPA_PLATFORM='offscreen' pip install . pip install coverage - name: Run coverage run: | coverage run -m unittest discover coverage json - name: Upload Coverage to Codecov uses: codecov/codecov-action@v3 with: verbose: true ================================================ FILE: .github_changelog_generator ================================================ since-tag=1.0.0 ================================================ FILE: .gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # IPython Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # dotenv .env # virtualenv venv/ ENV/ # Spyder project settings .spyderproject # Rope project settings .ropeproject # Vim stuff *.*~ *.swp *.h5 # project dependent stuff lib/*.so output/* .idea # figure folders *figs/* *.eps *.vtu output*/ snapshots/ # OSX files *.DS_Store # linear tools / tests cases *.prof figs/* \.spyproject/ # sharpy extension *.sharpy # Exceptions # !tests/coupled/multibody/floating_wind_turbine/oc3_cs_v07.floating.h5 !tests/coupled/multibody/floating_wind_turbine/oc3_cs_v07.floating.h* ================================================ FILE: .gitmodules ================================================ [submodule "lib/UVLM"] path = lib/UVLM url = http://github.com/imperialcollegelondon/UVLM [submodule "lib/xbeam"] path = lib/xbeam url = http://github.com/imperialcollegelondon/xbeam ================================================ FILE: .readthedocs.yaml ================================================ # .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-22.04 tools: python: "3.10" python: install: - requirements: docs/requirements_rtd # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/source/conf.py # We recommend specifying your dependencies to enable reproducible builds: # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html # python: # install: # - requirements: docs/requirements.txt ================================================ FILE: .version.json ================================================ { "schemaVersion": 1, "label": "release version", "message": "2.4", "color": "green" } ================================================ FILE: .zenodo.json ================================================ { "license": "other-open", "title": "SHARPy: A dynamic aeroelastic simulation toolbox for very flexible aircraft and wind turbines", "upload_type": "software", "creators": [ { "orcid": "0000-0002-8133-9481", "affiliation": "Imperial College London", "name": "Alfonso del Carre" }, { "orcid": "0000-0001-7445-5970", "affiliation": "Imperial College London", "name": "Norberto Goizueta" }, { "orcid": "0000-0003-4840-5392", "affiliation": "Imperial College London", "name": "Arturo Mu\u00f1oz-Sim\u00f3n" }, { "orcid": "0000-0002-6706-3220", "affiliation": "Imperial College London", "name": "Rafael Palacios" } ], "access_right": "open" } ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## [2.4](https://github.com/imperialcollegelondon/sharpy/tree/2.4) (2025-03-20) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/2.3...2.4) **Implemented enhancements:** - Add the automatic differentiation multibody solver based on JAX [\#305](https://github.com/ImperialCollegeLondon/sharpy/pull/305) ([ben-l-p](https://github.com/ben-l-p)) - Non-perpendicular hinge axis possible for multibody solver [\#299](https://github.com/ImperialCollegeLondon/sharpy/pull/299) ([kccwing](https://github.com/kccwing)) - Add hosting of built wheel at PyPI - develop [\#298](https://github.com/ImperialCollegeLondon/sharpy/pull/298) ([kccwing](https://github.com/kccwing)) - Add hosting of built wheel at PyPI - main [\#297](https://github.com/ImperialCollegeLondon/sharpy/pull/297) ([kccwing](https://github.com/kccwing)) **Fixed bugs:** - Updated docs to address issue with conda install failing [\#290](https://github.com/ImperialCollegeLondon/sharpy/pull/290) ([ben-l-p](https://github.com/ben-l-p)) **Closed issues:** - TypeError: unhashable type: 'UnstructuredGrid' [\#313](https://github.com/ImperialCollegeLondon/sharpy/issues/313) - Creating aileron control surfaces to induce roll [\#304](https://github.com/ImperialCollegeLondon/sharpy/issues/304) - Static Trim Routine Fails with Dynamic-Type Control Surfaces [\#300](https://github.com/ImperialCollegeLondon/sharpy/issues/300) - Not a Git Repository [\#295](https://github.com/ImperialCollegeLondon/sharpy/issues/295) **Merged pull requests:** - Merge develop into main for new version release [\#318](https://github.com/ImperialCollegeLondon/sharpy/pull/318) ([ben-l-p](https://github.com/ben-l-p)) - Xhale thrust node typo [\#317](https://github.com/ImperialCollegeLondon/sharpy/pull/317) ([kccwing](https://github.com/kccwing)) - Mayavi github dependency restored for hotfix - develop [\#316](https://github.com/ImperialCollegeLondon/sharpy/pull/316) ([kccwing](https://github.com/kccwing)) - Mayavi github dependency restored for hotfix [\#311](https://github.com/ImperialCollegeLondon/sharpy/pull/311) ([kccwing](https://github.com/kccwing)) - Update body and wake FoR multiplication [\#309](https://github.com/ImperialCollegeLondon/sharpy/pull/309) ([kccwing](https://github.com/kccwing)) - Bug fix for generate\_full\_structure [\#306](https://github.com/ImperialCollegeLondon/sharpy/pull/306) ([kccwing](https://github.com/kccwing)) - Fix AeroForcesCalculator giving forces in wrong frame of reference [\#301](https://github.com/ImperialCollegeLondon/sharpy/pull/301) ([ben-l-p](https://github.com/ben-l-p)) - Temporary Fix for NumPy 2.0 Issues \(Develop Branch\) [\#293](https://github.com/ImperialCollegeLondon/sharpy/pull/293) ([ben-l-p](https://github.com/ben-l-p)) - Temporary Fix for NumPy 2.0 Issues \(Main Branch\) [\#292](https://github.com/ImperialCollegeLondon/sharpy/pull/292) ([ben-l-p](https://github.com/ben-l-p)) ## [2.3](https://github.com/imperialcollegelondon/sharpy/tree/2.3) (2024-05-10) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/2.2...2.3) **Implemented enhancements:** - Version 2.3 update [\#289](https://github.com/ImperialCollegeLondon/sharpy/pull/289) ([ben-l-p](https://github.com/ben-l-p)) - Update develop branch with main [\#284](https://github.com/ImperialCollegeLondon/sharpy/pull/284) ([ben-l-p](https://github.com/ben-l-p)) - Added pip install \(with docs\) [\#280](https://github.com/ImperialCollegeLondon/sharpy/pull/280) ([ben-l-p](https://github.com/ben-l-p)) - Update beamplot.py to have stride option, consistent with aerogridplot.py [\#279](https://github.com/ImperialCollegeLondon/sharpy/pull/279) ([kccwing](https://github.com/kccwing)) **Fixed bugs:** - Fix Github Runner Docker build failing [\#285](https://github.com/ImperialCollegeLondon/sharpy/pull/285) ([ben-l-p](https://github.com/ben-l-p)) - Add scipy version info to env yml [\#277](https://github.com/ImperialCollegeLondon/sharpy/pull/277) ([SJ-Innovation](https://github.com/SJ-Innovation)) **Closed issues:** - Scipy 1.12.0 Incompatible [\#276](https://github.com/ImperialCollegeLondon/sharpy/issues/276) - BeamLoader postprocessor squishing answers [\#270](https://github.com/ImperialCollegeLondon/sharpy/issues/270) - Solving Environment gets killed. [\#268](https://github.com/ImperialCollegeLondon/sharpy/issues/268) - Error when running sharpy unittest: module scipy.sparse.\_sputils not found [\#227](https://github.com/ImperialCollegeLondon/sharpy/issues/227) - Potential bug in /sharpy/structure/utils/modalutils.py [\#208](https://github.com/ImperialCollegeLondon/sharpy/issues/208) **Merged pull requests:** - Added ability to turn aligned grid off [\#288](https://github.com/ImperialCollegeLondon/sharpy/pull/288) ([ben-l-p](https://github.com/ben-l-p)) - Update with main for mamba fixes [\#286](https://github.com/ImperialCollegeLondon/sharpy/pull/286) ([ben-l-p](https://github.com/ben-l-p)) - Correct typos caught by Divya Sanghi [\#283](https://github.com/ImperialCollegeLondon/sharpy/pull/283) ([bbahiam](https://github.com/bbahiam)) - Develop: Update environment.yml to fix scipy version issue [\#282](https://github.com/ImperialCollegeLondon/sharpy/pull/282) ([kccwing](https://github.com/kccwing)) - Update noaero.py for consistency in function input [\#275](https://github.com/ImperialCollegeLondon/sharpy/pull/275) ([kccwing](https://github.com/kccwing)) - A few minor bug fixes [\#273](https://github.com/ImperialCollegeLondon/sharpy/pull/273) ([sduess](https://github.com/sduess)) - Update XBeam version to include compiler optimisation [\#272](https://github.com/ImperialCollegeLondon/sharpy/pull/272) ([ben-l-p](https://github.com/ben-l-p)) - Update XBeam version to include compiler optimisation [\#271](https://github.com/ImperialCollegeLondon/sharpy/pull/271) ([ben-l-p](https://github.com/ben-l-p)) - Improve docs and code of newmark\_ss [\#267](https://github.com/ImperialCollegeLondon/sharpy/pull/267) ([bbahiam](https://github.com/bbahiam)) - Changed Github runner from Conda to Mamba [\#266](https://github.com/ImperialCollegeLondon/sharpy/pull/266) ([ben-l-p](https://github.com/ben-l-p)) - Changed Github runner from Conda to Mamba [\#265](https://github.com/ImperialCollegeLondon/sharpy/pull/265) ([ben-l-p](https://github.com/ben-l-p)) - Hotfix for documentation search [\#264](https://github.com/ImperialCollegeLondon/sharpy/pull/264) ([kccwing](https://github.com/kccwing)) - Hotfix for documentation - develop [\#263](https://github.com/ImperialCollegeLondon/sharpy/pull/263) ([kccwing](https://github.com/kccwing)) - Hotfix for documentation - main [\#262](https://github.com/ImperialCollegeLondon/sharpy/pull/262) ([kccwing](https://github.com/kccwing)) - Merging v2.2 into develop [\#261](https://github.com/ImperialCollegeLondon/sharpy/pull/261) ([kccwing](https://github.com/kccwing)) ## [2.2](https://github.com/imperialcollegelondon/sharpy/tree/2.2) (2023-10-18) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/2.1...2.2) **Implemented enhancements:** - Added environment for Apple Silicon \(ARM64\) [\#254](https://github.com/ImperialCollegeLondon/sharpy/pull/254) ([ben-l-p](https://github.com/ben-l-p)) - New Fuselage Model plus Minor Improvements [\#249](https://github.com/ImperialCollegeLondon/sharpy/pull/249) ([sduess](https://github.com/sduess)) **Fixed bugs:** - fix polars concatenation in assembly of aeroinformation - develop [\#253](https://github.com/ImperialCollegeLondon/sharpy/pull/253) ([kccwing](https://github.com/kccwing)) - fix polars concatenation in assembly of aeroinformation - main [\#252](https://github.com/ImperialCollegeLondon/sharpy/pull/252) ([kccwing](https://github.com/kccwing)) - Minor bug fixes in aero util functions and save data postprocessor [\#247](https://github.com/ImperialCollegeLondon/sharpy/pull/247) ([sduess](https://github.com/sduess)) **Closed issues:** - Automated tests failed : UnicodeEncodeError: 'ascii' codec can't encode character '\xe9' in position 47: ordinal not in range\(128\) [\#245](https://github.com/ImperialCollegeLondon/sharpy/issues/245) - Wrong key in settings for /cases/templates/flying\_wings.py [\#205](https://github.com/ImperialCollegeLondon/sharpy/issues/205) **Merged pull requests:** - merging develop into main for v2.2 [\#259](https://github.com/ImperialCollegeLondon/sharpy/pull/259) ([kccwing](https://github.com/kccwing)) - fix \[docker\] use correct environment name in docker bashrc [\#257](https://github.com/ImperialCollegeLondon/sharpy/pull/257) ([sduess](https://github.com/sduess)) - Update docker environment [\#255](https://github.com/ImperialCollegeLondon/sharpy/pull/255) ([sduess](https://github.com/sduess)) - Update README.md [\#246](https://github.com/ImperialCollegeLondon/sharpy/pull/246) ([rafapalacios](https://github.com/rafapalacios)) - bringing commits to main into develop [\#244](https://github.com/ImperialCollegeLondon/sharpy/pull/244) ([rafapalacios](https://github.com/rafapalacios)) - Updates to plotutils.py and the cantilever\_wing demo [\#242](https://github.com/ImperialCollegeLondon/sharpy/pull/242) ([boltyboi](https://github.com/boltyboi)) - Small additions to the installation guide. [\#241](https://github.com/ImperialCollegeLondon/sharpy/pull/241) ([boltyboi](https://github.com/boltyboi)) - Tutorial for closed-Loop Simulation with SHARPy as a hardware-in-the-loop system [\#240](https://github.com/ImperialCollegeLondon/sharpy/pull/240) ([sduess](https://github.com/sduess)) ## [2.1](https://github.com/imperialcollegelondon/sharpy/tree/2.1) (2023-05-31) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/2.0...2.1) **Implemented enhancements:** - SHARPy Docker now releases to GitHub Packages [\#218](https://github.com/ImperialCollegeLondon/sharpy/pull/218) ([ngoiz](https://github.com/ngoiz)) - Some Enhancements and Fixes for the Polar Correction, Wake Discretisation, and Gust Velocity Field Generator [\#217](https://github.com/ImperialCollegeLondon/sharpy/pull/217) ([sduess](https://github.com/sduess)) - Collective blade pitch PID control of wind turbines [\#176](https://github.com/ImperialCollegeLondon/sharpy/pull/176) ([ArturoMS13](https://github.com/ArturoMS13)) **Fixed bugs:** - Handle absolute tolerance for structural convergence tests as input settings and increase default value [\#221](https://github.com/ImperialCollegeLondon/sharpy/pull/221) ([sduess](https://github.com/sduess)) - Fix horseshoe wake handling in UVLM [\#204](https://github.com/ImperialCollegeLondon/sharpy/pull/204) ([sduess](https://github.com/sduess)) **Closed issues:** - No such file or directory during: source bin/sharpy\_vars.sh [\#233](https://github.com/ImperialCollegeLondon/sharpy/issues/233) - Twist direction inconsistent [\#212](https://github.com/ImperialCollegeLondon/sharpy/issues/212) **Merged pull requests:** - Version number updates for 2.1 from develop [\#237](https://github.com/ImperialCollegeLondon/sharpy/pull/237) ([kccwing](https://github.com/kccwing)) - Merge to main for version 2.0.1 release [\#236](https://github.com/ImperialCollegeLondon/sharpy/pull/236) ([kccwing](https://github.com/kccwing)) - Updates to Goland wing example [\#234](https://github.com/ImperialCollegeLondon/sharpy/pull/234) ([rafapalacios](https://github.com/rafapalacios)) - A bit of clean-up of the readme file of the repo [\#231](https://github.com/ImperialCollegeLondon/sharpy/pull/231) ([rafapalacios](https://github.com/rafapalacios)) - Dev internals [\#223](https://github.com/ImperialCollegeLondon/sharpy/pull/223) ([ACea15](https://github.com/ACea15)) - updating from python 3.7 to 3.10 [\#222](https://github.com/ImperialCollegeLondon/sharpy/pull/222) ([kccwing](https://github.com/kccwing)) - Fix typo in setting [\#206](https://github.com/ImperialCollegeLondon/sharpy/pull/206) ([sduess](https://github.com/sduess)) ## [2.0](https://github.com/imperialcollegelondon/sharpy/tree/2.0) (2022-07-04) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/1.3...2.0) **Implemented enhancements:** - Plot the aeroelastic mode shape to Paraview [\#202](https://github.com/ImperialCollegeLondon/sharpy/pull/202) ([ngoiz](https://github.com/ngoiz)) - Enhanced linear solver [\#196](https://github.com/ImperialCollegeLondon/sharpy/pull/196) ([ngoiz](https://github.com/ngoiz)) - Add pip support [\#175](https://github.com/ImperialCollegeLondon/sharpy/pull/175) ([DavidAnderegg](https://github.com/DavidAnderegg)) **Fixed bugs:** - Flap inputs in state-space model not working in certain wing configurations [\#192](https://github.com/ImperialCollegeLondon/sharpy/issues/192) - Fix mass matrix generation for lumped masses in case of several masses per node [\#194](https://github.com/ImperialCollegeLondon/sharpy/pull/194) ([sduess](https://github.com/sduess)) - Fix the sparse matrix in balancing ROM [\#186](https://github.com/ImperialCollegeLondon/sharpy/pull/186) ([AntonioWR](https://github.com/AntonioWR)) **Closed issues:** - scipy used for direct balancing method [\#184](https://github.com/ImperialCollegeLondon/sharpy/issues/184) - Potential Issue in the balancing direct method [\#183](https://github.com/ImperialCollegeLondon/sharpy/issues/183) - Why no pip support? [\#171](https://github.com/ImperialCollegeLondon/sharpy/issues/171) **Merged pull requests:** - Contain write operations within with statements [\#195](https://github.com/ImperialCollegeLondon/sharpy/pull/195) ([ngoiz](https://github.com/ngoiz)) - Support loading/saving state-spaces and gains to h5 files [\#188](https://github.com/ImperialCollegeLondon/sharpy/pull/188) ([ngoiz](https://github.com/ngoiz)) - Update installation docs [\#187](https://github.com/ImperialCollegeLondon/sharpy/pull/187) ([nacho-carnicero](https://github.com/nacho-carnicero)) - Unittest for nonlinear dynamic solver [\#185](https://github.com/ImperialCollegeLondon/sharpy/pull/185) ([sduess](https://github.com/sduess)) - Change in the io-Settings to add thrust. [\#164](https://github.com/ImperialCollegeLondon/sharpy/pull/164) ([Eriklyy](https://github.com/Eriklyy)) - UDP-Inout change for multiply cs\_surface\_deflections and loads/strain [\#162](https://github.com/ImperialCollegeLondon/sharpy/pull/162) ([Eriklyy](https://github.com/Eriklyy)) - Update AsymptoticStability and FrequencyResponse post-processors [\#103](https://github.com/ImperialCollegeLondon/sharpy/pull/103) ([ngoiz](https://github.com/ngoiz)) ## [1.3](https://github.com/imperialcollegelondon/sharpy/tree/1.3) (2021-11-11) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/v1.2.1...1.3) **Implemented enhancements:** - Include gravity direction as input for structural solvers [\#112](https://github.com/ImperialCollegeLondon/sharpy/issues/112) - Simulation settings check - Unrecognised settings raise Error [\#148](https://github.com/ImperialCollegeLondon/sharpy/pull/148) ([ngoiz](https://github.com/ngoiz)) - Aerodynamic forces correction enhancements [\#140](https://github.com/ImperialCollegeLondon/sharpy/pull/140) ([ngoiz](https://github.com/ngoiz)) - New feature: apply external forces to the system at runtime [\#125](https://github.com/ImperialCollegeLondon/sharpy/pull/125) ([ArturoMS13](https://github.com/ArturoMS13)) - Lift distribution post-processor [\#121](https://github.com/ImperialCollegeLondon/sharpy/pull/121) ([sduess](https://github.com/sduess)) **Fixed bugs:** - Fix bug in computing total moments in A frame [\#177](https://github.com/ImperialCollegeLondon/sharpy/pull/177) ([ngoiz](https://github.com/ngoiz)) **Closed issues:** - Unrecognised model in goland test case [\#143](https://github.com/ImperialCollegeLondon/sharpy/issues/143) **Merged pull requests:** - Implement GitHub Actions as testing suite provider [\#179](https://github.com/ImperialCollegeLondon/sharpy/pull/179) ([ngoiz](https://github.com/ngoiz)) - Update submodules and conda environments [\#161](https://github.com/ImperialCollegeLondon/sharpy/pull/161) ([sduess](https://github.com/sduess)) - Support element variables in UDP output [\#160](https://github.com/ImperialCollegeLondon/sharpy/pull/160) ([ngoiz](https://github.com/ngoiz)) - Output directory in the location specified in settings\[SHARPy\]\[log\_folder\] [\#130](https://github.com/ImperialCollegeLondon/sharpy/pull/130) ([ngoiz](https://github.com/ngoiz)) - Update and include features in multibody [\#126](https://github.com/ImperialCollegeLondon/sharpy/pull/126) ([ArturoMS13](https://github.com/ArturoMS13)) - Update linear UVLM to account for CFL not equal to one in the wake convection [\#124](https://github.com/ImperialCollegeLondon/sharpy/pull/124) ([ArturoMS13](https://github.com/ArturoMS13)) - Minor changes [\#123](https://github.com/ImperialCollegeLondon/sharpy/pull/123) ([ArturoMS13](https://github.com/ArturoMS13)) ## [v1.2.1](https://github.com/imperialcollegelondon/sharpy/tree/v1.2.1) (2021-02-09) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/v1.2...v1.2.1) **Implemented enhancements:** - Allow CFL != 1in shedding step [\#78](https://github.com/ImperialCollegeLondon/sharpy/issues/78) - Vortex wake managed by SHARPy [\#77](https://github.com/ImperialCollegeLondon/sharpy/issues/77) - Recover vortex core in UVLM [\#76](https://github.com/ImperialCollegeLondon/sharpy/issues/76) - Include viscous drag force from airfoil properties [\#75](https://github.com/ImperialCollegeLondon/sharpy/issues/75) **Fixed bugs:** - Bug in beamstructure.py [\#117](https://github.com/ImperialCollegeLondon/sharpy/issues/117) - Definition of control surfaces and impact of node ordering in mirrored wings [\#43](https://github.com/ImperialCollegeLondon/sharpy/issues/43) **Closed issues:** - examples refer to non-existent solver SHWUvlm [\#119](https://github.com/ImperialCollegeLondon/sharpy/issues/119) - Potential bug in xbeam and cbeam interfaces [\#89](https://github.com/ImperialCollegeLondon/sharpy/issues/89) - Update packages producing deprecation warnings and tackle other warnings [\#80](https://github.com/ImperialCollegeLondon/sharpy/issues/80) **Merged pull requests:** - Rigid coupled solver [\#120](https://github.com/ImperialCollegeLondon/sharpy/pull/120) ([ArturoMS13](https://github.com/ArturoMS13)) - Support to save ROM projector matrices [\#118](https://github.com/ImperialCollegeLondon/sharpy/pull/118) ([ngoiz](https://github.com/ngoiz)) ## [v1.2](https://github.com/imperialcollegelondon/sharpy/tree/v1.2) (2020-12-03) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/v1.1.1...v1.2) **Implemented enhancements:** - Quasi-steady solver in stepuvlm [\#114](https://github.com/ImperialCollegeLondon/sharpy/pull/114) ([ArturoMS13](https://github.com/ArturoMS13)) - Generator to modify structural mass at runtime [\#107](https://github.com/ImperialCollegeLondon/sharpy/pull/107) ([ngoiz](https://github.com/ngoiz)) - Sends simulation info to xplane via udp [\#104](https://github.com/ImperialCollegeLondon/sharpy/pull/104) ([wong-hl](https://github.com/wong-hl)) - Major new aerodynamic features [\#101](https://github.com/ImperialCollegeLondon/sharpy/pull/101) ([ArturoMS13](https://github.com/ArturoMS13)) - Simple post-processor to save simulation parameters for batch runs [\#91](https://github.com/ImperialCollegeLondon/sharpy/pull/91) ([ngoiz](https://github.com/ngoiz)) - SHARPy support for external inputs via UDP network [\#90](https://github.com/ImperialCollegeLondon/sharpy/pull/90) ([ngoiz](https://github.com/ngoiz)) - Vortex radius as input parameter [\#86](https://github.com/ImperialCollegeLondon/sharpy/pull/86) ([ArturoMS13](https://github.com/ArturoMS13)) - Enhanced Frequency Response post-processor and linear system input/output options [\#83](https://github.com/ImperialCollegeLondon/sharpy/pull/83) ([ngoiz](https://github.com/ngoiz)) - Pazi wing added to flying\_wing template [\#82](https://github.com/ImperialCollegeLondon/sharpy/pull/82) ([outoforderdev](https://github.com/outoforderdev)) - Several new aerodynamic enhancements [\#79](https://github.com/ImperialCollegeLondon/sharpy/pull/79) ([ArturoMS13](https://github.com/ArturoMS13)) **Fixed bugs:** - libss.py disc2cont doesn't accept SISO systems [\#88](https://github.com/ImperialCollegeLondon/sharpy/issues/88) - Dimension mismatch when assembling linear UVLM with "shortened" wake [\#71](https://github.com/ImperialCollegeLondon/sharpy/issues/71) - Fix bug wake shape generator StaticCoupled [\#85](https://github.com/ImperialCollegeLondon/sharpy/pull/85) ([ArturoMS13](https://github.com/ArturoMS13)) - Rework of direct balancing [\#74](https://github.com/ImperialCollegeLondon/sharpy/pull/74) ([outoforderdev](https://github.com/outoforderdev)) **Closed issues:** - \[develop\] Documentation for postprocs not rendering in RTD [\#109](https://github.com/ImperialCollegeLondon/sharpy/issues/109) - Numerical error in the linear UVLM output matrices when vortex radius is too small [\#105](https://github.com/ImperialCollegeLondon/sharpy/issues/105) - fatal error: mkl.h: No such file or directory [\#70](https://github.com/ImperialCollegeLondon/sharpy/issues/70) - lingebm.py -\> update\_matrices\_time\_scale [\#69](https://github.com/ImperialCollegeLondon/sharpy/issues/69) - Missing node connectivities figure in case files documentation [\#68](https://github.com/ImperialCollegeLondon/sharpy/issues/68) **Merged pull requests:** - \[fix\] Fixed bug in Gridbox class [\#111](https://github.com/ImperialCollegeLondon/sharpy/pull/111) ([sduess](https://github.com/sduess)) - Include linear UVLM stiffening and damping terms in the UVLM D matrix [\#108](https://github.com/ImperialCollegeLondon/sharpy/pull/108) ([ngoiz](https://github.com/ngoiz)) - Fix accuracy problem in UVLMLin [\#106](https://github.com/ImperialCollegeLondon/sharpy/pull/106) ([ArturoMS13](https://github.com/ArturoMS13)) - Minor improvements [\#102](https://github.com/ImperialCollegeLondon/sharpy/pull/102) ([ngoiz](https://github.com/ngoiz)) - Linearisation of externally applied follower forces [\#100](https://github.com/ImperialCollegeLondon/sharpy/pull/100) ([ngoiz](https://github.com/ngoiz)) - Updated docs for DataStructures and Multibody [\#99](https://github.com/ImperialCollegeLondon/sharpy/pull/99) ([ArturoMS13](https://github.com/ArturoMS13)) - Support linearised all-moving control surfaces [\#97](https://github.com/ImperialCollegeLondon/sharpy/pull/97) ([ngoiz](https://github.com/ngoiz)) - Update Linux and minimal environments [\#96](https://github.com/ImperialCollegeLondon/sharpy/pull/96) ([ngoiz](https://github.com/ngoiz)) - New approach to multibody computations [\#95](https://github.com/ImperialCollegeLondon/sharpy/pull/95) ([ArturoMS13](https://github.com/ArturoMS13)) - New SHARPy examples in the documentation [\#94](https://github.com/ImperialCollegeLondon/sharpy/pull/94) ([ArturoMS13](https://github.com/ArturoMS13)) - Add support for offline use of UDPout postproc [\#93](https://github.com/ImperialCollegeLondon/sharpy/pull/93) ([ngoiz](https://github.com/ngoiz)) - Option to transform rigid modes given at A FoR to centre of gravity and aligned with principal axes of inertia [\#92](https://github.com/ImperialCollegeLondon/sharpy/pull/92) ([ngoiz](https://github.com/ngoiz)) - Pazy wing modified to include the tip weight [\#87](https://github.com/ImperialCollegeLondon/sharpy/pull/87) ([outoforderdev](https://github.com/outoforderdev)) - Minor output clean up [\#81](https://github.com/ImperialCollegeLondon/sharpy/pull/81) ([ngoiz](https://github.com/ngoiz)) - Fixes assembly of linUVLM after plotting wake with minus m\_star [\#72](https://github.com/ImperialCollegeLondon/sharpy/pull/72) ([ngoiz](https://github.com/ngoiz)) ## [v1.1.1](https://github.com/imperialcollegelondon/sharpy/tree/v1.1.1) (2020-02-03) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/v1.1.0-2...v1.1.1) **Implemented enhancements:** - User-defined aerodynamic airfoil efficiency factor and constant force terms [\#59](https://github.com/ImperialCollegeLondon/sharpy/pull/59) ([ngoiz](https://github.com/ngoiz)) **Closed issues:** - Broken link on SHARPy Installation Guide [\#67](https://github.com/ImperialCollegeLondon/sharpy/issues/67) - Update citation instructions [\#62](https://github.com/ImperialCollegeLondon/sharpy/issues/62) - Incorrect version tag displayed when running SHARPy [\#61](https://github.com/ImperialCollegeLondon/sharpy/issues/61) - Clean up SHARPy linear interface with UVLM [\#48](https://github.com/ImperialCollegeLondon/sharpy/issues/48) **Merged pull requests:** - Documentation Improvements [\#66](https://github.com/ImperialCollegeLondon/sharpy/pull/66) ([ngoiz](https://github.com/ngoiz)) - Minor fixes and general code clean up of linear modules [\#65](https://github.com/ImperialCollegeLondon/sharpy/pull/65) ([ngoiz](https://github.com/ngoiz)) - Error log file created when program encounters exceptions [\#64](https://github.com/ImperialCollegeLondon/sharpy/pull/64) ([ngoiz](https://github.com/ngoiz)) - Update README.md [\#63](https://github.com/ImperialCollegeLondon/sharpy/pull/63) ([rafapalacios](https://github.com/rafapalacios)) - Clean up linear SHARPy's interface with UVLM [\#60](https://github.com/ImperialCollegeLondon/sharpy/pull/60) ([ngoiz](https://github.com/ngoiz)) ## [v1.1.0-2](https://github.com/imperialcollegelondon/sharpy/tree/v1.1.0-2) (2019-12-12) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/v1.1.0...v1.1.0-2) ## [v1.1.0](https://github.com/imperialcollegelondon/sharpy/tree/v1.1.0) (2019-12-12) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/v1.0.1...v1.1.0) **Implemented enhancements:** - Improvements to Model Reduction [\#56](https://github.com/ImperialCollegeLondon/sharpy/pull/56) ([ngoiz](https://github.com/ngoiz)) - Submodules and cmake build tools instead of Makefiles [\#52](https://github.com/ImperialCollegeLondon/sharpy/pull/52) ([fonsocarre](https://github.com/fonsocarre)) - New settings check against valid options [\#39](https://github.com/ImperialCollegeLondon/sharpy/pull/39) ([ngoiz](https://github.com/ngoiz)) - Default settings get type specified for the setting rather than their own type [\#34](https://github.com/ImperialCollegeLondon/sharpy/pull/34) ([ngoiz](https://github.com/ngoiz)) **Fixed bugs:** - Default value not correct in documentation when it is a numpy type. [\#32](https://github.com/ImperialCollegeLondon/sharpy/issues/32) - Documentation for Postprocessors being skipped by Sphinx in RTD [\#30](https://github.com/ImperialCollegeLondon/sharpy/issues/30) **Closed issues:** - Minor documentation issues [\#53](https://github.com/ImperialCollegeLondon/sharpy/issues/53) - WindTurbine case generation script does not produce sharpy file [\#50](https://github.com/ImperialCollegeLondon/sharpy/issues/50) - Considerations for building SHARPy [\#47](https://github.com/ImperialCollegeLondon/sharpy/issues/47) - Installation fails on macOS with Intel compiler [\#46](https://github.com/ImperialCollegeLondon/sharpy/issues/46) - run\_theo\_freq.py fails in Docker container [\#37](https://github.com/ImperialCollegeLondon/sharpy/issues/37) - Compare to other competing software in JOSS paper [\#36](https://github.com/ImperialCollegeLondon/sharpy/issues/36) **Merged pull requests:** - Example wind turbine [\#58](https://github.com/ImperialCollegeLondon/sharpy/pull/58) ([ArturoMS13](https://github.com/ArturoMS13)) - Small typo in README.md and updates to it [\#57](https://github.com/ImperialCollegeLondon/sharpy/pull/57) ([fonsocarre](https://github.com/fonsocarre)) - Restructuring of A Short Guide to SHARPy [\#55](https://github.com/ImperialCollegeLondon/sharpy/pull/55) ([ngoiz](https://github.com/ngoiz)) - JOSS Paper Minor typos fixed [\#54](https://github.com/ImperialCollegeLondon/sharpy/pull/54) ([ngoiz](https://github.com/ngoiz)) - Update .solver.txt extension to .sharpy [\#51](https://github.com/ImperialCollegeLondon/sharpy/pull/51) ([ArturoMS13](https://github.com/ArturoMS13)) - Fix typo in unittests using tearDowns instead of tearDown [\#49](https://github.com/ImperialCollegeLondon/sharpy/pull/49) ([ngoiz](https://github.com/ngoiz)) - Bug fixes in installation docs [\#45](https://github.com/ImperialCollegeLondon/sharpy/pull/45) ([rafmudaf](https://github.com/rafmudaf)) - Updated installation instructions [\#44](https://github.com/ImperialCollegeLondon/sharpy/pull/44) ([ngoiz](https://github.com/ngoiz)) - Travis CI now uses the minimal environment, the same as the Docker build [\#42](https://github.com/ImperialCollegeLondon/sharpy/pull/42) ([fonsocarre](https://github.com/fonsocarre)) - Remove calls to matplotlib \(or wrap in try except\) [\#41](https://github.com/ImperialCollegeLondon/sharpy/pull/41) ([ngoiz](https://github.com/ngoiz)) - Added information about competing software in JOSS paper [\#40](https://github.com/ImperialCollegeLondon/sharpy/pull/40) ([fonsocarre](https://github.com/fonsocarre)) - Removes deprecated case files from cases folder [\#38](https://github.com/ImperialCollegeLondon/sharpy/pull/38) ([ngoiz](https://github.com/ngoiz)) - Change position of --name argument in docs [\#35](https://github.com/ImperialCollegeLondon/sharpy/pull/35) ([petebachant](https://github.com/petebachant)) - Improvements in documentation [\#31](https://github.com/ImperialCollegeLondon/sharpy/pull/31) ([ngoiz](https://github.com/ngoiz)) ## [v1.0.1](https://github.com/imperialcollegelondon/sharpy/tree/v1.0.1) (2019-11-17) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/1.0.0...v1.0.1) **Implemented enhancements:** - New example of linearised flying wing [\#28](https://github.com/ImperialCollegeLondon/sharpy/pull/28) ([ngoiz](https://github.com/ngoiz)) - SHARPy can now be obtained from a Docker Hub container [\#27](https://github.com/ImperialCollegeLondon/sharpy/pull/27) ([fonsocarre](https://github.com/fonsocarre)) - Improved modal solver [\#26](https://github.com/ImperialCollegeLondon/sharpy/pull/26) ([fonsocarre](https://github.com/fonsocarre)) - Updated function calls for latest numpy 1.17 [\#25](https://github.com/ImperialCollegeLondon/sharpy/pull/25) ([ngoiz](https://github.com/ngoiz)) - Examples added to documentation [\#24](https://github.com/ImperialCollegeLondon/sharpy/pull/24) ([fonsocarre](https://github.com/fonsocarre)) - Improved linear solver documentation and minor Krylov ROM fixes [\#23](https://github.com/ImperialCollegeLondon/sharpy/pull/23) ([ngoiz](https://github.com/ngoiz)) - change log generator incorporated [\#22](https://github.com/ImperialCollegeLondon/sharpy/pull/22) ([ngoiz](https://github.com/ngoiz)) **Merged pull requests:** - Version v1.0.1 released [\#29](https://github.com/ImperialCollegeLondon/sharpy/pull/29) ([fonsocarre](https://github.com/fonsocarre)) ## [1.0.0](https://github.com/imperialcollegelondon/sharpy/tree/1.0.0) (2019-11-07) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/v1.0.0-rc...1.0.0) **Implemented enhancements:** - WriteVariablesTime output global beam variables and consistent out dir [\#19](https://github.com/ImperialCollegeLondon/sharpy/pull/19) ([ngoiz](https://github.com/ngoiz)) - Autodocumenter [\#16](https://github.com/ImperialCollegeLondon/sharpy/pull/16) ([ngoiz](https://github.com/ngoiz)) **Closed issues:** - Tests not passing due to them being outdated + test refactoring. [\#11](https://github.com/ImperialCollegeLondon/sharpy/issues/11) **Merged pull requests:** - Release of v1.0.0!!! [\#20](https://github.com/ImperialCollegeLondon/sharpy/pull/20) ([fonsocarre](https://github.com/fonsocarre)) - Documentation fixes/updates [\#18](https://github.com/ImperialCollegeLondon/sharpy/pull/18) ([ngoiz](https://github.com/ngoiz)) - Fix dynamic control surface and settings for aerogridloader [\#15](https://github.com/ImperialCollegeLondon/sharpy/pull/15) ([ngoiz](https://github.com/ngoiz)) ## [v1.0.0-rc](https://github.com/imperialcollegelondon/sharpy/tree/v1.0.0-rc) (2019-08-22) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/V0.2.1...v1.0.0-rc) **Closed issues:** - Output table [\#10](https://github.com/ImperialCollegeLondon/sharpy/issues/10) **Merged pull requests:** - Remove H5pyDeprecationWarning [\#14](https://github.com/ImperialCollegeLondon/sharpy/pull/14) ([ArturoMS13](https://github.com/ArturoMS13)) - Lagrange multipliers for Catapult Take Off works + clean tests [\#13](https://github.com/ImperialCollegeLondon/sharpy/pull/13) ([fonsocarre](https://github.com/fonsocarre)) ## [V0.2.1](https://github.com/imperialcollegelondon/sharpy/tree/V0.2.1) (2019-03-14) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/v0.2...V0.2.1) ## [v0.2](https://github.com/imperialcollegelondon/sharpy/tree/v0.2) (2019-03-14) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/v0.1...v0.2) **Closed issues:** - Add recovery options [\#9](https://github.com/ImperialCollegeLondon/sharpy/issues/9) ## [v0.1](https://github.com/imperialcollegelondon/sharpy/tree/v0.1) (2018-09-03) [Full Changelog](https://github.com/imperialcollegelondon/sharpy/compare/bd2b65974d57d2d6486ea90cdb68ef6324efbac8...v0.1) **Implemented enhancements:** - Hinge definition for control surface [\#8](https://github.com/ImperialCollegeLondon/sharpy/issues/8) - sharpy\_main.main does not return output [\#5](https://github.com/ImperialCollegeLondon/sharpy/issues/5) **Fixed bugs:** - Aerofoil data associated to the nodes instead of the elements [\#6](https://github.com/ImperialCollegeLondon/sharpy/issues/6) **Merged pull requests:** - Trimming routine working [\#4](https://github.com/ImperialCollegeLondon/sharpy/pull/4) ([fonsocarre](https://github.com/fonsocarre)) - Feature coupled dynamic [\#3](https://github.com/ImperialCollegeLondon/sharpy/pull/3) ([fonsocarre](https://github.com/fonsocarre)) - Refactored storage finished [\#2](https://github.com/ImperialCollegeLondon/sharpy/pull/2) ([fonsocarre](https://github.com/fonsocarre)) - Settings files are ConfigObjs now, not ConfigParser anymore [\#1](https://github.com/ImperialCollegeLondon/sharpy/pull/1) ([fonsocarre](https://github.com/fonsocarre)) \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.9) project(sharpy) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() add_subdirectory(lib) ================================================ FILE: Dockerfile ================================================ FROM centos:8 ENV PYTHONDONTWRITEBYTECODE=true ENV BASH_ENV ~/.bashrc SHELL ["/bin/bash", "-c"] ENV PATH=${PATH}:/mamba/bin # CENTOS 8 has reached end of life - Not yet an updated Docker base for CentOS stream # Point to the CentOS 8 vault in order to download dependencies RUN cd /etc/yum.repos.d/ && \ sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* && \ sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* && \ cd / # Development tools including compilers RUN yum groupinstall "Development Tools" -y --nogpgcheck && \ yum install dnf-plugins-core && \ yum config-manager --set-enabled powertools && \ yum install -y --nogpgcheck mesa-libGL libXt libXt-devel wget gcc-gfortran lapack vim tmux eigen3-devel && \ yum clean all # Install Mamba - swapped from Conda to Mamba due to Github runner memory constraint # Mambaforge is deprecated in latest miniforge https://github.com/conda-forge/miniforge/pull/704 RUN wget --no-check-certificate https://github.com/conda-forge/miniforge/releases/download/24.7.1-0/Mambaforge-Linux-x86_64.sh -O /mamba.sh && \ chmod +x /mamba.sh && \ /mamba.sh -b -p /mamba/ && \ rm /mamba.sh && hash -r ADD / /sharpy_dir/ # Initialise mamba installation RUN mamba init bash && \ mamba update -q conda && \ mamba env create -f /sharpy_dir/utils/environment.yml && \ find /mamba/ -follow -type f -name '*.a' -delete && \ find /mamba/ -follow -type f -name '*.pyc' -delete && \ find /mamba/ -follow -type f -name '*.js.map' -delete RUN ln -s /sharpy_dir/utils/docker/* /root/ RUN cd sharpy_dir && \ mamba activate sharpy && \ git submodule update --init --recursive && \ mkdir build && \ cd build && \ CXX=g++ FC=gfortran cmake .. && make install -j 4 && \ cd .. && \ pip install . && \ rm -rf build ENTRYPOINT ["/bin/bash", "--init-file", "/root/bashrc"] ================================================ FILE: LICENSE ================================================ BSD 3-Clause License Copyright (c) 2019, Loads Control and Aeroelastics, Imperial College London All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ # Simulation of High Aspect Ratio aeroplanes in Python [SHARPy] ![Version badge](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fraw.githubusercontent.com%2FImperialCollegeLondon%2Fsharpy%2Fmain%2F.version.json) ![Build Status](https://github.com/ImperialCollegeLondon/sharpy/actions/workflows/sharpy_tests.yaml/badge.svg) [![Documentation Status](https://readthedocs.org/projects/ic-sharpy/badge/?version=main)](https://ic-sharpy.readthedocs.io/en/main/?badge=main) [![codecov](https://codecov.io/gh/ImperialCollegeLondon/sharpy/branch/main/graph/badge.svg)](https://codecov.io/gh/ImperialCollegeLondon/sharpy) [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![status](https://joss.theoj.org/papers/f7ccd562160f1a54f64a81e90f5d9af9/status.svg)](https://joss.theoj.org/papers/f7ccd562160f1a54f64a81e90f5d9af9) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3531965.svg)](https://doi.org/10.5281/zenodo.3531965) SHARPy is a nonlinear aeroelastic analysis package originally developed at the Department of Aeronautics, Imperial College London. It can be used for the structural, aerodynamic and aeroelastic analysis of flexible wings, aircraft and wind turbines. It is shared here under a BSD 3-Clause permissive license. ![XHALE](./docs/source/_static/XHALE-render.jpg) ### Contact For more information on the [research team](http://www.imperial.ac.uk/aeroelastics/software/) developing SHARPy or to get in touch, [visit our homepage](http://www.imperial.ac.uk/aeroelastics). ## Physical Models SHARPy is a modular aeroelastic solver that currently uses two specific models for the structural and aerodynamic response of the system. For the structural model, SHARPy employs a geometrically-exact displacement-based composite beam formulation, augmented with Lagrange multipliers for additional kinematic constraints. This model has the advantage of providing the solution directly in the physical problem's degrees of freedom, making the coupling with the aerodynamic solver simple and not requiring any post-processing. The 1D beam formulation used limits the analyses that can be done by SHARPy to slender structures, such as high aspect ratio wings. The aerodynamic model utilises the Unsteady Vortex Lattice Method (UVLM). The aerodynamic surfaces are modelled as a thin vortex ring lattice with the boundary conditions enforced at the collocation points in the middle of the vortex rings. The Kutta condition is also enforced at the trailing edge. The wake can be simulated by either additional vortex rings or by infinitely long horseshoe vortices, which are ideally suited for steady simulations only. The aerodynamic model has recently been extended by a linear source panel method (SPM) to model nonlifting bodies for example fuselages. The SPM and UVLM can be coupled to model fuselage-wing configuration and a junction handling approach, based on phantom panels and circulation interpolation, has been added. The input problems can be structural, aerodynamic or coupled, yielding an aeroelastic system. ## [Capabilities](http://ic-sharpy.readthedocs.io/en/latest/content/capabilities.html) The base solver SHARPy is a nonlinear aeroelastic analysis package that can be used on free-flying flexible aircraft, wings and wind turbines. In addition, it supports linearisation of these nonlinear systems about arbitrary conditions and includes various tools such as: model reduction or frequency analysis. In short, SHARPy offers (amongst others) the following solutions to the user: * Static aerodynamic, structural and aeroelastic solutions including fuselage effects * Finding trim conditions for aeroelastic configurations * Nonlinear, dynamic time domain simulations under a large number of conditions such as: + Prescribed trajectories. + Free flight. + Dynamic follower forces. + Control inputs in thrust, control surface deflection... + Arbitrary time-domain gusts, including non span-constant ones. + Full 3D turbulent fields. * Multibody dynamics with hinges, articulations and prescribed nodal motions: + Applicable to wind turbines. + Hinged aircraft. + Catapult assisted takeoffs. * Linear analysis: + Linearisation around a nonlinear equilibrium. + Frequency response analysis. + Asymptotic stability analysis. * Model order reduction: + Krylov-subspace reduction methods. + Several balancing reduction methods. ## Documentation The documentation for SHARPy can be found [here](http://ic-sharpy.readthedocs.io). ## Installing SHARPy For the latest documentation, see the [installation docs](https://ic-sharpy.readthedocs.io/en/latest/content/installation.html). SHARPy can also be obtained from Docker Hub to avoid compilation and platform-dependant issues. If you are interested, make sure you check the [SHARPy Docker distribution docs](https://ic-sharpy.readthedocs.io/en/latest/content/installation.html#using-sharpy-from-a-docker-container). ## Contributing and Bug reports If you think you can add a useful feature to SHARPy, want to write documentation or you encounter a bug, by all means, check out the [collaboration guide](https://ic-sharpy.readthedocs.io/en/latest/content/contributing.html). ## Citing SHARPy SHARPy has been published in the Journal of Open Source Software (JOSS) and the relevant paper can be found [here](https://joss.theoj.org/papers/10.21105/joss.01885). If you are using SHARPy for your work, please remember to cite it using the paper in JOSS as: `del Carre et al., (2019). SHARPy: A dynamic aeroelastic simulation toolbox for very flexible aircraft and wind turbines. Journal of Open Source Software, 4(44), 1885, https://doi.org/10.21105/joss.01885` The bibtex entry for this citation is: ``` @Article{delCarre2019, doi = {10.21105/joss.01885}, url = {https://doi.org/10.21105/joss.01885}, year = {2019}, month = dec, publisher = {The Open Journal}, volume = {4}, number = {44}, pages = {1885}, author = {Alfonso del Carre and Arturo Mu{\~{n}}oz-Sim\'on and Norberto Goizueta and Rafael Palacios}, title = {{SHARPy}: A dynamic aeroelastic simulation toolbox for very flexible aircraft and wind turbines}, journal = {Journal of Open Source Software} } ``` ## Continuous Integration Status SHARPy uses Continuous Integration to control the integrity of its code. The status in the release and develop branches is: Main ![Build Status](https://github.com/ImperialCollegeLondon/sharpy/actions/workflows/sharpy_tests.yaml/badge.svg) ![Docker Status](https://github.com/ImperialCollegeLondon/sharpy/actions/workflows/docker_build.yaml/badge.svg) Develop ![Build Status](https://github.com/ImperialCollegeLondon/sharpy/actions/workflows/sharpy_tests.yaml/badge.svg?branch=develop) ================================================ FILE: docs/.nojekyll ================================================ ================================================ FILE: docs/JOSS/codemeta.json ================================================ { "@context": "https://raw.githubusercontent.com/codemeta/codemeta/master/codemeta.jsonld", "@type": "Code", "author": [ "Alfonso del Carre", "Arturo Muñoz-Simón", "Norberto Goizueta", "Rafael Palacios" ], "identifier": "", "codeRepository": "https://github.com/imperialcollegelondon/sharpy", "datePublished": "2019-11-04", "dateModified": "2019-11-04", "dateCreated": "2019-11-04", "description": "A dynamic aeroelastic simulation toolbox for very flexible aircraft and wind turbines", "keywords": "aeroelasticity, structural dynamics, aerodynamics, solar flight, perpetual flight, wing energy", "license": "BSD-3", "title": "SHARPy", "version": "v1.0.0-rc1" } ================================================ FILE: docs/JOSS/generate.rb ================================================ #!/usr/bin/ruby # For an OO language, this is distinctly procedural. Should probably fix that. require 'json' details = Hash.new({}) capture_params = [ { :name => "title", :message => "Enter project name." }, { :name => "url", :message => "Enter the URL of the project repository." }, { :name => "description", :message => "Enter the (short) project description." }, { :name => "license", :message => "Enter the license this software shared under. (hit enter to skip)\nFor example MIT, BSD, GPL v3.0, Apache 2.0" }, { :name => "doi", :message => "Enter the DOI of the archived version of this code. (hit enter to skip)\nFor example http://dx.doi.org/10.6084/m9.figshare.828487" }, { :name => "keywords", :message => "Enter keywords that should be associated with this project (hit enter to skip)\nComma-separated, for example: turkey, chicken, pot pie" }, { :name => "version", :message => "Enter the version of your software (hit enter to skip)\nSEMVER preferred: http://semver.org e.g. v1.0.0" } ] puts "I'm going to try and help you prepare some things for your JOSS submission" puts "If all goes well then we'll have a nice codemeta.json file soon..." puts "" puts "************************************" puts "* First, some basic details *" puts "************************************" puts "" # Loop through the desired captures and print out for clarity capture_params.each do |param| puts param[:message] print "> " input = gets details[param[:name]] = input.chomp puts "" puts "OK, your project has #{param[:name]}: #{input}" puts "" end puts "" puts "************************************" puts "* Experimental stuff *" puts "************************************" puts "" puts "Would you like me to try and build a list of authors for you?" puts "(You need to be running this script in a git repository for this to work)" print "> (Y/N)" answer = gets.chomp case answer.downcase when "y", "yes" # Use git shortlog to extract a list of author names and commit counts. # Note we don't extract emails here as there's often different emails for # each user. Instead we capture emails at the end. git_log = `git shortlog --summary --numbered --no-merges` # ["252\tMichael Jackson", "151\tMC Hammer"] authors_and_counts = git_log.split("\n").map(&:strip) authors_and_counts.each do |author_count| count, author = author_count.split("\t").map(&:strip) puts "Looks like #{author} made #{count} commits" puts "Add them to the output?" print "> (Y/N)" answer = gets.chomp # If a user chooses to add this author to the output then we ask for some # additional information including their email, ORCID and affiliation. case answer.downcase when "y", "yes" puts "What is #{author}'s email address? (hit enter to skip)" print "> " email = gets.chomp puts "What is #{author}'s ORCID? (hit enter to skip)" puts "For example: http://orcid.org/0000-0000-0000-0000" print "> " orcid = gets.chomp puts "What is #{author}'s affiliation? (hit enter to skip)" print "> " affiliation = gets.chomp details['authors'].merge!(author => { 'commits' => count, 'email' => email, 'orcid' => orcid, 'affiliation' => affiliation }) when "n", "no" puts "OK boss..." puts "" end end when "n", "no" puts "OK boss..." puts "" end puts "Reticulating splines" 5.times do print "." sleep 0.5 end puts "" puts "Generating some JSON goodness..." # TODO: work out how to use some kind of JSON template here. # Build the output list of authors from the inputs we've collected. output_authors = [] details['authors'].each do |author_name, values| entry = { "@id" => values['orcid'], "@type" => "Person", "email" => values['email'], "name" => author_name, "affiliation" => values['affiliation'] } output_authors << entry end # TODO: this is currently a static template (written out here). It would be good # to do something smarter here. output = { "@context" => "https://raw.githubusercontent.com/codemeta/codemeta/master/codemeta.jsonld", "@type" => "Code", "author" => output_authors, "identifier" => details['doi'], "codeRepository" => details['url'], "datePublished" => Time.now.strftime("%Y-%m-%d"), "dateModified" => Time.now.strftime("%Y-%m-%d"), "dateCreated" => Time.now.strftime("%Y-%m-%d"), "description" => details['description'], "keywords" => details['keywords'], "license" => details['license'], "title" => details['title'], "version" => details['version'] } File.open('codemeta.json', 'w') {|f| f.write(JSON.pretty_generate(output)) } ================================================ FILE: docs/JOSS/paper.bib ================================================ % Encoding: UTF-7 @Article{Patil2001, author = {Mayuresh J. Patil and Dewey H. Hodges and Carlos E. S. Cesnik}, title = {Nonlinear Aeroelasticity and Flight Dynamics of High-Altitude Long-Endurance Aircraft}, journal = {Journal of Aircraft}, year = {2001}, volume = {38}, number = {1}, pages = {88--94}, month = {jan}, doi = {10.2514/2.2738}, groups = {IFASD 2019}, publisher = {American Institute of Aeronautics and Astronautics ({AIAA})}, } @InProceedings{Simpson2013, author = {Robert J. Simpson and Rafael Palacios}, title = {Numerical aspects of nonlinear flexible aircraft flight dynamics modeling}, booktitle = {54th {AIAA}/{ASME}/{ASCE}/{AHS}/{ASC} Structures, Structural Dynamics, and Materials Conference}, year = {2013}, month = {apr}, publisher = {American Institute of Aeronautics and Astronautics}, doi = {10.2514/6.2013-1634}, groups = {IFASD 2019}, } @Article{Jones2015, author = {J. R. Jones and C. E. S. Cesnik}, title = {Preliminary flight test correlations of the {X-HALE} aeroelastic experiment}, journal = {The Aeronautical Journal}, year = {2015}, volume = {119}, number = {1217}, pages = {855--870}, month = {jul}, doi = {10.1017/s0001924000010952}, groups = {IFASD 2019}, publisher = {Cambridge University Press ({CUP})}, } @PhdThesis{Jones2017, author = {Jessica Renee Jones}, title = {Development of a Very Flexible Testbed Aircraft for the Validation of Nonlinear Aeroelastic Codes}, school = {University of Michigan}, year = {2017}, groups = {IFASD 2019}, } @InProceedings{Cesnik2011, author = {Carlos Cesnik and Weihua Su}, title = {Nonlinear Aeroelastic Simulation of X-{HALE}: a Very Flexible {UAV}}, booktitle = {49th {AIAA} Aerospace Sciences Meeting including the New Horizons Forum and Aerospace Exposition}, year = {2011}, month = {jan}, publisher = {American Institute of Aeronautics and Astronautics}, doi = {10.2514/6.2011-1226}, groups = {IFASD 2019}, } @inproceedings{Drela1999, doi = {10.2514/6.1999-1394}, url = {https://doi.org/10.2514/6.1999-1394}, year = {1999}, month = apr, publisher = {American Institute of Aeronautics and Astronautics}, author = {Mark Drela}, title = {Integrated simulation model for preliminary aerodynamic, structural, and control-law design of aircraft}, booktitle = {{40th Structures, Structural Dynamics, and Materials Conference and Exhibit}} } @PhdThesis{Shearer2006, author = {Christopher M. Shearer}, title = {Coupled nonlinear flight dynamics, aeroelasticity, and control of very flexible aircraft}, school = {University of Michigan}, year = {2006}, groups = {IFASD 2019}, } @Article{Cesnik2012, author = {Carlos E. S. Cesnik and Patrick J. Senatore and Weihua Su and Ella M. Atkins and Christopher M. Shearer}, title = {X-{HALE}: A Very Flexible Unmanned Aerial Vehicle for Nonlinear Aeroelastic Tests}, journal = {{AIAA} Journal}, year = {2012}, volume = {50}, number = {12}, pages = {2820--2833}, month = {dec}, doi = {10.2514/1.j051392}, groups = {IFASD 2019}, publisher = {American Institute of Aeronautics and Astronautics ({AIAA})}, } @Article{Cesnik2014, author = {Carlos E. S. Cesnik and Rafael Palacios and Eric Y. Reichenbach}, title = {Reexamined Structural Design Procedures for Very Flexible Aircraft}, journal = {Journal of Aircraft}, year = {2014}, volume = {51}, number = {5}, pages = {1580--1591}, month = {sep}, doi = {10.2514/1.c032464}, groups = {IFASD 2019}, publisher = {American Institute of Aeronautics and Astronautics ({AIAA})}, } @Article{Su2011, author = {Weihua Su and Carlos E. S. Cesnik}, title = {Dynamic Response of Highly Flexible Flying Wings}, journal = {{AIAA} Journal}, year = {2011}, volume = {49}, number = {2}, pages = {324--339}, month = {feb}, doi = {10.2514/1.j050496}, groups = {IFASD 2019}, publisher = {American Institute of Aeronautics and Astronautics ({AIAA})}, } @InProceedings{Dillsaver2013, author = {Matthew Dillsaver and Carlos E. S. Cesnik and Ilya Kolmanovsky}, title = {Trajectory Control of Very Flexible Aircraft with Gust Disturbance}, booktitle = {{AIAA} Atmospheric Flight Mechanics ({AFM}) Conference}, year = {2013}, month = {aug}, publisher = {American Institute of Aeronautics and Astronautics}, doi = {10.2514/6.2013-4745}, groups = {IFASD 2019}, } @article{Murua2012a, author = {Murua, Joseba and Palacios, Rafael and Graham, J. Michael R.}, doi = {10.1016/j.paerosci.2012.06.001}, journal = {Progress in Aerospace Sciences}, month = {nov}, pages = {46--72}, title = {{Applications of the unsteady vortex-lattice method in aircraft aeroelasticity and flight dynamics}}, volume = {55}, year = {2012} } @ARTICLE{deskos2019, author = {{Deskos}, Georgios and {del Carre}, Alfonso and {Palacios}, Rafael}, title = "{Assessment of low-altitude atmospheric turbulence models for aircraft aeroelasticity}", journal = {arXiv e-prints}, keywords = {Physics - Fluid Dynamics}, year = "2019", month = "Aug", eid = {arXiv:1908.00372}, pages = {arXiv:1908.00372}, archivePrefix = {arXiv}, eprint = {1908.00372}, primaryClass = {physics.flu-dyn}, adsurl = {https://ui.adsabs.harvard.edu/abs/2019arXiv190800372D}, adsnote = {Provided by the SAO/NASA Astrophysics Data System} } @Article{Palacios2010, author = {Rafael Palacios and Joseba Murua and Robert Cook}, title = {Structural and Aerodynamic Models in Nonlinear Flight Dynamics of Very Flexible Aircraft}, journal = {{AIAA} Journal}, year = {2010}, volume = {48}, number = {11}, pages = {2648--2659}, month = {nov}, doi = {10.2514/1.j050513}, groups = {IFASD 2019}, publisher = {American Institute of Aeronautics and Astronautics ({AIAA})}, } @InProceedings{Teixeira2018, author = {Patricia Teixeira and Carlos E. S. Cesnik}, title = {Propeller Effects on the Dynamic Response of {HALE} Aircraft}, booktitle = {2018 {AIAA}/{ASCE}/{AHS}/{ASC} Structures, Structural Dynamics, and Materials Conference}, year = {2018}, month = {jan}, publisher = {American Institute of Aeronautics and Astronautics}, doi = {10.2514/6.2018-1202}, groups = {IFASD 2019}, } @Article{Cook2013, author = {Robert G. Cook and Rafael Palacios and Paul Goulart}, title = {Robust Gust Alleviation and Stabilization of Very Flexible Aircraft}, journal = {{AIAA} Journal}, year = {2013}, volume = {51}, number = {2}, pages = {330--340}, month = {feb}, doi = {10.2514/1.j051697}, publisher = {American Institute of Aeronautics and Astronautics ({AIAA})}, } @InProceedings{Patil2006, author = {Mayuresh Patil and Dana Taylor}, title = {Gust Response of Highly Flexible Aircraft}, booktitle = {47th AIAA/ASME/ASCE/AHS/ASC Structures, Structural Dynamics, and Materials Conference Newport, Rhode Island}, year = {2006}, month = {may}, publisher = {American Institute of Aeronautics and Astronautics}, doi = {10.2514/6.2002-1719}, } @InProceedings{Cesnik2005, author = {Cesnik, Carlos E. S. and Brown, Eric L.}, title = {Modelling of High Aspect Ratio Active Flexible Wings for Roll Control}, booktitle = {43rd Structures, Structural Dynamics, and Material Conference}, year = {2002}, month = {apr}, publisher = {American Institute of Aeronautics and Astronautics}, doi = {10.2514/6.2006-1638}, address = {Denver, CO}, } @article{Hesse2014a, abstract = {This paper investigates the linearization, using perturbation methods, of the structural deformations in the nonlinear flight dynamic response of aircraft with slender, flexible wings. The starting point is the coupling of a displacement-based geometrically nonlinear flexible-body dynamics formulation with a three-dimensional unsteady vortex lattice method. This is followed by a linearization of the structural degrees of freedom, which are assumed to be small in a body-fixed reference frame. The translations and rotations of that reference frame and their time derivatives, which describe the vehicle flight dynamics, can still be arbitrarily large. The resulting system preserves all couplings between rigid and elastic motions and can be projected onto a few vibration modes of the unconstrained aircraft with geometrically nonlinear static deflections at a trim condition. Equally, the unsteady aerodynamics can be approximated on a fixed lattice defined by the deformed static geometry. Numerical studies on a representative highaltitude long-endurance aircraft are presented to illustrate the approach. The results show an improvement compared to those obtained using the mean-axes approximation. {\textcopyright} 2013 by Henrik Hesse, Rafael Palacios, and Joseba Murua.}, author = {Hesse, Henrik and Palacios, Rafael and Murua, Joseba}, doi = {10.2514/1.J052316}, journal = {AIAA Journal}, month = {mar}, number = {3}, pages = {528--538}, title = {Consistent Structural Linearization in Flexible Aircraft Dynamics with Large Rigid-Body Motion}, volume = {52}, year = {2014} } @article{Su2011b, abstract = {}, author = {Su, Weihua and Cesnik, Carlos E. S.}, doi = {10.1016/j.ijsolstr.2011.04.012}, journal = {International Journal of Solids and Structures}, pages = {2349–-2360}, title = {{Strain-based Geometrically Nonlinear Beam Formulation for Modeling Very Flexible Aircraft}}, url = {http://arc.aiaa.org/doi/10.2514/1.C032477}, volume = {48}, year = {2012} } @article{Su2012, abstract = {A strain-based geometrically nonlinear beam formulation for structural dynamic and aeroelastic analysis of slender beams and wings has been successfully applied to study the coupled nonlinear aeroelasticity and flight dynamics of different very flexible aircraft. As an extension to the solution technique based on the nonlinear finite element developed by the authors, a modal-based approach is discussed in this paper to solve the strain-based nonlinear beam equations. The modal approach is applied to study geometrically nonlinear static and transient problems of constrained and free slender beams, subject to external structural or aero-dynamic excitations. The efficiency of the modal solution is also compared to that of the finite-element approach.}, author = {Su, Weihua and Cesnik, Carlos E. S.}, doi = {10.2514/1.C032477}, isbn = {9781600869372 (ISBN)}, issn = {0021-8669}, journal = {Journal of Aircraft}, number = {3}, pages = {890--903}, title = {{Strain-Based Analysis for Geometrically Nonlinear Beams: A Modal Approach}}, url = {http://arc.aiaa.org/doi/10.2514/1.C032477}, volume = {51}, year = {2012} } @article{Su2010, abstract = {Blended-wing-body (BWB) aircraft with high-aspect-ratio wings is an important configu- ration for high-altitude long-endurance unmanned aerial vehicles (HALE UAV). Recently, Northrop Grumann created a wind tunnel model under the Air Force's High Lift over Drag Active (HiLDA) Wing program to study the aeroelastic characteristics of blended-wing-body for a potential Sensorcraft concept. This paper presents a study on the coupled aeroelastic / flight dynamics stability and response of a BWB aircraft that is modified from the HiLDA experimental model. An effective method is used to model very flexible BWB vehicles based on a low-order aeroelastic formulation that is capable of capturing the important structural nonlinear effects and couplings with the flight dynamics degrees of freedom. A nonlinear strain-based beam finite element formulation is used. Finite-state unsteady subsonic aerodynamic loads are incorporated to be coupled with all lifting surfaces, including the flexible body. Based on the proposed model, body-freedom flutter is studied, and is compared with the flutter results with all or partial rigid-body degrees of freedom constrained. The applicability of wind tunnel aeroelastic results (where the rigid-body motion is limited) is discussed in view of the free flight conditions (with all 6 rigid-body degrees of freedom). Furthermore, effects of structural and aerodynamic nonlinearities as well as wing bending/torsion rigidity coupling on the stability and gust response are also studied in this paper. I.}, author = {Su, Weihua and Cesnik, Carlos E. S.}, doi = {10.2514/1.47317}, isbn = {9781563479731}, issn = {0021-8669}, journal = {Journal of Aircraft}, number = {5}, pages = {1539--1553}, title = {{Nonlinear Aeroelasticity of a Very Flexible Blended-Wing-Body Aircraft}}, url = {http://arc.aiaa.org/doi/10.2514/1.47317}, volume = {47}, year = {2010} } @article{Shearer2007, author = {Shearer, Christopher M. and Cesnik, Carlos E.S.}, doi = {10.2514/1.27606}, isbn = {0021-8669}, issn = {0021-8669}, journal = {Journal of Aircraft}, number = {5}, pages = {1528--1545}, title = {{Nonlinear Flight Dynamics of Very Flexible Aircraft}}, url = {http://arc.aiaa.org/doi/10.2514/1.27606}, volume = {44}, year = {2007} } @article{Teixeira2017, author = {Teixeira, Patr{\'{i}}cia C and Cesnik, Carlos E S}, journal = {International Forum on Aeroelasticity and Structural Dynamics (IFASD)}, keywords = {abstract,aerodynamic propeller effects,aerovironments helios,aircraft,although propeller propulsion is,coupled nonlinear,e,etc,g,long-endurance,of such vehicles for,of the propeller slipstream,on the aeroelastic behavior,particle vortex aerodynamics,present in many high-altitude,s x-hale uas,the effect,university of michigan,very flexible wing}, number = {June}, pages = {1--18}, title = {{Inclusion of Propeller Effects on Aeroelastic Behavior of Very Flexible Aircraft}}, year = {2017} } @article{Peters1994, author = {A. Peters, David and J. Johnson, Mark}, year = {1994}, month = {01}, pages = {1-28}, title = {Finite-state airloads for deformable airfoils on fixed and rotating wings}, volume = {44}, booktitle = {American Society of Mechanical Engineers, Aerospace Division (Publication) AD} } @phdthesis{Skujins2012, address = {Ann Arbor, MI}, author = {Skujins, Torstens}, booktitle = {Aerospace Engineering}, publisher = {University of Michigan}, title = {{Reduced-Order Modeling of Unsteady Aerodynamics Across Multiple Mach Regimes}}, volume = {Ph. D.}, year = {2012} } @book{geradin2001, title={Flexible multibody dynamics: a finite element approach}, author={G{\'e}radin, M. and Cardona, A.}, isbn={9780471489900}, lccn={00043685}, year={2001}, publisher={John Wiley} } @phdthesis{Murua2012, title={Flexible aircraft dynamics with a geometrically-nonlinear description of the unsteady aerodynamics}, author={Murua, Joseba}, year={2012}, school={Imperial College London} } @MISC{eigenweb, author = {Ga\"{e}l Guennebaud and Beno\^{i}t Jacob and others}, title = {Eigen v3}, howpublished = {http://eigen.tuxfamily.org}, year = {2010} } @article{Hesse2016, doi = {10.2514/1.g000715}, url = {https://doi.org/10.2514/1.g000715}, year = {2016}, month = apr, publisher = {American Institute of Aeronautics and Astronautics ({AIAA})}, volume = {39}, number = {4}, pages = {801--813}, author = {Henrik Hesse and Rafael Palacios}, title = {Dynamic Load Alleviation in Wake Vortex Encounters}, journal = {Journal of Guidance, Control, and Dynamics} } @inproceedings{del2019efficient, title={Low-Altitude Dynamics of Very Flexible Aircraft}, author={Del Carre, Alfonso and Palacios, Rafael}, address={San Diego, California}, year={2019}, publisher = {American Institute of Aeronautics and Astronautics}, doi={10.2514/6.2019-2038}, crossref={scitech19} } @proceedings{scitech19, title={{AIAA SciTech 2019 Forum}}, publisher = {American Institute of Aeronautics and Astronautics}, venue={San Diego, California}, year={2019} } @article{melin2013tornado, title={Tornado}, author={Melin, Tomas}, journal={KTH, Stockholm, Sweden, Masters Thesis and continued development. {http://www.flyg.kth.se/divisions/aero/software/tornado}}, year={2013} } @article{Murua2012-2, author = {Murua, Joseba and Palacios, Rafael and R. Graham, J. Michael}, title = {Assessment of Wake-Tail Interference Effects on the Dynamics of Flexible Aircraft}, journal = {AIAA Journal}, volume = {50}, number = {7}, pages = {1575-1585}, year = {2012}, doi = {10.2514/1.J051543}, URL = { https://doi.org/10.2514/1.J051543 }, eprint = { https://doi.org/10.2514/1.J051543 } } @article{Simpson2013-2, author = {Simpson, Robert J. S. and Palacios, Rafael and Murua, Joseba}, title = {Induced-Drag Calculations in the Unsteady Vortex Lattice Method}, journal = {AIAA Journal}, volume = {51}, number = {7}, pages = {1775-1779}, year = {2013}, doi = {10.2514/1.J052136}, URL = { https://doi.org/10.2514/1.J052136 }, eprint = { https://doi.org/10.2514/1.J052136 } } @book{Katz2001, title={Low-Speed Aerodynamics}, author={Katz, J. and Plotkin, A.}, isbn={9781107717428}, series={{Cambridge Aerospace Series}}, year={2001}, publisher={Cambridge University Press} } @book{etkin1982dynamics, title={Dynamics of flight: stability and control}, author={Etkin, B.}, isbn={9780471089360}, lccn={81013058}, url={https://books.google.co.uk/books?id=4n5TAAAAMAAJ}, year={1982}, publisher={John Wiley \& Sons Australia, Limited} } @inbook{DeMarco2007, author = {Agostino De Marco and Eugene Duke and Jon Berndt}, title = {A General Solution to the Aircraft Trim Problem}, booktitle = {AIAA Modeling and Simulation Technologies Conference and Exhibit}, chapter = {}, year={2007}, pages = {}, doi = {10.2514/6.2007-6703}, URL = {https://arc.aiaa.org/doi/abs/10.2514/6.2007-6703}, eprint = {https://arc.aiaa.org/doi/pdf/10.2514/6.2007-6703} } @book{wiener1950, title={Extrapolation, interpolation, and smoothing of stationary time series: with engineering applications}, author={Wiener, Norbert and Mass.) Massachusetts Institute of Technology (Cambridge}, year={1950}, publisher={Technology Press} } @article{broyden1965class, title={A class of methods for solving nonlinear simultaneous equations}, author={Broyden, Charles G}, journal={Mathematics of computation}, volume={19}, number={92}, pages={577--593}, year={1965}, publisher={JSTOR} } @Article{faa, title = {Dynamic Gust Loads - {AC 25.341-1}}, author={{FAA}}, journal = {{FAA} Advisory Circular}, year = {2014} } @inproceedings{del2019ifasd, title={Nonlinear response of a very flexible aircraft under lateal gust}, author={Del Carre, Alfonso and Teixeira, Patricia, and Cesnik, Carlos E. S. and Palacios, Rafael}, booktitle={{IFASD 2019 Forum}}, address={Savannah, Georgia}, year={2019}, publisher = {}, doi={} } @Article{vonKarman1948, author = {von K{\'a}rm{\'a}n, Theodore}, title = {Progress in the Statistical Theory of Turbulence.}, journal = {Proceedings of the National Academy of Sciences of the United States of America}, year = {1948}, volume = {11}, number = {34}, pages = {530-539}, issn = {0027-8424}, } @article{KaimalEtAl1972, author = {Kaimal, J. C. and Wyngaard, J. C. and Izumi, Y. and Coté, O. R.}, title = {Spectral characteristics of surface-layer turbulence}, journal = {Quarterly Journal of the Royal Meteorological Society}, volume = {98}, number = {417}, pages = {563-589}, doi = {10.1002/qj.49709841707}, abstract = {Abstract The behaviour of spectra and cospectra of turbulence in the surface layer is described within the framework of similarity theory using wind and temperature fluctuation data obtained in the 1968 AFCRL Kansas experiments. With appropriate normalization, the spectra and cospectra are each reduced to a family of curves which spread out according to z/L at low frequencies but converge to a single universal curve in the inertial subrange. The paper compares these results with data obtained by other investigators over both land and water. Spectral constants for velocity and temperature are determined and the variability in the recent estimates of the constants is discussed. The high-frequency behaviour is consistent with local isotropy. In the inertial subrange, where the spectra fall as n−5/3, the cospectra fall faster: uω and ωθ as n−7/3, and uθ, on the average, as n−5/2. The 4/3 ratio between the transverse and longitudinal spectral levels is observed at wavelengths of the order of the height above ground under unstable conditions and at wavelengths of the order of L/10 under stable conditions. This lower isotropic limit is shown to be governed by the combined effects of shear and buoyancy on small-scale eddies.}, year = {1972} } @Article{GonzaleEtAl2018, author = {Jes\'{u}s Gonzalo and Deibi L\'{o}pez and Diego Dom\'{i}nguez and Adri\'{a}n Garc\'{i}a and Alberto Escapa}, title = {On the capabilities and limitations of high altitude pseudo-satellites}, journal = {Progress in Aerospace Sciences}, year = {2018}, volume = {98}, pages = {37 - 56}, issn = {0376-0421}, doi = {https://doi.org/10.1016/j.paerosci.2018.03.006}, abstract = {The idea of self-sustaining air vehicles that excited engineers in the seventies has nowadays become a reality as proved by several initiatives worldwide. High altitude platforms, or Pseudo-satellites (HAPS), are unmanned vehicles that take advantage of weak stratospheric winds and solar energy to operate without interfering with current commercial aviation and with enough endurance to provide long-term services as satellites do. Target applications are communications, Earth observation, positioning and science among others. This paper reviews the major characteristics of stratospheric flight, where airplanes and airships will compete for best performance. The careful analysis of involved technologies and their trends allow budget models to shed light on the capabilities and limitations of each solution. Aerodynamics and aerostatics, structures and materials, propulsion, energy management, thermal control, flight management and ground infrastructures are the critical elements revisited to assess current status and expected short-term evolutions. Stratospheric airplanes require very light wing loading, which has been demonstrated to be feasible but currently limits their payload mass to few tenths of kilograms. On the other hand, airships need to be large and operationally complex but their potential to hover carrying hundreds of kilograms with reasonable power supply make them true pseudo-satellites with enormous commercial interest. This paper provides useful information on the relative importance of the technology evolutions, as well as on the selection of the proper platform for each application or set of payload requirements. The authors envisage prompt availability of both types of HAPS, aerodynamic and aerostatic, providing unprecedented services.}, keywords = {High altitude platforms, Pseudo-satellite, HAPS, Stratospheric flight, Long endurance, Solar-powered}, } @article{Maraniello2019, title = {State-Space Realizations and Internal Balancing in Potential-Flow Aerodynamics with Arbitrary Kinematics}, year = {2019}, journal = {AIAA Journal}, author = {Maraniello, Salvatore and Palacios, Rafael}, number = {6}, pages = {1--14}, volume = {57}, url = {https://arc.aiaa.org/doi/10.2514/1.J058153}, doi = {10.2514/1.J058153}, issn = {0001-1452} } @misc{openfast, title = {{NWTC Information Portal (OpenFAST)}}, author = {{National Wind Technology Center (NWTC)}}, url = {https://nwtc.nrel.gov/OpenFAST}, note = {Accessed: 2019-11-25}} ================================================ FILE: docs/JOSS/paper.md ================================================ --- title: 'SHARPy: A dynamic aeroelastic simulation toolbox for very flexible aircraft and wind turbines' tags: - aeroelasticity - structural dynamics - aerodynamics - solar flight - wind energy authors: - name: Alfonso del Carre orcid: 0000-0002-8133-9481 affiliation: 1 - name: Arturo Muñoz-Simón orcid: 0000-0003-4840-5392 affiliation: 1 - name: Norberto Goizueta orcid: 0000-0001-7445-5970 affiliation: 1 - name: Rafael Palacios orcid: 0000-0002-6706-3220 affiliation: 1 affiliations: - name: Department of Aeronautics, Imperial College London. London, UK. index: 1 date: 13 August 2019 bibliography: paper.bib --- # Summary Aeroelasticity is the study of the dynamic interaction between unsteady aerodynamics and structural dynamics on flexible streamlined bodies, which may include rigid-body dynamics. Industry standard solutions in aeronautics and wind energy are built on the assumption of small structural displacements, which lead to linear or quasi-linear theories. However, advances in areas such as energy storage and generation, and composite material manufacturing have fostered a new kind of aeroelastic structures that may undergo large displacements under aerodynamic forces. In particular, solar-powered High-Altitude Long-Endurance (HALE) aircraft have recently seen very significant progress. New configurations are now able to stay airborne for longer than three weeks at a time. Extreme efficiency is achieved by reducing the total weight of the aircraft while increasing the lifting surfaces' aspect ratio. In a similar quest for extreme efficiency, the wind energy industry is also trending towards longer and more slender blades, specially for off-shore applications, where the largest blades are now close to 100-m long. These longer and more slender structures can present large deflections and have relatively low frequency structural modes which, in the case of aircraft, can interact with the flight dynamics modes with potentially unstable couplings. In the case of offshore wind turbines, platform movement may generate important rotor excursions that cause complex aeroelastic phenomena which conventional quasi-linear methods may not accurately capture. ``SHARPy`` (Simulation of High-Aspect Ratio aeroplanes in Python) is a dynamic aeroelasticity simulation toolbox for aircraft and wind turbines. It features a versatile interface and core code written in Python 3, while computationally expensive routines are included in libraries coded in C++ and Modern Fortran. SHARPy is easily extended through a modular object-oriented design, and includes tools for linear and nonlinear analysis of the time-domain aeroelastic response of flexible bodies in a large number of cases, such as 3-D discrete gust [@del2019ifasd], turbulent field input [@Hesse2016; @deskos2019], control surface deflection and prescribed motion [@del2019efficient]. In addition, linearised state-space models can be obtained for frequency domain analysis, controller design and model reduction. Few open source options are available for nonlinear aeroelastic analysis. A well known code for wind energy is OpenFAST [@openfast], developed at NREL and distributed under Apache 2.0 license. OpenFAST features a Geometrically-Exact Composite Beam structural model and an actuator-line based aerodynamic solver. The wake is modelled using quasi-steady Blade Element Momentum theory. To the knowledge of the authors, no other nonlinear aeroelasticity framework for aircraft is available under open-source terms. An example of a commonly-used aircraft-oriented software is ASWING [@Drela1999]. It is based on geometrically nonlinear beams with approximated rigid body dynamics. Aerodynamic loads are calculated using a lifting-line theory solver with prescribed coefficients. Lastly, UM/NAST [@del2019ifasd] is a nonlinear aeroelastic code with GECB and UVLM solvers. However, it is a research code which has not been released as open source. ``SHARPy`` relies only on freely-available open-source dependencies such as [Paraview](https://paraview.org) for post-processing The computationally expensive routines written in C++ and Fortran have been designed with Fluid-Structure Interaction (FSI) problems in mind, resulting in minimal overhead between function calls. ## Features The [structural model](https://github.com/imperialcollegelondon/xbeam) included in ``SHARPy`` is a Geometrically-Exact Composite Beam (GECB) [@geradin2001; @Hesse2014a] supports multibody features such as hinges, joints and absolute and relative nodal velocity constraints through Lagrange Multipliers. Rigid body motion can be prescribed or simulated. The structural solver supports distributed and lumped mass formulation (or a combination of both). Time-integration is carried out using a Newmark-$\beta$ scheme. The [aerodynamic solver](https://github.com/imperialcollegelondon/uvlm) is an Unsteady Vortex Lattice Method (UVLM) [@Katz2001; @Simpson2013-2]. It can simulate an arbitrary number of surfaces together with their interactions. A non conventional force evaluation scheme is used [@Simpson2013-2] in order to support large sideslip angles and obtain an induced drag estimation. In addition to this, added mass effects can be obtained and introduced in the FSI problem. This can be especially important in the case of very light flexible aircraft flying at low altitude. The coupling algorithm included in the code is designed to allow fully coupled nonlinear simulations, although weakly coupled solutions can be obtained. Independent structural or aerodynamic simulation are supported natively. The nonlinear system can also be linearised taking an arbitrary reference condition. The linearised system can be used for frequency domain analysis, linear model order reduction methods and controller design. ![Aerodynamic grid and forces in the static aeroelastic equilibrium configuration on the XHALE aircraft [@del2019ifasd]](https://github.com/ImperialCollegeLondon/sharpy/raw/main/docs/source/_static/XHALE-render.jpg) # Acknowledgements A. Carre gratefully acknowledges the support provided by Airbus Defence and Space. Norberto Goizueta's acknowledges and thanks the Department of Aeronautics at Imperial College for sponsoring his research. Arturo Muñoz-Simón's research has received funding from the EU's H2020 research and innovation programme under the Marie Sklodowska-Curie grant agreement 765579. # References ================================================ FILE: docs/Makefile ================================================ # Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SHARPy.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SHARPy.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/SHARPy" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SHARPy" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." ================================================ FILE: docs/docignore.yml ================================================ # Docs to build list # once this is fully implemented this will become the file that specifies which packages are to be ignored for the # purposes of building documentation packages: - folder: 'sharpy/controllers' docs_folder: 'controllers' - folder: 'sharpy/rom' docs_folder: 'rom' modules: - 'sharpy/postproc' - 'sharpy/solvers' - 'sharpy/linear/dev' - 'sharpy/linear/src/uvlmutils.py' - 'sharpy/aero/utils/uvlmlib.py' - 'sharpy/aero/utils/utils.py' - 'sharpy/utils/plot_utils.py' - 'sharpy/utils/solver_interface.py' - 'sharpy/linear/test' - 'sharpy/linear/utils' # - 'sharpy/generators' - 'sharpy/presharpy' - 'sharpy/lib' - 'sharpy/__pycache__' ================================================ FILE: docs/requirements_rtd ================================================ sphinx>=3.0,<7 ipykernel nbsphinx sphinx-rtd-theme>=1.2.2 recommonmark ================================================ FILE: docs/source/_static/.placeholder ================================================ ================================================ FILE: docs/source/_static/sharpy_guide/sharpy_intro.html ================================================ sharpy_intro

Introduction to SHARPy

Version: June 2019

Overview

SHARPy (Simulation of High Aspect Ratio Planes in Python is a framework for linear and nonlinear aeroelastic analysis of flexible structures.

It is developed by the Loads Control and Aeroelasticity lab at the Department of Aeronautics. All the code is open source and readily available in GitHub.

Important links:

Loads Control and Aeroelasticity lab https://imperial.ac.uk/aeroelastics

Main github repository https://github.com/imperialcollegelondon/sharpy

Documentation(!!!) https://ic-sharpy.readthedocs.io

UVLM solver (C++) https://github.com/imperialcollegelondon/uvlm

Structural solver (Fortran) https://github.com/imperialcollegelondon/xbeam

SHARPy is mainly coded in Python, but the expensive routines, such as aero and structural solvers are coded in faster languages such as C++ and Fortran.

A number of different structures and analysis methods can be run. For example:

Very flexible aircraft nonlinear aeroelasticity (Alfonso)

The modular design of SHARPy allows to simulate complex aeroelastic cases involving very flexible aircraft. The structural solver supports very complex beam arrangements, while retaining geometrical nonlinearity. The UVLM solver features different wake modelling fidelities while supporting large lifting surface deformations in a native way.

Among the problems studied, a few interesting ones, in no particular order are:

  • Catapult take off of a very flexible aircraft analysis [Paper]. In this type of simulations, a PID controller was used in order to enforce displacements and velocities in a number of structural nodes (the clamping points). Then, several take off strategies were studied in order to analyse the influence of the structural stiffness in this kind of procedures. This case is a very good example of the type of problems where nonlinear aerolasticity is essential.

  • Flight in full 3D atmospheric boundary layer (to be published). A very flexible aircraft is flown immersed in a turbulent boundary layer obtained from HPC LES simulations. The results are compared against simpler turbulence models such as von Karman and Kaimal. Intermittency and coherence features in the LES field are absent or less remarkable in the synthetic turbulence fields.

  • Lateral gust reponse of a realistic very flexible aircraft. For this problem (to be published), a realistic very flexible aircraft (University of Michigan X-HALE) model has been created in SHARPy and validated against their own aeroelastic solver for static and dynamic cases. A set of vertical and lateral gust responses have been simulated. (Results to be presented at IFASD 2019).

Wind turbine aeroelasticity (Arturo)

SHARPy is suitable to simulate wind turbine aeroelasticity.

On the structural side, it accounts for material anisotropy which is needed to characterize composite blades and for geometrically non-linear deformations observed in current blades due to the increasing length and flexibility. Both rigid and flexible simulations can be performed and the structural modes can be computed accounting for rotational effects (Campbell diagrams). The rotor-tower interaction is modelled through a multibody approach based on the theory of Lagrange multipliers. Finally, he tower base can be fixed or subjected to prescribed linear and angular velocities.

On the aerodynamic side, the use of potential flow theory allows the characterization of flow unsteadiness at a reasonable computational cost. Specifically, steady and dynamic simulations can be performed. The steady simulations are carried out in a non-inertial frame of reference linked to the rotor under uniform steady wind with the assumption of prescribed helicoidal wake. On the other hand, dynamic simulations can be enriched with a wide variety of incoming winds such as shear and yaw. Moreover, the wake shape can be freely computed under no assumptions accounting for self-induction and wake expansion or can be prescribed to an helicoidal shape for computational efficiency.

PD: aft-loaded airfoils can be included through the definition of the camber line of the blades.

Model Order Reduction

Numerical models of physical phenomena require fine discretisations to show convergence and agreement with their real counterparts, and, in the case of SHARPy's aeroelastic systems, hundreds of thousands of states are not an uncommon encounter. However, modern hardware or the use of these models for other applications such as controller synthesis may limit their size, and we must turn to model order reduction techniques to achieve lower dimensional representations that can then be used.

SHARPy offers several model order reduction methods to reduce the initially large system to a lower dimension, attending to the user's requirements of numerical efficiency or global error bound.

Krylov Methods for Model Order Reduction - Moment Matching

Model reduction by moment matching can be seen as approximating a transfer function through a power series expansion about a user defined point in the complex plane. The reduction by projection retains the moments between the full and reduced systems as long as the projection matrices span certain Krylov subspaces dependant on the expansion point and the system's matrices. This can be taken advantage of, in particular for aeroelastic applications where the interest resides in the low frequency behaviour of the system, the ROM can be expanded about these low frequency points discarding accuracy higher up the frequency spectrum.

Example 1 - Aerodynamics - Frequency response of a high AR flat plate subject to a sinusoidal gust

The objective is to compare SHARPy's solution of a very high aspect ratio flat plate subject to a sinusoidal gust to the closed form solution obtained by Sears (1944 - Ref). SHARPy's inherent 3D nature makes comparing results to the 2D solution require very high aspect ratio wings with fine discretisations, resulting in very large state space models. In this case, we would like to utilise a Krylov ROM to approximate the low frequency behaviour and perform a frequency response analysis on the reduced system, since it would represent too much computational cost if it were performed on the full system.

The full order model was reduced utilising Krylov methods, in particular the Arnoldi iteration, with an expansion about zero frequency to produce the following result.

As it can be seen from the image above, the ROM approximates well the low frequency, quasi-steady state and loses accuracy as the frequency is increased, just as intended. Still, perfect matching is never achieved even at the expansion frequency given the 3D nature of the wing compared to the 2D analytical solution.

Example 2 - Aeroelastics - Flutter analysis of a Goland wing with modal projection

The Goland wing flutter example is presented next. The aerodynamic surface is finely discretised for the UVLM solution, resulting in not only a large state space but also in large input/output dimensionality. Therefore, to reduce the number of inputs and outputs, the UVLM is projected onto the structural mode shapes, the first four in this particular case. The resulting multi input multi output system (mode shapes -> UVLM -> modal forces) was subsequently reduced using Krylov methods aimed at MIMO systems which use variations of the block Arnoldi iteration. Again, the expansion frequency selected was the zero frequency. As a sample, the transfer function from two inputs to two outputs is shown to illustrate the performance of the reduced model against the full order UVLM.

The reduced aerodynamic model projected onto the modal shapes was then coupled to the linearised beam model, and the stability analysed against a change in velocity. Note that the UVLM model and its ROM are actually scaled to be independent on the freestream velocity, hence only one UVLM and ROM need to be computed. The structural model needs to be updated at each test velocity but its a lot less costly in computational terms. The resulting stability of the aeroelastic system is plotted on the Argand diagram below with changing freestream velocity.

Flutter speed [m/s] Frequency [rad/s]
164 70.27

SHARPy installation

Check https://ic-sharpy.readthedocs.io/en/latest/content/installation.html for an in-depth guide on how to install SHARPy.

This assumes a few things about your computer:

  • It is Linux or MacOS (definitely not Windows - if you only have Windows, check VirtualBox and make an Ubuntu or CentOS virtual machine).

  • It has an up-to-date compiler (this might not be straightforward, run g++ --version. If lower than 5.0, you will need to update it). An up-to-date Intel Compiler for C++ and Fortran is a good option as well.

SHARPy relies on Anaconda https://www.anaconda.com/distribution/ to handle all the python packages. Install the Python 3 version.

You then need to download the code:

1) Create a folder. Here it will be called code

mkdir code
cd code

2) Clone all the necessary repos and make sure you are in the correct branch -- usually develop

git clone https://github.com/imperialcollegelondon/sharpy --branch=develop
git clone https://github.com/imperialcollegelondon/xbeam --branch=develop
git clone https://github.com/imperialcollegelondon/uvlm --branch=develop

3) Create the conda environment and activate it

conda env create -f sharpy/utils/environment_linux.yml

(or environment_macos.yml if on MacOS.

Now activate the conda environment

conda activate sharpy_env you are going to have to do this every time to start a new terminal and want to run SHARPy

It sometimes fails in the pip stage when installing. If it says something about distwheel failed in mayavi, activate the environment and run pip install mayavi manually.

4) Compile xbeam and uvlm.

Move to the xbeam folder (cd xbeam) and run sh run_make.sh. Wait until it finishes. Please make sure your anaconda env is active before running this.

Now the same for uvlm: cd ../uvlm and sh run_make.sh.

5) SHARPy is now hopefully ready to go!

Navigate to the sharpy folder: cd ../sharpy and run:

source bin/sharpy_vars.sh

This command is important, as it loads the program in the terminal variables.

We can run the tests now:

python -m unittest

If everything has been installed properly, the tests should pass.

----------------------------------------------------------------------
Ran 28 tests in 23.465s

OK

Remember to run every time you start a new terminal:

conda activate sharpy_env
source <path-to-sharpy>/bin/sharpy_vars.sh

Tip: edit your ~\.bashrc (linux) or \.bash_profile (MacOS) and add the following:

alias load_sharpy="conda activate sharpy_env && source <path-to-sharpy>/bin/sharpy_vars.sh"
# Remember to modify the <path-to-sharpy> gap with your path

Then you just need to run in the console load_sharpy to load everything in one go.

Basic cases

A cantilever beam (Geradin)

This case can be found in sharpy/tests/xbeam/geradin, it is part of the test suite.

Basically, it is a 5 metres long cantilevered beam with a mass at the tip. Stiffness properties: $\mathcal{S} = \mathrm{diag}(EI, GA_y, GA_z, GJ, EI_y, EI_z) = \mathrm{diag}(4.8e8, 3.231e8, 3.231e8, 1e6, 9.346e6, 9.346e6)$

There is no distributed mass, only one at the tip. $M = 6e5/9.81$.

The main point about the Geradin beam is that the deflections are past the linear range.

If you open sharpy/tests/xbeam/geradin/generate_geradin.py, you are going to see a basic input file for a structural only case.

The last part is the one that is the most important (function generate_solver_file from line 134). This is where the solver settings and the overall flow of the program is given.

  • The flow variable controls the solvers and postprocessors that are going to be run in this simulation (and in which order). When you run sharpy with a valid header file, you will see a complete list of available solvers:
--------------------------------------------------------------------------------
            ######  ##     ##    ###    ########  ########  ##    ##
           ##    ## ##     ##   ## ##   ##     ## ##     ##  ##  ##
           ##       ##     ##  ##   ##  ##     ## ##     ##   ####
            ######  ######### ##     ## ########  ########     ##
                 ## ##     ## ######### ##   ##   ##           ##
           ##    ## ##     ## ##     ## ##    ##  ##           ##
            ######  ##     ## ##     ## ##     ## ##           ##
--------------------------------------------------------------------------------
Aeroelastics Lab, Aeronautics Department.
    Copyright (c), Imperial College London.
    All rights reserved.
Running SHARPy from /home/ad214/run/code/sharpy/tests/xbeam/geradin
SHARPy being run is in /home/ad214/run/code/sharpy
The branch being run is develop
The version and commit hash are: v0.1-731-g85eb5ab-85eb5ab
The available solvers on this session are:
PreSharpy
AerogridLoader
BeamLoader
Modal
NonLinearDynamic
NonLinearDynamicCoupledStep
NonLinearDynamicPrescribedStep
NonLinearStatic
PIDTrajectoryControl
PrescribedUvlm
StaticCoupled
StaticTrim
StaticUvlm
StepUvlm
Trim
RigidDynamicPrescribedStep
DynamicUVLM
StepLinearUVLM
StaticLinearUvlm
InitializeMultibody
LagrangeMultipliersTrajectoryControl
NonLinearDynamicMultibody
SHWUvlm
StaticCoupledRBM
SteadyHelicoidalWake
DynamicCoupled
AeroForcesCalculator
AerogridPlot
BeamLoads
BeamPlot
Cleanup
SaveData
StallCheck
WriteVariablesTime
PlotFlowField
CreateSnapshot
LiftDistribution

For our simple structural simulation, we only need a few blocks: BeamLoader for reading the input file and generating the beam data structure, NonLinearStatic is the structural solver for nonlinear, static beams, BeamPlot outputs the deformed shape and other quantities to a Paraview file structure (more on this later), and WriteVariablesTime outputs the data we want to text files.

  • The case is a name for the simulation so we can identify results.

  • The route is the path to the .sharpy file, so that SHARPy can find all the other necessary files.

Then, the other parts of the .sharpy (or .sharpy) file are based on the following structure:

[Header_name]
variable1 = 1
variable2 = a
variable3 = [a, a, b]

It is important to note that you need a header per every solver indicated in flow. If that solver needs no settings, you still need to indicate the header.

There is also a main [SHARPy] header that contains the main program settings, such as the flow and the case. It also has a setting called write_screen. It is equal to off because this is a test case, and you don't want tons of output. If you modify it to 'on', you'll be able to see what's going on on the screen.

Simple modifications of the case

If we want to (for example), run a stiffer beam, we can do so easily:

Open generate_geradin.py and move to line 50 more or less where there are a few lines looking like:

ea = ...
ga = ...
#...
ei = ...

and modify them to look like:

stiffness_multiplier = 10
ea = ... * stiffness_multiplier
ga = ... * stiffness_multiplier
#...
ei = ... * stiffness_multiplier

you can also change the name of the case so that the results are not overwritten: In line 6, case_name = geradin_stiff.

Now run again the case. First, generate the files: python generate_geradin.py, and then run the case sharpy geradin_stiff.sharpy.

You can now have a look at the wing tip deformations for both cases in output/<case_name>/WriteVariablesTime/struct_pos_node-1.dat.

Guide to model definition in SHARPy

This section will take a bit of time and is quite tough to follow, but keep this as a reference.

Structural data

The case.fem.h5 file has several components. We go one by one:

  • num_node_elem [int] is always 3 in our case (3 nodes per structural elements - quadratic beam elements).

  • num_elem [int] number of structural elements.

  • num_node [int] number of nodes. For simple structures, it is num_elem*(num_node_elem - 1) - 1. For more complicated ones, you need to calculate it properly.

  • coordinates [num_node, 3] coordinates of the nodes in body-attached FoR.

  • connectivites [num_elem, num_node_elem] a tricky one. Every row refers to an element, and the three integers in that row are the indices of the three nodes belonging to that elem. Now, the catch: the ordering is not as you'd think. Order them as $[0, 2, 1]$. That means, first one, last one, central one. The following image shows the node indices inside the circles representing the nodes, the element indices in blue and the resulting connectivities matrix next to it. Connectivities are tricky when considering complex configurations. Pay attention at the beginning and you'll save yourself a lot of trouble.

  • stiffness_db [:, 6, 6] database of stiffness matrices. The first dimension has as many elements as different stiffness matrices are in the model.

  • elem_stiffness [num_elem] array of indices (starting at 0). Basically, it links every element (index) to the stiffness matrix index in stiffness_db. For example elem_stiffness[0] = 0; elem_stiffness[2] = 1 means that the element 0 has a stiffness matrix equal to stiffness_db[0, :, :], and the second element has a stiffness matrix equal to stiffness_db[1, :, :].

The shape of a stiffness matrix, $\mathrm{S}$ is: $$\mathrm{S} = \begin{bmatrix} EA & & & & & \\ & GA_y & & & & \\ & & GA_z & & & \\ & & & GJ & & \\ & & & & EI_y & \\ & & & & & EI_z \\ \end{bmatrix} $$ with the cross terms added if needed.

  • mass_db and elem_mass follow the same scheme than the stiffness, but the mass matrix is given by: $$\mathrm{M} = \begin{bmatrix} m\mathbf{I} & -\tilde{\xi}_{cg}m \\ \tilde{\xi}_{cg}m & J\\ \end{bmatrix} $$ where $m$ is the distributed mass per unit length [kg/m], $\tilde{\bullet}$ is the skew-symmetric matrix of a vector and $\xi_{cg}$ is the location of the centre of gravity with respect to the elastic axis in MATERIAL (local) FoR.

And what is the Material FoR? This is an important point, because all the inputs that move WITH the beam are in material FoR. For example: follower forces, stiffness, mass, lumped masses...

The material frame of reference is noted as $B$. Essentially, the $x$ component is tangent to the beam in the increasing node ordering, $z$ looks up generally and $y$ is oriented such that the FoR is right handed.

In the practice (vertical surfaces, structural twist effects...) it is more complicated than this. The only sure thing about $B$ is that its $x$ direction is tangent to the beam in the increasing node number direction. However, with just this, we have an infinite number of potential reference frames, with $y$ and $z$ being normal to $x$ but rotating around it. The solution is to indicate a for_delta, or frame of reference delta vector ($\Delta$).

Now we can define unequivocally the material frame of reference. With $x_B$ and $\Delta$ defining a plane, $y_b$ is chosen such that the $z$ component is oriented upwards with respect to the lifting surface.

From this definition comes the only constraint to $\Delta$: it cannot be parallel to $x_B$.

  • frame_fo_reference_delta [num_elem, num_node_elem, 3] contains the $\Delta$ vector in body-attached ($A$) frame of reference. As a rule of thumb: $$\Delta = \left\{ \begin{matrix} [-1, 0, 0], \quad \text{if right wing} \\ [1, 0, 0], \quad \text{if left wing} \\ [0, 1, 0], \quad \text{if fuselage} \\ [-1, 0, 0], \quad \text{if vertical fin} \\ \end{matrix} \right. $$

These rules of thumb only work if the nodes increase towards the tip of the surfaces (and the tail in the case of the fuselage).

  • structural_twist [num_elem, num_node_elem] is technically not necessary, as the same effect can be achieved with FoR_delta. CAUTION previous versions of SHARPy had structural twist defined differently:
structural_twist = np.zeros((num_node, num_node_elem)) # this is wrong now, and will trigger and error in SHARPy, change it!
structural_twist = np.zeros((num_elem, num_node_elem)) # this is right.
  • boundary_conditions [num_node] is an array of integers (np.zeros((num_node, ), dtype=int)) and contains all 0 EXCEPT FOR:

    • One node NEEDS to have a 1, this is the reference node. Usually, the first node has 1 and is located in [0, 0, 0]. This makes things much easier.
    • If the node is a tip of a beam (is not attached to 2 elements, but just 1), it needs to have a -1.
  • beam_number [num_elem] is another array of integers. Usually you don't need to modify its value. Leave it at 0.

  • app_forces [num_elem, 6] contains the applied forces app_forces[:, 0:3] and moments app_forces[:, 3:6] in a given node. Important points: the forces are given in Material FoR (check above). That means that in a symmetrical model, a thrust force oriented upstream would have the shape $[0, T, 0, 0, 0, 0]$ in the right wing, while the left would be $[0, -T, 0, 0, 0, 0]$. Likewise, a torsional moment for twisting the wing leading edge up would be $[0, 0, 0, M, 0, 0]$ for the right, and $[0, 0, 0, -M, 0, 0]$ for the left. But careful, because an out-of-plane bending moment (wing tip up) has the same sign (think about it).

  • lumped_mass [:] is an array with as many masses as needed (in kg this time). Their order is important, as more information is required to implement them in a model.

  • lumped_mass_nodes [:] is an array of integers. It contains the index of the nodes related to the masses given in lumped_mass in order.

  • lumped_mass_inertia [:, 3, 3] is an array of 3x3 inertial tensors. The relationship is set by the ordering as well.

  • lumped_mass_position [:, 3] is the relative position of the lumped mass wrt the node (given in lumped_masss_nodes) coordinates. ATTENTION: the lumped mass is solidly attached to the node, and thus, its position is given in Material FoR.

Aerodynamic data

All the aero data is contained in case.aero.h5.

It is important to know that the input for aero is usually based on elements (and inside the elements, their nodes). This causes sometimes an overlap in information, as some nodes are shared by two adjacent elements (like in the connectivities graph in the previous section). The easier way of dealing with this is to make sure the data is consistent, so that the properties of the last node of the first element are the same than the first node of the second element.

Item by item:

  • In the aero.h5 file, there is a Group called airfoils. The airfoils are stored in this group (which acts as a folder) as a two-column matrix with $x/c$ and $y/c$ in each column. They are named '0', '1', and so on.

  • chords [num_elem, num_node_elem] is an array with the chords of every airfoil given in an element/node basis.

  • twist [num_elem, num_node_elem] has the twist angle in radians. It is implemented as a rotation around the local $x$ axis.

  • sweep [num_elem, num_node_elem] same here, just a rotation around $z$.

  • airfoil_distribution_input [num_elem, num_node_elem] contains the indices of the airfoils that you put previously in airfoils

  • surface_distribution_input [num_elem] is an integer array. It contains the index of the surface the element belongs to. Surfaces need to be continuous, so please note that if your beam numbering is not continuous, you need to make a surface per continuous section.

  • surface_m [num_surfaces] is an integer array with the number of chordwise panels for every surface.

  • m_distribution [string] is a string with the chordwise panel distribution. In almost all cases, leave it at uniform.

  • aero_node_input [num_node] is a boolean (True/False) array that indicates if that node has a lifting surface attached to it.

  • elastic_axis [num_elem, num_node_elem] indicates the elastic axis location with respect to the leading edge as a fraction of the chord of that rib. Note that the elastic axis is already determined, as the beam is fixed now, so this settings controls the location of the lifting surface wrt the beam.

  • control_surface [num_elem, num_node_elem] is an integer array containing -1 if that section has no control surface associated to it, and 0, 1, 2 ... if the section belongs to the control surface 0, 1, 2 ... respectively.

  • control_surface_type [num_control_surface] contains 0 if the control surface deflection is static, and 1 is it is dynamic (if you need to run dynamic control surfaces, come see me).

  • control_surface_chord [num_control_surface] is an INTEGER array with the number of panels belonging to the control surface. For example, if $M = 4$ and you want your control surface to be $0.25c$, you need to put 1.

  • control_surface_hinge_coord [num_control_surface] only necessary for lifting surfaces that are deflected as a whole, like some horizontal tails in some aircraft. Leave it at 0 if you are not modelling this. If you are, come see me.

Common solver settings

The solver settings are covered almost entirely in the documentation (again: https://ic-sharpy.readthedocs.io). I'm going to explain in more detail the important ones. The defaults settings are generally good enough for a majority of cases.

BeamLoader

BeamLoader reads the solver.txt and the fem.h5 files and generates the beam data structure. Its settings are simple:

  • unsteady leave it on
  • orientation is what is used to set the attitude angles. It is given as a quaternion (CAREFUL: a null rotation in quaternions is $[1, 0, 0, 0]$). You can give it in Euler angles as: 'orientation' = algebra.euler2quat(np.array([roll, alpha, beta])).

AerogridLoader

  • mstar number of chordwise panels for the wake. A good value is $$M^* = \frac{L_{\mathrm{wake}}}{\Delta t u_\infty}$$, which means that the wake panels are the same size as the main wing ones.

  • freestream_dir is a different approach to modifying the attitude angles. I'd leave alone unless you really need it.

NonLinearStatic

The static beam solver settings. Important ones:

  • max_iterations maximum number of iterations for the structural solver. These are not the same as the ones in DynamicCoupled.

  • num_load_steps if > 1, the applied forces and gravity are applied progressively in several steps in order to avoid numerical divergence. Leave it at 1 unless you have problems with convergence of static cases.

  • delta_curved leave it at $1e-1$.

  • min_delta this one is more tricky. Usually $1e-6$ works well for flexible structures. If you are running more stiff stuff, you might need to lower it even more. Don't go under $1e-11$. If you don't know, start at $1e-6$, note the wing tip deflection and lower it even more. A too low value will cause the beam solver to reach max_iterations without convergence. When this happens, note the residual value and set something larger than that.

  • gravity_on on if gravity, off if not.

  • gravity $9.81$ if you are on Earth. Setting it to 0 is the same as gravity_on = off, but the latter is quicker.

StaticUvlm

  • horseshoe if this is on, mstar (in AerogridLoader) has to be 1. It controls the wake modelling. Usual wakes with mstar > 1 are discretised and are of finite length. The horsehoe modelling is derived from the analytical expansion of an discretised wake of infinite length. ATTENTION: use only in static simulations.

  • n_rollup how many steps are carried out to convect the wake with full free-wake. This usually should be 0, but if you want a pretty picture, you can use n_rollup = int(mstar*1.5).

  • rollup_dt if n_rollup > 0, set rollup_dt to $$ \Delta t_{\mathrm{rollup}} = \frac{c/M}{u_\infty} $$

  • 'velocity_field_generator' a few options available here. This paragraph is applicable to every aero solver that has a velocity_field_generator setting.

    • SteadyVelocityField: quite straightforward. Give a u_inf value in u_inf and a direction in u_inf_direction.
    • GustVelocityField: this one generate gusts in several profiles. The ones you will probably use: gust_shape: '1-cos', or 'continuous_sin'.
      • u_inf and u_inf_direction already explained
      • gust_length: equvalent to $2H$ in metres.
      • gust_intensity: reference (peak) velocity of the gust, in m/s.
      • offset x coord. of the first point of the gust with respect to the $[0, 0, 0]$ in inertial.
      • span: span of the aircraft (you are probably not going to use this, it is implemented for DARPA gusts).
    • VelocityFieldGenerator: a full unsteady 3D field of velocities is input. Ask me if you want to use it.

StaticCoupled

  • print_info: set it to on in almost all cases.
  • structural_solver and aero_solver: these are strings with the name of the structural and aerodynamic solvers you want to use for the coupled simulation. A solver with that name needs to exist in SHARPy (check the list of available solvers at the start of a simulation).
  • structural_solver_settings and aero_solver_settings: a dictionary (each) that is basically the same you added before if you wanted to run a standalone structural or aero simulation. A code example:
settings = dict()
settings['NonLinearStatic'] = {
    'print_info': 'on',
    'max_iterations': 150
    # ...
}
settings['StaticUvlm'] = {
    'print_info': 'on',
    'horseshoe': 'off'
    # ...
}

settings['StaticCoupled'] = {
    'structural_solver': 'NonLinearStatic',
    'structural_solver_settings': settings['NonLinearStatic'],
    'aero_solver': 'StaticUvlm',
    'aero_solver_settings': settings['StaticUvlm'],
    # ...
  • max_iter: max number of FSI iterations
  • n_load_steps: if > 1, it ramps the aero and gravity forces slowly to improve convergence. Leave at 0 unless you really need it, then try 4 or 5.
  • tolerance: threshold for convergence of the FSI iteration. Make sure you are choosing a reasonable value for the case. If it converges in 1 iteration: lower it. If it takes more than 10: unless it is a very complicated case (next to flutter or overspeed conditions), lower it.
  • relaxation_factor a real number $\omega \in [0, 1)$. $\omega = 0$ means no relaxation, $\omega \to 1$ means every iteration affects very little to the state of the system. Usually 0.3 is a good value. If you are (again) close to overspeed, flutter... you are going to need to raise it to 0.6 or even more.

Static trim

This solver acts like a wrapper of StaticCoupled, just like StaticCoupled is a wrap for the structural and aero solver. That means that when we initialise the StaticTrim solver, we also create a StaticCoupled, a NonlinearStatic and a StaticUvlm instance.

It is important to know that StaticTrim only trims the longitudinal variables ($F_x, F_z, M_y$) by modifying the angle of attack, tail deflection and thrust. No lateral/directional variables are considered.

  • solver: probably you want StaticCoupled
  • solver_settings: most likely, something similar to settings['StaticCoupled]`.
  • initial_alpha, initial_deflection, initial_thrust: initial values for the angle of attack, elevator deflection and thrust per engine.

DynamicCoupled

This is where things get interesting. DynamicCoupled performs the time stepping and FSI iteration processes. Just as StaticCoupled, it requires a structural solver (for free-flight elastic aircraft it will be NonLinearDynamicCoupledStep, ask for others), and an aero solver, which will be StepUvlm.

The structural_solver and structural_solver_settings (and the aero equivalents) are set up the same way I showed in the StaticCoupled.

  • fsi_substeps: max iterations in FSI loop
  • fsi_tolerance: quite descriptive. What I said for the static coupled tolerance still applies.
  • relaxation_factor: exactly the same. There are more settings to control the relaxation, and make it vary as the simulation progresses, but you probably won't need it.
  • minimum_steps: minimum FSI steps to run even if convergence is reached.
  • n_time_steps: quite descriptive.
  • dt: $\Delta t$
  • include_unsteady_force_contribution: this activates the added mass effects calculation. It is good to have it, but it makes the simulation a bit more challenging from a numerical point of view. Run some numbers by hand and decide if it is worth it. Removing this contribution makes the code faster.
  • postprocessors: the fun begins. postprocessors are modules run every time step after convergence. For example, to calculate the beam loads, or output to paraview. This variable is an array of strings (['one_module', 'another_module']) and the modules are run in the order they are indicated. A typical workflow would be:
'postprocessors': [
    'BeamLoads',     # Calculate the loads at every beam element
    'BeamPlot',      # Output the beam data to paraview (including the beam loads - that's why beamloads goes first)
    'AerogridPlot',  # Output the aero grid to paraview
]
  • postprocessor_settings: hopefully by this time you already get the _settings thing. I won't explain the settings of the processors here, you can do it in the code.

NonLinearDynamicCoupledStep

Almost same settings as NonLinearStatic, so I'm going to explain only the settings that are different.

  • newmark_damp: artificial damping parameter. Increasing this damps the higher frequencies while leaving the low frequency modes relatively untouched (please note the relatively). Start with $5\times 10^{-4}$ an increase it if needed. No more than $10^{-2}$.
  • num_steps: number of time steps
  • dt: $\Delta t$
  • initial_velocity: if you want to aircraft to fly with a velocity, this is the place to put it. Instead, you can leave it at 0 and put the velocity as a u_inf contribution. Results will be the same.

StepUvlm

  • convection_scheme: you probably want to leave it at 2. This convects the wake with the background flow (no influence from the aircraft). 3 is a full free-wake, which looks very good, takes very long and results don't change if compared to 2 in most of the cases.
  • gamma_dot_filtering: if you added the unsteady_forces_contribution in DynamicCoupled, you probably want to put 7 or 9 here. If not, it won't be considered.

A very short intro to the Command Line

The command line is where you are going to spend quite a lot of your time. Make your life easier and learn how to use it.

When you open a terminal, the default location to land is your HOME folder. In the terminal, your home folder is identified as ~ or $HOME.

You can navigate to the folder of your choice using cd (change directory) for example, if you want to go to Downloads:

cd Downloads

Extra tip: write cd Dow and press Tab to autocomplete.

If you haven't created the folder you want to move to, you can make dir: mkdir. For example, to create a folder called Code in you Home directory:

# go to your home folder if you are not there yet
cd ~ # or `cd ` or `cd $HOME`, all equivalent

# create the dir
mkdir Code

If you have a file in Downloads called very_private_stuff.txt, you can copy it (cp) or move it (mv) to your Code folder:

cd Code

# copy it
cp ../Downloads/very_private_stuff.txt ./

# or move it
mv  ../Downloads/very_private_stuff.txt ./

Things that require explanation:

  • .. denotes the parent folder of a location. For example: ../Downloads means "go to the previous folder and then to Downloads"
  • . (a single period) denotes the current location. For example: ./ means "right here"

You can also use mv to rename files:

mv very_private_stuff.txt homework.txt

If you want copy a folder, you need to add -r (this is called a flag) after cp. Example: we have a folder in Downloads called justin_bieber_complete_discography and we want to copy it to Music and call it arctic_monkeys instead:

cd ~
mkdir Music

# note the space between cp and -r
cp -r Downloads/justin_bieber_complete_discography Music/arctic_monkeys

You can also delete or remove (rm) files and folders. Now you don't want your Justin Bieber tunes in your Downloads folder, so you run:

cd ~/Downloads

# note the -r here too
rm -r justin_bieber_complete_discography

ATTENTION the rm command IS NOT REVERTIBLE. No Rubbish Bin, Trash, helmet or parachute. Make sure you are very very sure you actually want to delete exactly that (and nothing else).

A note: some files cannot be deleted with a simple rm file, and the computer will say it is protected. Then add the flag -f (force), but this makes rm even more dangerous, so try not to use it. The git internal files are sometimes protected, so if you want to delete an old copy of SHARPy, you probably will need to use it.

These are the very basic commands for command line survival. I'm going to add a few non-essential ones, but useful nevertheless:

  • which: it tells you which executable is running. For example which sharpy returns: /home/user/code/sharpy/bin/sharpy. It is useful for knowing which version of anything you are running and where it comes from. For example, it is useful for knowing if you are using the updated compilers (usually in /usr/local/bin) or the default ones (/usr/bin probably).
  • top: shows you the processes running and how much RAM they're using.
  • touch: creates and empty file with the name you tell after the command.
  • ipython: starts a nice Python terminal with autocomplete and some colours. Much better than python.
  • history: if you forgot that nice command that did what I wanted and now I can't remember. Extra: history | grep "command" (without the quotes) will only show you the history lines that contain command.
  • *: wildcard for copying, removing, moving... Everything. Example: cp *.txt ./my_files/". Again: careful with rm and * together. Be VERY careful with rm and -rf and * together.
  • pwd: for when you get lost and don't know where you are in the folder structure.
  • nano: simple text editor. Save with ctrl+o, exit with ctrl+x.
  • more: have a look at a file quickly
  • head: show the first lines of a file
  • tail: show the last lines of a file

Debugging guide

When the program fails, there are a number of typical reasons:

  • Did you forget conda activate sharpy_env and source bin/sharpy_vars.sh?
    • Check my alias tip at the beginning of the document.
    • If you do in the terminal: which sharpy, do you get the one you want?
    • If you do which python, does the result point to anaconda3/envs/sharpy_env/bin (or similar)?
  • Wrong input (inconsistent connectivities, mass = 0...)
    • Sometimes not easy to detect. For the structural model, run BeamLoader and BeamPlot with no structural solver in between. Go over the structure in Paraview. Check the fem.h5 file with HDFView.
    • Remember that connectivities are ordered as $[0, 2, 1]$ (the central node goes last).
    • Make sure the num_elem and num_node variables are actually your correct number of elements and nodes.
  • Not running the actual case you want to.
    • Cleanup the folder and regenerate the case
  • Not running the SHARPy version you want.
    • Check at the beginning of the execution the path to the SHARPy folder.
  • Not running the correct branch of the code.
    • You probably want to use develop. Again, check the first few lines of SHARPy output.
  • Very different (I'm talking orders of magnitude) stiffnesses between nodes or directions?
  • The UVLM requires a smaller vortex core cutoff?
  • Newmark damping is not enough for this case?
  • Do you have an element with almost 0 mass?
  • Have a look at the $\dot{\Gamma}$ filtering and numerical parameters in the settings of StepUvlm and DynamicCoupled.
  • Add more relaxation to the StaticCoupled or DynamicCoupled solvers.
  • The code has a bug (depending on where, it may be likely).
    • Go over the rest of the list. Plot the case in paraview. Go over the rest of the list again. Prepare the simplest example that reproduces the problem and come see us.
  • The code diverges because it has to (physical unstable behaviour)
    • Then don't complain
  • Your model still doesn't work and you don't know why.
    • import pdb; pdb.set_trace() and patience
  • If nothing else works... get a rubber duck (or a very very patient good friend) and go over every step

If your model doesn't do what it is supposed to do:

  • Check for symmetric response where the model is symmetric.

    • If it is not, run the beam solver first and make sure your properties are correct. Make sure the matrices for mass and stiffness are rotated if they need to be (remember the Material FoR definition and the for_delta?)
    • Now run the aerodynamic solver only and double check that the forces are symmetric.
    • Make sure your tolerances are low enough so that at least 4 FSI iterations are performed in StaticCoupled or DynamicCoupled.
  • Make sure your inputs are correct. For example: a dynamic case can be run with $u_\infty = 0$ and the plane moving forwards, or $u_\infty = $whatever and the plane velocity = 0. It is very easy to mix both, and end up with double the effective incoming speed (or none).

  • Run simple stuff before coupling it. For example, if your wing tip deflections don't match what you'd expect, calculate the deflection under a small tip force (not too small, make sure the deflection is > 1% of the length!) by hand, and compare.

  • It is more difficult to do the same with the UVLM, as you need a VERY VERY high aspect ratio to get close to the 2D potential solutions. You are going to have to take my word for it: the UVLM works.

  • But check the aero grid geometry in Paraview, including chords lengths and angles.

Other useful software

  • HDFView for opening and inspecting the .h5 files
  • Gitkraken is a good graphical interface for Git.
  • Pycharm for editing python and running cases.
  • Jupyter notebook (this was made with it) for results and tests.
================================================ FILE: docs/source/conf.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # # SHARPy documentation build configuration file, created by # sphinx-quickstart on Wed Oct 19 16:40:48 2016. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys # import guzzle_sphinx_theme # sys.path.insert(0, os.path.abspath('../')) sys.path.insert(0, os.path.abspath('./../../')) # sys.path.insert(0, os.path.abspath('..')) # import recommonmark # from recommonmark.transform import AutoStructify # # Markdown Source Parsers # from recommonmark.parser import CommonMarkParser # print(sys.path) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '3.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.coverage', 'sphinx.ext.mathjax', 'sphinx.ext.viewcode', # 'sphinx.ext.githubpages', 'sphinx.ext.napoleon', 'nbsphinx', 'sphinx.ext.autosectionlabel', 'sphinxcontrib.jquery', 'recommonmark' ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # Mathjax path for equation rendering mathjax_path = "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" # Markdown Source Parsers source_parsers = { '.md': 'recommonmark.parser.CommonMarkParser'} # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'SHARPy' copyright = '2025, LoCA Lab ICL' author = 'Aeroelastics Lab, Imperial College London' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '2.4' # The full version, including alpha/beta/rc tags. release = '2.4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ["**/.so", "../../lib/*", "../*", "/_static", "/content/", '_build', '**.ipynb_checkpoints'] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # Exclude matplotlib - avoids conflicts when running sphinx # http://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#confval-autodoc_mock_imports autodoc_mock_imports = ["matplotlib", "numpy", "colorama", "h5py", "scipy", "ctypes", "tvtk", "sharpy.lib", "sharpy.utils.ctypes_utils", "sharpy.linear.src.libuvlm", "sharpy.linear.src.lib_dbiot", "pandas", "lxml", 'mpl_toolkits', # 'sharpy.aero.utils', 'yaml', 'sharpy.aero.utils.uvlmlib', 'sharpy.linear.src.uvlmutils', 'sharpy.structure.utils.xbeamlib', 'sharpy.utils.constants'] # "interp", "multisurfaces", "assembly", "libss",] # Note: N. Goizueta 3/12/18: mocking imports from sharpy.linear.src that contain numpy # operators outside any function definition as these are causing problems. If numpy is not # mocked, it doesn't build on RTD. If it is mocked, it raises an error when compiling locally # N. Goizueta 23/7/19 - Removing sharpy.linear until its ready to be merged. Too many errors are # causing a build failure on RTD. # # Exclude selected modules # from unittest.mock import MagicMock # # class Mock(MagicMock): # @classmethod # def __getattr__(cls, name): # return MagicMock() # # MOCK_MODULES = ["matplotlib", "numpy", "colorama", "h5py", "scipy", # "sharpy.lib.libxbeam.so", "sharpy.utils.ctypes_utils.import_ctypes_lib"] # sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) # # Sphinx extension to execute code in the documentation # https://github.com/jpsenior/sphinx-execute-code # extensions.append('sphinx_execute_code') # -- Options for HTML output ---------------------------------------------- # html_theme_path = guzzle_sphinx_theme.html_theme_path() # html_theme = 'guzzle_sphinx_theme' # # # Register the theme as an extension to generate a sitemap.xml # extensions.append("guzzle_sphinx_theme") # # # Guzzle theme options (see theme.conf for more information) # html_theme_options = { # # Set the name of the project to appear in the sidebar # "project_nav_name": "SHARPy", # } # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. # " v documentation" by default. # # html_title = 'SHARPy v0.0' # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = False # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'SHARPydoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'a4paper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '12pt', # Additional stuff for the LaTeX preamble. # 'preamble': r'\setcounter{tocdepth}{2}', # Latex figure (float) alignment # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'SHARPy.tex', 'SHARPy Documentation', 'LoCA Lab ICL', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # It false, will not define \strong, \code, itleref, \crossref ... but only # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added # packages. # # latex_keep_old_macro_names = True # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'sharpy', 'SHARPy Documentation', [author], 1) ] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'SHARPy', 'SHARPy Documentation', author, 'SHARPy', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False # Recommonmark function #def setup(app): # app.add_config_value('recommonmark_config', { # 'url_resolver': lambda url: github_doc_root + url, # 'auto_toc_tree_section': 'Contents', # }, True) # app.add_transform(AutoStructify) ================================================ FILE: docs/source/content/capabilities.md ================================================ # Capabilities This is just the tip of the iceberg, possibilities are nearly endless and once you understand how SHARPy's modular interface works, you will be capable of running very complex simulations. ## Very flexible aircraft nonlinear aeroelasticity The modular design of SHARPy allows to simulate complex aeroelastic cases involving very flexible aircraft. The structural solver supports very complex beam arrangements, while retaining geometrical nonlinearity. The UVLM solver features different wake modelling fidelities while supporting large lifting surface deformations in a native way. Among the problems studied, a few interesting ones, in no particular order are: * Catapult take off of a very flexible aircraft analysis [\[Paper\]](https://arc.aiaa.org/doi/abs/10.2514/6.2019-2038). In this type of simulations, a PID controller was used in order to enforce displacements and velocities in a number of structural nodes (the clamping points). Then, several take off strategies were studied in order to analyse the influence of the structural stiffness in this kind of procedures. This case is a very good example of the type of problems where nonlinear aeroelasticity is essential. ![_Catapult Takeoff of Flexible Aircraft_](../_static/capabilities/hale_cruise.png) * Flight in full 3D atmospheric boundary layer (to be published). A very flexible aircraft is flown immersed in a turbulent boundary layer obtained from HPC LES simulations. The results are compared against simpler turbulence models such as von Karman and Kaimal. Intermittency and coherence features in the LES field are absent or less remarkable in the synthetic turbulence fields. ![_HALE Aircraft in a Turbulent Field_](../_static/capabilities/hale_turb.jpeg) * Lateral gust reponse of a realistic very flexible aircraft. For this problem (to be published), a realistic very flexible aircraft (University of Michigan X-HALE) model has been created in SHARPy and validated against their own aeroelastic solver for static and dynamic cases. A set of vertical and lateral gust responses have been simulated. ![_X-HALE_](../_static/capabilities/xhale.png) ## Wind turbine aeroelasticity SHARPy is suitable to simulate wind turbine aeroelasticity. On the structural side, it accounts for material anisotropy which is needed to characterize composite blades and for geometrically non-linear deformations observed in current blades due to the increasing length and flexibility. Both rigid and flexible simulations can be performed and the structural modes can be computed accounting for rotational effects (Campbell diagrams). The rotor-tower interaction is modelled through a multibody approach based on the theory of Lagrange multipliers. Finally, the tower base can be fixed or subjected to prescribed linear and angular velocities. On the aerodynamic side, the use of potential flow theory allows the characterization of flow unsteadiness at a reasonable computational cost. Specifically, steady and dynamic simulations can be performed. The steady simulations are carried out in a non-inertial frame of reference linked to the rotor under uniform steady wind with the assumption of prescribed helicoidal wake. On the other hand, dynamic simulations can be enriched with a wide variety of incoming winds such as shear and yaw. Moreover, the wake shape can be freely computed under no assumptions accounting for self-induction and wake expansion or can be prescribed to an helicoidal shape for computational efficiency. ![Wind Turbine](../_static/capabilities/turbine.png) ## Model Order Reduction Numerical models of physical phenomena require fine discretisations to show convergence and agreement with their real counterparts, and, in the case of SHARPy's aeroelastic systems, hundreds of thousands of states are not an uncommon encounter. However, modern hardware or the use of these models for other applications such as controller synthesis may limit their size, and we must turn to model order reduction techniques to achieve lower dimensional representations that can then be used. SHARPy offers several model order reduction methods to reduce the initially large system to a lower dimension, attending to the user's requirements of numerical efficiency or global error bound. ### Krylov Methods for Model Order Reduction - Moment Matching Model reduction by moment matching can be seen as approximating a transfer function through a power series expansion about a user defined point in the complex plane. The reduction by projection retains the moments between the full and reduced systems as long as the projection matrices span certain Krylov subspaces dependant on the expansion point and the system's matrices. This can be taken advantage of, in particular for aeroelastic applications where the interest resides in the low frequency behaviour of the system, the ROM can be expanded about these low frequency points discarding accuracy higher up the frequency spectrum. #### Example 1 - Aerodynamics - Frequency response of a high AR flat plate subject to a sinusoidal gust The objective is to compare SHARPy's solution of a very high aspect ratio flat plate subject to a sinusoidal gust to the closed form solution obtained by Sears (1944 - Ref). SHARPy's inherent 3D nature makes comparing results to the 2D solution require very high aspect ratio wings with fine discretisations, resulting in very large state space models. In this case, we would like to utilise a Krylov ROM to approximate the low frequency behaviour and perform a frequency response analysis on the reduced system, since it would represent too much computational cost if it were performed on the full system. The full order model was reduced utilising Krylov methods, in particular the Arnoldi iteration, with an expansion about zero frequency to produce the following result. ![_Sears Gust Bode Plot_](../_static/capabilities/sears.png) As it can be seen from the image above, the ROM approximates well the low frequency, quasi-steady state and loses accuracy as the frequency is increased, just as intended. Still, perfect matching is never achieved even at the expansion frequency given the 3D nature of the wing compared to the 2D analytical solution. #### Example 2 - Aeroelastics - Flutter analysis of a Goland wing with modal projection The Goland wing flutter example is presented next. The aerodynamic surface is finely discretised for the UVLM solution, resulting in not only a large state space but also in large input/output dimensionality. Therefore, to reduce the number of inputs and outputs, the UVLM is projected onto the structural mode shapes, the first four in this particular case. The resulting multi input multi output system (mode shapes -> UVLM -> modal forces) was subsequently reduced using Krylov methods aimed at MIMO systems which use variations of the block Arnoldi iteration. Again, the expansion frequency selected was the zero frequency. As a sample, the transfer function from two inputs to two outputs is shown to illustrate the performance of the reduced model against the full order UVLM. ![_Goland Reduced Order Model Transfer Functions_](../_static/capabilities/goland_rom.png) The reduced aerodynamic model projected onto the modal shapes was then coupled to the linearised beam model, and the stability analysed against a change in velocity. Note that the UVLM model and its ROM are actually scaled to be independent on the freestream velocity, hence only one UVLM and ROM need to be computed. The structural model needs to be updated at each test velocity but its a lot less costly in computational terms. The resulting stability of the aeroelastic system is plotted on the Argand diagram below with changing freestream velocity. ![_Goland Flutter_](../_static/capabilities/goland_flutter.png) ================================================ FILE: docs/source/content/casefiles.rst ================================================ The SHARPy Case files ===================== SHARPy takes as input a series of ``.h5`` files that contain the numerical data and a ``.sharpy`` file that contains the settings for each of the solvers. How these files are generated is at the user's discretion, though templates are provided, and all methods are valid as long as the required variables are provided with the appropriate format. Modular Framework ----------------- SHARPy is built with a modular framework in mind. The following diagram shows the strutuctre of a nonlinear, time marching aeroelastic simulation .. image:: ../_static/case_files/sharpy_modular.png :target: ../_static/case_files/sharpy_modular.png :alt: SHARPy's modular structure Each of the blocks correspond to individual solvers with specific settings. How we choose which solvers to run, in which order and with what settings is done through the solver configuration file, explained in the next section. Solver configuration file ------------------------- The solver configuration file is the main input to SHARPy. It is a ConfigObj_ formatted file with the ``.sharpy`` extension. It contains the settings for each of the solvers and the order in which to run them. .. _ConfigObj: http://pypi.org/project/configobj/ A typical way to assemble the solver configuration file is to place all your desired settings in a dictionary and then convert to and write your ``ConfigObj``. If a setting is not provided the default value will be used. The settings that each solver takes, its type and default value are explained in their relevant documentation pages. .. code-block:: python import configobj filename = '/.sharpy' config = configobj.ConfigObj() config.filename = filename config['SHARPy'] = {'case': '', # an example setting # Rest of your settings for the PreSHARPy class } config['BeamLoader'] = {'orientation': [1., 0., 0.], # an example setting # Rest of settings for the BeamLoader solver } # Continue as above for the remainder of solvers that you would like to include # finally, write the config file config.write() The resulting ``.sharpy`` file is a plain text file with your specified settings for each of the solvers. Note that, therefore, if one of your settings is a ``np.array``, it will get transformed into a string of plain text before being read by SHARPy. However, any setting with ``list(float)`` specified as its setting type will get converted into a ``np.array`` once it is read by SHARPy. FEM file -------- The ``case.fem.h5`` file has several components. We go one by one: * ``num_node_elem [int]`` : number of nodes per element. Always 3 in our case (3 nodes per structural elements - quadratic beam elements). * ``num_elem [int]`` : number of structural elements. * ``num_node [int]`` : number of nodes. For simple structures, it is ``num_elem*(num_node_elem - 1) - 1``. For more complicated ones, you need to calculate it properly. * ``coordinates [num_node, 3]``: coordinates of the nodes in body-attached FoR (A). * ``connectivites [num_elem, num_node_elem]`` : Beam element's connectivities. Every row refers to an element, and the three integers in that row are the indices of the three nodes belonging to that elem. Now, the catch: the ordering is not as you'd think. Order them as ``[0, 2, 1]``. That means, first one, last one, central one. The following image shows the node indices inside the circles representing the nodes, the element indices in blue and the resulting connectivities matrix next to it. Connectivities are tricky when considering complex configurations. Pay attention at the beginning and you'll save yourself a lot of trouble. .. image:: ./../_static/case_files/connectivities.png :target: ./../_static/case_files/connectivities.png :alt: SHARPy Beam Element Connectivities * ``stiffness_db [:, 6, 6]``: database of stiffness matrices. The first dimension has as many elements as different stiffness matrices are in the model. * ``elem_stiffness [num_elem]`` : array of indices (starting at 0). It links every element (index) to the stiffness matrix index in ``stiffness_db``. For example ``elem_stiffness[0] = 0`` ; ``elem_stiffness[2] = 1`` means that the element ``0`` has a stiffness matrix equal to ``stiffness_db[0, :, :]`` , and the second element has a stiffness matrix equal to ``stiffness_db[1, :, :]``. The shape of a stiffness matrix, :math:`\mathrm{S}` is: .. math:: \mathrm{S} = \begin{bmatrix} EA & & & & & \\ & GA_y & & & & \\ & & GA_z & & & \\ & & & GJ & & \\ & & & & EI_y & \\ & & & & & EI_z \\ \end{bmatrix} with the cross terms added if needed. ``mass_db`` and ``elem_mass`` follow the same scheme than the stiffness, but the mass matrix is given by: .. math:: \mathrm{M} = \begin{bmatrix} m\mathbf{I} & -\tilde{\boldsymbol{\xi}}_{cg}m \\ \tilde{\boldsymbol{\xi}}_{cg}m & \mathbf{J}\\ \end{bmatrix} where :math:`m` is the distributed mass per unit length :math:`kg/m` , :math:`(\tilde{\bullet})` is the skew-symmetric matrix of a vector and :math:`\boldsymbol{\xi}_{cg}` is the location of the centre of gravity with respect to the elastic axis in MATERIAL (local) FoR. And what is the Material FoR? This is an important point, because all the inputs that move WITH the beam are in material FoR. For example: follower forces, stiffness, mass, lumped masses... .. image:: ./../_static/case_files/frames_of_reference.jpg :target: ./../_static/case_files/frames_of_reference.jpg :alt: SHARPy Frames of Reference The material frame of reference is noted as :math:`B`. Essentially, the :math:`x` component is tangent to the beam in the increasing node ordering, :math:`z` looks up generally and :math:`y` is oriented such that the FoR is right handed. In the practice (vertical surfaces, structural twist effects...) it is more complicated than this. The only sure thing about :math:`B` is that its :math:`x` direction is tangent to the beam in the increasing node number direction. However, with just this, we have an infinite number of potential reference frames, with :math:`y` and :math:`z` being normal to :math:`x` but rotating around it. The solution is to indicate a ``for_delta``, or frame of reference delta vector (:math:`\Delta`). .. image:: ../_static/case_files/frame_of_reference_delta.jpg :target: ../_static/case_files/frame_of_reference_delta.jpg :alt: Frame of Reference Delta Vector Now we can define unequivocally the material frame of reference. With :math:`x_B` and :math:`\Delta` defining a plane, :math:`y_b` is chosen such that the :math:`z` component is oriented upwards with respect to the lifting surface. From this definition comes the only constraint to :math:`\Delta`: it cannot be parallel to :math:`x_B`. * ``frame_of_reference_delta [num_elem, num_node_elem, 3]``: rotation vector to FoR :math:`B`. contains the :math:`\Delta` vector in body-attached (:math:`A`) frame of reference. As a rule of thumb: .. math:: \Delta = \begin{cases} [-1, 0, 0], \quad \text{if right wing} \\ [1, 0, 0], \quad \text{if left wing} \\ [0, 1, 0], \quad \text{if fuselage} \\ [-1, 0, 0], \quad \text{if vertical fin} \\ \end{cases} These rules of thumb only work if the nodes increase towards the tip of the surfaces (and the tail in the case of the fuselage). * ``structural_twist [num_elem, num_node_elem]``: Element twist. Technically not necessary, as the same effect can be achieved with ``FoR_delta``. * ``boundary_conditions [num_node]``: boundary conditions. An array of integers ``(np.zeros((num_node, ), dtype=int))`` and contains all ``0`` except for - One node NEEDS to have a ``1`` , this is the reference node. Usually, the first node has 1 and is located in ``[0, 0, 0]``. This makes things much easier. - If the node is a tip of a beam (is not attached to 2 elements, but just 1), it needs to have a ``-1``. * ``beam_number [num_elem]``: beam index. Is another array of integers. Usually you don't need to modify its value. Leave it at 0. * ``app_forces [num_elem, 6]``: applied forces and moments. Contains the applied forces ``app_forces[:, 0:3]`` and moments ``app_forces[:, 3:6]`` in a given node. Important points: the forces are given in Material FoR (check above). That means that in a symmetrical model, a thrust force oriented upstream would have the shape ``[0, T, 0, 0, 0, 0]`` in the right wing, while the left would be ``[0, -T, 0, 0, 0, 0]``. Likewise, a torsional moment for twisting the wing leading edge up would be ``[0, 0, 0, M, 0, 0]`` for the right, and ``[0, 0, 0, -M, 0, 0]`` for the left. But careful, because an out-of-plane bending moment (wing tip up) has the same sign (think about it). * ``lumped_mass [:]``: lumped masses. Is an array with as many masses as needed (in kg this time). Their order is important, as more information is required to implement them in a model. * ``lumped_mass_nodes [:]``: Lumped mass nodes. Is an array of integers. It contains the index of the nodes related to the masses given in lumped_mass in order. * ``lumped_mass_inertia [:, 3, 3]``: Lumped mass inertia. Is an array of ``3x3`` inertial tensors. The relationship is set by the ordering as well. * ``lumped_mass_position [:, 3]``: Lumped mass position. Is the relative position of the lumped mass with respect to the node (given in ``lumped_masss_nodes`` ) coordinates. ATTENTION: the lumped mass is solidly attached to the node, and thus, its position is given in Material FoR. Aerodynamics file ----------------- All the aerodynamic data is contained in ``case.aero.h5``. It is important to know that the input for aero is usually based on elements (and inside the elements, their nodes). This causes sometimes an overlap in information, as some nodes are shared by two adjacent elements (like in the connectivities graph in the previous section). The easier way of dealing with this is to make sure the data is consistent, so that the properties of the last node of the first element are the same than the first node of the second element. Item by item: * ``airfoils``: Airfoil group. In the ``aero.h5`` file, there is a Group called ``airfoils``. The airfoils are stored in this group (which acts as a folder) as a two-column matrix with :math:`x/c` and :math:`y/c` in each column. They are named ``'0', '1'`` , and so on. * ``chords [num_elem, num_node_elem]``: Chord Is an array with the chords of every airfoil given in an element/node basis. * ``twist [num_elem, num_node_elem]``: Twist. Has the twist angle in radians. It is implemented as a rotation around the local :math:`x` axis. * ``sweep [num_elem, num_node_elem]``: Sweep. Same here, just a rotation around :math:`z`. * ``airfoil_distribution [num_elem, num_node_elem]``: Airfoil distribution. Contains the indices of the airfoils that you put previously in ``airfoils``. * ``surface_distribution [num_elem]``: Surface integer array. It contains the index of the surface the element belongs to. Surfaces need to be continuous, so please note that if your beam numbering is not continuous, you need to make a surface per continuous section. * ``surface_m [num_surfaces]``: Chordwise panelling. Is an integer array with the number of chordwise panels for every surface. * ``m_distribution [string]``: Discretisation method. Is a string with the chordwise panel distribution. In almost all cases, leave it at ``uniform``. * ``aero_node [num_node]``: Aerodynamic node definition. Is a boolean (``True`` or ``False``) array that indicates if that node has a lifting surface attached to it. * ``elastic_axis [num_elem, num_node_elem]``: elastic axis. Indicates the elastic axis location with respect to the leading edge as a fraction of the chord of that rib. Note that the elastic axis is already determined, as the beam is fixed now, so this settings controls the location of the lifting surface wrt the beam. * ``control_surface [num_elem, num_node_elem]``: Control surface. Is an integer array containing ``-1`` if that section has no control surface associated to it, and ``0, 1, 2 ...`` if the section belongs to the control surface ``0, 1, 2 ...`` respectively. * ``control_surface_type [num_control_surface]``: Control Surface type. Contains ``0`` if the control surface deflection is static, and ``1`` is it is dynamic. * ``control_surface_chord [num_control_surface]``: Control surface chord. Is an INTEGER array with the number of panels belonging to the control surface. For example, if ``M = 4`` and you want your control surface to be :math:`0.25c`, you need to put ``1``. * ``control_surface_hinge_coord [num_control_surface]``: Control surface hinge coordinate. Only necessary for lifting surfaces that are deflected as a whole, like some horizontal tails in some aircraft. Leave it at ``0`` if you are not modelling this. * ``airfoil_efficiency [num_elem, num_node_elem, 2, 3]``: Airfoil efficiency. This is an optional setting that introduces a user-defined efficiency and constant terms to the mapping between the aerodynamic forces calculated at the lattice grid and the structural nodes. The formatting of the 4-dimensional array is simple. The first two dimensions correspond to the element index and the local node index. The third index is whether the term is the multiplier to the force ``0`` or a constant term ``1``. The final term refers to, in the **local, body-attached** ``B`` frame, the factors and constant terms for: ``fy, fz, mx``. For more information on how these factors are included in the mapping terms see :func:`sharpy.aero.utils.mapping.aero2struct_force_mapping`. * ``polars`` Group (optional): Use airfoil polars to correct aerodynamic forces. This is an optional group to add if correcting the aerodynamic forces using airfoil polars is desired. A polar should be included for each airfoil defined. Each entry consists of a 4-column table. The first column corresponds to the angle of attack (in radians) and then the ``C_L``, ``C_D`` and ``C_M``. Multibody file -------------- All the aerodynamic data is contained in ``case.mb.h5``. This file encapsulates both the initial conditions for the multiple bodies, and the constraints between them. Item by item: * ``num_bodies``: Number of bodies. * ``num_constraints``: Number of constraints between bodies. The initial conditions for each body and the constraint definitions are defined in groups. The body groups are named as ``body_xx``, where the xx is replaced with a two digit body number starting from 00, e.g. ``body_00``. Each of these groups should have the following items: * ``FoR_acceleration [6]``: Frame of reference initial acceleration. An array of the stacked linear and rotational initial accelerations in the inertial frame. * ``FoR_velocity [6]``: Frame of reference initial velocity. An array of the stacked linear and rotational initial velocities in the inertial frame. * ``FoR_position [6]``: Frame of reference initial position. An array of the stacked linear and rotational initial positions in the inertial frame. * ``quat [4]``: Frame of reference initial orientation. A quaternion describing the initial rotation between the body attached and inertial frames. * ``body_number``: Body number. An integer used to identify the body when creating constraints. * ``FoR_movement``: Type of frame of reference movement. Use "free" to include rigid body motion, or "prescribed" for a clamped body. The constraint groups are named similarly to the bodies, using ``constraint_xx`` where xx is the two digit constraint number starting from 00. Each of these groups should have the following items: * ``scalingFactor``: Scaling factor. This value scales the multibody equations, where generally settings to ``dt^2`` provides acceptable results. * ``behaviour``: Constraint behaviour. This string defines the type of constraint applied to a single or multiple bodies. A wide range of standard lower-pair kinematic joints are available, such as hinge and spherical joints, as well as prescribed rotation joints. A list of the available joints can be found in ``sharpy/structure/utils/lagrangeconstraints.py`` and ``sharpy/structure/utils/lagrangeconstraintsjax.py``, depending on the solver being used. Due to every constraint being different, further parameters depend upon the constraint type used. These parameters are added as variables to the constraint group. Some examples which may be included are listed below: * ``body_FoR``: Body frame of reference number. The number of the body which is constrained by its body attached frame of reference. For example for a double pendulum, this would be the lower link. * ``body``: Body number. The number of the body which is constrained by one of its nodes. For example for a double pendulum, this would be the upper link. * ``node_in_body``: Node in body. This is paired to the ``body`` parameter, and this indicates which node within the body is to be constrained. * ``rot_axisB [3]``: Rotation axis in the B frame. For a hinge constraint, this defines a vector for the hinge axis for the ``body``. This is defined in the material frame. * ``rot_axisA2 [3]``: Rotation axis in the A frame. For a hinge constraint, this defines a vector for the hinge axis for the ``body_FoR``. This is defined in the body attached frame. * ``controller_id``: Controller ID for using an actuated constraint. This should use the same ID as the ``MultibodyController`` used in the ``DynamicCoupled`` simulation, allowing the rotation to be controlled over time. * ``aerogrid_warp_factor [num_elem, 3]``: Aerodynamic grid warping factor. For simulating wings with dynamic sweep by a local rotation around the z axis, the aerodynamic grid needs to warp to account for this. To create a smooth warp, it can be preferred to gradually change the sweep (and therefore chord to maintain the "same" geometry) at elements around the discontinuity. This parameter sets the sweep per node using this parameter to scale the input angle. If not included, it will be ignored when generating the aerodynamic grid and have no effect. Nonlifting Body file ----------------- All the nonlifting body data is contained in ``case.nonlifting_body.h5``. The idea behind the structure of the model definition of nonlifting bodies in SHARPy is similiar to the aerodynamic one for lifting surfaces. Again for each node or element we define several parameters. Item by item: * ``shape``: Type of geometrical form of 3D nonlifting body. In the ``nonlifting_body.h5`` file, there is a Group called ``shape``. The shape indicates the geometrical form of the nonlifting body. Common options for this parameter are ``'cylindrical'`` and ``'specific'``. For the former, SHARPy expects rotational symmetric cross-section for which only a radius is required for each node. For the ``'specific'`` option, SHARPy can create a more unique nonlifting body geometry by creating an ellipse at each fuselage defined by :math:`\frac{y^2}{a^2}+\frac{z^2}{b^2}=1` with the given ellipse axis lengths :math:`a` and :math:`b`. Further, SHARPy lets define the user to create a vertical offset from the node with :math:`z_0`. * ``radius [num_node]``: Cross-sectional radius. Is an array with the radius of specified for each fuselage node. * ``a_ellipse [num_node]``: Elliptical axis lengths along the local y-axis. Is an array with the length of the elliptical axis along the y-axis. * ``b_ellipse [num_node]``: Elliptical axis lengths along the local z-axis. Is an array with the length of the elliptical axis along the z-axis. * ``z_0_ellipse [num_node]``: Vertical offset of the ellipse center from the beam node. Is an array with the vertical offset of the center of the elliptical cross-section from the fuselage node. * ``surface_m [num_surfaces]``: Radial panelling. Is an integer array with the number of radial panels for every surface. * ``nonlifting_body_node [num_node]``: Nonlifting body node definition. Is a boolean (``True`` or ``False``) array that indicates if that node has a nonlifting body attached to it. * ``surface_distribution [num_elem]``: Nonlifting Surface integer array. It contains the index of the surface the element belongs to. Surfaces need to be continuous, so please note that if your beam numbering is not continuous, you need to make a surface per continuous section. Time-varying force input file (``.dyn.h5``) ------------------------------------------- The ``.dyn.h5`` file is an *optional* input file that may contain force and acceleration inputs that vary with time. This is intended for use in dynamic problems. For SHARPy to look for and use this file the setting ``unsteady`` in the :class:`~sharpy.solvers.beamloader.BeamLoader` must be turned to ``on``. Appropriate data entries in the ``.dyn.h5`` include: * ``dynamic_forces [num_t_steps, num_node, 6]``: Dynamic forces in body attached ``B`` frame. Forces given at each time step, for each node and then for the 6 degrees of freedom (``fx, fy, fz, mx, my, mz``) in a body-attached (local) frame of reference ``B``. * ``for_pos [num_t_steps, 6]``: Body frame of reference (A FoR) position. Position of the reference frame A in time. * ``for_vel [num_t_steps, 6]``: Body frame of reference (A FoR) velocity. Velocity of the reference frame A in time. * ``for_acc [num_t_steps, 6]``: Body frame of reference (A FoR) acceleration. Acceleration of the reference frame A in time. If a case is restarted from a pickle file, the .dyn.h5 file should include the dynamic information for the previous simulation (that will be discarded) and the information for the new simulation. ================================================ FILE: docs/source/content/contributing.md ================================================ # Contributing to SHARPy ## Bug fixes and features SHARPy is a collaborative effort, and this means that some coding practices need to be encouraged so that the code is kept tidy and consistent. Any user is welcome to raise issues for bug fixes and feature proposals through Github. If you are submitting a bug report: 1. Make sure your SHARPy, xbeam and uvlm local copies are up to date and in the same branch. 2. Double check that your python distribution is updated by comparing with the `utils/environment.yml` file. 3. Try to assemble a minimal working example that can be run quickly and easily. 4. Describe as accurately as possible your setup (OS, path, compilers...) and the problem. 5. Raise an issue with all this information in the Github repo and label it `potential bug`. Please bear in mind that we do not have the resources to provide support for user modifications of the code through Github. If you have doubts about how to modify certain parts of the code, contact us through email and we will help you as much as we can. If you are fixing a bug: 1. THANKS! 2. Please create a pull request from your modified fork, and describe in a few lines which bug you are fixing, a minimal example that triggers the bug and how you are fixing it. We will review it ASAP and hopefully it will be incorporated in the code! If you have an idea for new functionality but do not know how to implement it: 1. We welcome tips and suggestions from users, as it allow us to broaden the scope of the code. The more people using it, the better! 2. Feel free to fill an issue in Github, and tag it as `feature proposal`. Please understand that the more complete the description of the potential feature, the more likely it is that some of the developers will give it a go. If you have developed new functionality and you want to share it with the world: 1. AWESOME! Please follow the same instructions than for the bug fix submission. If you have some peer-reviewed references related to the new code, even better, as it will save us some precious time. ## Code formatting We try to follow the [PEP8](https://www.python.org/dev/peps/pep-0008/) standards (with spaces, no tabs please!) and [Google Python Style Guide](http://google.github.io/styleguide/pyguide.html). We do not ask you to freak out over formatting, but please, try to keep it tidy and descriptive. A good tip is to run `pylint` [https://www.pylint.org/](https://www.pylint.org/) to make sure there are no obvious formatting problems. ## Documentation Contributing to SHARPy's documentation benefits everyone. As a developer, writing documentation helps you better understand what you have done and whether your functions etc make logical sense. As a user, any documentation is better than digging through the code. The more we have documented, the easier the code is to use and the more users we can have. If you want to contribute by documenting code, you have come to the right place. SHARPy is documented using Sphinx and it extracts the documentation directly from the source code. It is then sorted into directories automatically and a human readable website generated. The amount of work you need to do is minimal. That said, the recipe for a successfully documented class, function, module is the following: 1. Your documentation has to be written in ReStructuredText (rst). I know, another language... hence I will leave a few tips: - Inline code is written using two backticks ` `` ` - Inline math is written as ``:math:`1+\exp^{i\pi} = 0` ``. Don't forget the backticks! - Math in a single or multiple lines is simple: ```rst .. math:: 1 + \exp{i\pi} = 0 ``` - Lists in ReStructuredText are tricky, I must admit. Therefore, I will link to some [examples](http://docutils.sourceforge.net/docs/user/rst/quickref.html#enumerated-lists). The key resides in not forgetting the spaces, in particular when you go onto a second line! - The definite example list can be found [here](http://docutils.sourceforge.net/docs/user/rst/quickref.html). 2. Titles and docstrings, the bare minimum: - Start docstrings with `r` such that they are interpreted raw: ```python r""" My docstring """ ``` - All functions, modules and classes should be given a title that goes in the first line of the docstring - If you are writing a whole package with an `__init__.py` file, even if it's empty, give it a human readable docstring. This will then be imported into the documentation - For modules with several functions, the module docstring has to be at the very top of the file, prior to the `import` statements. 2. We use the [Google documentation](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) style. A very good set of examples of Google style documentation for functions, modules, classes etc. can be found [here](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html). 3. Function arguments and returns: - Function arguments are simple to describe: ```python def func(arg1, arg2): """Summary line. Extended description of function. Args: arg1 (int): Description of arg1 arg2 (str): Description of arg2 Returns: bool: Description of return value """ return True ``` 4. Solver settings: - If your code has a settings dictionary, with defaults and types then make sure that: - They are defined as class variables and not instance attributes. - Define a `settings_types`, `settings_default` and `settings_description` dictionaries. - After all your settings, update the docstring with the automatically generated settings table. You will need to import the `sharpy.utils.settings` module ```python settings_types = dict() settings_default = dict() settings_description = dict() # keep adding settings settings_table = sharpy.utils.settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default ,settings_description) ``` 5. See how your docs looks like! - Once you are done, run the following ``SHARPy`` command: ```bash sharpy any_string -d ``` - If you are making minor updates to docstrings (i.e. you are not documenting a previously undocumented function/class/module) you can simply change directory to `sharpy/docs` and run ```bash make html ``` - Your documentation will compile and warnings will appear etc. You can check the result by opening ```bash docs/build/index.html ``` and navigating to your recently created page. - Make sure that **before committing** any changes in the documentation you update the entire ``docs`` directory by running ```bash sharpy any_string -d ``` Thank you for reading through this and contributing to make SHARPy a better documented, more user friendly code! ## Git branching model For the development of SHARPy, we try to follow [this](https://nvie.com/posts/a-successful-git-branching-model/) branching model summarised by the schematic ![BranchingModel](https://nvie.com/img/git-model@2x.png) _Credit: Vincent Driessen https://nvie.com/posts/a-successful-git-branching-model/_ Therefore, attending to this model our branches have the following versions of the code: * `main`: latest stable release - paired with the appropriate tag. * `develop`: latest stable development build. Features get merged to develop. * `rc-**`: release candidate branch. Prior to releasing tests are performed on this branch. * `dev_doc`: documentation development branch. All work relating to documentation gets done here. * `fix_**`: hotfix branch. * `dev_**`: feature development branch. If you contribute, please make sure you know what branch to work from. If in doubt please ask! Commit names are also important since they are the backbone of the code's change log. Please write concise commit titles and explain the main changes in the body of the commit message. An excellent guide on writing good commit messages can be found [here](https://chris.beams.io/posts/git-commit/). # For developers: ## Releasing a new SHARPy version In the release candidate branch: 1. Update the version number in the docs configuration file `docs/source/conf.py`. Update variables `version` and `release` 2. Update `version.json` file 3. Update version in `sharpy/version.py` file 4. Commit, push and wait for tests to pass 5. Merge release candidate branch into `main` branch In the `main` branch: 1. Create a release tag. IMPORTANT: ensure it is an *annotated* tag, otherwise the version and commit number in SHARPy will not display properly ``` git tag -a git push origin --tags -f ``` where `` is something like `2.0`. 2. Run the [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator) tool locally with the following parameters: ``` github_changelog_generator -u imperialcollegelondon -p sharpy -t --future-release ``` 3. Push the changes to the `CHANGELOG.md` file 4. Create the GitHub release, choosing the newly created tag from the dropdown menu. Do not create a tag from the dropdown menu directly because it will not be an annotated tag 5. (Optional) Merge `main` branch back into `develop` branch ================================================ FILE: docs/source/content/debug.rst ================================================ A Short Debugging Guide ----------------------- We have put together a list of common traps you may fall into, hopefully you will find the tools here to get yourself out of them! * Did you forget `conda activate sharpy_env` and `source bin/sharpy_vars.sh`? - If you do in the terminal: `which sharpy`, do you get the one you want? - If you do `which python`, does the result point to `anaconda3/envs/sharpy_env/bin` (or similar)? * Wrong input (inconsistent connectivities, mass = 0...) * Sometimes not easy to detect. For the structural model, run `BeamLoader` and `BeamPlot` with no structural solver in between. Go over the structure in Paraview. Check the `fem.h5` file with HDFView. * Remember that connectivities are ordered as $[0, 2, 1]$ (the central node goes last). * Make sure the `num_elem` and `num_node` variables are actually your correct number of elements and nodes. * Not running the actual case you want to. * Cleanup the folder and regenerate the case * Not running the SHARPy version you want. * Check at the beginning of the execution the path to the SHARPy folder. * Not running the correct branch of the code. * You probably want to use `develop`. Again, check the first few lines of SHARPy output. * Very different (I'm talking orders of magnitude) stiffnesses between nodes or directions? * Maybe the UVLM requires a smaller a smaller vortex core cutoff (only for linear UVLM simulations, as the nonlinear uses another vortex core model). * Newmark damping is not enough for this case? * Do you have an element with almost 0 mass or inertia? * Are you mass matrices consistent? Check that :math:`I_{xx} = I_{yy} + I_{zz}`. * Have a look at the :math:`\dot{\Gamma}` filtering and numerical parameters in the settings of `StepUvlm` and `DynamicCoupled`. * Add more relaxation to the `StaticCoupled` or `DynamicCoupled` solvers. * The code has a bug (depending on where, it may be likely). * Go over the rest of the list. Plot the case in paraview. Go over the rest of the list again. Prepare the simplest example that reproduces the problem and raise an issue. * The code diverges because it has to (physical unstable behaviour) * Then don't complain * Your model still doesn't work and you don't know why. * `import pdb; pdb.set_trace()` and patience * If nothing else works... get a rubber duck (or a very very patient good friend) and go over every step .. image:: ../_static/debugguide/rubberduck.png :target: ../_static/debugguide/rubberduck.png :alt: A rubber duck could be a good friend If your model doesn't do what it is supposed to do: * Check for symmetric response where the model is symmetric. * If it is not, run the beam solver first and make sure your properties are correct. Make sure the matrices for mass and stiffness are rotated if they need to be (remember the Material FoR definition and the `for_delta`?) * Now run the aerodynamic solver only and double check that the forces are symmetric. * Make sure your tolerances are low enough so that at least 4 FSI iterations are performed in `StaticCoupled` or `DynamicCoupled`. * Make sure your inputs are correct. For example: a dynamic case can be run with :math:`u_\infty = 0` and the plane moving forwards, or :math:`u_\infty = x` whatever and the plane velocity = 0. It is very easy to mix both, and end up with double the effective incoming speed (or none). * Run simple stuff before coupling it. For example, if your wing tip deflections don't match what you'd expect, calculate the deflection under a small tip force (not too small, make sure the deflection is > 1% of the length!) by hand, and compare. * It is more difficult to do the same with the UVLM, as you need a VERY VERY high aspect ratio to get close to the 2D potential solutions. You are going to have to take my word for it: the UVLM works. * But check the aero grid geometry in Paraview, including chords lengths and angles. ================================================ FILE: docs/source/content/example_notebooks/UDP_control/control_design_script.m ================================================ function [success] = control_design_script(route_directory) addpath(strcat(route_directory,'/matlab_functions/')); success = false; %% Define parameters; case_name = 'pazy_ROM'; output_folder = strcat(route_directory,'/output/',case_name); output_folder_linear = strcat(output_folder, '/linear_results/'); complex_matrices = true; %% Reade from SHARPy generated state space model [state_space_system, eta_ref] = read_SHARPy_state_space_system(... strcat(case_name, '.linss.h5'), ... strcat(output_folder, '/savedata/'), ... complex_matrices... ); %% Load simulation settings and store in struct load(strcat(output_folder_linear, 'simulation_parameters.mat')); n_nodes = uint8(n_nodes); rigid_body_motions = false; input_settings = set_input_parameters(u_inf, ... state_space_system.Ts, ... num_modes, ... num_aero_states, ... rigid_body_motions, ... simulation_time, ... num_control_surfaces, ... n_nodes, ... control_input_start, ... gust_input_start); %% Remove unused input and outputs from the generated ROM in SHARPy and link deflection and its rate state_space_system = adjust_state_space_system(state_space_system, input_settings); %% Get gust input gust = get_1minuscosine_gust_input(... gust_length, ... gust_intensity, ... state_space_system.Ts, ... u_inf, ... simulation_time); %% Configure Simulink Inputs model_name = "PID_linear_model"; simIn = Simulink.SimulationInput(model_name); simIn = setVariable(simIn,'D_gain',0); simIn = setVariable(simIn,'P_gain',0); simIn = setVariable(simIn,'I_gain',0); simIn = setVariable(simIn,'state_space_system',state_space_system); simIn = setVariable(simIn,'input_settings',input_settings); simIn = setVariable(simIn,'gust', gust); %% Run Simulink with PID Controller warning('off','all'); % open loop fprintf('Simulate linear open-loop gust response.') simIn = setVariable(simIn,'controller_on',0); out_open_loop = sim(simIn); % closed loop P = 5 simIn = setVariable(simIn,'controller_on',1); simIn = setVariable(simIn,'P_gain',5); fprintf('Simulate linear closed-loop gust response with P=5.') out_PID_5 = sim(simIn); % closed loop P = 10 simIn = setVariable(simIn,'P_gain',10); fprintf('Simulate linear closed-loop gust response with P=10.') out_PID_10 = sim(simIn); %% Save results deflection = [out_PID_5.delta.Data(:,1)... out_PID_10.delta.Data(:,1)... zeros(size(out_PID_5.delta.Time, 1), 1)]; deflection_rate = [out_PID_5.delta_dot.Data(:,1)... out_PID_10.delta_dot.Data(:,1)... zeros(size(out_PID_5.delta_dot.Time, 1), 1)]; tip_deflection = [out_PID_5.actual_output.Data(:,1)... out_PID_10.actual_output.Data(:,1)... out_open_loop.actual_output.Data(:,1)]; tip_deflection= tip_deflection + eta_ref(n_nodes/2*6-3); time_array = out_open_loop.tout; output_folder_linear = strcat(output_folder, '/linear_results/') writematrix(deflection, strcat(output_folder_linear, 'deflection.txt'),'Delimiter',','); writematrix(time_array, strcat(output_folder_linear, 'time_array_linear.txt'),'Delimiter',','); writematrix(deflection_rate,strcat(output_folder_linear, './deflection_rate.txt'),'Delimiter',','); writematrix(tip_deflection,strcat(output_folder_linear, './tip_deflection.txt'),'Delimiter',','); success = true end ================================================ FILE: docs/source/content/example_notebooks/UDP_control/get_settings_udp.py ================================================ """ Script to set the required SHARPy settings for all cases within the UDP Control example notebook. """ def get_settings_udp(model, flow, **kwargs): gust = kwargs.get('gust', False) horseshoe = kwargs.get('horseshoe', False) gravity = kwargs.get('gravity', True) wake_length_factor = kwargs.get('wake_length_factor', 10) num_cores = kwargs.get('num_cores', 2) num_modes = kwargs.get('num_modes', 20) free_flight = kwargs.get('free_flight', False) tolerance = kwargs.get('tolerance', 1e-6) n_load_steps = kwargs.get('n_load_steps', 5) fsi_tolerance = kwargs.get('fsi_tolerance', 1e-6) relaxation_factor = kwargs.get('relaxation_factor', 0.1) newmark_damp = kwargs.get('newmark_damp', 0.5e-4) output_folder = kwargs.get('output_folder', './output/') model.config['SHARPy'] = {'case': model.case_name, 'route': model.route, 'flow': flow, 'write_screen': kwargs.get('write_screen', 'on'), 'write_log': 'on', 'save_settings': 'on', 'log_folder': output_folder + '/', 'log_file': model.case_name + '.log'} model.config['BeamLoader'] = {'unsteady': 'off', 'orientation': model.quat} model.config['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'mstar': wake_length_factor*model.M, #int(20/tstep_factor), 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': { 'u_inf':model.u_inf, 'u_inf_direction': [1., 0., 0.], 'dt':model.dt, }, } if horseshoe: model.config['AerogridLoader']['mstar'] = 1 model.config['StaticCoupled'] = { 'print_info': 'on', 'max_iter': 200, 'n_load_steps': n_load_steps, 'tolerance': 1e-5, 'relaxation_factor': relaxation_factor, 'aero_solver': 'StaticUvlm', 'aero_solver_settings': { 'rho': model.rho, 'print_info': 'off', 'horseshoe': 'on', 'num_cores': num_cores, 'n_rollup': 0, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': model.u_inf, 'u_inf_direction': model.u_inf_direction}, 'vortex_radius': 1e-9}, 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 200, 'num_load_steps': 5, 'delta_curved': 1e-6, 'min_delta': 1e-8, 'gravity': gravity, 'gravity': 9.81}, } model.config['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0} model.config['BeamPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on'} model.config['WriteVariablesTime'] = {'structure_variables': ['pos'], 'structure_nodes': list(range(0, model.num_node_surf)), 'cleanup_old_solution': 'on', } model.config['DynamicCoupled'] = {'print_info': 'on', 'structural_substeps': 10, 'dynamic_relaxation': 'on', 'cleanup_previous_solution': 'on', 'structural_solver': 'NonLinearDynamicPrescribedStep', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 950, 'delta_curved': 1e-1, 'min_delta': tolerance, 'newmark_damp': newmark_damp, 'gravity': gravity, 'gravity': 9.81, 'num_steps': model.n_tstep, 'dt':model.dt, }, 'aero_solver': 'StepUvlm', 'aero_solver_settings': {'print_info': 'on', 'num_cores': num_cores, 'convection_scheme': 2, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': model.u_inf, 'u_inf_direction': [1., 0., 0.]}, 'rho': model.rho, 'n_time_steps': model.n_tstep, 'vortex_radius': 1e-9, 'dt': model.dt, 'gamma_dot_filtering': 3}, 'fsi_substeps': 200, 'fsi_tolerance': fsi_tolerance, 'relaxation_factor': model.relaxation_factor, 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': 0.0, 'n_time_steps': model.n_tstep, 'dt': model.dt, 'include_unsteady_force_contribution': kwargs.get('unsteady_force_distribution', True), 'postprocessors': ['WriteVariablesTime', 'BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}, 'StallCheck': {}, 'AerogridPlot': { 'u_inf': model.u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, 'WriteVariablesTime': { 'structure_variables': ['pos', 'psi'], 'structure_nodes': [model.num_node_surf - 1, model.num_node_surf, model.num_node_surf + 1], }, }, 'network_settings': kwargs.get('network_settings', {}), } if gust: gust_settings = kwargs.get('gust_settings', {'gust_shape': '1-cos', 'gust_length': 10., 'gust_intensity': 0.01, 'gust_offset': 0.}) model.config['DynamicCoupled']['aero_solver_settings']['velocity_field_generator'] = 'GustVelocityField' model.config['DynamicCoupled']['aero_solver_settings']['velocity_field_input'] = {'u_inf': model.u_inf, 'u_inf_direction': [1., 0, 0], 'relative_motion': bool(not free_flight), 'offset': gust_settings['gust_offset'], 'gust_shape': gust_settings['gust_shape'], 'gust_parameters': { 'gust_length': gust_settings['gust_length'], 'gust_intensity': gust_settings['gust_intensity'] * model.u_inf, } } model.config['PickleData'] = {} model.config['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', 'inout_coordinates': 'nodes', # 'recover_accelerations': True, 'linear_system_settings': { 'beam_settings': {'modal_projection': True, 'inout_coords': 'modes', 'discrete_time': True, 'newmark_damp': newmark_damp, 'discr_method': 'newmark', 'dt': model.dt, 'proj_modes': 'undamped', 'num_modes': num_modes, 'print_info': 'on', 'gravity': gravity, 'remove_dofs': []}, 'aero_settings': {'dt': model.dt, 'integr_order': 2, 'density': model.rho, 'remove_predictor': True, 'use_sparse': 'off', 'gust_assembler': 'LeadingEdge', 'ScalingDict': kwargs.get('scaling_dict', {'length':1, 'speed': 1, 'density': 1}), }, 'track_body': free_flight, 'use_euler': free_flight, }} model.config['Modal'] = {'print_info': True, 'use_undamped_modes': True, 'NumLambda': num_modes, 'rigid_body_modes': free_flight, 'write_modes_vtk': False, 'print_matrices': False, 'continuous_eigenvalues': 'off', 'dt': model.dt, 'plot_eigenvalues': False, } model.config['SaveData']['save_linear'] = True if kwargs.get('remove_gust_input_in_statespace', False): model.config['LinearAssembler']['linear_system_settings']['aero_settings']['remove_inputs'] = ['u_gust'] rom_settings = kwargs.get('rom_settings', {'use': False}) if rom_settings['use']: model.config['LinearAssembler']['linear_system_settings']['aero_settings']['rom_method'] = rom_settings['rom_method'], model.config['LinearAssembler']['linear_system_settings']['aero_settings']['rom_method_settings'] = rom_settings['rom_method_settings'] return model.config ================================================ FILE: docs/source/content/example_notebooks/UDP_control/matlab_functions/adjust_state_space_system.m ================================================ function state_space_converted = adjust_state_space_system(state_space_system, input_settings) %convert_state_space_for_LQR Adjust extracted state space system % The loaded state space system exported from SHARPy is adjusted here for % the closed-loop simulations. This includes: % - extracts the column from the B and D matrices that describe the % effect of the gust input to the states % - removing all unused inputs and rearranging for example the % deflection of a control surface and its rate as both depend on % each other % - removes not considered outputs % - assembles updated discrete state space system %% Reduce model by deleting unused outupts (only tip deflection relevant here) C_sensor = state_space_system.C(input_settings.index.tip_displacement,:); D_sensor = state_space_system.D(input_settings.index.tip_displacement,:); %% Reduce model with not used inputs idx_input_end =input_settings.index.control_input_start + 2 * input_settings.num_control_surfaces - 1; idx_inputs = [input_settings.index.control_input_start:idx_input_end]; B_cs = state_space_system.B(:,idx_inputs); D_cs = D_sensor(:,idx_inputs); %% Extract delta to state new_A_colum = B_cs(:,1 :input_settings.num_control_surfaces); A = [state_space_system.A new_A_colum; ... zeros(input_settings.num_control_surfaces,... (size(state_space_system.A,2)+ input_settings.num_control_surfaces))]; for counter = 0:1:input_settings.num_control_surfaces-1 A(size(A,1)-counter,size(A,2)-counter) = 1; % Explain one end %Delete delta input and add delta_dot influence on delta B_cs(:,1:input_settings.num_control_surfaces) = []; B_cs = [B_cs; eye(input_settings.num_control_surfaces) * state_space_system.Ts]; % Adding feed through of the delta-input on the output on C and deleting % column added in C out of D; new column C = [C_sensor D_cs(:,1:input_settings.num_control_surfaces)]; D_cs(:, 1:input_settings.num_control_surfaces) = []; %% Get Disturbance Matrices G = [state_space_system.B(:,input_settings.index.gust_input_start); ... zeros(input_settings.num_control_surfaces, 1)]; H = D_sensor(:,input_settings.index.gust_input_start); %% Assemble final state space system state_space_converted = ss(A,[B_cs G],C,[D_cs H],state_space_system.Ts); end ================================================ FILE: docs/source/content/example_notebooks/UDP_control/matlab_functions/get_1minuscosine_gust_input.m ================================================ function gust_time_series = get_1minuscosine_gust_input(gust_length, gust_intensity, dt, u_inf, simulation_time) %get_1minuscosine_gust_input Gust input is generated and stored. gust_time = [0:dt:simulation_time]; offset_gust = 0; gust_intensity = gust_intensity*u_inf; end_gust = gust_length/u_inf; x = [0:dt:end_gust]*u_inf; % spatial coordinate gust_cos = (1.0 - cos(2*pi*x/ gust_length)) * gust_intensity / 2; gust_vel_z = [zeros(1,offset_gust) gust_cos]; %% Adjust vector length delta_vector_length = size(gust_time,2) - size(gust_vel_z,2); gust_vel_z = [gust_vel_z zeros(1, delta_vector_length)]; %% Store gust velocities in time history gust_time_series = [gust_time; gust_vel_z]'; end ================================================ FILE: docs/source/content/example_notebooks/UDP_control/matlab_functions/read_SHARPy_state_space_system.m ================================================ function [state_space_system, eta_ref] = read_SHARPy_state_space_system(file_name_sharpy, folder, complex) %read_SHARPy_state_space_system Gets sate space model from SHARPy output absolute_file_path = strcat(folder, file_name_sharpy); % Reference Point and Timestep eta_ref = h5read(absolute_file_path, '/linearisation_vectors/eta'); dt = h5read(absolute_file_path, '/ss/dt'); % Read state space matrices if complex A = h5read(absolute_file_path, '/ss/A').r; B = h5read(absolute_file_path, '/ss/B').r; C = h5read(absolute_file_path, '/ss/C').r; D = h5read(absolute_file_path, '/ss/D'); else A = h5read(absolute_file_path, '/ss/A'); B = h5read(absolute_file_path, '/ss/B'); C = h5read(absolute_file_path, '/ss/C'); D = h5read(absolute_file_path, '/ss/D'); end % Sometimes matrices are exported transposed if size(A, 1) ~= size(B, 1) A = transpose(A); B = transpose(B); end if size(C, 1) ~= size(D, 1) C = transpose(C); D = transpose(D); end % Assemble final discrete state space system state_space_system = ss(A, B, C, D, dt); end ================================================ FILE: docs/source/content/example_notebooks/UDP_control/matlab_functions/set_input_parameters.m ================================================ function input = set_input_parameters(u_inf, ... dt, ... num_modes, ... num_aero_states, ... rbm_considered, ... flight_time, ... num_control_surfaces, ... n_nodes, ... control_input_start, ... gust_input_start) %set_input_parameters Save input settings to struct. % This function returns a struct that contains all necessary input % settings to adjust the state space model for the control design. input.u_inf = u_inf; input.num_modes = num_modes; input.num_aero_states = num_aero_states; input.rbm = rbm_considered; input.flight_time = flight_time; input.num_control_surfaces = num_control_surfaces; input.rbm = rbm_considered; input.dt = dt; %% Set indices input.index.control_input_start = control_input_start; input.index.gust_input_start = gust_input_start; input.index.tip_displacement = n_nodes * 6 + n_nodes / 2 * 6 - 3; % forces + right wing nodes ================================================ FILE: docs/source/content/example_notebooks/UDP_control/parameter_UDP_control_pazy_udp_closed_loop_gust_response.json ================================================ {"server_ip_addr": "127.0.0.1", "client_ip_addr": "127.0.0.1", "port_in_network": 64017, "port_out_network_server": 59017, "port_out_network_client": 59007, "output_folder": "/home/rpalacio/sharpy/docs/source/content/example_notebooks/UDP_control/output", "dt": 0.000625, "simulation_time": 1.0, "num_sensors": 1, "initial_cs_deflection": 0, "reference_deflection": 0.02290911} ================================================ FILE: docs/source/content/example_notebooks/UDP_control/pazy_PID_controller_UDP.py ================================================ import socket import logging import struct import sharpy.io.message_interface as message_interface import numpy as np from pid_controller import PID_Controller import json import os """ This script establishes a UDP connection to SHARPy from which it receives sensor measurements. Based on these measurements, a PID-Controller computes an actuator input that is feedback to SHARPy again using UDP. The user just needs to specify the name of the case name (bottom of the script) and run the main function 'run_controller(case_name)'. The case name is needed to find the appropriate json input file for all required information for the following steps: 1) Load input settings 2) Establish a connection to the UDP i/o network created by SHARPy (server) 3) Initialise PID Controller 4) Enter the time loop including: a) Sending a control input (starting with the initial value loaded from the init file) to SHARPy; b) The resulting sensor measurement for the next simulation time step is sent back to this client. c) Based on the error to the defined target value, the PID controller computes a control input. d) Both the received sensor measurement and generated control input are saved in .txt-files. a) Next step, would be a) again. But now, we send the generated control input. 5) Closes UDP connections """ def create_and_bind_client(ip_addr, port): """ Creates and bind the client to the server socket using UDP. """ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((ip_addr, port)) return sock def setup_udp_network(dict_parameters): """ This function connects the present client to two sockets, specified within the information (i.e. ip addresses and ports). Further, a timeout of 120 s are set for SHARPy's output network, i.e. if new sensor measurements are not received (normally caused by an error), the network connection is closed. """ server_ip_addr = dict_parameters["server_ip_addr"] # SHARPy client_ip_addr = dict_parameters["client_ip_addr"] # controller port_in_network = dict_parameters["port_in_network"] port_out_network_client = dict_parameters["port_out_network_client"] port_in_network_client = port_out_network_client - 8 # needs to be different from port of network client sharpy_in_network= (server_ip_addr, port_in_network) in_sock = create_and_bind_client(client_ip_addr, port_in_network_client) out_sock = create_and_bind_client(client_ip_addr, port_out_network_client) out_sock.settimeout(120) return sharpy_in_network, in_sock, out_sock def init_logger(): logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=20) return logging.getLogger(__name__) def write_data_to_file(data, file_path, init_file=False): if init_file: file_mode = "w" else: file_mode = "ab" with open(file_path, file_mode) as f: np.savetxt(f, data, delimiter=',', newline='\r\n') def receive_sensor_measurements(logger, out_sock, msg_len, num_sensors): """ UDP socket waits and receives sensor measurements from SHARPy. After, the message is decoded and unpacked in an appropriate format and returned to the main function. """ try: msg, conn = out_sock.recvfrom(msg_len) except socket.timeout: logger.info('Socket time out') raise logger.info('Received {}-byte long data "{}" from {}'.format(len(msg), msg, conn)) values = message_interface.decoder(msg) logger.info('Received {}'.format(values)) results = np.zeros((num_sensors,)) for isensor in range(num_sensors): results[isensor] = values[isensor][1] return results def send_control_input(logger, in_sock, sharpy_in_network, control_input_to_send): """ The generated control input is packed into a binary message readable from SHARPy. Note that, this message includes the control input twice, since the ailerons on right and left wing have different IDs. """ ctrl_value = struct.pack('<5sifif', b'RREF0', 0, control_input_to_send, 1, control_input_to_send) logger.info('Sending control input of size {} bytes.'.format(len(ctrl_value))) in_sock.sendto(ctrl_value, sharpy_in_network) logger.info('Sent control input {} to {}.'.format(control_input_to_send, sharpy_in_network)) def run_controller(case_name): # 1) Load input settings route_file_dir = os.path.abspath('') with open(route_file_dir + '/parameter_UDP_control_{}.json'.format(case_name), 'r') as fp: dict_simulation_parameters = json.load(fp) # define simulation settings curr_ts = 0 num_sensors = dict_simulation_parameters["num_sensors"] msg_len = 5 + 8 * num_sensors dt = dict_simulation_parameters["dt"] number_timesteps = int(dict_simulation_parameters["simulation_time"] / dt) output_folder = dict_simulation_parameters["output_folder"] init_cs_deflection = float(dict_simulation_parameters["initial_cs_deflection"]) reference_deflection= dict_simulation_parameters["reference_deflection"] # 2) Establish UDP connections sharpy_in_network, in_sock, out_sock = setup_udp_network(dict_simulation_parameters) # setup network and data output logger = init_logger() file_path_sensor = os.path.join(output_folder, case_name, "{}_{}".format(case_name, "sensor_measurement")) file_path_control = os.path.join(output_folder, case_name, "{}_{}".format(case_name, "control_input")) # 3) Initialise controller and control input PID = PID_Controller(10., 0., 0., target=reference_deflection) control_input = init_cs_deflection # 4) Enter time control loop while curr_ts < number_timesteps: # a) send_control_input(logger, in_sock, sharpy_in_network, control_input) # b) sensor_data = receive_sensor_measurements(logger, out_sock, msg_len, num_sensors) # c) Generate control input with PID controller if curr_ts >= 5: control_input = PID.generate_control_input(sensor_data, dt) # d) Store sensor and control input to .txt-file write_data_to_file(sensor_data, file_path_sensor, init_file= bool(curr_ts==0)) write_data_to_file([control_input], file_path_control, init_file= bool(curr_ts==0)) curr_ts += 1 # 5) Close sockets out_sock.close() in_sock.close() logger.info('Closed input and output sockets') if __name__ == '__main__': case_name = "pazy_udp_closed_loop_gust_response" run_controller(case_name) ================================================ FILE: docs/source/content/example_notebooks/UDP_control/pazy_network_info.yml ================================================ --- - name: 'control_surface_deflection' var_type: 'control_surface' inout: 'in' position: 0 - name: 'control_surface_deflection' var_type: 'control_surface' inout: 'in' position: 1 - name: 'pos' inout: 'out' position: 8 index: 2 var_type: 'node' ... ================================================ FILE: docs/source/content/example_notebooks/UDP_control/pid_controller.py ================================================ class PID_Controller(object): def __init__(self, P_gain, I_gain, D_gain, target=0): self.P_gain = P_gain self.I_gain = I_gain self.D_gain = D_gain self.target = target self.error = 0. self.integral_error = 0. self.derivative_error = 0. self.previous_error = 0. def generate_control_input(self, sensor_measurement, dt): self.error = self.target - sensor_measurement self.integral_error += self.error * dt self.derivative_error = (self.error - self.previous_error) * dt self.previous_error = self.error return self.P_gain * self.error + self.I_gain * self.integral_error + self.derivative_error * self.D_gain ================================================ FILE: docs/source/content/example_notebooks/UDP_control/tutorial_udp_control.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Closed-Loop Simulation of the Pazy wing with SHARPy as a hardware-in-the-loop system" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This tutorial exemplifies the application of an external controller on a nonlinear aeroelastic system using SHARPy as a hardware-in-the-loop system. The controller receives sensor measurements from SHARPy using an interface based on the User Datagram Protocol (UDP). After generating the control input, the control interface feeds this input back to the actuators incorporated in SHARPy again through the UDP interface. \n", "\n", "\n", "The notebook includes examples of the main methods in SHARPy for controller design, including both the generation of a linearized reduced-order model (ROM) of the nonlinear aeroelastic model (around a nonlinear equilibium point) for control design and, finally, how to apply their controller on a nonlinear aeroelastic simulation. The test case is the Pazy wing as modelled by Goizueta et al (JAircraft, 2022).\n", "\n", "Reading material recommended are:\n", "\n", "[1] Artola, M., Goizueta, N., Wynn, A., and Palacios, R., “Proof of Concept for a Hardware-in-the-Loop Nonlinear ControlFramework for Very Flexible Aircraft,”AIAA Scitech 2021 Forum, 2021. https://doi.org/10.2514/6.2021-1392.\n", "\n", "[2] Stefanie Duessler, Thulasi Mylvaganam and Rafael Palacios. \"LQG-based Gust Load Alleviation Systems for Very Flexible Aircraft,\" AIAA 2023-2571. AIAA SCITECH 2023 Forum. January 2023. https://doi.org/10.2514/6.2023-2571.\n", "\n", "And other useful references for the Pazy model and Krylov-based model order reduction scheme are:\n", "\n", "[3] Norberto Goizueta, Andrew Wynn, Rafael Palacios, Ariel Drachinsky, and Daniella E. Raveh, \"Flutter Predictions for Very Flexible Wing Wind Tunnel Test\",\n", "Journal of Aircraft 2022 59:4, 1082-1097. https://doi.org/10.2514/1.C036710.\n", "\n", "[4] Norberto Goizueta, Andrew Wynn, and Rafael Palacios, \"Adaptive Sampling for Interpolation of Reduced-Order Aeroelastic Systems\", AIAA Journal 2022 60:11, 6183-6202 https://doi.org/10.2514/1.J062050." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The notebook is structured such that first, we design the model and its operational point. Second, the model is linearized and reduced. The resulting reduced-order model (ROM) is then used for control design using Matlab. Third, we apply via a UDP Interface the designed controller on a nonlinear aeroelastic simulation performed with SHARPy.\n", "\n", "Please note that this notebook requires 12 hours to be completed on a standard desktop computer. The longest simulation is the open-loop nonlinear gust response simulation taking around 8 hours, while the closed-loop nonlinear gust response simulation only takes 4 hours. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Requirements\n", "\n", "This notebook needs to be run within the sharpy environment. Please check if you have installed SHARPy correctly. Matlab with a version of 2022 or newer is required for the preliminary control design (the matlab engine is also available in older versions of Matlab butno through pip install)." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import os\n", "import sharpy.cases.templates.flying_wings as wings\n", "import sharpy.sharpy_main\n", "import json\n", "\n", "! pip install matlabengine\n", "import matlab.engine" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Demonstrator Model \n", "\n", "The very-flexible Pazy wing serves as a demonstrator model and an aileron located near the wingtip as an actuator. The control objective in this example is to alleviate the wingtip displacement due to gust-induced wing loads. As an example, we use a P-controller implemented in a Python interface with the objective to reduce the wingtip displacement induced by a gust encounter. However, the reader can test any controller run on any platform as long as a working UDP connection to SHARPy is established. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Wind tunnel conditions" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "u_inf = 40 # m/s\n", "alpha_deg = 1\n", "rho = 1.225" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Wing and Wake Lattice Discretisation\n", "\n", "Note that for convergence, we need a finer lattice grid. However, this would unnecessarily increase this notebook's duration which is already high enough with these coarse settings." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "number_chordwise_panels = 4\n", "number_spanwise_nodes = 16\n", "wake_length_factor = 10\n", "\n", "num_control_surfaces = 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### General Simulation Settings" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "num_cores = 4\n", "simulation_time =1.0" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "cases_folder = './cases/' # folder to store input files \n", "output_folder = './output' # folder to save results\n", "route_notebook_dir = os.path.abspath('')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Open-Loop Gust Response\n", "After, defining the general model settings, we now simulate the open-loop response of the Pazy wing to a gust. For this, we first have to generate input files for the Pazy model aerodynamic and structural properties, giving the above-defined flight conditions, discretisation, and simulation settings." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "case_name = 'pazy_open_loop_gust_response'\n", "pazy_model_open_loop = wings.PazyControlSurface(M=number_chordwise_panels,\n", " N=number_spanwise_nodes,\n", " Mstar_fact=wake_length_factor,\n", " u_inf=u_inf,\n", " alpha=alpha_deg,\n", " rho=rho,\n", " n_surfaces=2,\n", " route=cases_folder + '/' + case_name,\n", " case_name=case_name,\n", " physical_time=simulation_time,)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "def generate_aero_and_fem_input_files(model):\n", " model.clean_test_files()\n", " model.update_derived_params()\n", " model.generate_aero_file()\n", " model.generate_fem_file()\n", " \n", "generate_aero_and_fem_input_files(pazy_model_open_loop)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we define the gust shape used, i.e. a 1-cosine gust with a gust length of 10 m and intensity of 2 %." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "gust = True\n", "gust_settings ={'gust_shape': '1-cos',\n", " 'gust_length': 10.,\n", " 'gust_intensity': 0.02,\n", " 'gust_offset': 0.} " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and create the final settings for the open-loop gust response simulation. Most importantly here, is the `flow` list which defines which solver will be run in which order. For more information about each solver, we kindly ask you to check our documentation (https://ic-sharpy.readthedocs.io/en/latest/content/solvers.html).\n", "\n", "We are using the `get_setttings_udp` function to create our final settings. Basic settings can be adjusted by using different inputs. Please check the function code to be aware of the parameters used here." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "flow = ['BeamLoader',\n", " 'AerogridLoader',\n", " 'StaticCoupled',\n", " 'AerogridPlot',\n", " 'BeamPlot',\n", " 'DynamicCoupled',\n", " ]\n", "from get_settings_udp import get_settings_udp\n", "\n", "pazy_model_open_loop.set_default_config_dict()\n", "pazy_model_open_loop.config = get_settings_udp(pazy_model_open_loop,\n", " flow,\n", " num_cores=num_cores,\n", " wake_length_factor=wake_length_factor,\n", " output_folder = output_folder,\n", " gust=gust,\n", " gust_settings=gust_settings)\n", "pazy_model_open_loop.config.write()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Run SHARPy Simulation (open-loop test)\n", "Having all input files ready, let us start the simulation. Note that this simulation takes up to eight hours. \n" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--------------------------------------------------------------------------------\u001b[0m\n", " ###### ## ## ### ######## ######## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ####\u001b[0m\n", " ###### ######### ## ## ######## ######## ##\u001b[0m\n", " ## ## ## ######### ## ## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ##\u001b[0m\n", " ###### ## ## ## ## ## ## ## ##\u001b[0m\n", "--------------------------------------------------------------------------------\u001b[0m\n", "Aeroelastics Lab, Aeronautics Department.\u001b[0m\n", " Copyright (c), Imperial College London.\u001b[0m\n", " All rights reserved.\u001b[0m\n", " License available at https://github.com/imperialcollegelondon/sharpy\u001b[0m\n", "\u001b[36mRunning SHARPy from /home/rpalacio/sharpy/docs/source/content/example_notebooks/UDP_control\u001b[0m\n", "\u001b[36mSHARPy being run is in /home/rpalacio/anaconda3/envs/sharpy/lib/python3.10/site-packages\u001b[0m\n", "SHARPy output folder set\u001b[0m\n", "\u001b[34m\t./output//pazy_open_loop_gust_response/\u001b[0m\n", "\u001b[36mGenerating an instance of BeamLoader\u001b[0m\n", "Variable for_pos has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0, 0]\u001b[0m\n", "\u001b[36mGenerating an instance of AerogridLoader\u001b[0m\n", "Variable freestream_dir has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [1.0, 0.0, 0.0]\u001b[0m\n", "Variable control_surface_deflection has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: []\u001b[0m\n", "Variable control_surface_deflection_generator_settings has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable dx1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: -1.0\u001b[0m\n", "Variable ndx1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable r has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1.0\u001b[0m\n", "Variable dxmax has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: -1.0\u001b[0m\n", "\u001b[34mThe aerodynamic grid contains 2 surfaces\u001b[0m\n", "\u001b[34m Surface 0, M=4, N=8\u001b[0m\n", " Wake 0, M=40, N=8\u001b[0m\n", "\u001b[34m Surface 1, M=4, N=8\u001b[0m\n", " Wake 1, M=40, N=8\u001b[0m\n", " In total: 64 bound panels\u001b[0m\n", " In total: 640 wake panels\u001b[0m\n", " Total number of panels = 704\u001b[0m\n", "\u001b[36mGenerating an instance of StaticCoupled\u001b[0m\n", "Variable correct_forces_method has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable correct_forces_settings has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable runtime_generators has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "\u001b[36mGenerating an instance of NonLinearStatic\u001b[0m\n", "Variable abs_threshold has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-13\u001b[0m\n", "Variable newmark_damp has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0001\u001b[0m\n", "Variable gravity_on has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable gravity_dir has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 1.0]\u001b[0m\n", "Variable relaxation_factor has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.3\u001b[0m\n", "Variable dt has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.01\u001b[0m\n", "Variable num_steps has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 500\u001b[0m\n", "Variable initial_position has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0. 0. 0.]\u001b[0m\n", "Variable initial_velocity has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0. 0. 0. 0. 0. 0.]\u001b[0m\n", "\u001b[36mGenerating an instance of StaticUvlm\u001b[0m\n", "Variable rollup_dt has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.1\u001b[0m\n", "Variable rollup_aic_refresh has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable rollup_tolerance has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0001\u001b[0m\n", "Variable iterative_solver has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable iterative_tol has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0001\u001b[0m\n", "Variable iterative_precond has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable cfl1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable vortex_radius_wake_ind has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "Variable rbm_vel_g has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]\u001b[0m\n", "Variable centre_rot_g has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 0.0]\u001b[0m\n", "Variable map_forces_on_struct has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "|=====|=====|============|==========|==========|==========|==========|==========|==========|\u001b[0m\n", "|iter |step | log10(res) | Fx | Fy | Fz | Mx | My | Mz |\u001b[0m\n", "|=====|=====|============|==========|==========|==========|==========|==========|==========|\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "fatal: not a git repository (or any of the parent directories): .git\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "| 0 | 0 | 0.00000 | -0.0277 | 0.0000 | 1.9185 | 0.0000 | 0.0387 | -0.0000 |\u001b[0m\n", "| 1 | 0 | -6.69935 | -0.0290 | 0.0000 | 1.9636 | 0.0000 | 0.0396 | -0.0000 |\u001b[0m\n", "| 0 | 1 | 0.00000 | -0.0566 | 0.0000 | 3.9396 | -0.0000 | 0.0794 | -0.0000 |\u001b[0m\n", "| 1 | 1 | -6.06871 | -0.0592 | 0.0000 | 4.0325 | -0.0000 | 0.0812 | -0.0000 |\u001b[0m\n", "| 0 | 2 | 0.00000 | -0.0866 | 0.0000 | 6.0718 | -0.0000 | 0.1222 | -0.0000 |\u001b[0m\n", "| 1 | 2 | -5.68671 | -0.0906 | 0.0000 | 6.2157 | -0.0000 | 0.1250 | -0.0000 |\u001b[0m\n", "| 0 | 3 | 0.00000 | -0.1177 | 0.0000 | 8.3244 | -0.0000 | 0.1673 | -0.0000 |\u001b[0m\n", "| 1 | 3 | -5.40580 | -0.1233 | 0.0000 | 8.5230 | -0.0000 | 0.1712 | -0.0000 |\u001b[0m\n", "| 0 | 4 | 0.00000 | -0.1501 | 0.0000 | 10.7075 | -0.0000 | 0.2149 | -0.0000 |\u001b[0m\n", "| 1 | 4 | -5.17976 | -0.1572 | 0.0000 | 10.9648 | -0.0000 | 0.2200 | -0.0000 |\u001b[0m\n", "\u001b[36mGenerating an instance of AerogridPlot\u001b[0m\n", "Variable include_forward_motion has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable include_unsteady_applied_forces has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable name_prefix has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable u_inf has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0\u001b[0m\n", "Variable dt has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0\u001b[0m\n", "Variable include_velocities has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable include_incidence_angle has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable num_cores has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable vortex_radius has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "Variable stride has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable save_wake has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "\u001b[34m...Finished\u001b[0m\n", "\u001b[36mGenerating an instance of BeamPlot\u001b[0m\n", "Variable include_FoR has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable include_applied_moments has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable name_prefix has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable output_rbm has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "\u001b[34m...Finished\u001b[0m\n", "\u001b[36mGenerating an instance of DynamicCoupled\u001b[0m\n", "Variable controller_id has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable controller_settings has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable steps_without_unsteady_force has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable pseudosteps_ramp_unsteady_force has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable correct_forces_method has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable correct_forces_settings has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable runtime_generators has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "\u001b[36mGenerating an instance of NonLinearDynamicPrescribedStep\u001b[0m\n", "\u001b[36mGenerating an instance of StepUvlm\u001b[0m\n", "Variable num_load_steps has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable abs_threshold has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-13\u001b[0m\n", "Variable gravity_on has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable gravity_dir has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 1.0]\u001b[0m\n", "Variable relaxation_factor has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.3\u001b[0m\n", "Variable iterative_solver has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable iterative_tol has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0001\u001b[0m\n", "Variable iterative_precond has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable cfl1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable vortex_radius_wake_ind has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "Variable interp_coords has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable filter_method has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable interp_method has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable yaw_slerp has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0\u001b[0m\n", "Variable centre_rot has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 0.0]\u001b[0m\n", "Variable quasi_steady has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable gust_component has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 2\u001b[0m\n", "\u001b[36mGenerating an instance of WriteVariablesTime\u001b[0m\n", "Variable delimiter has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable FoR_variables has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: ['']\u001b[0m\n", "Variable FoR_number has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0]\u001b[0m\n", "Variable aero_panels_variables has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: ['']\u001b[0m\n", "Variable aero_panels_isurf has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0]\u001b[0m\n", "Variable aero_panels_im has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0]\u001b[0m\n", "Variable aero_panels_in has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0]\u001b[0m\n", "Variable aero_nodes_variables has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: ['']\u001b[0m\n", "Variable aero_nodes_isurf has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0]\u001b[0m\n", "Variable aero_nodes_im has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0]\u001b[0m\n", "Variable aero_nodes_in has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0]\u001b[0m\n", "Variable cleanup_old_solution has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable vel_field_variables has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: []\u001b[0m\n", "Variable vel_field_points has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0. 0. 0.]\u001b[0m\n", "\u001b[36mGenerating an instance of BeamPlot\u001b[0m\n", "Variable include_FoR has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable include_applied_moments has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable name_prefix has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable output_rbm has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "\u001b[36mGenerating an instance of AerogridPlot\u001b[0m\n", "Variable include_forward_motion has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable include_unsteady_applied_forces has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable name_prefix has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable dt has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0\u001b[0m\n", "Variable include_velocities has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable include_incidence_angle has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable num_cores has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable vortex_radius has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "Variable stride has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable save_wake has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "|=======|========|======|==============|==============|==============|==============|==============|\u001b[0m\n", "| ts | t | iter | struc ratio | iter time | residual vel | FoR_vel(x) | FoR_vel(z) |\u001b[0m\n", "|=======|========|======|==============|==============|==============|==============|==============|\u001b[0m\n", "| 1 | 0.0006 | 5 | 0.968365 | 18.671949 | -6.417211 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 2 | 0.0013 | 4 | 0.971972 | 15.336584 | -6.496227 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 3 | 0.0019 | 3 | 0.969326 | 12.041152 | -6.173354 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 4 | 0.0025 | 3 | 0.968342 | 10.516750 | -6.020512 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 5 | 0.0031 | 4 | 0.955032 | 9.720251 | -6.536090 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 6 | 0.0037 | 4 | 0.966989 | 13.089750 | -6.365510 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 7 | 0.0044 | 4 | 0.963588 | 12.110149 | -6.233103 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 8 | 0.0050 | 4 | 0.951098 | 9.182460 | -6.162708 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 9 | 0.0056 | 4 | 0.979628 | 28.828536 | -6.113240 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 10 | 0.0063 | 4 | 0.956612 | 13.303912 | -6.082344 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 11 | 0.0069 | 4 | 0.966229 | 12.650505 | -6.038075 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 12 | 0.0075 | 5 | 0.960982 | 14.281275 | -6.727378 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 13 | 0.0081 | 5 | 0.956631 | 12.562050 | -6.701112 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 14 | 0.0088 | 5 | 0.947390 | 10.019976 | -6.665381 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 15 | 0.0094 | 5 | 0.954709 | 11.545903 | -6.619206 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 16 | 0.0100 | 5 | 0.948316 | 9.931642 | -6.570860 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 17 | 0.0106 | 5 | 0.952382 | 11.503413 | -6.528343 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 18 | 0.0112 | 5 | 0.947785 | 10.348513 | -6.489641 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 19 | 0.0119 | 5 | 0.948694 | 9.952890 | -6.453494 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 20 | 0.0125 | 5 | 0.964102 | 15.367483 | -6.426783 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 21 | 0.0131 | 5 | 0.950298 | 10.537648 | -6.404302 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 22 | 0.0138 | 5 | 0.950145 | 10.997150 | -6.381964 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 23 | 0.0144 | 5 | 0.951866 | 10.756376 | -6.365094 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 24 | 0.0150 | 5 | 0.953387 | 11.333439 | -6.348193 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 25 | 0.0156 | 5 | 0.949008 | 10.640736 | -6.323539 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 26 | 0.0163 | 5 | 0.951558 | 11.234257 | -6.293924 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 27 | 0.0169 | 5 | 0.953764 | 11.976087 | -6.266339 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 28 | 0.0175 | 5 | 0.954230 | 12.160064 | -6.244295 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 29 | 0.0181 | 5 | 0.955667 | 11.934807 | -6.226476 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 30 | 0.0187 | 5 | 0.946544 | 9.848974 | -6.210525 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 31 | 0.0194 | 5 | 0.958163 | 12.588759 | -6.197849 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 32 | 0.0200 | 5 | 0.941142 | 9.263582 | -6.187658 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 33 | 0.0206 | 5 | 0.946805 | 10.078831 | -6.176206 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 34 | 0.0213 | 5 | 0.948141 | 10.518540 | -6.164869 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 35 | 0.0219 | 5 | 0.952281 | 11.594154 | -6.153254 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 36 | 0.0225 | 5 | 0.956436 | 12.668008 | -6.138480 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 37 | 0.0231 | 5 | 0.962601 | 14.975047 | -6.121525 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 38 | 0.0238 | 5 | 0.973890 | 25.366286 | -6.108135 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 39 | 0.0244 | 5 | 0.949656 | 10.873095 | -6.100178 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 40 | 0.0250 | 5 | 0.948873 | 11.715339 | -6.094354 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 41 | 0.0256 | 5 | 0.949846 | 10.766982 | -6.088927 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 42 | 0.0262 | 5 | 0.947597 | 10.630621 | -6.083978 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 43 | 0.0269 | 5 | 0.951180 | 11.054296 | -6.078006 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 44 | 0.0275 | 5 | 0.951628 | 10.999336 | -6.071380 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 45 | 0.0281 | 5 | 0.965219 | 16.030360 | -6.065627 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 46 | 0.0288 | 5 | 0.977969 | 27.407470 | -6.059853 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 47 | 0.0294 | 5 | 0.973189 | 21.817333 | -6.052922 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 48 | 0.0300 | 5 | 0.948717 | 11.354869 | -6.045901 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 49 | 0.0306 | 5 | 0.948421 | 10.883957 | -6.040864 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 50 | 0.0312 | 5 | 0.948296 | 10.736110 | -6.040590 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 51 | 0.0319 | 5 | 0.948139 | 10.621232 | -6.041367 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 52 | 0.0325 | 5 | 0.948025 | 10.336440 | -6.038935 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 53 | 0.0331 | 5 | 0.952062 | 11.658173 | -6.034811 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 54 | 0.0338 | 5 | 0.952953 | 11.996480 | -6.029771 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 55 | 0.0344 | 5 | 0.952641 | 11.794019 | -6.023840 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 56 | 0.0350 | 5 | 0.948103 | 10.625961 | -6.019904 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 57 | 0.0356 | 5 | 0.950134 | 11.525078 | -6.017249 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 58 | 0.0362 | 5 | 0.942222 | 9.724377 | -6.013493 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 59 | 0.0369 | 5 | 0.945251 | 10.558316 | -6.010749 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 60 | 0.0375 | 5 | 0.948057 | 10.545861 | -6.010847 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 61 | 0.0381 | 5 | 0.950653 | 11.133913 | -6.013553 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 62 | 0.0387 | 5 | 0.949715 | 11.242525 | -6.017042 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 63 | 0.0394 | 5 | 0.949644 | 10.870169 | -6.017983 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 64 | 0.0400 | 5 | 0.947384 | 10.770161 | -6.016882 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 65 | 0.0406 | 5 | 0.951051 | 11.028067 | -6.016366 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 66 | 0.0413 | 5 | 0.947521 | 10.962657 | -6.016870 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 67 | 0.0419 | 5 | 0.953326 | 11.602128 | -6.018436 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 68 | 0.0425 | 5 | 0.943913 | 10.217246 | -6.021504 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 69 | 0.0431 | 5 | 0.950180 | 11.015258 | -6.024162 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 70 | 0.0438 | 5 | 0.956848 | 12.476811 | -6.025761 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 71 | 0.0444 | 5 | 0.949333 | 11.022746 | -6.029208 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 72 | 0.0450 | 5 | 0.952842 | 10.957240 | -6.034612 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 73 | 0.0456 | 5 | 0.950195 | 11.372289 | -6.039117 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 74 | 0.0462 | 5 | 0.952230 | 10.951780 | -6.041620 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 75 | 0.0469 | 5 | 0.944391 | 10.321632 | -6.043629 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 76 | 0.0475 | 5 | 0.940703 | 9.038255 | -6.046624 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 77 | 0.0481 | 5 | 0.951349 | 11.076794 | -6.052081 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 78 | 0.0488 | 5 | 0.943967 | 9.884030 | -6.059680 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 79 | 0.0494 | 5 | 0.945705 | 10.703932 | -6.067778 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 80 | 0.0500 | 5 | 0.943330 | 9.678802 | -6.075491 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 81 | 0.0506 | 5 | 0.954047 | 11.499293 | -6.082424 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 82 | 0.0513 | 5 | 0.946896 | 10.192189 | -6.089625 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 83 | 0.0519 | 5 | 0.943645 | 9.952958 | -6.098219 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 84 | 0.0525 | 5 | 0.945091 | 10.095940 | -6.105934 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 85 | 0.0531 | 5 | 0.947015 | 10.301849 | -6.110879 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 86 | 0.0537 | 5 | 0.935800 | 8.870328 | -6.115130 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 87 | 0.0544 | 5 | 0.947104 | 10.836437 | -6.120945 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 88 | 0.0550 | 5 | 0.963095 | 15.823192 | -6.127793 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 89 | 0.0556 | 5 | 0.947735 | 10.736601 | -6.135927 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 90 | 0.0563 | 5 | 0.947219 | 10.730737 | -6.144368 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 91 | 0.0569 | 5 | 0.948428 | 11.076037 | -6.150790 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 92 | 0.0575 | 5 | 0.940665 | 9.837327 | -6.155716 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 93 | 0.0581 | 5 | 0.948581 | 11.200799 | -6.161582 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 94 | 0.0588 | 5 | 0.946228 | 10.552050 | -6.168338 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 95 | 0.0594 | 5 | 0.948208 | 10.675161 | -6.174927 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 96 | 0.0600 | 5 | 0.943809 | 10.275394 | -6.180289 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 97 | 0.0606 | 5 | 0.935073 | 9.028701 | -6.184616 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 98 | 0.0612 | 5 | 0.948258 | 10.438629 | -6.190591 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 99 | 0.0619 | 5 | 0.954885 | 12.366372 | -6.198655 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 100 | 0.0625 | 5 | 0.947114 | 10.622774 | -6.206748 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 101 | 0.0631 | 5 | 0.950739 | 11.377920 | -6.214272 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 102 | 0.0638 | 5 | 0.955202 | 12.591837 | -6.220168 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 103 | 0.0644 | 5 | 0.945275 | 10.215581 | -6.224393 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 104 | 0.0650 | 5 | 0.952762 | 11.989062 | -6.229463 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 105 | 0.0656 | 5 | 0.952733 | 11.871830 | -6.236478 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 106 | 0.0663 | 5 | 0.948673 | 10.807147 | -6.243449 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 107 | 0.0669 | 5 | 0.930800 | 8.894847 | -6.249473 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 108 | 0.0675 | 5 | 0.957262 | 13.341049 | -6.255890 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 109 | 0.0681 | 5 | 0.947861 | 11.027717 | -6.263542 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 110 | 0.0688 | 5 | 0.952574 | 11.537263 | -6.272266 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 111 | 0.0694 | 5 | 0.947710 | 10.136458 | -6.281527 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 112 | 0.0700 | 5 | 0.935899 | 9.226067 | -6.289751 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 113 | 0.0706 | 5 | 0.942306 | 10.016803 | -6.295951 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 114 | 0.0713 | 5 | 0.939406 | 9.518147 | -6.302064 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 115 | 0.0719 | 5 | 0.943245 | 10.038173 | -6.309255 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 116 | 0.0725 | 5 | 0.951977 | 11.464750 | -6.318358 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 117 | 0.0731 | 5 | 0.945140 | 10.013828 | -6.329243 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 118 | 0.0737 | 5 | 0.948454 | 10.736181 | -6.338812 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 119 | 0.0744 | 5 | 0.938591 | 9.071753 | -6.347747 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 120 | 0.0750 | 5 | 0.946253 | 10.567693 | -6.359016 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 121 | 0.0756 | 5 | 0.947073 | 11.273658 | -6.370755 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 122 | 0.0762 | 5 | 0.941092 | 9.599160 | -6.381991 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 123 | 0.0769 | 5 | 0.944881 | 10.358563 | -6.392966 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 124 | 0.0775 | 5 | 0.947911 | 10.805558 | -6.401508 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 125 | 0.0781 | 5 | 0.945897 | 10.731285 | -6.409650 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 126 | 0.0788 | 5 | 0.940708 | 10.767903 | -6.420684 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 127 | 0.0794 | 5 | 0.940233 | 9.522662 | -6.432840 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 128 | 0.0800 | 5 | 0.944515 | 10.353698 | -6.444843 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 129 | 0.0806 | 5 | 0.949921 | 10.744679 | -6.455002 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 130 | 0.0813 | 5 | 0.955518 | 13.083724 | -6.462719 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 131 | 0.0819 | 5 | 0.943537 | 10.295544 | -6.471294 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 132 | 0.0825 | 5 | 0.947005 | 10.478883 | -6.481146 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 133 | 0.0831 | 5 | 0.950212 | 11.168756 | -6.489653 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 134 | 0.0838 | 5 | 0.947648 | 11.245141 | -6.496921 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 135 | 0.0844 | 5 | 0.952376 | 11.929442 | -6.503753 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 136 | 0.0850 | 5 | 0.955622 | 13.096018 | -6.511057 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 137 | 0.0856 | 5 | 0.940609 | 9.735925 | -6.520895 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 138 | 0.0863 | 5 | 0.940370 | 9.500913 | -6.533221 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 139 | 0.0869 | 5 | 0.944022 | 10.989595 | -6.543976 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 140 | 0.0875 | 5 | 0.945349 | 10.576644 | -6.550689 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 141 | 0.0881 | 5 | 0.942640 | 9.924819 | -6.556179 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 142 | 0.0887 | 5 | 0.947886 | 11.171925 | -6.562681 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 143 | 0.0894 | 5 | 0.953569 | 13.078407 | -6.569487 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 144 | 0.0900 | 5 | 0.935608 | 8.818526 | -6.576170 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 145 | 0.0906 | 5 | 0.942645 | 10.197815 | -6.580802 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 146 | 0.0912 | 5 | 0.949960 | 11.496152 | -6.584265 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 147 | 0.0919 | 5 | 0.945986 | 11.480662 | -6.589730 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 148 | 0.0925 | 5 | 0.937520 | 10.207823 | -6.596222 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 149 | 0.0931 | 5 | 0.943962 | 10.327430 | -6.602564 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 150 | 0.0938 | 5 | 0.960532 | 14.213603 | -6.607404 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 151 | 0.0944 | 5 | 0.956264 | 14.017086 | -6.608611 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 152 | 0.0950 | 5 | 0.937707 | 9.309265 | -6.610140 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 153 | 0.0956 | 5 | 0.945661 | 11.116661 | -6.615885 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 154 | 0.0963 | 5 | 0.947168 | 10.966585 | -6.623153 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 155 | 0.0969 | 5 | 0.947456 | 11.300364 | -6.630447 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 156 | 0.0975 | 5 | 0.943753 | 10.825258 | -6.637990 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 157 | 0.0981 | 5 | 0.963825 | 15.814815 | -6.645485 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 158 | 0.0988 | 5 | 0.945822 | 11.052504 | -6.653934 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 159 | 0.0994 | 5 | 0.948607 | 11.214047 | -6.662756 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 160 | 0.1000 | 5 | 0.946378 | 10.745310 | -6.670552 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 161 | 0.1006 | 5 | 0.947401 | 11.221507 | -6.676330 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 162 | 0.1013 | 5 | 0.944233 | 10.110627 | -6.680679 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 163 | 0.1019 | 5 | 0.947101 | 11.862333 | -6.686866 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 164 | 0.1025 | 5 | 0.967109 | 18.147130 | -6.696421 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 165 | 0.1031 | 5 | 0.940993 | 10.605252 | -6.707483 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 166 | 0.1038 | 5 | 0.955383 | 13.030459 | -6.716984 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 167 | 0.1044 | 5 | 0.948422 | 11.665669 | -6.724584 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 168 | 0.1050 | 4 | 0.955500 | 11.664639 | -6.005053 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 169 | 0.1056 | 4 | 0.951802 | 10.576800 | -6.013451 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 170 | 0.1062 | 4 | 0.951200 | 9.567037 | -6.021257 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 171 | 0.1069 | 4 | 0.968419 | 14.355263 | -6.029494 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 172 | 0.1075 | 4 | 0.966190 | 13.662715 | -6.037786 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 173 | 0.1081 | 4 | 0.956161 | 12.371329 | -6.045909 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 174 | 0.1087 | 4 | 0.951665 | 11.085254 | -6.056996 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 175 | 0.1094 | 4 | 0.953042 | 10.715927 | -6.069746 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 176 | 0.1100 | 4 | 0.958061 | 11.712813 | -6.079731 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 177 | 0.1106 | 4 | 0.960356 | 12.695643 | -6.085509 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 178 | 0.1113 | 4 | 0.954514 | 11.793410 | -6.088583 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 179 | 0.1119 | 4 | 0.950668 | 10.003142 | -6.091305 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 180 | 0.1125 | 4 | 0.946102 | 9.886626 | -6.093073 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 181 | 0.1131 | 4 | 0.949649 | 10.301983 | -6.093995 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 182 | 0.1138 | 4 | 0.953119 | 10.652363 | -6.095412 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 183 | 0.1144 | 4 | 0.951766 | 10.832586 | -6.097424 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 184 | 0.1150 | 4 | 0.955132 | 10.462639 | -6.097812 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 185 | 0.1156 | 4 | 0.960911 | 13.618529 | -6.096289 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 186 | 0.1163 | 4 | 0.956462 | 11.546490 | -6.093168 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 187 | 0.1169 | 4 | 0.962654 | 12.813508 | -6.088310 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 188 | 0.1175 | 4 | 0.968176 | 18.430304 | -6.081391 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 189 | 0.1181 | 4 | 0.946358 | 9.833844 | -6.074350 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 190 | 0.1188 | 4 | 0.947277 | 9.157477 | -6.068791 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 191 | 0.1194 | 4 | 0.968489 | 15.667897 | -6.062826 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 192 | 0.1200 | 4 | 0.950315 | 9.516710 | -6.057464 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 193 | 0.1206 | 4 | 0.940345 | 8.711639 | -6.053352 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 194 | 0.1212 | 4 | 0.950416 | 10.270131 | -6.047761 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 195 | 0.1219 | 4 | 0.949720 | 9.165432 | -6.038768 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 196 | 0.1225 | 4 | 0.943330 | 8.940929 | -6.027309 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 197 | 0.1231 | 4 | 0.945974 | 9.224462 | -6.017552 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 198 | 0.1237 | 4 | 0.938437 | 7.985228 | -6.009420 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 199 | 0.1244 | 4 | 0.949442 | 9.156467 | -6.001601 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 200 | 0.1250 | 5 | 0.948617 | 11.723775 | -6.730831 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 201 | 0.1256 | 5 | 0.936233 | 9.874182 | -6.724077 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 202 | 0.1263 | 5 | 0.948015 | 11.857640 | -6.717044 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 203 | 0.1269 | 5 | 0.937598 | 10.443674 | -6.710956 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 204 | 0.1275 | 5 | 0.950856 | 12.267857 | -6.704661 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 205 | 0.1281 | 5 | 0.949025 | 11.520669 | -6.696225 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 206 | 0.1288 | 5 | 0.952569 | 12.509402 | -6.686150 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 207 | 0.1294 | 5 | 0.945493 | 11.217238 | -6.675868 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 208 | 0.1300 | 5 | 0.943550 | 10.795355 | -6.668776 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 209 | 0.1306 | 5 | 0.963850 | 17.410402 | -6.662688 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 210 | 0.1313 | 5 | 0.964352 | 17.017205 | -6.655037 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 211 | 0.1319 | 5 | 0.944406 | 10.911295 | -6.647090 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 212 | 0.1325 | 5 | 0.941326 | 10.069910 | -6.637080 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 213 | 0.1331 | 5 | 0.949649 | 11.424372 | -6.628286 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 214 | 0.1338 | 5 | 0.948237 | 11.950824 | -6.618372 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 215 | 0.1344 | 5 | 0.933445 | 9.096234 | -6.607924 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 216 | 0.1350 | 5 | 0.954119 | 12.891909 | -6.600956 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 217 | 0.1356 | 5 | 0.949010 | 11.880124 | -6.591819 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 218 | 0.1363 | 5 | 0.946764 | 11.708015 | -6.578566 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 219 | 0.1369 | 5 | 0.939680 | 10.037507 | -6.568256 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 220 | 0.1375 | 5 | 0.949051 | 10.831389 | -6.560834 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 221 | 0.1381 | 5 | 0.951881 | 12.069925 | -6.550114 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 222 | 0.1388 | 5 | 0.946708 | 11.655207 | -6.546699 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 223 | 0.1394 | 5 | 0.945491 | 11.092597 | -6.535719 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 224 | 0.1400 | 5 | 0.942582 | 10.299325 | -6.518312 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 225 | 0.1406 | 5 | 0.943477 | 11.023694 | -6.502154 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 226 | 0.1413 | 5 | 0.941513 | 10.661141 | -6.487594 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 227 | 0.1419 | 5 | 0.944854 | 11.009248 | -6.471501 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 228 | 0.1425 | 5 | 0.947037 | 11.111865 | -6.455693 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 229 | 0.1431 | 5 | 0.943006 | 11.418393 | -6.445663 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 230 | 0.1438 | 5 | 0.937785 | 10.281053 | -6.436643 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 231 | 0.1444 | 5 | 0.953180 | 13.515919 | -6.423862 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 232 | 0.1450 | 5 | 0.942807 | 10.828190 | -6.409260 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 233 | 0.1456 | 5 | 0.947758 | 12.155366 | -6.416705 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 234 | 0.1462 | 5 | 0.947850 | 11.147242 | -6.389836 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 235 | 0.1469 | 5 | 0.949502 | 11.667867 | -6.370341 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 236 | 0.1475 | 5 | 0.941159 | 10.366536 | -6.357640 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 237 | 0.1481 | 5 | 0.941645 | 11.566625 | -6.343108 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 238 | 0.1487 | 5 | 0.943970 | 10.571422 | -6.329128 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 239 | 0.1494 | 5 | 0.948570 | 12.000235 | -6.315768 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 240 | 0.1500 | 5 | 0.956057 | 14.176120 | -6.302302 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 241 | 0.1506 | 5 | 0.941344 | 10.372841 | -6.286996 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 242 | 0.1512 | 5 | 0.944239 | 11.163841 | -6.271065 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 243 | 0.1519 | 5 | 0.941355 | 10.750759 | -6.256601 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 244 | 0.1525 | 5 | 0.946149 | 11.848520 | -6.242143 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 245 | 0.1531 | 5 | 0.953282 | 13.054623 | -6.224536 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 246 | 0.1537 | 5 | 0.939600 | 10.533198 | -6.204649 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 247 | 0.1544 | 5 | 0.950865 | 12.636092 | -6.191911 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 248 | 0.1550 | 5 | 0.939138 | 10.755056 | -6.177627 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 249 | 0.1556 | 5 | 0.941024 | 11.774469 | -6.163797 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 250 | 0.1562 | 5 | 0.945149 | 11.603998 | -6.149284 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 251 | 0.1569 | 5 | 0.944137 | 11.027009 | -6.157241 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 252 | 0.1575 | 5 | 0.945240 | 11.557927 | -6.124949 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 253 | 0.1581 | 5 | 0.946122 | 11.934396 | -6.108130 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 254 | 0.1588 | 5 | 0.935588 | 11.200220 | -6.093652 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 255 | 0.1594 | 5 | 0.948408 | 11.680672 | -6.078210 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 256 | 0.1600 | 5 | 0.943591 | 10.678600 | -6.061033 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 257 | 0.1606 | 5 | 0.945829 | 11.584085 | -6.042628 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 258 | 0.1613 | 5 | 0.947088 | 11.800646 | -6.025314 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 259 | 0.1619 | 5 | 0.944964 | 11.772600 | -6.010586 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 260 | 0.1625 | 6 | 0.945209 | 13.291276 | -6.734883 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 261 | 0.1631 | 6 | 0.943456 | 12.680267 | -6.718964 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 262 | 0.1638 | 6 | 0.944619 | 13.215398 | -6.702060 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 263 | 0.1644 | 6 | 0.947007 | 14.169829 | -6.686353 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 264 | 0.1650 | 6 | 0.938508 | 12.289283 | -6.673467 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 265 | 0.1656 | 6 | 0.951268 | 14.610949 | -6.660933 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 266 | 0.1663 | 6 | 0.935965 | 11.988576 | -6.647986 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 267 | 0.1669 | 6 | 0.946580 | 14.197086 | -6.636073 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 268 | 0.1675 | 6 | 0.942800 | 12.822154 | -6.626319 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 269 | 0.1681 | 6 | 0.947284 | 13.338966 | -6.620655 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 270 | 0.1688 | 6 | 0.939962 | 12.287910 | -6.617902 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 271 | 0.1694 | 6 | 0.949752 | 16.501225 | -6.615042 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 272 | 0.1700 | 6 | 0.939267 | 12.324404 | -6.613633 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 273 | 0.1706 | 6 | 0.944347 | 13.207239 | -6.615915 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 274 | 0.1713 | 6 | 0.942063 | 12.524115 | -6.624740 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 275 | 0.1719 | 6 | 0.945643 | 13.180858 | -6.632674 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 276 | 0.1725 | 6 | 0.936511 | 12.016970 | -6.639310 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 277 | 0.1731 | 6 | 0.947054 | 13.827464 | -6.647491 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 278 | 0.1738 | 6 | 0.935859 | 11.941455 | -6.659334 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 279 | 0.1744 | 6 | 0.939836 | 12.367750 | -6.673970 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 280 | 0.1750 | 6 | 0.936526 | 12.331560 | -6.678839 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 281 | 0.1756 | 6 | 0.938118 | 12.166943 | -6.693248 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 282 | 0.1762 | 6 | 0.935611 | 11.910315 | -6.709670 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 283 | 0.1769 | 6 | 0.944449 | 13.326162 | -6.733336 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 284 | 0.1775 | 5 | 0.939992 | 11.053350 | -6.012363 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 285 | 0.1781 | 5 | 0.948281 | 12.083963 | -6.030102 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 286 | 0.1787 | 5 | 0.948533 | 11.727205 | -6.047338 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 287 | 0.1794 | 5 | 0.939986 | 10.345996 | -6.062876 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 288 | 0.1800 | 5 | 0.945252 | 11.362258 | -6.079639 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 289 | 0.1806 | 5 | 0.947508 | 12.199549 | -6.097383 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 290 | 0.1812 | 5 | 0.933631 | 9.339983 | -6.115169 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 291 | 0.1819 | 5 | 0.940224 | 10.936754 | -6.132664 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 292 | 0.1825 | 5 | 0.939724 | 10.449330 | -6.147923 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 293 | 0.1831 | 5 | 0.948690 | 12.151484 | -6.163659 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 294 | 0.1837 | 5 | 0.942595 | 11.039602 | -6.181324 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 295 | 0.1844 | 5 | 0.945059 | 11.630183 | -6.198729 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 296 | 0.1850 | 5 | 0.945800 | 12.725906 | -6.215272 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 297 | 0.1856 | 5 | 0.942269 | 10.736765 | -6.230292 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 298 | 0.1862 | 5 | 0.942436 | 11.227497 | -6.244567 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 299 | 0.1869 | 5 | 0.939725 | 10.225151 | -6.260472 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 300 | 0.1875 | 5 | 0.944196 | 11.060303 | -6.276916 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 301 | 0.1881 | 5 | 0.960094 | 15.880141 | -6.291941 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 302 | 0.1888 | 5 | 0.948014 | 12.149703 | -6.305562 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 303 | 0.1894 | 5 | 0.941994 | 10.651260 | -6.318965 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 304 | 0.1900 | 5 | 0.950609 | 12.495602 | -6.333482 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 305 | 0.1906 | 5 | 0.940519 | 10.488050 | -6.348189 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 306 | 0.1913 | 5 | 0.947913 | 12.691566 | -6.361913 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 307 | 0.1919 | 5 | 0.942720 | 10.632595 | -6.374253 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 308 | 0.1925 | 5 | 0.956283 | 14.595177 | -6.385687 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 309 | 0.1931 | 5 | 0.938529 | 9.986877 | -6.399126 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 310 | 0.1938 | 5 | 0.941697 | 11.624213 | -6.413159 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 311 | 0.1944 | 5 | 0.947358 | 11.831380 | -6.425737 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 312 | 0.1950 | 5 | 0.940657 | 10.664760 | -6.437914 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 313 | 0.1956 | 5 | 0.945992 | 11.745995 | -6.449198 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 314 | 0.1963 | 5 | 0.945434 | 11.700196 | -6.461378 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 315 | 0.1969 | 5 | 0.952461 | 13.212226 | -6.475399 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 316 | 0.1975 | 5 | 0.946748 | 12.410715 | -6.487818 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 317 | 0.1981 | 5 | 0.942489 | 11.260109 | -6.498905 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 318 | 0.1988 | 5 | 0.954916 | 13.542801 | -6.510605 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 319 | 0.1994 | 5 | 0.951023 | 13.047720 | -6.522468 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 320 | 0.2000 | 5 | 0.941290 | 10.351259 | -6.535653 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 321 | 0.2006 | 5 | 0.949801 | 12.561128 | -6.548605 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 322 | 0.2013 | 5 | 0.933351 | 9.742402 | -6.559248 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 323 | 0.2019 | 5 | 0.945824 | 11.669179 | -6.570035 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 324 | 0.2025 | 5 | 0.940618 | 11.432235 | -6.581942 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 325 | 0.2031 | 5 | 0.943817 | 11.426316 | -6.594593 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 326 | 0.2038 | 5 | 0.953734 | 13.505159 | -6.606923 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 327 | 0.2044 | 5 | 0.931261 | 10.402201 | -6.617755 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 328 | 0.2050 | 5 | 0.949553 | 12.230616 | -6.628032 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 329 | 0.2056 | 5 | 0.943117 | 11.498853 | -6.638920 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 330 | 0.2063 | 5 | 0.943764 | 11.763657 | -6.651286 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 331 | 0.2069 | 5 | 0.943443 | 11.352340 | -6.663433 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 332 | 0.2075 | 5 | 0.939813 | 10.792876 | -6.673535 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 333 | 0.2081 | 5 | 0.939146 | 10.863336 | -6.683450 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 334 | 0.2087 | 5 | 0.942934 | 11.064193 | -6.694206 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 335 | 0.2094 | 5 | 0.947165 | 11.997910 | -6.705246 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 336 | 0.2100 | 5 | 0.951135 | 12.933304 | -6.716584 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 337 | 0.2106 | 5 | 0.946188 | 11.651776 | -6.725582 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 338 | 0.2112 | 4 | 0.947764 | 9.462267 | -6.006966 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 339 | 0.2119 | 4 | 0.945007 | 9.856144 | -6.016166 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 340 | 0.2125 | 4 | 0.952493 | 11.507456 | -6.025795 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 341 | 0.2131 | 4 | 0.950982 | 10.546667 | -6.035361 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 342 | 0.2137 | 4 | 0.952015 | 10.293428 | -6.044313 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 343 | 0.2144 | 4 | 0.947798 | 10.362436 | -6.050845 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 344 | 0.2150 | 4 | 0.947564 | 9.734232 | -6.057454 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 345 | 0.2156 | 4 | 0.942505 | 9.236773 | -6.065222 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 346 | 0.2162 | 4 | 0.947544 | 9.717363 | -6.072005 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 347 | 0.2169 | 4 | 0.938501 | 8.694150 | -6.077386 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 348 | 0.2175 | 4 | 0.949458 | 10.271883 | -6.081917 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 349 | 0.2181 | 4 | 0.938702 | 8.538571 | -6.086876 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 350 | 0.2188 | 4 | 0.939707 | 8.995470 | -6.092985 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 351 | 0.2194 | 4 | 0.946523 | 9.639948 | -6.099074 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 352 | 0.2200 | 4 | 0.935855 | 8.213983 | -6.104246 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 353 | 0.2206 | 4 | 0.944748 | 9.607103 | -6.107678 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 354 | 0.2213 | 4 | 0.956262 | 12.531544 | -6.110114 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 355 | 0.2219 | 4 | 0.942209 | 9.275017 | -6.112920 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 356 | 0.2225 | 4 | 0.946568 | 9.987548 | -6.115915 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 357 | 0.2231 | 4 | 0.935081 | 8.506918 | -6.118795 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 358 | 0.2238 | 4 | 0.940927 | 8.792735 | -6.120967 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 359 | 0.2244 | 4 | 0.950028 | 11.037147 | -6.122486 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 360 | 0.2250 | 4 | 0.941703 | 10.319008 | -6.124768 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 361 | 0.2256 | 4 | 0.944156 | 9.300880 | -6.126662 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 362 | 0.2263 | 4 | 0.949011 | 10.375520 | -6.126332 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 363 | 0.2269 | 4 | 0.942776 | 9.523808 | -6.124668 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 364 | 0.2275 | 4 | 0.940965 | 9.249205 | -6.121803 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 365 | 0.2281 | 4 | 0.943005 | 9.560917 | -6.118109 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 366 | 0.2288 | 4 | 0.942491 | 9.377592 | -6.114540 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 367 | 0.2294 | 4 | 0.966386 | 15.220141 | -6.110829 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 368 | 0.2300 | 4 | 0.968380 | 16.586067 | -6.106324 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 369 | 0.2306 | 4 | 0.952023 | 11.253964 | -6.100741 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 370 | 0.2313 | 4 | 0.950157 | 10.531796 | -6.094886 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 371 | 0.2319 | 4 | 0.950168 | 10.427808 | -6.088197 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 372 | 0.2325 | 4 | 0.936196 | 8.918983 | -6.080290 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 373 | 0.2331 | 4 | 0.942862 | 9.217407 | -6.071418 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 374 | 0.2338 | 4 | 0.931575 | 8.374174 | -6.062450 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 375 | 0.2344 | 4 | 0.943912 | 9.646384 | -6.054102 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 376 | 0.2350 | 4 | 0.952382 | 10.994409 | -6.045824 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 377 | 0.2356 | 4 | 0.953536 | 10.789837 | -6.037022 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 378 | 0.2363 | 4 | 0.934100 | 8.654277 | -6.028280 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 379 | 0.2369 | 4 | 0.942988 | 9.373151 | -6.019596 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 380 | 0.2375 | 4 | 0.944104 | 9.815691 | -6.010129 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 381 | 0.2381 | 4 | 0.937398 | 8.652296 | -6.000383 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 382 | 0.2388 | 5 | 0.938554 | 10.822313 | -6.725142 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 383 | 0.2394 | 5 | 0.947037 | 11.900135 | -6.714875 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 384 | 0.2400 | 5 | 0.941750 | 11.175656 | -6.704971 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 385 | 0.2406 | 5 | 0.945792 | 12.536697 | -6.695529 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 386 | 0.2412 | 5 | 0.939800 | 11.015844 | -6.686894 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 387 | 0.2419 | 5 | 0.935986 | 10.217669 | -6.678073 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 388 | 0.2425 | 5 | 0.943653 | 12.185779 | -6.668896 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 389 | 0.2431 | 5 | 0.951555 | 13.377180 | -6.660090 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 390 | 0.2437 | 5 | 0.943060 | 12.209697 | -6.651496 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 391 | 0.2444 | 5 | 0.946853 | 12.046725 | -6.642875 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 392 | 0.2450 | 5 | 0.935869 | 10.530684 | -6.634471 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 393 | 0.2456 | 5 | 0.944948 | 11.274202 | -6.626020 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 394 | 0.2462 | 5 | 0.939311 | 10.513262 | -6.617542 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 395 | 0.2469 | 5 | 0.940147 | 11.755655 | -6.609586 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 396 | 0.2475 | 5 | 0.946002 | 12.170166 | -6.601957 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 397 | 0.2481 | 5 | 0.951292 | 13.279384 | -6.594634 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 398 | 0.2487 | 5 | 0.940548 | 10.914453 | -6.586754 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 399 | 0.2494 | 5 | 0.944334 | 12.162813 | -6.577747 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 400 | 0.2500 | 5 | 0.944412 | 11.971015 | -6.567671 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 401 | 0.2506 | 5 | 0.942391 | 11.417440 | -6.556234 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 402 | 0.2513 | 5 | 0.946196 | 11.651955 | -6.544422 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 403 | 0.2519 | 5 | 0.954220 | 13.544177 | -6.532738 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 404 | 0.2525 | 5 | 0.942893 | 11.522388 | -6.520930 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 405 | 0.2531 | 5 | 0.937732 | 11.174606 | -6.508777 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 406 | 0.2538 | 5 | 0.942429 | 11.498338 | -6.496583 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 407 | 0.2544 | 5 | 0.951117 | 13.381841 | -6.484564 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 408 | 0.2550 | 5 | 0.946943 | 11.972833 | -6.472461 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 409 | 0.2556 | 5 | 0.939902 | 10.688227 | -6.459836 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 410 | 0.2562 | 5 | 0.941495 | 11.588936 | -6.446709 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 411 | 0.2569 | 5 | 0.940684 | 11.439794 | -6.433787 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 412 | 0.2575 | 5 | 0.940707 | 11.230869 | -6.420827 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 413 | 0.2581 | 5 | 0.943504 | 11.691810 | -6.407676 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 414 | 0.2587 | 5 | 0.946487 | 12.529424 | -6.394133 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 415 | 0.2594 | 5 | 0.946490 | 12.476834 | -6.380069 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 416 | 0.2600 | 5 | 0.940115 | 11.243180 | -6.365979 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 417 | 0.2606 | 5 | 0.946679 | 12.310042 | -6.351862 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 418 | 0.2612 | 5 | 0.930529 | 9.259821 | -6.337979 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 419 | 0.2619 | 5 | 0.944208 | 11.701038 | -6.324252 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 420 | 0.2625 | 5 | 0.943378 | 11.615270 | -6.310184 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 421 | 0.2631 | 5 | 0.948905 | 12.821126 | -6.295848 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 422 | 0.2637 | 5 | 0.956700 | 14.825340 | -6.281795 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 423 | 0.2644 | 5 | 0.948575 | 13.258096 | -6.268152 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 424 | 0.2650 | 5 | 0.953930 | 14.677462 | -6.254451 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 425 | 0.2656 | 5 | 0.959606 | 20.267950 | -6.240666 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 426 | 0.2662 | 5 | 0.946560 | 13.043301 | -6.226753 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 427 | 0.2669 | 5 | 0.946216 | 26.652811 | -6.213063 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 428 | 0.2675 | 5 | 0.939458 | 11.337829 | -6.199728 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 429 | 0.2681 | 5 | 0.944511 | 12.469610 | -6.186771 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 430 | 0.2687 | 5 | 0.951583 | 13.959556 | -6.173960 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 431 | 0.2694 | 5 | 0.947895 | 12.948082 | -6.161021 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 432 | 0.2700 | 5 | 0.940363 | 11.396358 | -6.148138 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 433 | 0.2706 | 5 | 0.940611 | 11.959174 | -6.135531 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 434 | 0.2712 | 5 | 0.944905 | 11.612634 | -6.123427 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 435 | 0.2719 | 5 | 0.931596 | 10.630004 | -6.111485 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 436 | 0.2725 | 5 | 0.941282 | 11.077741 | -6.099515 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 437 | 0.2731 | 5 | 0.944526 | 11.820778 | -6.087707 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 438 | 0.2737 | 5 | 0.945823 | 11.672671 | -6.076067 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 439 | 0.2744 | 5 | 0.947116 | 12.356394 | -6.064688 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 440 | 0.2750 | 5 | 0.946607 | 11.309584 | -6.053461 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 441 | 0.2756 | 5 | 0.940612 | 10.561857 | -6.042264 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 442 | 0.2762 | 5 | 0.944981 | 11.875639 | -6.030655 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 443 | 0.2769 | 5 | 0.938942 | 10.323446 | -6.018751 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 444 | 0.2775 | 5 | 0.944209 | 11.620808 | -6.006922 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 445 | 0.2781 | 6 | 0.948086 | 14.365045 | -6.736619 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 446 | 0.2787 | 6 | 0.941974 | 12.894964 | -6.724916 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 447 | 0.2794 | 6 | 0.941747 | 12.867233 | -6.712885 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 448 | 0.2800 | 6 | 0.939683 | 12.638412 | -6.700640 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 449 | 0.2806 | 6 | 0.937655 | 12.234413 | -6.688255 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 450 | 0.2812 | 6 | 0.943827 | 13.398312 | -6.676011 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 451 | 0.2819 | 6 | 0.948795 | 14.855482 | -6.663571 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 452 | 0.2825 | 6 | 0.937684 | 12.584784 | -6.650797 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 453 | 0.2831 | 6 | 0.940853 | 12.808068 | -6.637730 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 454 | 0.2838 | 6 | 0.949003 | 15.933166 | -6.624188 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 455 | 0.2844 | 6 | 0.943138 | 14.585177 | -6.610740 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 456 | 0.2850 | 6 | 0.975023 | 45.558647 | -6.597484 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 457 | 0.2856 | 6 | 0.945492 | 12.665898 | -6.583956 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 458 | 0.2863 | 6 | 0.943885 | 12.642572 | -6.570163 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 459 | 0.2869 | 6 | 0.946598 | 14.066256 | -6.556328 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 460 | 0.2875 | 6 | 0.945733 | 13.002777 | -6.542348 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 461 | 0.2881 | 6 | 0.944734 | 13.666282 | -6.528596 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 462 | 0.2888 | 6 | 0.961025 | 19.318107 | -6.515143 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 463 | 0.2894 | 6 | 0.960909 | 17.956889 | -6.501700 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 464 | 0.2900 | 6 | 0.962195 | 19.095729 | -6.488667 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 465 | 0.2906 | 6 | 0.955146 | 18.395397 | -6.475893 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 466 | 0.2913 | 6 | 0.932831 | 10.788197 | -6.463861 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 467 | 0.2919 | 6 | 0.923409 | 9.725337 | -6.452981 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 468 | 0.2925 | 6 | 0.939919 | 11.987191 | -6.442848 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 469 | 0.2931 | 6 | 0.935204 | 11.246918 | -6.433444 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 470 | 0.2938 | 6 | 0.950340 | 14.473156 | -6.425044 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 471 | 0.2944 | 6 | 0.941014 | 13.263558 | -6.417914 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 472 | 0.2950 | 6 | 0.951109 | 15.029684 | -6.412338 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 473 | 0.2956 | 6 | 0.952923 | 15.108398 | -6.408441 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 474 | 0.2963 | 6 | 0.933879 | 11.287061 | -6.406075 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 475 | 0.2969 | 6 | 0.940943 | 13.381000 | -6.405447 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 476 | 0.2975 | 6 | 0.933473 | 10.651840 | -6.406601 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 477 | 0.2981 | 6 | 0.933221 | 11.707809 | -6.409774 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 478 | 0.2988 | 6 | 0.931398 | 12.543991 | -6.415210 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 479 | 0.2994 | 6 | 0.931353 | 10.779868 | -6.422252 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 480 | 0.3000 | 6 | 0.959892 | 17.280849 | -6.430833 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 481 | 0.3006 | 6 | 0.953455 | 17.051079 | -6.440865 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 482 | 0.3013 | 6 | 0.929366 | 9.798513 | -6.452285 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 483 | 0.3019 | 6 | 0.915877 | 9.415046 | -6.465101 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 484 | 0.3025 | 6 | 0.946327 | 13.611439 | -6.479036 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 485 | 0.3031 | 6 | 0.929681 | 10.096231 | -6.493792 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 486 | 0.3038 | 6 | 0.942053 | 12.171144 | -6.509114 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 487 | 0.3044 | 6 | 0.941548 | 11.788646 | -6.524777 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 488 | 0.3050 | 6 | 0.928641 | 10.165608 | -6.540846 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 489 | 0.3056 | 6 | 0.926683 | 10.096948 | -6.557527 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 490 | 0.3063 | 6 | 0.943579 | 13.089129 | -6.574203 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 491 | 0.3069 | 6 | 0.933035 | 11.317091 | -6.590579 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 492 | 0.3075 | 6 | 0.933329 | 10.826166 | -6.606798 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 493 | 0.3081 | 6 | 0.925417 | 9.954762 | -6.622960 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 494 | 0.3088 | 6 | 0.934412 | 10.903793 | -6.639268 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 495 | 0.3094 | 6 | 0.936710 | 10.941266 | -6.655432 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 496 | 0.3100 | 6 | 0.939284 | 11.306272 | -6.671282 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 497 | 0.3106 | 6 | 0.934598 | 11.031119 | -6.686646 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 498 | 0.3113 | 6 | 0.926747 | 10.244288 | -6.701629 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 499 | 0.3119 | 6 | 0.936242 | 11.094012 | -6.716416 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 500 | 0.3125 | 6 | 0.927101 | 10.435871 | -6.731094 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 501 | 0.3131 | 5 | 0.925367 | 7.898205 | -6.003962 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 502 | 0.3138 | 5 | 0.930463 | 8.841769 | -6.017855 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 503 | 0.3144 | 5 | 0.935369 | 9.849760 | -6.031653 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 504 | 0.3150 | 5 | 0.927636 | 8.436575 | -6.045529 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 505 | 0.3156 | 5 | 0.931649 | 8.961717 | -6.059573 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 506 | 0.3163 | 5 | 0.933398 | 9.575869 | -6.073762 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 507 | 0.3169 | 5 | 0.934445 | 9.244844 | -6.088188 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 508 | 0.3175 | 5 | 0.924025 | 7.798754 | -6.102694 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 509 | 0.3181 | 5 | 0.940258 | 10.032370 | -6.117317 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 510 | 0.3188 | 5 | 0.932599 | 9.222431 | -6.132483 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 511 | 0.3194 | 5 | 0.934663 | 9.527827 | -6.148232 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 512 | 0.3200 | 5 | 0.933843 | 8.860781 | -6.164623 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 513 | 0.3206 | 5 | 0.932188 | 9.639876 | -6.181155 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 514 | 0.3212 | 5 | 0.931656 | 9.692169 | -6.197924 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 515 | 0.3219 | 5 | 0.927283 | 8.518492 | -6.215445 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 516 | 0.3225 | 5 | 0.944497 | 10.796695 | -6.233601 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 517 | 0.3231 | 5 | 0.943116 | 10.604323 | -6.252496 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 518 | 0.3237 | 5 | 0.937520 | 9.753980 | -6.271885 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 519 | 0.3244 | 5 | 0.926112 | 8.010724 | -6.291855 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 520 | 0.3250 | 5 | 0.933361 | 9.099026 | -6.312470 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 521 | 0.3256 | 5 | 0.936647 | 9.274324 | -6.334090 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 522 | 0.3262 | 5 | 0.934850 | 9.366708 | -6.356764 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 523 | 0.3269 | 5 | 0.934609 | 9.331188 | -6.380315 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 524 | 0.3275 | 5 | 0.938180 | 9.845226 | -6.404181 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 525 | 0.3281 | 5 | 0.925627 | 8.063320 | -6.428499 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 526 | 0.3287 | 5 | 0.925067 | 7.981261 | -6.453779 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 527 | 0.3294 | 5 | 0.933576 | 9.620721 | -6.479463 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 528 | 0.3300 | 5 | 0.931757 | 8.652095 | -6.506077 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 529 | 0.3306 | 5 | 0.926375 | 8.612870 | -6.533230 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 530 | 0.3312 | 5 | 0.931660 | 8.723508 | -6.560532 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 531 | 0.3319 | 5 | 0.922246 | 7.936984 | -6.588388 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 532 | 0.3325 | 5 | 0.937330 | 9.551329 | -6.616327 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 533 | 0.3331 | 5 | 0.941238 | 10.138527 | -6.645330 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 534 | 0.3337 | 5 | 0.935196 | 9.255770 | -6.675195 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 535 | 0.3344 | 5 | 0.939544 | 9.773071 | -6.704531 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 536 | 0.3350 | 5 | 0.940899 | 10.424059 | -6.733701 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 537 | 0.3356 | 4 | 0.930236 | 6.745155 | -6.026964 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 538 | 0.3362 | 4 | 0.938923 | 8.294867 | -6.058196 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 539 | 0.3369 | 4 | 0.933831 | 7.289612 | -6.090975 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 540 | 0.3375 | 4 | 0.937717 | 8.467063 | -6.124695 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 541 | 0.3381 | 4 | 0.946206 | 9.085426 | -6.158014 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 542 | 0.3387 | 4 | 0.935028 | 7.815512 | -6.192894 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 543 | 0.3394 | 4 | 0.955943 | 11.529022 | -6.228397 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 544 | 0.3400 | 4 | 0.967932 | 16.747506 | -6.266371 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 545 | 0.3406 | 4 | 0.969599 | 18.050268 | -6.307156 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 546 | 0.3412 | 4 | 0.959489 | 13.083305 | -6.348949 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 547 | 0.3419 | 4 | 0.958625 | 12.513767 | -6.395057 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 548 | 0.3425 | 4 | 0.950902 | 10.697214 | -6.448425 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 549 | 0.3431 | 4 | 0.947183 | 9.394122 | -6.510814 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 550 | 0.3438 | 4 | 0.949121 | 9.796854 | -6.583580 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 551 | 0.3444 | 4 | 0.946961 | 10.422144 | -6.670334 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 552 | 0.3450 | 3 | 0.952130 | 8.454171 | -6.040947 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 553 | 0.3456 | 3 | 0.958336 | 9.321894 | -6.153069 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 554 | 0.3463 | 3 | 0.954985 | 8.974295 | -6.240227 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 555 | 0.3469 | 3 | 0.918559 | 5.521563 | -6.152514 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 556 | 0.3475 | 4 | 0.926450 | 8.000008 | -6.705685 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 557 | 0.3481 | 4 | 0.928557 | 7.739259 | -6.578881 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 558 | 0.3488 | 4 | 0.925500 | 6.604689 | -6.480316 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 559 | 0.3494 | 4 | 0.929234 | 7.475034 | -6.427569 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 560 | 0.3500 | 4 | 0.929693 | 7.300120 | -6.355897 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 561 | 0.3506 | 4 | 0.936551 | 8.216637 | -6.307655 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 562 | 0.3513 | 4 | 0.932202 | 8.202960 | -6.252951 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 563 | 0.3519 | 4 | 0.931593 | 7.225138 | -6.207181 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 564 | 0.3525 | 4 | 0.933346 | 7.804957 | -6.164819 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 565 | 0.3531 | 4 | 0.935896 | 7.675990 | -6.122506 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 566 | 0.3538 | 4 | 0.921092 | 6.466938 | -6.083487 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 567 | 0.3544 | 4 | 0.932215 | 7.272089 | -6.047872 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 568 | 0.3550 | 4 | 0.934220 | 7.519600 | -6.014768 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 569 | 0.3556 | 5 | 0.928175 | 8.861348 | -6.721297 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 570 | 0.3563 | 5 | 0.942519 | 10.784671 | -6.691721 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 571 | 0.3569 | 5 | 0.921757 | 8.404438 | -6.665698 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 572 | 0.3575 | 5 | 0.938649 | 9.751264 | -6.641548 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 573 | 0.3581 | 5 | 0.929423 | 9.337398 | -6.619602 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 574 | 0.3588 | 5 | 0.931252 | 9.026243 | -6.599179 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 575 | 0.3594 | 5 | 0.935800 | 10.026755 | -6.580532 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 576 | 0.3600 | 5 | 0.927667 | 8.713964 | -6.563055 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 577 | 0.3606 | 5 | 0.932789 | 9.022897 | -6.546303 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 578 | 0.3613 | 5 | 0.932648 | 9.384868 | -6.530497 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 579 | 0.3619 | 5 | 0.926850 | 9.294795 | -6.515608 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 580 | 0.3625 | 5 | 0.935148 | 9.624212 | -6.502089 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 581 | 0.3631 | 5 | 0.939544 | 10.602550 | -6.489060 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 582 | 0.3638 | 5 | 0.928811 | 9.074335 | -6.476485 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 583 | 0.3644 | 5 | 0.935111 | 9.725538 | -6.464369 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 584 | 0.3650 | 5 | 0.935930 | 9.911181 | -6.453149 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 585 | 0.3656 | 5 | 0.937111 | 10.114750 | -6.442445 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 586 | 0.3663 | 5 | 0.926297 | 8.935411 | -6.431856 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 587 | 0.3669 | 5 | 0.934241 | 9.131717 | -6.421411 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 588 | 0.3675 | 5 | 0.937798 | 10.259390 | -6.410763 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 589 | 0.3681 | 5 | 0.936302 | 9.777354 | -6.400459 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 590 | 0.3688 | 5 | 0.933755 | 9.580929 | -6.390617 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 591 | 0.3694 | 5 | 0.924450 | 8.299854 | -6.381067 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 592 | 0.3700 | 5 | 0.933161 | 9.576209 | -6.371199 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 593 | 0.3706 | 5 | 0.933705 | 9.373520 | -6.361041 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 594 | 0.3713 | 5 | 0.927635 | 9.260982 | -6.350829 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 595 | 0.3719 | 5 | 0.934219 | 9.348347 | -6.340259 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 596 | 0.3725 | 5 | 0.933626 | 9.463258 | -6.329682 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 597 | 0.3731 | 5 | 0.937334 | 10.124435 | -6.318650 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 598 | 0.3738 | 5 | 0.929193 | 9.029926 | -6.307391 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 599 | 0.3744 | 5 | 0.940768 | 10.412051 | -6.295963 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 600 | 0.3750 | 5 | 0.931805 | 9.225028 | -6.284483 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 601 | 0.3756 | 5 | 0.926832 | 8.961371 | -6.273228 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 602 | 0.3763 | 5 | 0.921379 | 8.998819 | -6.262315 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 603 | 0.3769 | 5 | 0.930263 | 8.900437 | -6.251239 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 604 | 0.3775 | 5 | 0.924616 | 9.045664 | -6.239790 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 605 | 0.3781 | 5 | 0.920190 | 8.237810 | -6.228678 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 606 | 0.3788 | 5 | 0.938616 | 9.932866 | -6.217750 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 607 | 0.3794 | 5 | 0.928041 | 8.335514 | -6.207167 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 608 | 0.3800 | 5 | 0.942613 | 10.677613 | -6.196759 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 609 | 0.3806 | 5 | 0.931734 | 9.114785 | -6.186313 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 610 | 0.3813 | 5 | 0.930289 | 8.809337 | -6.176285 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 611 | 0.3819 | 5 | 0.920796 | 8.183632 | -6.166577 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 612 | 0.3825 | 5 | 0.936148 | 10.184320 | -6.157476 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 613 | 0.3831 | 5 | 0.939177 | 11.175948 | -6.148647 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 614 | 0.3838 | 5 | 0.935055 | 9.418275 | -6.139905 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 615 | 0.3844 | 5 | 0.929043 | 9.167510 | -6.131462 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 616 | 0.3850 | 5 | 0.931399 | 9.493777 | -6.123619 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 617 | 0.3856 | 5 | 0.925089 | 8.847464 | -6.116364 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 618 | 0.3862 | 5 | 0.923769 | 8.729304 | -6.109657 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 619 | 0.3869 | 5 | 0.930690 | 9.356319 | -6.103320 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 620 | 0.3875 | 5 | 0.938783 | 9.958771 | -6.097263 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 621 | 0.3881 | 5 | 0.929590 | 9.402966 | -6.091912 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 622 | 0.3887 | 5 | 0.932349 | 9.076035 | -6.086617 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 623 | 0.3894 | 5 | 0.926166 | 8.987947 | -6.081825 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 624 | 0.3900 | 5 | 0.930624 | 9.119325 | -6.077522 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 625 | 0.3906 | 5 | 0.923748 | 8.322446 | -6.072933 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 626 | 0.3912 | 5 | 0.935525 | 10.054455 | -6.068706 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 627 | 0.3919 | 5 | 0.925626 | 8.581022 | -6.064775 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 628 | 0.3925 | 5 | 0.919861 | 8.527264 | -6.061124 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 629 | 0.3931 | 5 | 0.934803 | 9.801661 | -6.057910 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 630 | 0.3937 | 5 | 0.937310 | 11.318866 | -6.054666 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 631 | 0.3944 | 5 | 0.939446 | 16.229450 | -6.051305 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 632 | 0.3950 | 5 | 0.907581 | 11.782864 | -6.048460 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 633 | 0.3956 | 5 | 0.922724 | 9.168325 | -6.045802 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 634 | 0.3962 | 5 | 0.925209 | 9.681161 | -6.043305 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 635 | 0.3969 | 5 | 0.917343 | 7.917290 | -6.041244 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 636 | 0.3975 | 5 | 0.918506 | 8.494464 | -6.038823 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 637 | 0.3981 | 5 | 0.934074 | 9.517499 | -6.036782 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 638 | 0.3987 | 5 | 0.945360 | 11.787720 | -6.035121 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 639 | 0.3994 | 5 | 0.923969 | 9.638591 | -6.033548 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 640 | 0.4000 | 5 | 0.931064 | 9.747886 | -6.032156 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 641 | 0.4006 | 5 | 0.931296 | 9.784440 | -6.030611 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 642 | 0.4012 | 5 | 0.934377 | 10.178377 | -6.029328 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 643 | 0.4019 | 5 | 0.933453 | 10.652106 | -6.028448 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 644 | 0.4025 | 5 | 0.935375 | 9.793001 | -6.027806 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 645 | 0.4031 | 5 | 0.929348 | 9.390796 | -6.027589 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 646 | 0.4037 | 5 | 0.932922 | 9.311741 | -6.027943 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 647 | 0.4044 | 5 | 0.933876 | 9.519163 | -6.028567 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 648 | 0.4050 | 5 | 0.937401 | 9.873745 | -6.029863 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 649 | 0.4056 | 5 | 0.934438 | 9.459772 | -6.031861 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 650 | 0.4062 | 5 | 0.932149 | 9.280485 | -6.034463 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 651 | 0.4069 | 5 | 0.929717 | 9.812798 | -6.038039 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 652 | 0.4075 | 5 | 0.924271 | 8.601479 | -6.041779 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 653 | 0.4081 | 5 | 0.933553 | 9.629294 | -6.046324 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 654 | 0.4088 | 5 | 0.931576 | 9.439751 | -6.051770 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 655 | 0.4094 | 5 | 0.921814 | 8.409003 | -6.057736 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 656 | 0.4100 | 5 | 0.931731 | 9.062441 | -6.064569 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 657 | 0.4106 | 5 | 0.924062 | 8.879098 | -6.071819 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 658 | 0.4113 | 5 | 0.933687 | 9.972103 | -6.079378 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 659 | 0.4119 | 5 | 0.924142 | 8.578628 | -6.087705 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 660 | 0.4125 | 5 | 0.934456 | 10.038193 | -6.096779 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 661 | 0.4131 | 5 | 0.925543 | 8.665427 | -6.106337 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 662 | 0.4138 | 5 | 0.935794 | 9.913590 | -6.116549 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 663 | 0.4144 | 5 | 0.924256 | 8.054446 | -6.126980 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 664 | 0.4150 | 5 | 0.926648 | 8.618163 | -6.138140 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 665 | 0.4156 | 5 | 0.923350 | 8.456890 | -6.150216 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 666 | 0.4163 | 5 | 0.934001 | 9.499949 | -6.162310 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 667 | 0.4169 | 5 | 0.947611 | 13.289274 | -6.174956 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 668 | 0.4175 | 5 | 0.927692 | 8.764567 | -6.187704 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 669 | 0.4181 | 5 | 0.922900 | 8.442053 | -6.200579 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 670 | 0.4188 | 5 | 0.920997 | 8.795515 | -6.213696 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 671 | 0.4194 | 5 | 0.938697 | 10.923995 | -6.226687 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 672 | 0.4200 | 5 | 0.960364 | 16.481206 | -6.239806 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 673 | 0.4206 | 5 | 0.966614 | 19.566016 | -6.252727 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 674 | 0.4213 | 5 | 0.951101 | 14.935261 | -6.265094 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 675 | 0.4219 | 5 | 0.936364 | 10.476861 | -6.277193 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 676 | 0.4225 | 5 | 0.931706 | 9.630969 | -6.289225 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 677 | 0.4231 | 5 | 0.927414 | 9.848959 | -6.300631 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 678 | 0.4238 | 5 | 0.933644 | 10.014491 | -6.311949 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 679 | 0.4244 | 5 | 0.918683 | 7.996044 | -6.322765 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 680 | 0.4250 | 5 | 0.934194 | 9.662433 | -6.333101 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 681 | 0.4256 | 5 | 0.929059 | 9.097349 | -6.343831 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 682 | 0.4263 | 5 | 0.940407 | 10.432632 | -6.354240 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 683 | 0.4269 | 5 | 0.930849 | 9.559272 | -6.364710 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 684 | 0.4275 | 5 | 0.935518 | 10.127651 | -6.374817 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 685 | 0.4281 | 5 | 0.929643 | 9.210879 | -6.384399 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 686 | 0.4288 | 5 | 0.934259 | 9.823093 | -6.394270 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 687 | 0.4294 | 5 | 0.921517 | 8.424458 | -6.404307 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 688 | 0.4300 | 5 | 0.930758 | 9.553632 | -6.414187 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 689 | 0.4306 | 5 | 0.924880 | 8.471291 | -6.424036 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 690 | 0.4313 | 5 | 0.926839 | 9.262191 | -6.434595 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 691 | 0.4319 | 5 | 0.928622 | 9.686299 | -6.445463 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 692 | 0.4325 | 5 | 0.930559 | 8.966420 | -6.457199 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 693 | 0.4331 | 5 | 0.928816 | 9.617107 | -6.469469 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 694 | 0.4338 | 5 | 0.931878 | 9.150726 | -6.482595 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 695 | 0.4344 | 5 | 0.931842 | 9.893392 | -6.496841 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 696 | 0.4350 | 5 | 0.918096 | 7.897612 | -6.511907 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 697 | 0.4356 | 5 | 0.932114 | 9.527637 | -6.528416 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 698 | 0.4363 | 5 | 0.932973 | 9.747239 | -6.546356 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 699 | 0.4369 | 5 | 0.930940 | 9.409062 | -6.566168 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 700 | 0.4375 | 5 | 0.929531 | 8.970919 | -6.587375 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 701 | 0.4381 | 5 | 0.935546 | 10.324452 | -6.610220 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 702 | 0.4388 | 5 | 0.930878 | 9.284216 | -6.634628 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 703 | 0.4394 | 5 | 0.934634 | 10.035476 | -6.660417 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 704 | 0.4400 | 5 | 0.939653 | 11.487262 | -6.688445 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 705 | 0.4406 | 5 | 0.924306 | 8.595590 | -6.717924 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 706 | 0.4413 | 4 | 0.921074 | 6.832913 | -6.012933 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 707 | 0.4419 | 4 | 0.927642 | 7.818493 | -6.046633 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 708 | 0.4425 | 4 | 0.934645 | 7.879602 | -6.083937 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 709 | 0.4431 | 4 | 0.925286 | 7.188123 | -6.125166 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 710 | 0.4438 | 4 | 0.932295 | 8.384217 | -6.169749 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 711 | 0.4444 | 4 | 0.930219 | 7.790628 | -6.218257 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 712 | 0.4450 | 4 | 0.923999 | 6.846884 | -6.272441 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 713 | 0.4456 | 4 | 0.937155 | 8.161222 | -6.330917 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 714 | 0.4463 | 4 | 0.913784 | 6.635980 | -6.393803 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 715 | 0.4469 | 4 | 0.914941 | 6.048444 | -6.467574 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 716 | 0.4475 | 4 | 0.925868 | 7.530941 | -6.547091 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 717 | 0.4481 | 4 | 0.929802 | 7.972495 | -6.637820 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 718 | 0.4487 | 3 | 0.926613 | 5.864296 | -6.021062 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 719 | 0.4494 | 3 | 0.936496 | 6.354412 | -6.150994 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 720 | 0.4500 | 3 | 0.933876 | 6.440199 | -6.332472 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 721 | 0.4506 | 3 | 0.919855 | 5.085086 | -6.580921 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 722 | 0.4512 | 2 | 0.932419 | 5.011490 | -6.286639 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 723 | 0.4519 | 3 | 0.934949 | 6.479562 | -6.567144 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 724 | 0.4525 | 3 | 0.918759 | 4.897590 | -6.301854 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 725 | 0.4531 | 3 | 0.921584 | 5.574742 | -6.142787 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 726 | 0.4537 | 3 | 0.920810 | 5.319923 | -6.026335 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 727 | 0.4544 | 4 | 0.932090 | 8.023124 | -6.656484 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 728 | 0.4550 | 4 | 0.929770 | 7.631246 | -6.582534 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 729 | 0.4556 | 4 | 0.930141 | 7.458421 | -6.522854 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 730 | 0.4562 | 4 | 0.923609 | 7.282485 | -6.476099 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 731 | 0.4569 | 4 | 0.937906 | 8.195758 | -6.446946 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 732 | 0.4575 | 4 | 0.929661 | 7.371895 | -6.415274 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 733 | 0.4581 | 4 | 0.938033 | 8.674495 | -6.394423 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 734 | 0.4587 | 4 | 0.915943 | 6.780173 | -6.368030 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 735 | 0.4594 | 4 | 0.941495 | 9.210612 | -6.338490 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 736 | 0.4600 | 4 | 0.934103 | 8.341763 | -6.314331 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 737 | 0.4606 | 4 | 0.932541 | 8.698184 | -6.288407 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 738 | 0.4612 | 4 | 0.921989 | 7.507051 | -6.259720 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 739 | 0.4619 | 4 | 0.915109 | 7.382231 | -6.228349 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 740 | 0.4625 | 4 | 0.920841 | 7.135293 | -6.203928 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 741 | 0.4631 | 4 | 0.934369 | 8.067266 | -6.177878 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 742 | 0.4637 | 4 | 0.919072 | 6.727871 | -6.150242 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 743 | 0.4644 | 4 | 0.948047 | 10.231599 | -6.122731 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 744 | 0.4650 | 4 | 0.940385 | 9.194453 | -6.095159 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 745 | 0.4656 | 4 | 0.937440 | 9.083191 | -6.067169 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 746 | 0.4662 | 4 | 0.924112 | 7.717238 | -6.044879 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 747 | 0.4669 | 4 | 0.935212 | 7.848541 | -6.020451 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 748 | 0.4675 | 5 | 0.927123 | 9.004796 | -6.734322 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 749 | 0.4681 | 5 | 0.927374 | 9.352152 | -6.713032 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 750 | 0.4688 | 5 | 0.927125 | 9.103034 | -6.692085 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 751 | 0.4694 | 5 | 0.930933 | 9.221716 | -6.673396 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 752 | 0.4700 | 5 | 0.931296 | 10.072722 | -6.656070 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 753 | 0.4706 | 5 | 0.923142 | 8.925032 | -6.638683 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 754 | 0.4713 | 5 | 0.932875 | 9.662591 | -6.623003 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 755 | 0.4719 | 5 | 0.929516 | 9.521673 | -6.609374 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 756 | 0.4725 | 5 | 0.930071 | 9.035976 | -6.595895 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 757 | 0.4731 | 5 | 0.926474 | 8.695106 | -6.582927 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 758 | 0.4738 | 5 | 0.924512 | 9.216887 | -6.570494 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 759 | 0.4744 | 5 | 0.920378 | 8.357069 | -6.558830 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 760 | 0.4750 | 5 | 0.934047 | 9.965198 | -6.548863 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 761 | 0.4756 | 5 | 0.936664 | 10.491479 | -6.539285 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 762 | 0.4763 | 5 | 0.934627 | 9.910685 | -6.531018 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 763 | 0.4769 | 5 | 0.926491 | 9.685667 | -6.523587 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 764 | 0.4775 | 5 | 0.932191 | 9.751607 | -6.516481 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 765 | 0.4781 | 5 | 0.929089 | 9.675435 | -6.510323 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 766 | 0.4788 | 5 | 0.938217 | 10.443324 | -6.504507 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 767 | 0.4794 | 5 | 0.933689 | 10.570690 | -6.499659 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 768 | 0.4800 | 5 | 0.929295 | 8.878479 | -6.495284 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 769 | 0.4806 | 5 | 0.930110 | 9.058920 | -6.491093 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 770 | 0.4813 | 5 | 0.927112 | 8.865057 | -6.487179 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 771 | 0.4819 | 5 | 0.924207 | 9.043484 | -6.483610 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 772 | 0.4825 | 5 | 0.935184 | 11.335469 | -6.479769 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 773 | 0.4831 | 5 | 0.935779 | 10.724381 | -6.476207 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 774 | 0.4838 | 5 | 0.913179 | 7.971472 | -6.471980 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 775 | 0.4844 | 5 | 0.943513 | 11.730250 | -6.466756 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 776 | 0.4850 | 5 | 0.920746 | 8.335757 | -6.462200 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 777 | 0.4856 | 5 | 0.924331 | 9.116222 | -6.457480 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 778 | 0.4863 | 5 | 0.921914 | 8.293705 | -6.452995 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 779 | 0.4869 | 5 | 0.928560 | 8.960990 | -6.448512 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 780 | 0.4875 | 5 | 0.928040 | 9.185435 | -6.443459 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 781 | 0.4881 | 5 | 0.931650 | 10.068263 | -6.438887 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 782 | 0.4888 | 5 | 0.928517 | 9.562702 | -6.434237 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 783 | 0.4894 | 5 | 0.924984 | 8.882981 | -6.429086 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 784 | 0.4900 | 5 | 0.928633 | 8.887591 | -6.423551 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 785 | 0.4906 | 5 | 0.924556 | 8.774083 | -6.417949 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 786 | 0.4913 | 5 | 0.934734 | 10.173882 | -6.411811 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 787 | 0.4919 | 5 | 0.931321 | 9.234596 | -6.406140 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 788 | 0.4925 | 5 | 0.932815 | 9.572152 | -6.400657 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 789 | 0.4931 | 5 | 0.922462 | 8.687736 | -6.395335 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 790 | 0.4938 | 5 | 0.931916 | 9.546295 | -6.390520 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 791 | 0.4944 | 5 | 0.929535 | 9.834749 | -6.385541 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 792 | 0.4950 | 5 | 0.924741 | 9.686103 | -6.381138 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 793 | 0.4956 | 5 | 0.918371 | 8.332913 | -6.377091 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 794 | 0.4963 | 5 | 0.931072 | 9.600378 | -6.373732 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 795 | 0.4969 | 5 | 0.929066 | 9.401269 | -6.371031 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 796 | 0.4975 | 5 | 0.932095 | 9.411825 | -6.368646 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 797 | 0.4981 | 5 | 0.911633 | 8.534316 | -6.366853 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 798 | 0.4988 | 5 | 0.931736 | 9.614760 | -6.365811 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 799 | 0.4994 | 5 | 0.929255 | 9.798591 | -6.365832 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 800 | 0.5000 | 5 | 0.933150 | 10.205395 | -6.366059 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 801 | 0.5006 | 5 | 0.929677 | 9.759404 | -6.366406 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 802 | 0.5012 | 5 | 0.932164 | 9.958121 | -6.366706 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 803 | 0.5019 | 5 | 0.921897 | 8.490702 | -6.367635 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 804 | 0.5025 | 5 | 0.904407 | 6.961878 | -6.369248 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 805 | 0.5031 | 5 | 0.927244 | 9.957100 | -6.370965 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 806 | 0.5038 | 5 | 0.938502 | 10.927201 | -6.373256 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 807 | 0.5044 | 5 | 0.934453 | 10.172743 | -6.375951 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 808 | 0.5050 | 5 | 0.924405 | 9.326037 | -6.379399 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 809 | 0.5056 | 5 | 0.934565 | 9.772870 | -6.383271 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 810 | 0.5062 | 5 | 0.959726 | 16.875979 | -6.387779 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 811 | 0.5069 | 5 | 0.955313 | 16.020174 | -6.392195 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 812 | 0.5075 | 5 | 0.940828 | 11.803230 | -6.396531 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 813 | 0.5081 | 5 | 0.921266 | 8.758789 | -6.401020 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 814 | 0.5088 | 5 | 0.919964 | 8.749492 | -6.404953 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 815 | 0.5094 | 5 | 0.930982 | 9.768547 | -6.409237 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 816 | 0.5100 | 5 | 0.919187 | 8.417535 | -6.413285 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 817 | 0.5106 | 5 | 0.931183 | 9.933064 | -6.416823 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 818 | 0.5112 | 5 | 0.932770 | 9.724156 | -6.420107 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 819 | 0.5119 | 5 | 0.926745 | 10.083216 | -6.422785 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 820 | 0.5125 | 5 | 0.928231 | 9.450696 | -6.425145 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 821 | 0.5131 | 5 | 0.930732 | 10.784650 | -6.427746 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 822 | 0.5138 | 5 | 0.935950 | 11.084179 | -6.430038 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 823 | 0.5144 | 5 | 0.927837 | 9.801143 | -6.431388 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 824 | 0.5150 | 5 | 0.943368 | 12.068285 | -6.433341 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 825 | 0.5156 | 5 | 0.932240 | 10.050045 | -6.435286 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 826 | 0.5162 | 5 | 0.925537 | 8.921238 | -6.437849 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 827 | 0.5169 | 5 | 0.911140 | 7.980349 | -6.440593 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 828 | 0.5175 | 5 | 0.926102 | 8.894458 | -6.442666 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 829 | 0.5181 | 5 | 0.930764 | 10.031692 | -6.445239 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 830 | 0.5188 | 5 | 0.935228 | 10.487921 | -6.447810 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 831 | 0.5194 | 5 | 0.921017 | 8.795281 | -6.450495 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 832 | 0.5200 | 5 | 0.907694 | 7.670427 | -6.453366 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 833 | 0.5206 | 5 | 0.909943 | 7.740737 | -6.456276 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 834 | 0.5212 | 5 | 0.914818 | 8.427780 | -6.459461 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 835 | 0.5219 | 5 | 0.927102 | 9.244189 | -6.463422 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 836 | 0.5225 | 5 | 0.906902 | 8.015583 | -6.467999 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 837 | 0.5231 | 5 | 0.930278 | 9.826530 | -6.473423 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 838 | 0.5238 | 5 | 0.933573 | 9.926509 | -6.479936 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 839 | 0.5244 | 5 | 0.929016 | 10.408976 | -6.486744 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 840 | 0.5250 | 5 | 0.930422 | 9.604165 | -6.494973 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 841 | 0.5256 | 5 | 0.919496 | 8.642593 | -6.503515 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 842 | 0.5262 | 5 | 0.929341 | 9.691619 | -6.513077 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 843 | 0.5269 | 5 | 0.928971 | 9.842893 | -6.524211 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 844 | 0.5275 | 5 | 0.933453 | 11.009402 | -6.535251 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 845 | 0.5281 | 5 | 0.931839 | 9.558106 | -6.547078 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 846 | 0.5288 | 5 | 0.934031 | 9.772532 | -6.559367 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 847 | 0.5294 | 5 | 0.908875 | 7.996691 | -6.572194 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 848 | 0.5300 | 5 | 0.925056 | 8.901146 | -6.586218 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 849 | 0.5306 | 5 | 0.928417 | 9.443220 | -6.600248 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 850 | 0.5312 | 5 | 0.924937 | 9.401289 | -6.613841 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 851 | 0.5319 | 5 | 0.920601 | 8.422577 | -6.628553 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 852 | 0.5325 | 5 | 0.931052 | 9.638048 | -6.643800 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 853 | 0.5331 | 5 | 0.923216 | 9.223827 | -6.659203 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 854 | 0.5338 | 5 | 0.922922 | 8.963944 | -6.675844 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 855 | 0.5344 | 5 | 0.925591 | 9.590381 | -6.691497 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 856 | 0.5350 | 5 | 0.924429 | 8.931645 | -6.707915 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 857 | 0.5356 | 5 | 0.919343 | 8.353310 | -6.724414 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 858 | 0.5363 | 4 | 0.930092 | 7.819427 | -6.003178 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 859 | 0.5369 | 4 | 0.926550 | 7.337783 | -6.018391 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 860 | 0.5375 | 4 | 0.933688 | 8.158798 | -6.031969 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 861 | 0.5381 | 4 | 0.919090 | 6.962004 | -6.044801 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 862 | 0.5388 | 4 | 0.919216 | 6.874953 | -6.056879 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 863 | 0.5394 | 4 | 0.928819 | 8.566498 | -6.067630 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 864 | 0.5400 | 4 | 0.931326 | 8.006476 | -6.077553 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 865 | 0.5406 | 4 | 0.933139 | 8.072888 | -6.087544 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 866 | 0.5413 | 4 | 0.916050 | 6.480457 | -6.096423 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 867 | 0.5419 | 4 | 0.922485 | 7.022250 | -6.104988 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 868 | 0.5425 | 4 | 0.938603 | 9.019238 | -6.113887 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 869 | 0.5431 | 4 | 0.916699 | 6.557981 | -6.122476 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 870 | 0.5438 | 4 | 0.916775 | 7.073197 | -6.133422 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 871 | 0.5444 | 4 | 0.940131 | 9.969909 | -6.143271 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 872 | 0.5450 | 4 | 0.931838 | 8.227085 | -6.152831 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 873 | 0.5456 | 4 | 0.918185 | 6.862832 | -6.164185 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 874 | 0.5463 | 4 | 0.929651 | 7.530676 | -6.175673 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 875 | 0.5469 | 4 | 0.927331 | 7.491924 | -6.188140 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 876 | 0.5475 | 4 | 0.925307 | 7.429103 | -6.200923 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 877 | 0.5481 | 4 | 0.928364 | 8.051866 | -6.214512 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 878 | 0.5487 | 4 | 0.935477 | 8.357555 | -6.229388 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 879 | 0.5494 | 4 | 0.921338 | 7.114463 | -6.247460 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 880 | 0.5500 | 4 | 0.921997 | 7.338384 | -6.266307 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 881 | 0.5506 | 4 | 0.910171 | 7.071233 | -6.288111 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 882 | 0.5513 | 4 | 0.935646 | 8.676680 | -6.311155 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 883 | 0.5519 | 4 | 0.923082 | 7.320006 | -6.337258 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 884 | 0.5525 | 4 | 0.907399 | 6.766790 | -6.370621 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 885 | 0.5531 | 4 | 0.940883 | 9.564549 | -6.408803 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 886 | 0.5537 | 4 | 0.916768 | 6.487341 | -6.458586 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 887 | 0.5544 | 4 | 0.926042 | 7.786680 | -6.510359 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 888 | 0.5550 | 4 | 0.928651 | 7.969324 | -6.570389 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 889 | 0.5556 | 4 | 0.921681 | 7.016701 | -6.647174 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 890 | 0.5563 | 3 | 0.923542 | 5.854110 | -6.008526 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 891 | 0.5569 | 3 | 0.913919 | 5.762097 | -6.094751 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 892 | 0.5575 | 3 | 0.930279 | 6.320222 | -6.278467 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 893 | 0.5581 | 3 | 0.914855 | 5.142106 | -6.557239 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 894 | 0.5587 | 2 | 0.935250 | 4.606866 | -6.032091 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 895 | 0.5594 | 3 | 0.928966 | 6.051311 | -6.485477 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 896 | 0.5600 | 3 | 0.938568 | 7.121828 | -6.264643 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 897 | 0.5606 | 3 | 0.937551 | 7.141019 | -6.114279 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 898 | 0.5613 | 3 | 0.920823 | 5.830745 | -6.006076 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 899 | 0.5619 | 4 | 0.920828 | 7.319262 | -6.646641 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 900 | 0.5625 | 4 | 0.916745 | 7.090214 | -6.579032 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 901 | 0.5631 | 4 | 0.932117 | 8.046887 | -6.524637 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 902 | 0.5637 | 4 | 0.925768 | 7.525246 | -6.485084 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 903 | 0.5644 | 4 | 0.926730 | 7.667699 | -6.460293 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 904 | 0.5650 | 4 | 0.933479 | 8.427721 | -6.441458 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 905 | 0.5656 | 4 | 0.934279 | 8.615420 | -6.424789 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 906 | 0.5663 | 4 | 0.916425 | 6.461447 | -6.414601 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 907 | 0.5669 | 4 | 0.918330 | 7.395758 | -6.401844 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 908 | 0.5675 | 4 | 0.921588 | 7.214110 | -6.392842 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 909 | 0.5681 | 4 | 0.930740 | 7.987823 | -6.387306 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 910 | 0.5687 | 4 | 0.920880 | 7.448220 | -6.376787 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 911 | 0.5694 | 4 | 0.941468 | 9.277546 | -6.372014 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 912 | 0.5700 | 4 | 0.930895 | 8.117703 | -6.367281 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 913 | 0.5706 | 4 | 0.934344 | 8.807338 | -6.361640 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 914 | 0.5713 | 4 | 0.921214 | 7.875672 | -6.354599 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 915 | 0.5719 | 4 | 0.922923 | 7.908524 | -6.346507 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 916 | 0.5725 | 4 | 0.925797 | 7.766734 | -6.337215 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 917 | 0.5731 | 4 | 0.927046 | 7.655915 | -6.327300 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 918 | 0.5737 | 4 | 0.921664 | 7.412390 | -6.314234 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 919 | 0.5744 | 4 | 0.923584 | 7.685994 | -6.302328 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 920 | 0.5750 | 4 | 0.912803 | 7.107680 | -6.290808 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 921 | 0.5756 | 4 | 0.919204 | 6.893781 | -6.279909 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 922 | 0.5763 | 4 | 0.937091 | 9.115775 | -6.271519 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 923 | 0.5769 | 4 | 0.932698 | 9.973019 | -6.261935 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 924 | 0.5775 | 4 | 0.923969 | 7.173532 | -6.250019 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 925 | 0.5781 | 4 | 0.929120 | 8.145913 | -6.237347 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 926 | 0.5787 | 4 | 0.931338 | 8.004418 | -6.223171 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 927 | 0.5794 | 4 | 0.923704 | 7.463983 | -6.208411 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 928 | 0.5800 | 4 | 0.929193 | 8.073174 | -6.192242 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 929 | 0.5806 | 4 | 0.922863 | 7.412166 | -6.175945 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 930 | 0.5813 | 4 | 0.923749 | 7.464325 | -6.160154 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 931 | 0.5819 | 4 | 0.916635 | 6.873594 | -6.144373 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 932 | 0.5825 | 4 | 0.931041 | 8.090923 | -6.128385 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 933 | 0.5831 | 4 | 0.932154 | 8.207330 | -6.114957 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 934 | 0.5837 | 4 | 0.931573 | 8.200356 | -6.102245 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 935 | 0.5844 | 4 | 0.919409 | 7.053110 | -6.091573 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 936 | 0.5850 | 4 | 0.931813 | 8.658322 | -6.080312 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 937 | 0.5856 | 4 | 0.912471 | 7.760416 | -6.069333 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 938 | 0.5863 | 4 | 0.933213 | 8.285897 | -6.061676 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 939 | 0.5869 | 4 | 0.924844 | 7.949592 | -6.053914 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 940 | 0.5875 | 4 | 0.929657 | 7.916140 | -6.046597 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 941 | 0.5881 | 4 | 0.931614 | 8.213557 | -6.040712 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 942 | 0.5887 | 4 | 0.917494 | 6.778910 | -6.035029 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 943 | 0.5894 | 4 | 0.921157 | 7.833076 | -6.029995 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 944 | 0.5900 | 4 | 0.934626 | 8.828546 | -6.025737 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 945 | 0.5906 | 4 | 0.916082 | 7.140080 | -6.020625 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 946 | 0.5913 | 4 | 0.921550 | 7.457766 | -6.016519 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 947 | 0.5919 | 4 | 0.928874 | 8.125375 | -6.014127 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 948 | 0.5925 | 4 | 0.924238 | 8.466355 | -6.011464 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 949 | 0.5931 | 4 | 0.931175 | 8.528595 | -6.010995 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 950 | 0.5938 | 4 | 0.915849 | 6.853404 | -6.010663 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 951 | 0.5944 | 4 | 0.924345 | 7.463991 | -6.010842 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 952 | 0.5950 | 4 | 0.914219 | 6.780014 | -6.012660 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 953 | 0.5956 | 4 | 0.917663 | 7.709888 | -6.013694 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 954 | 0.5963 | 4 | 0.919802 | 7.781216 | -6.015258 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 955 | 0.5969 | 4 | 0.930770 | 8.124918 | -6.017034 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 956 | 0.5975 | 4 | 0.925433 | 7.340804 | -6.018758 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 957 | 0.5981 | 4 | 0.944448 | 10.579975 | -6.020152 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 958 | 0.5988 | 4 | 0.954444 | 13.695401 | -6.021178 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 959 | 0.5994 | 4 | 0.951249 | 11.892416 | -6.021435 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 960 | 0.6000 | 4 | 0.947216 | 11.365227 | -6.021983 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 961 | 0.6006 | 4 | 0.926662 | 7.708466 | -6.021462 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 962 | 0.6013 | 4 | 0.915030 | 6.858607 | -6.019392 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 963 | 0.6019 | 4 | 0.928690 | 8.081657 | -6.017495 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 964 | 0.6025 | 4 | 0.931064 | 8.268210 | -6.014669 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 965 | 0.6031 | 4 | 0.914736 | 6.586943 | -6.012901 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 966 | 0.6038 | 4 | 0.929174 | 8.826159 | -6.010773 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 967 | 0.6044 | 4 | 0.922514 | 7.269420 | -6.008125 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 968 | 0.6050 | 4 | 0.934059 | 8.398957 | -6.006511 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 969 | 0.6056 | 4 | 0.919612 | 7.183256 | -6.004561 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 970 | 0.6063 | 4 | 0.918401 | 7.122052 | -6.003055 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 971 | 0.6069 | 4 | 0.918601 | 7.398527 | -6.001156 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 972 | 0.6075 | 5 | 0.934709 | 10.635267 | -6.736096 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 973 | 0.6081 | 5 | 0.920950 | 9.403478 | -6.732933 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 974 | 0.6088 | 5 | 0.925128 | 9.822489 | -6.730927 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 975 | 0.6094 | 5 | 0.925081 | 9.443124 | -6.728293 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 976 | 0.6100 | 5 | 0.919867 | 8.730676 | -6.726088 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 977 | 0.6106 | 5 | 0.935123 | 11.108385 | -6.724638 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 978 | 0.6112 | 5 | 0.924291 | 9.531157 | -6.723093 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 979 | 0.6119 | 5 | 0.926213 | 9.206035 | -6.722831 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 980 | 0.6125 | 5 | 0.921258 | 9.455391 | -6.722275 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 981 | 0.6131 | 5 | 0.919319 | 9.037999 | -6.722818 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 982 | 0.6138 | 5 | 0.922101 | 9.649626 | -6.724609 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 983 | 0.6144 | 5 | 0.914127 | 9.180961 | -6.726867 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 984 | 0.6150 | 5 | 0.929605 | 9.903099 | -6.729894 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 985 | 0.6156 | 5 | 0.921145 | 8.583618 | -6.733872 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 986 | 0.6162 | 4 | 0.930872 | 8.484389 | -6.002413 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 987 | 0.6169 | 4 | 0.916844 | 7.861012 | -6.008323 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 988 | 0.6175 | 4 | 0.905383 | 6.545824 | -6.014689 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 989 | 0.6181 | 4 | 0.928213 | 8.051449 | -6.020522 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 990 | 0.6188 | 4 | 0.929307 | 7.931838 | -6.026718 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 991 | 0.6194 | 4 | 0.917231 | 7.924858 | -6.033235 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 992 | 0.6200 | 4 | 0.914644 | 7.073856 | -6.039820 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 993 | 0.6206 | 4 | 0.915804 | 7.031604 | -6.046759 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 994 | 0.6212 | 4 | 0.915532 | 6.851505 | -6.053125 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 995 | 0.6219 | 4 | 0.926133 | 7.924493 | -6.060672 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 996 | 0.6225 | 4 | 0.915122 | 6.845819 | -6.068738 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 997 | 0.6231 | 4 | 0.908980 | 7.367004 | -6.077364 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 998 | 0.6238 | 4 | 0.924743 | 7.651999 | -6.085551 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 999 | 0.6244 | 4 | 0.932288 | 8.875839 | -6.093536 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1000 | 0.6250 | 4 | 0.931087 | 8.401884 | -6.101440 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1001 | 0.6256 | 4 | 0.925997 | 7.514845 | -6.108311 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1002 | 0.6262 | 4 | 0.928103 | 8.755970 | -6.115036 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1003 | 0.6269 | 4 | 0.924007 | 7.877962 | -6.120053 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1004 | 0.6275 | 4 | 0.923370 | 7.606824 | -6.125083 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1005 | 0.6281 | 4 | 0.931847 | 8.271467 | -6.129330 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1006 | 0.6288 | 4 | 0.924153 | 7.350740 | -6.131815 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1007 | 0.6294 | 4 | 0.928794 | 9.078392 | -6.134081 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1008 | 0.6300 | 4 | 0.922034 | 7.871533 | -6.135853 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1009 | 0.6306 | 4 | 0.930759 | 8.319058 | -6.137238 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1010 | 0.6312 | 4 | 0.920082 | 7.321156 | -6.138090 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1011 | 0.6319 | 4 | 0.925351 | 7.838752 | -6.139210 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1012 | 0.6325 | 4 | 0.926898 | 7.595031 | -6.139927 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1013 | 0.6331 | 4 | 0.932205 | 8.338744 | -6.143038 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1014 | 0.6338 | 4 | 0.931919 | 8.428863 | -6.146324 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1015 | 0.6344 | 4 | 0.924758 | 7.827160 | -6.149373 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1016 | 0.6350 | 4 | 0.929116 | 8.540733 | -6.153742 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1017 | 0.6356 | 4 | 0.915126 | 7.437961 | -6.157307 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1018 | 0.6362 | 4 | 0.927702 | 8.517067 | -6.162357 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1019 | 0.6369 | 4 | 0.936099 | 9.611543 | -6.167346 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1020 | 0.6375 | 4 | 0.935214 | 9.719461 | -6.171501 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1021 | 0.6381 | 4 | 0.917992 | 7.913717 | -6.176618 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1022 | 0.6388 | 4 | 0.914312 | 6.582554 | -6.183004 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1023 | 0.6394 | 4 | 0.931955 | 8.374164 | -6.190415 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1024 | 0.6400 | 4 | 0.919650 | 7.426191 | -6.199218 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1025 | 0.6406 | 4 | 0.914648 | 7.586108 | -6.209307 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1026 | 0.6412 | 4 | 0.922771 | 7.578438 | -6.219444 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1027 | 0.6419 | 4 | 0.918334 | 7.000283 | -6.232960 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1028 | 0.6425 | 4 | 0.919806 | 7.717802 | -6.245844 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1029 | 0.6431 | 4 | 0.918627 | 7.365761 | -6.262885 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1030 | 0.6438 | 4 | 0.916576 | 7.045100 | -6.281386 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1031 | 0.6444 | 4 | 0.918363 | 7.295040 | -6.301876 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1032 | 0.6450 | 4 | 0.932141 | 8.764861 | -6.325443 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1033 | 0.6456 | 4 | 0.931872 | 8.280653 | -6.348106 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1034 | 0.6462 | 4 | 0.923205 | 7.308350 | -6.371098 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1035 | 0.6469 | 4 | 0.918940 | 7.740210 | -6.393510 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1036 | 0.6475 | 4 | 0.927996 | 8.016123 | -6.413080 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1037 | 0.6481 | 4 | 0.916981 | 7.068324 | -6.433704 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1038 | 0.6488 | 4 | 0.921732 | 8.072070 | -6.454810 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1039 | 0.6494 | 4 | 0.933029 | 8.710908 | -6.478630 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1040 | 0.6500 | 4 | 0.923166 | 7.583035 | -6.504847 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1041 | 0.6506 | 4 | 0.929650 | 7.991677 | -6.530908 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1042 | 0.6512 | 4 | 0.910652 | 7.831387 | -6.549571 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1043 | 0.6519 | 4 | 0.915165 | 7.414356 | -6.567332 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1044 | 0.6525 | 4 | 0.918041 | 7.230979 | -6.591474 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1045 | 0.6531 | 4 | 0.918513 | 7.130405 | -6.617837 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1046 | 0.6538 | 4 | 0.935045 | 10.248333 | -6.644649 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1047 | 0.6544 | 4 | 0.911800 | 7.263334 | -6.662403 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1048 | 0.6550 | 4 | 0.935323 | 8.808576 | -6.670366 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1049 | 0.6556 | 4 | 0.917601 | 7.382260 | -6.674614 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1050 | 0.6562 | 4 | 0.927703 | 8.327516 | -6.678070 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1051 | 0.6569 | 4 | 0.929240 | 8.159486 | -6.682884 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1052 | 0.6575 | 4 | 0.927343 | 8.167163 | -6.682254 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1053 | 0.6581 | 4 | 0.918255 | 7.118270 | -6.676019 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1054 | 0.6588 | 4 | 0.924514 | 7.911668 | -6.673981 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1055 | 0.6594 | 4 | 0.908841 | 7.212744 | -6.671337 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1056 | 0.6600 | 4 | 0.932677 | 8.829904 | -6.672329 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1057 | 0.6606 | 4 | 0.937924 | 9.915553 | -6.686767 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1058 | 0.6613 | 4 | 0.919435 | 7.155833 | -6.703246 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1059 | 0.6619 | 3 | 0.920376 | 5.931020 | -6.003585 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1060 | 0.6625 | 3 | 0.915874 | 5.160785 | -6.029415 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1061 | 0.6631 | 3 | 0.936080 | 7.283641 | -6.059212 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1062 | 0.6638 | 3 | 0.926326 | 6.584482 | -6.099238 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1063 | 0.6644 | 3 | 0.917920 | 5.606686 | -6.141490 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1064 | 0.6650 | 3 | 0.914574 | 5.980095 | -6.192046 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1065 | 0.6656 | 3 | 0.929508 | 7.013428 | -6.247208 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1066 | 0.6663 | 3 | 0.929459 | 6.676799 | -6.316483 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1067 | 0.6669 | 3 | 0.925459 | 6.066367 | -6.395493 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1068 | 0.6675 | 3 | 0.933261 | 7.170071 | -6.488118 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1069 | 0.6681 | 3 | 0.932382 | 7.179632 | -6.602509 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1070 | 0.6688 | 3 | 0.919540 | 6.003355 | -6.686659 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1071 | 0.6694 | 3 | 0.931598 | 6.937881 | -6.645541 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1072 | 0.6700 | 3 | 0.927833 | 6.351259 | -6.529632 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1073 | 0.6706 | 3 | 0.918263 | 5.491571 | -6.386503 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1074 | 0.6713 | 3 | 0.926692 | 6.385859 | -6.273671 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1075 | 0.6719 | 3 | 0.917119 | 5.999285 | -6.176272 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1076 | 0.6725 | 3 | 0.925408 | 5.932480 | -6.094158 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1077 | 0.6731 | 3 | 0.935232 | 7.047719 | -6.029750 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1078 | 0.6737 | 4 | 0.921706 | 8.294545 | -6.698059 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1079 | 0.6744 | 4 | 0.911691 | 7.933428 | -6.654229 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1080 | 0.6750 | 4 | 0.926209 | 7.677143 | -6.616279 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1081 | 0.6756 | 4 | 0.926396 | 8.077297 | -6.591037 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1082 | 0.6763 | 4 | 0.928085 | 8.697632 | -6.572259 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1083 | 0.6769 | 4 | 0.924713 | 8.636628 | -6.560263 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1084 | 0.6775 | 4 | 0.913943 | 6.868651 | -6.546583 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1085 | 0.6781 | 4 | 0.934523 | 9.129627 | -6.532780 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1086 | 0.6787 | 4 | 0.928840 | 8.193750 | -6.526252 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1087 | 0.6794 | 4 | 0.922079 | 7.691099 | -6.521036 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1088 | 0.6800 | 4 | 0.928793 | 9.001772 | -6.515684 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1089 | 0.6806 | 4 | 0.928990 | 8.050145 | -6.506385 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1090 | 0.6813 | 4 | 0.911485 | 7.048428 | -6.500763 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1091 | 0.6819 | 4 | 0.913823 | 6.982267 | -6.496410 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1092 | 0.6825 | 4 | 0.919510 | 7.312097 | -6.493355 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1093 | 0.6831 | 4 | 0.921880 | 8.064265 | -6.491628 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1094 | 0.6837 | 4 | 0.924319 | 8.168887 | -6.490720 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1095 | 0.6844 | 4 | 0.916849 | 8.145098 | -6.493842 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1096 | 0.6850 | 4 | 0.933056 | 9.022156 | -6.496825 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1097 | 0.6856 | 4 | 0.930876 | 9.288652 | -6.500247 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1098 | 0.6863 | 4 | 0.928344 | 8.776482 | -6.503331 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1099 | 0.6869 | 4 | 0.929797 | 8.445000 | -6.506542 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1100 | 0.6875 | 4 | 0.932857 | 8.977255 | -6.508816 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1101 | 0.6881 | 4 | 0.914474 | 6.950047 | -6.508330 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1102 | 0.6887 | 4 | 0.915831 | 6.985880 | -6.504984 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1103 | 0.6894 | 4 | 0.919720 | 7.433220 | -6.498664 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1104 | 0.6900 | 4 | 0.924345 | 8.435698 | -6.493188 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1105 | 0.6906 | 4 | 0.919239 | 7.499495 | -6.483664 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1106 | 0.6913 | 4 | 0.927702 | 8.312528 | -6.477971 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1107 | 0.6919 | 4 | 0.905430 | 7.064338 | -6.471588 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1108 | 0.6925 | 4 | 0.929447 | 8.317395 | -6.462329 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1109 | 0.6931 | 4 | 0.920270 | 7.443997 | -6.454981 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1110 | 0.6937 | 4 | 0.907604 | 7.078348 | -6.445337 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1111 | 0.6944 | 4 | 0.944930 | 11.707186 | -6.436921 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1112 | 0.6950 | 4 | 0.947047 | 12.491962 | -6.427934 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1113 | 0.6956 | 4 | 0.955191 | 13.723619 | -6.419523 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1114 | 0.6963 | 4 | 0.949932 | 12.809653 | -6.409602 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1115 | 0.6969 | 4 | 0.941285 | 10.841859 | -6.398473 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1116 | 0.6975 | 4 | 0.950025 | 12.612672 | -6.387562 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1117 | 0.6981 | 4 | 0.928513 | 9.529809 | -6.376837 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1118 | 0.6987 | 4 | 0.952280 | 13.225460 | -6.366434 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1119 | 0.6994 | 4 | 0.930254 | 8.762119 | -6.355948 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1120 | 0.7000 | 4 | 0.916934 | 7.582739 | -6.346619 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1121 | 0.7006 | 4 | 0.918606 | 8.007747 | -6.338990 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1122 | 0.7013 | 4 | 0.930048 | 8.570686 | -6.335392 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1123 | 0.7019 | 4 | 0.920564 | 8.365584 | -6.328398 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1124 | 0.7025 | 4 | 0.919386 | 7.555674 | -6.322689 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1125 | 0.7031 | 4 | 0.908091 | 7.353988 | -6.320174 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1126 | 0.7037 | 4 | 0.925578 | 7.913533 | -6.318050 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1127 | 0.7044 | 4 | 0.928327 | 8.114483 | -6.319327 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1128 | 0.7050 | 4 | 0.913671 | 7.522904 | -6.320312 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1129 | 0.7056 | 4 | 0.930980 | 8.752463 | -6.321696 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1130 | 0.7063 | 4 | 0.940251 | 10.005117 | -6.325615 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1131 | 0.7069 | 4 | 0.926527 | 8.024006 | -6.329173 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1132 | 0.7075 | 4 | 0.907798 | 7.109459 | -6.332008 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1133 | 0.7081 | 4 | 0.922298 | 8.640346 | -6.335240 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1134 | 0.7087 | 4 | 0.896194 | 6.614045 | -6.337656 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1135 | 0.7094 | 4 | 0.937337 | 9.121761 | -6.340842 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1136 | 0.7100 | 4 | 0.921886 | 8.392108 | -6.346003 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1137 | 0.7106 | 4 | 0.916224 | 7.859331 | -6.348157 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1138 | 0.7113 | 4 | 0.915565 | 8.024887 | -6.352561 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1139 | 0.7119 | 4 | 0.918944 | 8.247203 | -6.355931 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1140 | 0.7125 | 4 | 0.928581 | 8.213575 | -6.359025 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1141 | 0.7131 | 4 | 0.922211 | 7.502078 | -6.364355 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1142 | 0.7137 | 4 | 0.928090 | 8.158745 | -6.367767 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1143 | 0.7144 | 4 | 0.920783 | 8.535763 | -6.371420 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1144 | 0.7150 | 4 | 0.937610 | 9.167599 | -6.375872 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1145 | 0.7156 | 4 | 0.914974 | 7.609418 | -6.379270 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1146 | 0.7163 | 4 | 0.925423 | 8.059713 | -6.380910 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1147 | 0.7169 | 4 | 0.927696 | 8.380200 | -6.383113 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1148 | 0.7175 | 4 | 0.915872 | 8.763745 | -6.383455 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1149 | 0.7181 | 4 | 0.916754 | 7.811686 | -6.382649 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1150 | 0.7188 | 4 | 0.904398 | 7.312649 | -6.380434 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1151 | 0.7194 | 4 | 0.898388 | 6.233123 | -6.378116 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1152 | 0.7200 | 4 | 0.924043 | 8.260602 | -6.375061 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1153 | 0.7206 | 4 | 0.929171 | 8.306413 | -6.370896 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1154 | 0.7213 | 4 | 0.924714 | 7.813347 | -6.367199 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1155 | 0.7219 | 4 | 0.914168 | 7.571872 | -6.364520 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1156 | 0.7225 | 4 | 0.925041 | 8.776365 | -6.361529 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1157 | 0.7231 | 4 | 0.925620 | 8.343365 | -6.361940 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1158 | 0.7238 | 4 | 0.922507 | 7.930578 | -6.361511 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1159 | 0.7244 | 4 | 0.934175 | 8.782314 | -6.360446 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1160 | 0.7250 | 4 | 0.920790 | 7.707391 | -6.359915 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1161 | 0.7256 | 4 | 0.910863 | 7.289048 | -6.359643 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1162 | 0.7263 | 4 | 0.917009 | 7.602118 | -6.359626 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1163 | 0.7269 | 4 | 0.921294 | 8.246427 | -6.360292 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1164 | 0.7275 | 4 | 0.926745 | 8.517843 | -6.360161 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1165 | 0.7281 | 4 | 0.925840 | 8.038491 | -6.361011 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1166 | 0.7288 | 4 | 0.926431 | 8.348977 | -6.364340 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1167 | 0.7294 | 4 | 0.935242 | 10.113046 | -6.366325 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1168 | 0.7300 | 4 | 0.927744 | 8.453601 | -6.370783 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1169 | 0.7306 | 4 | 0.921554 | 7.747966 | -6.373253 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1170 | 0.7313 | 4 | 0.922679 | 8.202883 | -6.378563 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1171 | 0.7319 | 4 | 0.910188 | 7.777112 | -6.387620 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1172 | 0.7325 | 4 | 0.917733 | 7.207897 | -6.397110 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1173 | 0.7331 | 4 | 0.916014 | 7.393832 | -6.408907 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1174 | 0.7338 | 4 | 0.916073 | 7.744147 | -6.423190 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1175 | 0.7344 | 4 | 0.924179 | 7.997136 | -6.438644 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1176 | 0.7350 | 4 | 0.919824 | 8.388396 | -6.453451 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1177 | 0.7356 | 4 | 0.920209 | 7.862722 | -6.466161 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1178 | 0.7362 | 4 | 0.898070 | 7.402616 | -6.477981 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1179 | 0.7369 | 4 | 0.934894 | 9.805250 | -6.490331 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1180 | 0.7375 | 4 | 0.926073 | 7.958758 | -6.501302 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1181 | 0.7381 | 4 | 0.918172 | 7.780018 | -6.508742 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1182 | 0.7388 | 4 | 0.926471 | 9.004369 | -6.517199 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1183 | 0.7394 | 4 | 0.939136 | 10.829570 | -6.524190 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1184 | 0.7400 | 4 | 0.916182 | 8.729566 | -6.529659 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1185 | 0.7406 | 4 | 0.921064 | 8.598056 | -6.536999 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1186 | 0.7412 | 4 | 0.924541 | 7.885106 | -6.544560 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1187 | 0.7419 | 4 | 0.907229 | 6.759805 | -6.553980 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1188 | 0.7425 | 4 | 0.915924 | 7.317449 | -6.561419 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1189 | 0.7431 | 4 | 0.929004 | 8.739841 | -6.568440 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1190 | 0.7438 | 4 | 0.906303 | 7.346764 | -6.572663 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1191 | 0.7444 | 4 | 0.915393 | 8.164053 | -6.575985 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1192 | 0.7450 | 4 | 0.899735 | 7.143547 | -6.577017 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1193 | 0.7456 | 4 | 0.924635 | 8.377289 | -6.576245 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1194 | 0.7462 | 4 | 0.918561 | 7.572548 | -6.573967 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1195 | 0.7469 | 4 | 0.926113 | 7.928270 | -6.569815 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1196 | 0.7475 | 4 | 0.920556 | 7.788774 | -6.566726 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1197 | 0.7481 | 4 | 0.908997 | 6.947657 | -6.562847 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1198 | 0.7488 | 4 | 0.929165 | 8.826927 | -6.560783 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1199 | 0.7494 | 4 | 0.920913 | 7.702865 | -6.557858 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1200 | 0.7500 | 4 | 0.933831 | 8.945611 | -6.558579 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1201 | 0.7506 | 4 | 0.924699 | 8.864619 | -6.562230 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1202 | 0.7512 | 4 | 0.911546 | 7.443252 | -6.568334 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1203 | 0.7519 | 4 | 0.935485 | 9.703718 | -6.577404 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1204 | 0.7525 | 4 | 0.923595 | 8.795860 | -6.585200 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1205 | 0.7531 | 4 | 0.929596 | 8.778910 | -6.596626 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1206 | 0.7538 | 4 | 0.926712 | 8.130308 | -6.607278 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1207 | 0.7544 | 4 | 0.928199 | 8.880169 | -6.617869 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1208 | 0.7550 | 4 | 0.924128 | 7.796787 | -6.627011 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1209 | 0.7556 | 4 | 0.939659 | 9.793255 | -6.626992 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1210 | 0.7562 | 4 | 0.924773 | 8.361683 | -6.639151 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1211 | 0.7569 | 4 | 0.922705 | 7.717882 | -6.653354 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1212 | 0.7575 | 4 | 0.927571 | 8.588771 | -6.663088 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1213 | 0.7581 | 4 | 0.931787 | 8.685981 | -6.678892 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1214 | 0.7588 | 4 | 0.932016 | 8.870283 | -6.712668 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1215 | 0.7594 | 3 | 0.936448 | 7.131936 | -6.028008 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1216 | 0.7600 | 3 | 0.935406 | 7.003290 | -6.071563 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1217 | 0.7606 | 3 | 0.924934 | 6.910270 | -6.119044 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1218 | 0.7612 | 3 | 0.929694 | 6.615141 | -6.170889 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1219 | 0.7619 | 3 | 0.909327 | 5.570117 | -6.234240 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1220 | 0.7625 | 3 | 0.932986 | 7.149140 | -6.295446 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1221 | 0.7631 | 3 | 0.924188 | 6.457875 | -6.365778 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1222 | 0.7638 | 3 | 0.909953 | 5.871146 | -6.434445 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1223 | 0.7644 | 3 | 0.928547 | 6.499115 | -6.511122 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1224 | 0.7650 | 3 | 0.922555 | 6.261577 | -6.592566 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1225 | 0.7656 | 3 | 0.913798 | 6.026391 | -6.668235 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1226 | 0.7662 | 2 | 0.930050 | 5.004120 | -6.029681 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1227 | 0.7669 | 2 | 0.909977 | 4.486438 | -6.109226 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1228 | 0.7675 | 2 | 0.895722 | 3.440246 | -6.202233 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1229 | 0.7681 | 2 | 0.940644 | 5.820162 | -6.281046 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1230 | 0.7688 | 2 | 0.930324 | 5.041517 | -6.400145 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1231 | 0.7694 | 2 | 0.916581 | 4.403132 | -6.514832 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1232 | 0.7700 | 2 | 0.920085 | 4.437169 | -6.710137 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1233 | 0.7706 | 1 | 0.952456 | 4.143759 | -6.231334 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1234 | 0.7712 | 1 | 0.939911 | 3.289889 | -6.199109 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1235 | 0.7719 | 1 | 0.948967 | 3.912819 | -6.279833 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1236 | 0.7725 | 1 | 0.924042 | 3.470309 | -6.186977 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1237 | 0.7731 | 1 | 0.924761 | 3.568120 | -6.210239 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1238 | 0.7738 | 1 | 0.951496 | 4.155957 | -6.175174 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1239 | 0.7744 | 2 | 0.914439 | 4.638218 | -6.643518 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1240 | 0.7750 | 2 | 0.922798 | 4.622485 | -6.472152 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1241 | 0.7756 | 2 | 0.909284 | 4.378345 | -6.351047 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1242 | 0.7762 | 2 | 0.928217 | 4.809230 | -6.270035 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1243 | 0.7769 | 2 | 0.923236 | 4.858469 | -6.200196 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1244 | 0.7775 | 2 | 0.893751 | 4.178158 | -6.173887 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1245 | 0.7781 | 2 | 0.922553 | 4.384358 | -6.188209 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1246 | 0.7788 | 2 | 0.909896 | 4.370389 | -6.231758 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1247 | 0.7794 | 2 | 0.922965 | 4.873047 | -6.265387 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1248 | 0.7800 | 2 | 0.899864 | 4.570790 | -6.220014 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1249 | 0.7806 | 2 | 0.913468 | 4.285283 | -6.119855 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1250 | 0.7812 | 2 | 0.932397 | 5.044742 | -6.026681 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1251 | 0.7819 | 3 | 0.929710 | 7.061525 | -6.636329 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1252 | 0.7825 | 3 | 0.906493 | 5.589907 | -6.562926 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1253 | 0.7831 | 3 | 0.943008 | 8.304963 | -6.490258 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1254 | 0.7838 | 3 | 0.926622 | 6.655013 | -6.422651 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1255 | 0.7844 | 3 | 0.931639 | 6.835680 | -6.366936 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1256 | 0.7850 | 3 | 0.916696 | 6.467256 | -6.317927 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1257 | 0.7856 | 3 | 0.926912 | 6.755943 | -6.270930 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1258 | 0.7863 | 3 | 0.922191 | 6.677117 | -6.227701 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1259 | 0.7869 | 3 | 0.930687 | 7.118596 | -6.186990 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1260 | 0.7875 | 3 | 0.930117 | 6.684295 | -6.144891 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1261 | 0.7881 | 3 | 0.929701 | 7.005025 | -6.113824 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1262 | 0.7888 | 3 | 0.918863 | 6.294075 | -6.078622 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1263 | 0.7894 | 3 | 0.932202 | 6.840877 | -6.047364 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1264 | 0.7900 | 3 | 0.910877 | 5.613191 | -6.021068 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1265 | 0.7906 | 4 | 0.925607 | 8.073141 | -6.719737 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1266 | 0.7913 | 4 | 0.912307 | 7.263875 | -6.704270 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1267 | 0.7919 | 4 | 0.930200 | 8.913673 | -6.689412 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1268 | 0.7925 | 4 | 0.929462 | 8.768475 | -6.679864 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1269 | 0.7931 | 4 | 0.922741 | 8.526130 | -6.674705 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1270 | 0.7938 | 4 | 0.904746 | 7.523337 | -6.674535 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1271 | 0.7944 | 4 | 0.905644 | 7.542059 | -6.676006 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1272 | 0.7950 | 4 | 0.920402 | 7.629049 | -6.677811 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1273 | 0.7956 | 4 | 0.925692 | 9.695210 | -6.681055 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1274 | 0.7963 | 4 | 0.907982 | 7.327946 | -6.683734 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1275 | 0.7969 | 4 | 0.921152 | 7.715528 | -6.689100 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1276 | 0.7975 | 4 | 0.919780 | 7.797306 | -6.690455 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1277 | 0.7981 | 4 | 0.908231 | 6.997520 | -6.692961 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1278 | 0.7988 | 4 | 0.910907 | 6.868745 | -6.695419 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1279 | 0.7994 | 4 | 0.925940 | 8.451732 | -6.699436 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1280 | 0.8000 | 4 | 0.934993 | 10.338312 | -6.704191 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1281 | 0.8006 | 4 | 0.947827 | 13.149787 | -6.706832 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1282 | 0.8013 | 4 | 0.946172 | 12.332356 | -6.713397 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1283 | 0.8019 | 4 | 0.958371 | 15.994232 | -6.718868 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1284 | 0.8025 | 3 | 0.920216 | 6.571284 | -6.004194 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1285 | 0.8031 | 3 | 0.926825 | 6.706288 | -6.008552 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1286 | 0.8037 | 3 | 0.907671 | 5.892412 | -6.013252 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1287 | 0.8044 | 3 | 0.918813 | 6.421157 | -6.016678 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1288 | 0.8050 | 3 | 0.907797 | 5.761683 | -6.018253 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1289 | 0.8056 | 3 | 0.921126 | 6.379539 | -6.016380 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1290 | 0.8063 | 3 | 0.937599 | 7.956121 | -6.008141 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1291 | 0.8069 | 3 | 0.925893 | 6.658299 | -6.002442 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1292 | 0.8075 | 4 | 0.929607 | 9.408072 | -6.714071 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1293 | 0.8081 | 4 | 0.930463 | 8.916854 | -6.702464 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1294 | 0.8087 | 4 | 0.913496 | 8.402706 | -6.687466 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1295 | 0.8094 | 4 | 0.901661 | 7.318546 | -6.673721 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1296 | 0.8100 | 4 | 0.921061 | 8.009299 | -6.665851 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1297 | 0.8106 | 4 | 0.909855 | 7.818646 | -6.663710 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1298 | 0.8113 | 4 | 0.912429 | 7.054599 | -6.656185 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1299 | 0.8119 | 4 | 0.921310 | 8.087624 | -6.647634 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1300 | 0.8125 | 4 | 0.926882 | 8.309952 | -6.643482 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1301 | 0.8131 | 4 | 0.922634 | 8.101265 | -6.637931 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1302 | 0.8137 | 4 | 0.903305 | 7.810847 | -6.632792 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1303 | 0.8144 | 4 | 0.925649 | 8.853188 | -6.627386 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1304 | 0.8150 | 4 | 0.920076 | 8.582657 | -6.622565 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1305 | 0.8156 | 4 | 0.914099 | 7.787682 | -6.618596 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1306 | 0.8163 | 4 | 0.931229 | 9.168955 | -6.616898 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1307 | 0.8169 | 4 | 0.931456 | 10.647637 | -6.617723 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1308 | 0.8175 | 4 | 0.930434 | 8.912885 | -6.612092 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1309 | 0.8181 | 4 | 0.928742 | 8.830129 | -6.609815 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1310 | 0.8187 | 4 | 0.914587 | 7.686721 | -6.607506 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1311 | 0.8194 | 4 | 0.920700 | 9.108045 | -6.606688 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1312 | 0.8200 | 4 | 0.927001 | 8.420919 | -6.607645 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1313 | 0.8206 | 4 | 0.924258 | 8.255074 | -6.605577 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1314 | 0.8213 | 4 | 0.923330 | 8.297737 | -6.610603 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1315 | 0.8219 | 4 | 0.927977 | 9.260231 | -6.619429 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1316 | 0.8225 | 4 | 0.910872 | 7.814553 | -6.628394 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1317 | 0.8231 | 4 | 0.915706 | 8.115908 | -6.640888 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1318 | 0.8237 | 4 | 0.912054 | 7.342257 | -6.651894 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1319 | 0.8244 | 4 | 0.925312 | 8.489474 | -6.664372 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1320 | 0.8250 | 4 | 0.902651 | 7.504793 | -6.672463 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1321 | 0.8256 | 4 | 0.932649 | 9.117836 | -6.676945 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1322 | 0.8263 | 4 | 0.913811 | 7.966333 | -6.682830 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1323 | 0.8269 | 4 | 0.912447 | 8.078233 | -6.688385 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1324 | 0.8275 | 4 | 0.922237 | 8.321086 | -6.683467 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1325 | 0.8281 | 4 | 0.923167 | 8.182118 | -6.687963 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1326 | 0.8287 | 4 | 0.930753 | 9.513708 | -6.689673 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1327 | 0.8294 | 4 | 0.927944 | 8.866654 | -6.694972 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1328 | 0.8300 | 4 | 0.916398 | 8.443696 | -6.703402 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1329 | 0.8306 | 4 | 0.901440 | 7.634219 | -6.705796 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1330 | 0.8313 | 4 | 0.922754 | 8.935395 | -6.711642 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1331 | 0.8319 | 4 | 0.927116 | 8.510146 | -6.714483 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1332 | 0.8325 | 4 | 0.908065 | 7.132285 | -6.718255 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1333 | 0.8331 | 4 | 0.918213 | 8.339746 | -6.720112 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1334 | 0.8337 | 4 | 0.915420 | 7.949602 | -6.717928 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1335 | 0.8344 | 4 | 0.926254 | 8.569067 | -6.713696 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1336 | 0.8350 | 4 | 0.923414 | 9.492939 | -6.708814 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1337 | 0.8356 | 4 | 0.914152 | 8.191882 | -6.701642 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1338 | 0.8363 | 4 | 0.892766 | 6.287503 | -6.690712 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1339 | 0.8369 | 4 | 0.923519 | 8.217924 | -6.684021 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1340 | 0.8375 | 4 | 0.925387 | 8.864148 | -6.673170 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1341 | 0.8381 | 4 | 0.924136 | 8.206538 | -6.669380 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1342 | 0.8387 | 4 | 0.926988 | 9.034768 | -6.666147 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1343 | 0.8394 | 4 | 0.911987 | 7.943771 | -6.667502 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1344 | 0.8400 | 4 | 0.921000 | 8.354416 | -6.670236 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1345 | 0.8406 | 4 | 0.901436 | 6.994085 | -6.672476 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1346 | 0.8413 | 4 | 0.924168 | 8.719940 | -6.677506 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1347 | 0.8419 | 4 | 0.900978 | 7.824988 | -6.681746 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1348 | 0.8425 | 4 | 0.912713 | 7.374806 | -6.679422 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1349 | 0.8431 | 4 | 0.920766 | 8.125932 | -6.684184 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1350 | 0.8438 | 4 | 0.923853 | 8.162324 | -6.691453 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1351 | 0.8444 | 4 | 0.896098 | 6.970622 | -6.695730 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1352 | 0.8450 | 4 | 0.930811 | 9.435302 | -6.701364 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1353 | 0.8456 | 4 | 0.918538 | 8.275929 | -6.709699 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1354 | 0.8463 | 4 | 0.928470 | 8.698167 | -6.717348 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1355 | 0.8469 | 3 | 0.930309 | 7.341614 | -6.009742 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1356 | 0.8475 | 3 | 0.952561 | 11.640321 | -6.021302 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1357 | 0.8481 | 3 | 0.953932 | 12.938866 | -6.038162 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1358 | 0.8488 | 3 | 0.943049 | 9.969767 | -6.060154 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1359 | 0.8494 | 3 | 0.906790 | 5.582816 | -6.081769 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1360 | 0.8500 | 3 | 0.925486 | 6.801914 | -6.109987 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1361 | 0.8506 | 3 | 0.922876 | 6.741693 | -6.136498 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1362 | 0.8513 | 3 | 0.934594 | 7.493868 | -6.167731 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1363 | 0.8519 | 3 | 0.924786 | 7.474810 | -6.199347 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1364 | 0.8525 | 3 | 0.910837 | 5.500055 | -6.227932 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1365 | 0.8531 | 3 | 0.915531 | 5.932688 | -6.252630 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1366 | 0.8538 | 3 | 0.930793 | 7.041993 | -6.276321 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1367 | 0.8544 | 3 | 0.938088 | 8.756874 | -6.296855 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1368 | 0.8550 | 3 | 0.910723 | 6.451619 | -6.308915 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1369 | 0.8556 | 3 | 0.917983 | 7.778209 | -6.319756 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1370 | 0.8563 | 3 | 0.927807 | 7.532988 | -6.320335 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1371 | 0.8569 | 3 | 0.931662 | 7.979495 | -6.328537 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1372 | 0.8575 | 3 | 0.921234 | 6.817747 | -6.332177 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1373 | 0.8581 | 3 | 0.925978 | 6.832790 | -6.334080 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1374 | 0.8588 | 3 | 0.930740 | 7.744576 | -6.338519 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1375 | 0.8594 | 3 | 0.943918 | 9.086644 | -6.339174 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1376 | 0.8600 | 3 | 0.914050 | 5.699749 | -6.347613 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1377 | 0.8606 | 3 | 0.905264 | 6.346863 | -6.347356 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1378 | 0.8613 | 3 | 0.906315 | 5.285599 | -6.345342 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1379 | 0.8619 | 3 | 0.920860 | 7.811200 | -6.338786 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1380 | 0.8625 | 3 | 0.917868 | 6.642529 | -6.332586 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1381 | 0.8631 | 3 | 0.909967 | 5.732910 | -6.323180 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1382 | 0.8638 | 3 | 0.921635 | 6.648797 | -6.309623 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1383 | 0.8644 | 3 | 0.930756 | 7.780269 | -6.296340 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1384 | 0.8650 | 3 | 0.930719 | 7.890981 | -6.281010 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1385 | 0.8656 | 3 | 0.923992 | 6.398535 | -6.274691 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1386 | 0.8662 | 3 | 0.903839 | 6.690373 | -6.266116 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1387 | 0.8669 | 3 | 0.909279 | 6.162003 | -6.265344 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1388 | 0.8675 | 3 | 0.941367 | 8.677482 | -6.270572 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1389 | 0.8681 | 3 | 0.911446 | 6.014046 | -6.284428 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1390 | 0.8688 | 3 | 0.929819 | 6.943937 | -6.307708 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1391 | 0.8694 | 3 | 0.923763 | 6.919514 | -6.331954 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1392 | 0.8700 | 3 | 0.940189 | 8.590857 | -6.367560 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1393 | 0.8706 | 3 | 0.909837 | 6.339009 | -6.404126 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1394 | 0.8712 | 3 | 0.926164 | 6.626126 | -6.452202 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1395 | 0.8719 | 3 | 0.926851 | 6.704561 | -6.496296 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1396 | 0.8725 | 3 | 0.932812 | 8.590432 | -6.543041 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1397 | 0.8731 | 3 | 0.924830 | 6.746752 | -6.600877 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1398 | 0.8738 | 3 | 0.892529 | 5.109863 | -6.662386 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1399 | 0.8744 | 2 | 0.911400 | 4.365246 | -6.021849 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1400 | 0.8750 | 2 | 0.907444 | 4.026311 | -6.092594 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1401 | 0.8756 | 2 | 0.950411 | 7.357859 | -6.204895 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1402 | 0.8762 | 2 | 0.930811 | 5.691882 | -6.366503 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1403 | 0.8769 | 2 | 0.933039 | 5.277355 | -6.642130 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1404 | 0.8775 | 1 | 0.937317 | 3.483202 | -6.227275 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1405 | 0.8781 | 2 | 0.913230 | 5.123663 | -6.574511 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1406 | 0.8788 | 2 | 0.911899 | 6.244291 | -6.302770 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1407 | 0.8794 | 2 | 0.950953 | 7.910832 | -6.147566 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1408 | 0.8800 | 2 | 0.907901 | 4.279579 | -6.029678 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1409 | 0.8806 | 3 | 0.918393 | 6.419089 | -6.693765 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1410 | 0.8812 | 3 | 0.924358 | 6.599582 | -6.649497 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1411 | 0.8819 | 3 | 0.935163 | 8.815596 | -6.623082 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1412 | 0.8825 | 3 | 0.917370 | 6.024143 | -6.616261 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1413 | 0.8831 | 3 | 0.903074 | 5.630821 | -6.620242 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1414 | 0.8838 | 3 | 0.920935 | 6.477164 | -6.641287 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1415 | 0.8844 | 3 | 0.930969 | 7.159821 | -6.656438 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1416 | 0.8850 | 3 | 0.944903 | 9.192090 | -6.678032 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1417 | 0.8856 | 3 | 0.917504 | 6.419061 | -6.696381 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1418 | 0.8862 | 2 | 0.925873 | 4.906813 | -6.009344 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1419 | 0.8869 | 2 | 0.942563 | 6.497721 | -6.020023 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1420 | 0.8875 | 2 | 0.930500 | 6.719030 | -6.034870 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1421 | 0.8881 | 2 | 0.922138 | 4.961293 | -6.048726 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1422 | 0.8888 | 2 | 0.916820 | 4.927310 | -6.054676 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1423 | 0.8894 | 2 | 0.931824 | 5.187046 | -6.082993 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1424 | 0.8900 | 2 | 0.909958 | 4.764700 | -6.080217 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1425 | 0.8906 | 2 | 0.952513 | 8.509196 | -6.099373 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1426 | 0.8912 | 2 | 0.925765 | 5.302563 | -6.126336 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1427 | 0.8919 | 2 | 0.924751 | 4.762978 | -6.153369 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1428 | 0.8925 | 2 | 0.908702 | 4.654668 | -6.184855 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1429 | 0.8931 | 2 | 0.927439 | 4.797967 | -6.191623 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1430 | 0.8938 | 2 | 0.922327 | 4.872953 | -6.181712 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1431 | 0.8944 | 2 | 0.949224 | 7.255501 | -6.165610 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1432 | 0.8950 | 2 | 0.923494 | 4.752976 | -6.148035 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1433 | 0.8956 | 2 | 0.910790 | 4.072053 | -6.100759 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1434 | 0.8962 | 2 | 0.912764 | 4.493207 | -6.069348 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1435 | 0.8969 | 2 | 0.923236 | 4.706394 | -6.009632 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1436 | 0.8975 | 3 | 0.925365 | 7.674640 | -6.669228 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1437 | 0.8981 | 3 | 0.930127 | 8.028126 | -6.614993 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1438 | 0.8988 | 3 | 0.909886 | 5.641717 | -6.544955 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1439 | 0.8994 | 3 | 0.923842 | 6.524829 | -6.498984 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1440 | 0.9000 | 3 | 0.935796 | 9.013565 | -6.452873 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1441 | 0.9006 | 3 | 0.928812 | 7.637232 | -6.417525 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1442 | 0.9012 | 3 | 0.916514 | 6.729560 | -6.386488 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1443 | 0.9019 | 3 | 0.920521 | 6.535683 | -6.358110 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1444 | 0.9025 | 3 | 0.934488 | 8.036480 | -6.338567 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1445 | 0.9031 | 3 | 0.923621 | 8.000703 | -6.320279 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1446 | 0.9038 | 3 | 0.945605 | 10.816668 | -6.301856 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1447 | 0.9044 | 3 | 0.957473 | 13.628701 | -6.282845 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1448 | 0.9050 | 3 | 0.951944 | 10.663103 | -6.270388 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1449 | 0.9056 | 3 | 0.939845 | 8.595916 | -6.252747 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1450 | 0.9062 | 3 | 0.948901 | 9.769054 | -6.241028 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1451 | 0.9069 | 3 | 0.909184 | 6.239377 | -6.224971 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1452 | 0.9075 | 3 | 0.914978 | 6.686036 | -6.210477 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1453 | 0.9081 | 3 | 0.922125 | 6.350539 | -6.205791 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1454 | 0.9088 | 3 | 0.923767 | 7.956812 | -6.197706 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1455 | 0.9094 | 3 | 0.919677 | 6.447463 | -6.197969 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1456 | 0.9100 | 3 | 0.901075 | 5.856636 | -6.200958 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1457 | 0.9106 | 3 | 0.933259 | 7.972088 | -6.211285 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1458 | 0.9113 | 3 | 0.923401 | 6.754908 | -6.227387 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1459 | 0.9119 | 3 | 0.930110 | 7.010411 | -6.244895 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1460 | 0.9125 | 3 | 0.904108 | 5.327531 | -6.262087 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1461 | 0.9131 | 3 | 0.916977 | 6.080096 | -6.280465 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1462 | 0.9138 | 3 | 0.901609 | 6.342472 | -6.302012 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1463 | 0.9144 | 3 | 0.919275 | 6.615018 | -6.315333 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1464 | 0.9150 | 3 | 0.924563 | 6.948357 | -6.331984 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1465 | 0.9156 | 3 | 0.901309 | 6.283830 | -6.339009 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1466 | 0.9163 | 3 | 0.918418 | 6.435431 | -6.348970 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1467 | 0.9169 | 3 | 0.914044 | 6.364084 | -6.357608 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1468 | 0.9175 | 3 | 0.923417 | 6.583113 | -6.358147 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1469 | 0.9181 | 3 | 0.910274 | 6.727884 | -6.362814 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1470 | 0.9188 | 3 | 0.924321 | 6.566537 | -6.362185 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1471 | 0.9194 | 3 | 0.919724 | 6.277768 | -6.369470 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1472 | 0.9200 | 3 | 0.925420 | 7.324469 | -6.372368 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1473 | 0.9206 | 3 | 0.911357 | 6.095469 | -6.375124 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1474 | 0.9213 | 3 | 0.930778 | 8.441899 | -6.377731 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1475 | 0.9219 | 3 | 0.923867 | 7.201074 | -6.379665 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1476 | 0.9225 | 3 | 0.924486 | 6.876316 | -6.378112 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1477 | 0.9231 | 3 | 0.925674 | 7.472106 | -6.367844 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1478 | 0.9238 | 3 | 0.927625 | 7.278294 | -6.355895 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1479 | 0.9244 | 3 | 0.921834 | 7.126721 | -6.335776 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1480 | 0.9250 | 3 | 0.926204 | 6.768756 | -6.320051 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1481 | 0.9256 | 3 | 0.924415 | 6.981813 | -6.294503 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1482 | 0.9263 | 3 | 0.923252 | 8.269953 | -6.269027 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1483 | 0.9269 | 3 | 0.915274 | 6.118142 | -6.247761 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1484 | 0.9275 | 3 | 0.895026 | 5.716490 | -6.227756 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1485 | 0.9281 | 3 | 0.914148 | 5.881200 | -6.215509 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1486 | 0.9287 | 3 | 0.932979 | 7.572946 | -6.201373 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1487 | 0.9294 | 3 | 0.914681 | 6.239722 | -6.195014 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1488 | 0.9300 | 3 | 0.925638 | 6.726851 | -6.191173 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1489 | 0.9306 | 3 | 0.919700 | 6.448708 | -6.192837 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1490 | 0.9313 | 3 | 0.923637 | 7.135001 | -6.194106 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1491 | 0.9319 | 3 | 0.926781 | 6.974635 | -6.194425 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1492 | 0.9325 | 3 | 0.934381 | 8.284605 | -6.198788 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1493 | 0.9331 | 3 | 0.939366 | 8.767719 | -6.201704 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1494 | 0.9337 | 3 | 0.922030 | 6.414749 | -6.207466 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1495 | 0.9344 | 3 | 0.907948 | 5.853225 | -6.208558 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1496 | 0.9350 | 3 | 0.906547 | 5.939189 | -6.213032 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1497 | 0.9356 | 3 | 0.908636 | 5.539818 | -6.217216 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1498 | 0.9363 | 3 | 0.898894 | 6.153936 | -6.224082 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1499 | 0.9369 | 3 | 0.907490 | 5.856698 | -6.232341 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1500 | 0.9375 | 3 | 0.913808 | 6.567728 | -6.240649 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1501 | 0.9381 | 3 | 0.922514 | 6.624142 | -6.259544 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1502 | 0.9387 | 3 | 0.907688 | 5.808735 | -6.278295 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1503 | 0.9394 | 3 | 0.919367 | 6.465359 | -6.305641 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1504 | 0.9400 | 3 | 0.920099 | 6.526947 | -6.331373 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1505 | 0.9406 | 3 | 0.927817 | 7.421338 | -6.360774 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1506 | 0.9413 | 3 | 0.928545 | 8.437538 | -6.393548 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1507 | 0.9419 | 3 | 0.902386 | 6.204882 | -6.420430 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1508 | 0.9425 | 3 | 0.923993 | 6.786077 | -6.444533 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1509 | 0.9431 | 3 | 0.919801 | 6.553246 | -6.457352 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1510 | 0.9437 | 3 | 0.921803 | 6.615594 | -6.472199 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1511 | 0.9444 | 3 | 0.925004 | 7.170310 | -6.478191 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1512 | 0.9450 | 3 | 0.905564 | 6.420444 | -6.480669 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1513 | 0.9456 | 3 | 0.897894 | 5.869495 | -6.471425 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1514 | 0.9463 | 3 | 0.933066 | 7.607843 | -6.465231 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1515 | 0.9469 | 3 | 0.923439 | 6.751990 | -6.463353 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1516 | 0.9475 | 3 | 0.925066 | 7.330558 | -6.455438 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1517 | 0.9481 | 3 | 0.918668 | 6.579083 | -6.455617 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1518 | 0.9487 | 3 | 0.909436 | 6.596379 | -6.445974 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1519 | 0.9494 | 3 | 0.919355 | 6.596168 | -6.448004 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1520 | 0.9500 | 3 | 0.924505 | 6.784022 | -6.447369 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1521 | 0.9506 | 3 | 0.915205 | 7.034473 | -6.439085 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1522 | 0.9513 | 3 | 0.921929 | 7.147249 | -6.433309 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1523 | 0.9519 | 3 | 0.917739 | 6.480462 | -6.421102 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1524 | 0.9525 | 3 | 0.920645 | 7.268439 | -6.409265 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1525 | 0.9531 | 3 | 0.924978 | 6.762246 | -6.392637 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1526 | 0.9537 | 3 | 0.904161 | 5.662068 | -6.375022 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1527 | 0.9544 | 3 | 0.894575 | 5.943442 | -6.352899 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1528 | 0.9550 | 3 | 0.916485 | 6.450057 | -6.340903 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1529 | 0.9556 | 3 | 0.911471 | 5.815902 | -6.327473 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1530 | 0.9563 | 3 | 0.946163 | 10.300556 | -6.318675 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1531 | 0.9569 | 3 | 0.920140 | 6.708279 | -6.319531 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1532 | 0.9575 | 3 | 0.927131 | 7.644479 | -6.322022 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1533 | 0.9581 | 3 | 0.925872 | 7.906648 | -6.340347 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1534 | 0.9587 | 3 | 0.953359 | 12.854882 | -6.355311 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1535 | 0.9594 | 3 | 0.957625 | 12.035864 | -6.374960 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1536 | 0.9600 | 3 | 0.922788 | 6.549969 | -6.399544 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1537 | 0.9606 | 3 | 0.923255 | 7.345718 | -6.425160 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1538 | 0.9613 | 3 | 0.894928 | 5.283814 | -6.454270 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1539 | 0.9619 | 3 | 0.920838 | 6.585240 | -6.477813 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1540 | 0.9625 | 3 | 0.919923 | 6.684282 | -6.503289 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1541 | 0.9631 | 3 | 0.901987 | 6.327917 | -6.528889 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1542 | 0.9637 | 3 | 0.922220 | 6.678324 | -6.563999 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1543 | 0.9644 | 3 | 0.927326 | 7.721633 | -6.587193 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1544 | 0.9650 | 3 | 0.921554 | 6.909383 | -6.621031 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1545 | 0.9656 | 3 | 0.903761 | 5.837015 | -6.657607 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1546 | 0.9663 | 3 | 0.922861 | 6.750733 | -6.703406 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1547 | 0.9669 | 2 | 0.919500 | 4.979636 | -6.058854 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1548 | 0.9675 | 2 | 0.882183 | 4.165048 | -6.115563 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1549 | 0.9681 | 2 | 0.885237 | 4.722028 | -6.214839 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1550 | 0.9688 | 2 | 0.931076 | 5.733022 | -6.317135 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1551 | 0.9694 | 2 | 0.911807 | 4.571640 | -6.398004 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1552 | 0.9700 | 2 | 0.936105 | 6.147134 | -6.467622 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1553 | 0.9706 | 2 | 0.908700 | 4.738175 | -6.490310 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1554 | 0.9713 | 2 | 0.920402 | 5.178498 | -6.517662 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1555 | 0.9719 | 2 | 0.915520 | 4.679663 | -6.599814 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1556 | 0.9725 | 2 | 0.922605 | 4.647103 | -6.640635 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1557 | 0.9731 | 2 | 0.896298 | 4.618234 | -6.569212 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1558 | 0.9738 | 2 | 0.922466 | 4.843003 | -6.507500 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1559 | 0.9744 | 2 | 0.916682 | 4.588394 | -6.398797 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1560 | 0.9750 | 2 | 0.926053 | 5.174229 | -6.324868 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1561 | 0.9756 | 2 | 0.925270 | 5.638033 | -6.266221 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1562 | 0.9763 | 2 | 0.927981 | 5.450152 | -6.214271 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1563 | 0.9769 | 2 | 0.916252 | 4.483924 | -6.213178 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1564 | 0.9775 | 2 | 0.935426 | 6.449234 | -6.200658 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1565 | 0.9781 | 2 | 0.915920 | 4.498245 | -6.200783 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1566 | 0.9788 | 2 | 0.904817 | 4.479567 | -6.183228 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1567 | 0.9794 | 2 | 0.927257 | 5.067844 | -6.170427 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1568 | 0.9800 | 2 | 0.918439 | 4.788952 | -6.161537 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1569 | 0.9806 | 2 | 0.925997 | 5.402627 | -6.142222 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1570 | 0.9813 | 2 | 0.926460 | 5.615353 | -6.116125 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1571 | 0.9819 | 2 | 0.922461 | 4.803652 | -6.079451 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1572 | 0.9825 | 2 | 0.918388 | 5.038670 | -6.063322 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1573 | 0.9831 | 2 | 0.875533 | 3.609758 | -6.039418 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1574 | 0.9838 | 2 | 0.927343 | 6.587288 | -6.032471 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1575 | 0.9844 | 2 | 0.904182 | 4.309741 | -6.028304 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1576 | 0.9850 | 2 | 0.905156 | 4.712267 | -6.046032 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1577 | 0.9856 | 2 | 0.930125 | 5.372373 | -6.097563 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1578 | 0.9863 | 2 | 0.930499 | 5.708138 | -6.167997 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1579 | 0.9869 | 2 | 0.891846 | 4.428921 | -6.282399 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1580 | 0.9875 | 2 | 0.933033 | 5.432235 | -6.425647 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1581 | 0.9881 | 1 | 0.931325 | 3.440839 | -6.003674 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1582 | 0.9888 | 1 | 0.926261 | 3.442987 | -6.310460 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1583 | 0.9894 | 1 | 0.914095 | 3.372041 | -6.055798 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1584 | 0.9900 | 2 | 0.931821 | 5.991383 | -6.550136 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1585 | 0.9906 | 2 | 0.920986 | 4.753026 | -6.433191 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1586 | 0.9913 | 2 | 0.922951 | 5.393609 | -6.328313 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1587 | 0.9919 | 2 | 0.926422 | 5.207035 | -6.277935 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1588 | 0.9925 | 2 | 0.918221 | 4.913140 | -6.221342 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1589 | 0.9931 | 2 | 0.918656 | 5.623120 | -6.184381 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1590 | 0.9938 | 2 | 0.935746 | 6.446742 | -6.132430 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1591 | 0.9944 | 2 | 0.944267 | 6.625097 | -6.092405 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1592 | 0.9950 | 2 | 0.928395 | 5.372510 | -6.057012 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1593 | 0.9956 | 2 | 0.924128 | 5.034784 | -6.021695 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1594 | 0.9962 | 3 | 0.911149 | 6.033995 | -6.711058 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1595 | 0.9969 | 3 | 0.911349 | 6.372252 | -6.670375 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1596 | 0.9975 | 3 | 0.914314 | 6.562514 | -6.652219 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1597 | 0.9981 | 3 | 0.899058 | 5.619607 | -6.635549 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1598 | 0.9988 | 3 | 0.883084 | 6.469685 | -6.635330 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1599 | 0.9994 | 3 | 0.924483 | 7.161764 | -6.640260 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 1600 | 1.0000 | 3 | 0.928814 | 7.948421 | -6.657385 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "\u001b[34m...Finished\u001b[0m\n", "\u001b[36mFINISHED - Elapsed time = 14865.6099746 seconds\u001b[0m\n", "\u001b[36mFINISHED - CPU process time = 110402.5020766 seconds\u001b[0m\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sharpy.sharpy_main.main(['', pazy_model_open_loop.route + pazy_model_open_loop.case_name + '.sharpy'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Plot wing tip deflection in gust response" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To get a feeling of the gust-induced deformation at the wingtip, we now display the vertical tip deflection." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "scrolled": true }, "outputs": [ { "data": { "image/png": "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xDvvvFMsKCjwcNW+Z8OGDZd8DXDX67cgiuzTIyIiIuXyuTlDRERERB3BMERERESKxjBEREREisYwRERERIrGMERERESKxjBEREREisYwRERERIrGMERERESKxjBERB61ceNGCIKA6upqqUshIgIAcAdqInKbCRMmYMiQIVi2bJnzWlNTEyorKxETEwNBEKQrjoiomZ/UBRCRsvj7+yM2NlbqMoiInDhMRkRucc8992DTpk146623IAgCBEHA6dOnWw2TrVq1CmFhYfj+++/Rt29f6PV63Hrrraivr8eHH36Inj17olu3bnj00UdhtVqd929qasJTTz2FhIQEBAYG4qqrrsLGjRs7VOPGjRtx5ZVXIjAwEGFhYRgzZgzOnDnj/Ph///tfDBs2DDqdDr1798aSJUtgsVicH6+ursaDDz6ImJgY6HQ6pKen4/vvv+/S142IPI89Q0TkFm+99Rays7ORnp6OF154AQAQFRWF06dPt3qs0WjE22+/jTVr1sBgMGDmzJmYOXMmwsLCsHbtWuTm5uKWW27B1Vdfjdtvvx0AcO+99+L06dNYs2YN4uPj8fXXX2Pq1Kk4fPgwUlJSLlufxWLBzTffjAceeACffvopmpqasGvXLufQ3fr163HXXXfh7bffxtixY3Hy5Ek8+OCDAIBFixbBZrNh2rRpMBgM+Pjjj5GcnIzMzEyo1WoXfQWJyGM6fM49EVE7jR8/Xpw/f36Laxs2bBABiFVVVaIoiuLKlStFAOKJEyecj5k7d66o1+tFg8HgvHbdddeJc+fOFUVRFE+cOCEKgiAWFha2uPe1114rLly4sF21VVRUiADEjRs3tvnxsWPHii+//HKLax999JEYFxcniqIorl+/XlSpVGJWVla7no+I5Is9Q0QkOb1ej+TkZOf7MTEx6NmzJ4KCglpcKysrAwDs27cPoigiNTW1xX1MJhMiIiLa9Zzh4eG45557cN1112Hy5MmYNGkSZs2ahbi4OADA3r17sXv3brz00kvOz7FarWhsbITRaMSBAweQmJjYqgYi8j4MQ0QkOY1G0+J9QRDavGaz2QAANpsNarUae/fubTUsdX6AupyVK1fisccew7p16/DZZ5/h+eefR0ZGBkaOHAmbzYYlS5Zg5syZrT5Pp9MhICCg3c9DRPLGMEREbuPv799i0rOrDB06FFarFWVlZRg7dmyX7zV06FAsXLgQo0aNwurVqzFy5EhcccUVyMrKQp8+fdr8vEGDBqGgoADZ2dnsHSLycgxDROQ2PXv2xM6dO3H69GkEBQUhPDzcJfdNTU3FnXfeibvvvht/+9vfMHToUJSXl+N///sfBg4ciOnTp1/2HqdOncI///lP3HjjjYiPj0dWVhays7Nx9913AwD+/Oc/4/rrr0dSUhJuu+02qFQqHDp0CIcPH8aLL76I8ePHY9y4cbjlllvwxhtvoE+fPjh+/DgEQcDUqVNd8u8kIs/g0noicpsnn3wSarUaAwYMQFRUFPLy8lx275UrV+Luu+/GH/7wB/Tt2xc33ngjdu7ciaSkJOdjBEHAqlWr2vx8vV6P48eP45ZbbkFqaioefPBBPPLII5g7dy4A4LrrrsP333+PjIwMjBgxAiNHjsQbb7yBHj16OO/x5ZdfYsSIEbjjjjswYMAAPPXUU27pCSMi9+IO1ETkk06fPo2UlBRkZma2a6k9ESkXe4aIyCetW7cODz74IIMQEV0We4aIiIhI0dgzRERERIrGMERERESKxjBEREREisYwRERERIrGMERERESKxjBEREREisYwRERERIrGMERERESKxjBEREREivb/AaUnMPvDR2g6AAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def get_resulting_vertical_tip_displacement(output_folder, model): \n", " file_results = os.path.join(output_folder, \n", " model.case_name, \n", " 'WriteVariablesTime',\n", " 'struct_pos_node{}.dat'.format(model.num_node_surf))\n", " vertical_tip_displacement = np.loadtxt(file_results)[:,-1]\n", " \n", " \n", " return vertical_tip_displacement\n", "\n", "\n", "tip_displacement_open_loop = get_resulting_vertical_tip_displacement(output_folder, \n", " pazy_model_open_loop)\n", "\n", "\n", "time_array = np.arange(0, len(tip_displacement_open_loop) * pazy_model_open_loop.dt, pazy_model_open_loop.dt)\n", "normalised_tip_displacement = tip_displacement_open_loop/ (0.5*pazy_model_open_loop.b_ref) #normalise by half wing span\n", "normalised_tip_displacement *=100# cconvert to percentnvert to percent\n", "plt.plot(time_array, normalised_tip_displacement)\n", "plt.xlabel('time, sec')\n", "plt.ylabel('$z_{tip}/(b_{ref}/2)$, %')\n", "plt.xlim([0., simulation_time])\n", "plt.grid()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Generate linear ROM\n", "\n", "We continue with the linearization of the nonlinear aeroelastic model of the Pazy wing at the previously defined operational point and a subsequent reduction of the resulting linear state-space system. The structural and aerodynamic models are linearized and reduced separately and finally couple both resulting linear systems. Please see [2] for more details. \n", "\n", "The generation of the linear model for the coarse discretisation used, just takes a few seconds, as well as the execution of the linear response computation using Matlab. No need to skip anything here to save time. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Define Simulation Settings\n", "We can re-use the simulation settings from the open-loop case with a few exceptions. Besides the name, of course, we need to extend the simulation `flow` list by the solvers needed for the linearization and export of the resulting linear state-space system. Further, we need to change the simulation time as we need to run only on the simulation timestep of the `DynamicCoupled` solver before the linearization. Also, no gust encounter is needed as we linearise the model around its equilibrium during steady-flight conditions." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "case_name = 'pazy_ROM'\n", "\n", "flow = ['BeamLoader',\n", " 'AerogridLoader',\n", " 'StaticCoupled',\n", " 'DynamicCoupled',\n", " 'Modal',\n", " 'LinearAssembler',\n", " 'SaveData',\n", " ]\n", "\n", "simulation_time_ROM = pazy_model_open_loop.dt # only one timestep has to be performed\n", "gust = False" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We further need to define the method used to reduce the linearized aerodynamic model. Here, we are going to apply a Krylov-based model order reduction scheme for the aerodynamic system, see [4] for more information. Please note that this is only one of the reduction methods implemented in SHARPy. We also offer various reduction schemes based on balancing methods." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "use_rom = True\n", "rom_settings = {\n", " 'use': use_rom,\n", " 'rom_method': 'Krylov',\n", " 'rom_method_settings': {'Krylov': {\n", " 'algorithm': 'mimo_rational_arnoldi',\n", " 'r': 4, \n", " 'frequency': np.array([0]),\n", " 'single_side': 'observability',\n", " },\n", " }\n", " }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The linearised structural model is reduced by using modal projection and truncating non-dominant modes. Here, we keep the first 20 modes." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "num_modes = 20\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Further, we have deactivated the unsteady force contribution before generating the linear model and indicate that for the resulting linear system, we would like to retain the gust input for our preliminary control design study." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "unsteady_force_contribution = False\n", "remove_gust_input_in_statespace = False" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Generate Input Files\n", "As before, we generate all necessary input files for the simulation." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "pazy_model_ROM = wings.PazyControlSurface(M=number_chordwise_panels,\n", " N=number_spanwise_nodes,\n", " Mstar_fact=wake_length_factor,\n", " u_inf=u_inf,\n", " alpha=alpha_deg,\n", " rho=rho,\n", " n_surfaces=2,\n", " route=cases_folder + '/' + case_name,\n", " case_name=case_name,\n", " physical_time=simulation_time_ROM,)\n", "\n", "generate_aero_and_fem_input_files(pazy_model_ROM)\n" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "pazy_model_ROM.set_default_config_dict()\n", "pazy_model_ROM.config = get_settings_udp(pazy_model_ROM,\n", " flow,\n", " num_cores=num_cores,\n", " wake_length_factor=wake_length_factor,\n", " output_folder = output_folder,\n", " gust=gust,\n", " gust_settings=gust_settings,\n", " num_modes = num_modes,\n", " rom_settings = rom_settings,\n", " remove_gust_input_in_statespace=remove_gust_input_in_statespace,\n", " unsteady_force_distribution=unsteady_force_contribution)\n", "pazy_model_ROM.config.write()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Run SHARPy Simulation"] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sharpy.sharpy_main.main(['', pazy_model_ROM.route + pazy_model_ROM.case_name + '.sharpy'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Prepare Linear System for specific Controller\n", "An overview of the generated linear ROM can be found near the end of the log file, including the number of states, inputs, and outputs as well as the indices mapped to each parameter. The saved linear ROM contains several unused input and output variables. The final simulation is expected to take 8 hours. Again, the results are uploaded *here* and it is still recommended to check if everything is running properly instead of skipping the entire simulation.\n", "\n", "We start with reading the matrices from the h5 file and get rid of all unused input and output variables that are not needed for the control design. More precisely, we keep the gust and control surface inputs as well as the vertical tip displacements as an output. \n", "\n", "The system input includes the control surface deflection and its rate but does not know that they are related. For small timesteps, as for this case, we can assume a linear relationship through numerical gradients $\\dot{\\delta} = \\frac{\\delta - \\delta^-}{dt}$ with *-* indicating the previous timestep.\n", "\n", "Subsequently, we simulate the open-loop and closed-loop responses for different proportional gains of the P-controller.\n", "\n", "But first, we save some parameters needed for this to a json file which is later loaded in the control script to avoid making changes in different files. " ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "folder_linear_results =os.path.join(output_folder, pazy_model_ROM.case_name,'linear_results')\n", "if not os.path.exists(folder_linear_results):\n", " os.makedirs(folder_linear_results)\n", " \n", "import scipy.io\n", "\n", "parameter_matlab = {'num_aero_states': 80, # see log file\n", " 'num_modes': 20, # see log file\n", " 'u_inf': float(pazy_model_ROM.u_inf),\n", " 'simulation_time': simulation_time,\n", " 'gust_length': float(gust_settings['gust_length']),\n", " 'gust_intensity': float(gust_settings['gust_intensity']),\n", " 'num_control_surfaces': num_control_surfaces,\n", " 'n_nodes': number_spanwise_nodes,\n", " 'control_input_start': 193 + 1, # see log file and + 1 for matlab indices\n", " 'gust_input_start': 192 + 1 , # see log file and + 1 for matlab indices\n", " }\n", "\n", "scipy.io.savemat(os.path.join(folder_linear_results, 'simulation_parameters.mat'), parameter_matlab)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, the matlab script *control_design_script.m* has to be executed which runs the linear open-loop gust response of this problem as well as two linear closed-loop gust responses using a P-controller with two different gain values. This P-controller is using the deviation of the vertical tip displacement to its reference condition as an error function. Finally, the script is saving the results as txt files in your output folder. \n", "\n", "Here, we use the MATLAB Engine API for Python to run the script:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "eng = matlab.engine.start_matlab()\n", "eng.control_design_script(route_notebook_dir)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Clean Up Simulink Files\n", "import shutil\n", "folder_path = os.path.join(route_notebook_dir, 'slprj')\n", "if os.path.exists(folder_path):\n", " shutil.rmtree(folder_path)\n", "\n", "file_path = os.path.join(route_notebook_dir, 'PID_linear_model.slxc')\n", "if os.path.exists(file_path):\n", " os.remove(file_path)\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Postprocessing\n", "Now, let us have a look at the linear results generated:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Load linear results\n", "tip_deflection_linear = np.loadtxt(folder_linear_results + '/tip_deflection.txt', delimiter = ',')\n", "time_array_linear = np.loadtxt(folder_linear_results + '/time_array_linear.txt', delimiter = ',')\n", "normalised_tip_deflection_linear = tip_deflection_linear / (0.5 *pazy_model_ROM.b_ref) * 100\n", "control_surface_deflection_deg = np.loadtxt(folder_linear_results + '/deflection.txt', delimiter = ',')\n", "control_surface_deflection_rate_deg = np.loadtxt(folder_linear_results + '/deflection_rate.txt', delimiter = ',')\n", "\n", "# Plot Data\n", "fig, axs = plt.subplots(3, 1)\n", "list_linestyles = ['-', '-', '--', ':']\n", "list_labels = ['linear P=5', 'linear P=10', 'linear open-loop', 'nonlinear open-loop']\n", "for i in range(3):\n", " axs[0].plot(time_array_linear, normalised_tip_deflection_linear[:,i], list_linestyles[i], label=list_labels[i])\n", "\n", "axs[0].plot(time_array, normalised_tip_displacement, list_linestyles[-1], label=list_labels[-1])\n", "axs[0].legend(loc ='upper right')\n", "axs[0].set(ylabel='$z_{tip}/(b_{ref}/2)$, %')\n", "\n", "for i in range(3):\n", " axs[1].plot(time_array_linear, control_surface_deflection_deg[:,i], list_linestyles[i])\n", " \n", "axs[1].set(ylabel='$\\delta$, deg')\n", "for i in range(3):\n", " axs[2].plot(time_array_linear, control_surface_deflection_rate_deg[:,i], list_linestyles[i])\n", " \n", "axs[2].set(ylabel='$\\dot{\\delta}$, deg/sec')\n", "for ax in axs.flat:\n", " ax.set(xlabel='time, sec')\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see here the slightly higher peak tip deflection in the nonlinear response. Further, we see a significant reduction in tip deflection by the P-controller. Since we only have slightly higher aileron deflection and rates for the higher gain of P=10, we chose this gain value for the final control design applied to the nonlinear aeroelastic gust response simulation." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Closed-Loop Nonlinear Gust Response\n", "Now, we need a similar simulation setup as the open-loop nonlinear response and activate the gust again." ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "case_name = 'pazy_udp_closed_loop_gust_response'\n", "gust = True\n", "flow = ['BeamLoader',\n", " 'AerogridLoader',\n", " 'StaticCoupled',\n", " 'DynamicCoupled',\n", " ]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, we need to inform SHARPy that our aileron will receive deflection values from the network which is done by setting the control surface type of both ailerons to `2`. The default value `0` would indicate a static deflection, and `1` that the control surface receives deflection inputs from the `dynamiccontrolsurface` generator. For the sake of completeness, we set the initial deflection of the ailerons to zero. " ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "initial_cs_deflection = [0, 0]\n", "cs_type = 2 " ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "pazy_model_closed = wings.PazyControlSurface(M=number_chordwise_panels,\n", " N=number_spanwise_nodes,\n", " Mstar_fact=wake_length_factor,\n", " u_inf=u_inf,\n", " alpha=alpha_deg,\n", " cs_deflection=initial_cs_deflection,\n", " rho=rho,\n", " n_surfaces=2,\n", " route=cases_folder + '/' + case_name,\n", " case_name=case_name,\n", " physical_time=simulation_time,\n", " cs_type=cs_type)\n", "generate_aero_and_fem_input_files(pazy_model_closed)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we need to define our network settings. Therefore, we need to define the IP addresses for the server (SHARPy) and client (on whatever machine you run your computer), and the ports used. Another important input is the `variables_filename` in which we define the in and output parameters. Have a look in the file and make sure to change the position of your output node in case you make the beam discretisation finer. As we only need to measure the vertical tip displacement for our controller to work, we only have one sensor. We just save this parameter here as it is important later for the client to know, how much data it should receive from SHARPy." ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [], "source": [ "# Network settings for nonlinear in closed-loop simulations \n", "server_ip_addr = '127.0.0.1' # SHARPy\n", "client_ip_addr = '127.0.0.1' # controller\n", "port_in_network = 64017\n", "port_out_network_server = 59017\n", "port_out_network_client = 59007 \n", "num_sensors = 1 \n", "network_settings = {'variables_filename': route_notebook_dir + '/pazy_network_info.yml', \n", " 'send_output_to_all_clients': False,\n", " 'byte_ordering': 'little',\n", " 'log_name': output_folder +'/'+ case_name + '/sharpy_network.log',\n", " 'file_log_level': 'debug',\n", " 'console_log_level': 'error',\n", " 'input_network_settings': {'address': server_ip_addr,\n", " 'port': port_in_network,\n", " },\n", " 'output_network_settings': {'send_on_demand': False,\n", " 'port': port_out_network_server,\n", " 'address':server_ip_addr, \n", " 'destination_address': [client_ip_addr],\n", " 'destination_ports': [port_out_network_client],\n", " }\n", " }\n", "\n" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [], "source": [ "pazy_model_closed.set_default_config_dict()\n", "pazy_model_closed.config = get_settings_udp(pazy_model_closed,\n", " flow,\n", " network_settings=network_settings,\n", " num_cores=num_cores,\n", " wake_length_factor=wake_length_factor,\n", " output_folder = output_folder,\n", " gust=gust,\n", " gust_settings=gust_settings,\n", " write_screen = False)\n", "pazy_model_closed.config.write()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As before, we save some essential parameters to a json file that is loaded from the client. This is done to avoid the need of changing parameters in different files." ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [], "source": [ "\n", "dict_parameters = {\"server_ip_addr\": server_ip_addr,\n", " \"client_ip_addr\": client_ip_addr,\n", " \"port_in_network\": port_in_network,\n", " \"port_out_network_server\": port_out_network_server,\n", " \"port_out_network_client\": port_out_network_client,\n", " \"output_folder\": os.path.abspath(output_folder),\n", " \"dt\": pazy_model_closed.dt,\n", " \"simulation_time\": simulation_time,\n", " \"num_sensors\": num_sensors,\n", " \"initial_cs_deflection\": initial_cs_deflection[0],\n", " \"reference_deflection\": tip_displacement_open_loop[0],\n", " }\n", "with open('./parameter_UDP_control_{}.json'.format(pazy_model_closed.case_name), 'w') as fp:\n", " json.dump(dict_parameters, fp)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let us start the nonlinear gust-response simulation. Within the `DynamicCoupled` solver, the network expects an input before the actual simulation start. Hence, let the script run until you enter this solver (< 1 min) and start the controller by running *pazy_PID_controller_UDP.py* located in the notebook directory. You can double-check the *sharpy_network.log* in the output folder of this case. If the network is waiting for inputs, you are good to run the client script from your terminal using Python. \n", "\n", "Note that the log file is not written here on the screen as it causes errors as the amount of output characters exceeds the available buffer size of the jupyter notebook. " ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "scrolled": true }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "fatal: not a git repository (or any of the parent directories): .git\n" ] } ], "source": [ "sharpy.sharpy_main.main(['', pazy_model_closed.route + pazy_model_closed.case_name + '.sharpy']);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Final Postprocessing" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [], "source": [ "control_surface_input = np.loadtxt(os.path.join(output_folder, pazy_model_closed.case_name, \"pazy_udp_closed_loop_gust_response_control_input\"))\n", "delta_dot = np.diff(np.rad2deg(control_surface_input))/pazy_model_closed.dt\n", "tip_displacement_nonlinear_P10 = np.loadtxt(os.path.join(output_folder, pazy_model_closed.case_name, \"pazy_udp_closed_loop_gust_response_sensor_measurement\"))\n", "time_array_nonlinear_closed_loop = np.arange(0, len(tip_displacement_nonlinear_P10) * pazy_model_closed.dt, pazy_model_closed.dt)\n", "tip_displacement_nonlinear_P10 /= pazy_model_closed.b_ref/2\n", "tip_displacement_nonlinear_P10 *= 100" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "image/png": "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fPLkSb755ht+/vln1qxZw759+3jyySft6xcvXsy0adOYPXs2R44cYc6cObz88sssXbq0xH6mTZvGc889x759+2jcuDEjRozAbDaXut7efvtt3nzzTd544w0OHDhA3759GTx4MCdOnACgoKCAfv364efnx86dO1mxYgUbNmzgqaeeKrGf3377jSNHjrBx40a++uorfvjhB2bOnFnqOMpFdaKePXs68/Bllp2drQJqdna2s0NxiKTcJLXLsi5q+y/aqwfPHiz9hpmnVXX186r62R2q+nqkqk73Lvn44YmLZS0WVV33iqruX66qWfE3/k0IUUMVFhaq0dHRamFh4WXropZEqVFLotSMwgz7skX7F6lRS6LU6dumlyjb8X8d1aglUWpCboJ92eeHP1ejlkSpUzZNKVG2+1fd1aglUeqJzBP2ZSuOrShz7D179lRvvvnmknF07Kg+//zzqqqq6rp161StVqvGxcXZ1x8+fFgF1B07dqiqqqrTp09X3d3d1ZycHHuZyZMnq507dy5xnAkTJthf16tXT33rrbfsrwH1pZdesr/Oy8tTFUVRf/31V1VVVfXll19W+/TpUyLO+Ph4FVCPHTt2xfeWlpamAurBg7b/qbGxsSqgLliw4Jp1Utr3rNVq1fj4i/9Lf/31V1Wj0ajJycmqqqpqeHi4+uWXX5bY9//93/+pXbt2LRHPxx9/fNlxjhw5ctX4pk+frrZu3dr+OjQ0VJ09e3aJMh07dlSfeML2//+jjz5S/fz81Ly8PPv6X375RdVoNGpKSoqqqqo6evRo1d/fX83Pz7eX+eCDD1RPT0/VYrFcFsO1PvNlOX879VJa586dWbhwoTNDENegKAotAlvQ1L8pTf2vcYnTVAjZl0z+q9HC3x/A6S1QcKFJWQGdG2gN4B1ysey5WNi2AL5/FN5qAe92gNVT4PRWsFoq4m0JIaqAVq1alXgdEhJCWprtjtgjR44QHh5OeHi4fX3z5s3x9fXlyJEj9mURERF4eXldcR/licPDwwMvLy/7Pnbv3s3GjRvx9PS0Py50B7lwuSwmJob777+fyMhIvL29qV/f1tocFxdX4jgdOnS4Zhylfc9169alTp069tddu3bFarVy7Ngxzp49S3x8PGPHji0R86xZsy6bgeLS9x0SYvuffeF9X7rtuHHjLos1JyeHpKQkbrrpphLLb7rpJnus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jjvucETINYazzoO6CtuzsEtPT8disRAUFFRieVBQECkpKVfcJiUl5YrlzWYz6enphISEVFi8VVV56vmf3nzzTfLz8xk2bFhFhFgtlKeeT5w4wdSpU9myZQs6nfzbKY3y1POpU6fYunUrrq6u/PDDD6Snp/PEE0+QmZkp/Yyuojz13K1bN5YtW8Z9991HUVERZrOZwYMH8+677zoi5BrDWedBaTFyIEVRSrxWVfWyZdcrf6XloqSy1vMFX331FTNmzGD58uXUrl27osKrNkpbzxaLhfvvv5+ZM2fSuHFjR4VXbZTl82y1WlEUhWXLltGpUycGDBjA/PnzWbJkibQaXUdZ6jk6Oprx48fzyiuvsHv3btasWUNsbCzjxo1zRKg1ijPOg/LVzQECAwPRarWXfftIS0u7LBu+IDg4+IrldTodAQEBFRZrVVaeer5g+fLljB07lhUrVtC7d++KDLPKK2s95+bmsmvXLvbu3ctTTz0F2E7gqqqi0+lYt24dt956q0Nir0rK83kOCQkhLCwMHx8f+7JmzZqhqioJCQk0atSoQmOuispTz6+99ho33XQTkydPBqBVq1Z4eHjQvXt3Zs2aJS36N4izzoPSYuQALi4utG/fnvXr15dYvn79erp163bFbbp27XpZ+XXr1tGhQwf0en2FxVqVlaeewdZSNGbMGL788kvpI1AKZa1nb29vDh48yL59++yPcePG0aRJE/bt20fnzp0dFXqVUp7P80033URSUhJ5eXn2ZcePH0ej0VCnTp0KjbeqKk89FxQUoNGUPH1qtVrgYouG+Pecdh6s0K7dwu7C7aCffPKJGh0drU6cOFH18PBQT58+raqqqk6dOlUdNWqUvfyF2xSfeeYZNTo6Wv3kk0/kdv1SKGs9f/nll6pOp1Pfe+89NTk52f7Iyspy1luoEspaz/8kd6WVTlnrOTc3V61Tp456zz33qIcPH1Y3bdqkNmrUSH3kkUec9RaqhLLW82effabqdDr1/fffV2NiYtStW7eqHTp0UDt16uSst1Al5Obmqnv37lX37t2rAur8+fPVvXv32odFqCznQbmUVgZWq5WkpCS8vLzKfH2zf//+vPbaa8yYMYOUlBSaN2/OihUr8PPzIycnhzNnzhAXF0dOTg4AAQEBrFixghdeeIGFCxcSEhLC66+/zu23324vIy5X1np+7733MJvN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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Plot Data\n", "fig, axs = plt.subplots(3, 1)\n", "list_linestyles = ['-', '--', ':']\n", "list_labels = ['linear P=10', 'nonlinear P=10', 'nonlinear open-loop']\n", "axs[0].plot(time_array_linear, normalised_tip_deflection_linear[:,1], list_linestyles[0], label=list_labels[0])\n", "axs[0].plot(time_array_nonlinear_closed_loop, \n", " tip_displacement_nonlinear_P10, \n", " list_linestyles[1], \n", " label=list_labels[1])\n", "axs[0].plot(time_array, normalised_tip_displacement, list_linestyles[-1], label=list_labels[-1])\n", "\n", "axs[0].legend()\n", "axs[0].set(ylabel='$z_{tip}/(b_{ref}/2)$, %')\n", "\n", "\n", "axs[1].plot(time_array_linear, control_surface_deflection_deg[:,1], list_linestyles[0])\n", "axs[1].plot(time_array_nonlinear_closed_loop, np.rad2deg(control_surface_input), list_linestyles[1])\n", " \n", "axs[1].set(ylabel='$\\delta$, deg')\n", "\n", "axs[2].plot(time_array_linear, control_surface_deflection_rate_deg[:,0], list_linestyles[0])\n", "axs[2].plot(time_array_nonlinear_closed_loop[1:], \n", " delta_dot, \n", " list_linestyles[1])\n", " \n", "axs[2].set(ylabel='$\\dot{\\delta}$, deg/sec')\n", "for ax in axs.flat:\n", " ax.set(xlabel='time, sec')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.12" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: docs/source/content/example_notebooks/cantilever/model_static_cantilever.py ================================================ import h5py as h5 import numpy as np import os def clean_test_files(route, case_name): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) def generate_fem_file(route, case_name, num_elem, deadforce=600e3, followerforce=0): length = 5 num_node_elem=3 num_node = (num_node_elem - 1)*num_elem + 1 angle = 0. * np.pi / 180. # Angle of the beam reference line within the x-y plane of the B frame. x = (np.linspace(0, length, num_node))*np.cos(angle) y = (np.linspace(0, length, num_node))*np.sin(angle) z = np.zeros((num_node,)) structural_twist = np.zeros((num_elem, num_node_elem)) frame_of_reference_delta = np.zeros((num_elem, num_node_elem, 3)) for ielem in range(num_elem): for inode in range(num_node_elem): frame_of_reference_delta[ielem, inode, :] = [-np.sin(angle), np.cos(angle), 0] scale = 1 x *= scale y *= scale z *= scale conn = np.zeros((num_elem, num_node_elem), dtype=int) for ielem in range(num_elem): conn[ielem, :] = (np.ones((3,)) * ielem * (num_node_elem - 1) + [0, 2, 1]) # stiffness array num_stiffness = 1 ea = 4.8e8 ga = 3.231e8 gj = 1.0e6 ei = 9.346e6 base_stiffness = np.diag([ea, ga, ga, gj, ei, ei]) stiffness = np.zeros((num_stiffness, 6, 6)) for i in range(num_stiffness): stiffness[i, :, :] = base_stiffness # element stiffness elem_stiffness = np.zeros((num_elem,), dtype=int) # mass array num_mass = 1 m_bar = 0. # Mass is made zero for the static analysis. j = 10 base_mass = np.diag([m_bar, m_bar, m_bar, j, j, j]) mass = np.zeros((num_mass, 6, 6)) for i in range(num_mass): mass[i, :, :] = base_mass # element masses elem_mass = np.zeros((num_elem,), dtype=int) # bocos boundary_conditions = np.zeros((num_node, 1), dtype=int) boundary_conditions[0] = 1 # Clamped at s=0 boundary_conditions[-1] = -1 # Free end at s=L # beam number beam_number = np.zeros((num_elem, 1), dtype=int) # Applied follower forces. app_forces = np.zeros((num_node, 6)) app_forces[-1, 2] = followerforce # Lmmped masses input -- Dead force is applied by a mass at the tip. n_lumped_mass = 1 lumped_mass_nodes = np.array([num_node - 1], dtype=int) lumped_mass = np.zeros((n_lumped_mass, )) lumped_mass[0] = deadforce/9.81 lumped_mass_inertia = np.zeros((n_lumped_mass, 3, 3)) lumped_mass_position = np.zeros((n_lumped_mass, 3)) #Store in h5 format. with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data = np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data = conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data = num_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data = num_node) num_elem_handle = h5file.create_dataset( 'num_elem', data = num_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data = stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data = elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data = mass) mass_handle = h5file.create_dataset( 'elem_mass', data = elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) lumped_mass_nodes_handle = h5file.create_dataset( 'lumped_mass_nodes', data=lumped_mass_nodes) lumped_mass_handle = h5file.create_dataset( 'lumped_mass', data=lumped_mass) lumped_mass_inertia_handle = h5file.create_dataset( 'lumped_mass_inertia', data=lumped_mass_inertia) lumped_mass_position_handle = h5file.create_dataset( 'lumped_mass_position', data=lumped_mass_position) return num_node, coordinates # Solver options def generate_solver_file (route, case_name): file_name = route + '/' + case_name + '.sharpy' import configobj config = configobj.ConfigObj() config.filename = file_name config['SHARPy'] = {'case': case_name, 'route': route, 'flow': ['BeamLoader', 'NonLinearStatic'], 'write_screen': 'off', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} config['BeamLoader'] = {'unsteady': 'off'} config['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 99, # Default 99 'num_load_steps': 10, # Default 10 'delta_curved': 1e-5, 'min_delta': 1e-8, # Default 1e-8 'gravity_on': 'on', 'gravity': 9.81} config.write() # eof ================================================ FILE: docs/source/content/example_notebooks/cantilever/static_cantilever.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# A prismatic cantilever beam under a tip load\n", "\n", "This is an example of the geometrically-exact beam structural solver in SHARPy.\n", "\n", "Reference:\n", "Simpson R.J.S., Palacios R., “Numerical Aspects of Nonlinear Flexible Aircraft Flight Dynamics Modeling.” 54th AIAA/ASME/ASCE/AHS/ASC Structures, Structural Dynamics and Materials Conference, 8-11 April 2013, Boston, Massachusetts, USA [http://hdl.handle.net/10044/1/11077, http://dx.doi.org/10.2514/6.2013-1634]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Required Packages" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import os\n", "import matplotlib.pyplot as plt\n", "import sharpy.sharpy_main # used to run SHARPy from Jupyter\n", "import model_static_cantilever as model # model definition\n", "from IPython.display import Image\n", "\n", "plt.rcParams.update({'font.size': 20}) # Large fonts in all plots" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Problem 1: Tip vertical dead load\n", "Consider first a massless beam with a heavy tip mass, such that the deformations are due to the resulting dead load P. The static equilibrium will be obtained for multiple values of P=0, 100, ..., 1000 kN" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "execution_count": 2, "metadata": { "image/png": { "width": 500 } }, "output_type": "execute_result" } ], "source": [ "Image('images/cantilever.png', width=500)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# Define temporary files to generate sharpy models.\n", "case_name= 'temp'\n", "route = './'\n", "\n", "Nforces=10 # Number of force steps\n", "DeltaForce=100e3 # Increment of forces\n", "Nelem=20 # Number of beam elements\n", "\n", "N=2*Nelem+1\n", "x1=np.zeros((Nforces,N))\n", "z1=np.zeros((Nforces,N))\n", "\n", "#Loop through all external forces\n", "for jForce in range(Nforces):\n", " model.clean_test_files(route, case_name)\n", " model.generate_fem_file(route, case_name, Nelem, float(jForce+1)*DeltaForce)\n", " model.generate_solver_file(route,case_name)\n", "\n", " case_data=sharpy.sharpy_main.main(['', route + case_name + '.sharpy'])\n", "\n", " x1[jForce,0:N]=case_data.structure.timestep_info[0].pos[:, 0]\n", " z1[jForce,0:N]=case_data.structure.timestep_info[0].pos[:, 2]\n", "\n", "#Store initial geometry\n", "x0=case_data.structure.ini_info.pos[:, 0]\n", "z0=case_data.structure.ini_info.pos[:, 2]" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "image/png": "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0+l05OzsTH5+fuTr60t+fn7k5+dHQUFBkkJ02LBhNHPmTMn6g4ODKTEx0ehzX19f2r59O6WlpVFaWho9ePCA0tLSaNasWZLnyczMpEWLFhkIZCWys7Np9OjRtGfPHnJzczPafv31V1q0aJF4vxMTEykiIoKISFaMql24MgzDMAxjHtVeiD7kdSL6hYiiBEHoQUQXiagDEYUR0WUiKj26uvjw39LumJlE1I2IJgmC0JqIThBRUyLqT3pv6RuPovEMU1P47LPPFIVnaf744w9q3ry5gRgtFqFERJ6entS9e3fq3r07EVXuMjTF5OXlEQCTvK+XL18W/y4WiK6urvTdd99JHg+AJk+eLB7foEEDatCgAVlbW9Ovv/4qHjdlyhRaunQpEek9zyNGjKBbt27R7du36dixY3Tr1i1Zkbxu3Tr6+++/qV69elSvXj0KCAgQ/3366adp7dq1RmUGDBgguXRNdHS0pHANCgqi69evU2ZmJqWmplJqaiqlpaVRamoqDRw4ULJdubm5dPz4cbp//z6lpaWVKdyzs7Pptddeo82bN5O7uzu5u7uTh4cHubu704ULFyg6OlrMzmmKcCVi8cowDMMwaqZGCNGHXtF2RPQBET1DRM+RPiQ3iojeB3DfxPPcEwThSSKaS0QDiKgrEd0joo1E9C6Avx9F+xmmNlMsOk2hR48e9MMPPxh93q1bN5o/fz4dPnyY9u/fT7/99hs9ePDApHMWFRWJS7yUhUajIR8fH3JzcyOtVksPHjygP//8U7HM9OnTqaioiO7evUvXr1+nAwcO0I0bNwyOiYqKMriW0NBQeumll6hJkybk6upKAEij0Uiev6CggJKSkujIkSN0/75Jjzravn07jRw5koKCgsjLy0u89ueee04yvPq5554jQRCoTp06VKdOHQoMDBT3BQUFyYrXK1euEJH+Hj948IDu379P9+/fpw4dOki2Kz8/n5KTk+mPP/6ge/fuKYZJZ2dn08iRIykqKoo8PDzIw8ODPD09xb8vXLhAn332mVnitaRwXblyJd28eZOFK8MwDMM8ImqEECUiAnCDiF418VjZEedD0Trh4cYwjIr4/vvvqWfPngZitEePHvT9998TEVHnzp1pxowZBICSkpIoODjYpPMWFhaSlVXZj0NPT0+ysbGh5ORkSklJMencixYtIiL9XM4WLVpQ3759jcRe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AFcAfFng/FwAt4UQW4UQi3OOg8hew+oIYBoRnbDgW2GMMcaKBVdXV+zbtw/lypVDx44d8ddff+lVTgiBJUuWIDg4GF9++SW++OILo+5fr149/PLLL3ByckLLli1RsWJFg/YqzU9qCrEQAg8fPsSaNWuMap+x90xMTER4eLgiRuU0Gg1q1aqldd6SAbq1SH3+zs7OGDx4MG7duoU2bdqgffv2OH/+fBG1kLHiweYDUQDtkb1faRCAejnnWuQ710rPetYDOA2gMYAhAEYAqAlgC4C3iMi4fw0ZY4yxEqhSpUr48ccfQURo2bIlqlatqlcwKITAihUrEBQUhOnTp6NPnz6oVq0aYmJiDAoka9WqhY8//hipqam4f/++0esYpdaihoWF4Z133sFHH32Ezz777IUtQcxB6p6hoaFo06YNhg4diqCgIDx9KrubnFWEhIRobTEjhEBCQgIGDx6MR48eFVHLTCf1+a9atQqrVq3CX3/9hfnz5+PMmTNo2LAh+vTpg4ULF6JatWpG/7GDsRJLLosRH6YdnDU3G2cLUx7uE+XhPlEe7hPzmTlzplEZSJ8/f07NmjXLK7NgwQKDs5cam9FXHxkZGTRkyBACQL1796bU1FST6yzM8+fPaebMmSSEoLp169KVK1csfk9dCmbNXbNmDU2aNIlUKhV5enrS7t27i7R9lpScnEyfffYZOTo6KibDLv/cUh7uEwtkzWWMMcYY04fU9FV9ktqo1WrcuXMn7/9PnDihd9lccusVzbGO0d7eHmFhYZg3bx42b96Md955BytWrLDoyJharcb06dPx448/IiEhAT4+Phg5cmSRjca5u7u/kCxq4MCBmDt3Lk6dOgV3d3d07doVgYGBSExMxIYNG4rVqGHZsmUREhKCl19+WetacUnaxJil2RV1AxhjjDFWfJkSDObPWLtjxw6DygLZ6xjj4uK0zru7u+tVvjBCCHzyySeoXr06+vbti5MnT+at38ydBgxkT/U0p/feew/nz59H27ZtsXz58rzzlrynIRo3boyYmJi8db7fffcd0tLSkJGRoah2mkP+P5bkVxySNjFmaQaNiAoh1gghvhZCyP4EF0L4CSEss3qfMcYYYzZFLgOprsykUq959dVXDSoLSCedUalUSEpKQkREhF516KNnz57w8PCw6t6flStXRlpamtZ5pYzGOTg4YMaMGYiJ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YcIEOnfuzPTp0xNe+TXeditnnXVWteqrjHSE32jR+40OGTKEWbNmUVxczFNPPUXXrl2ZOHGitngRqUGyOogCU4DTCQLkGmCUmY2O+uge9Z7zgI+AP5VvdPd/A5cTLHi02MymmdkdwPvAzwiC6tNJ/WpEREQkpcaNG0fPnj0ZOnQoLVq0qFbIqVWrFuvXr6/QXp3hrdFPB1u3bk379u3585//zOzZs6vUV2XFC7/nnHNOUq5XWXl5eYwcOZK5c+eyfPlyhg8fri1eRGqQbA+i7SLHpsAo4JYYH90r25m7zwa6Eew9egHwW2A3MBwYWMXFj0RERCTD1a5dm379+vHdd9+xbt26aoec1atXx2yvzvDW8k8HV65cydtvv83xxx/PgAEDmDdvXpX7q8z1yoffH/7whxx55JFMnjyZBx98MPTrVVXfvn1p0qRJhfZUzmMVkfBldRB19+7ubgf4GB31nkcj7ZfG6XOpu5/u7ge7e3137+juE6s571REREQy3NixYxNerCeZw1sPOugg5s2bR8eOHTn//PN57bXXDvymKopezOm9996jT58+DB06NOafT6qVlpbGbE/VPFYRCV9WB1ERERGRRMULM1UJObGGt+bl5XHLLbckVNteTZo0YeHChRxxxBGcddZZjBo1KqnzJQsKCnjhhRcoKirihhtuYMSIEZSV7XcZjqSKF+hbtIhe8kNEsoWCqIiIiOS0MJ5mRg9vbdq0KWVlZSxcuDC0AHfooYfyyiuv0KhRI2699dakz5fMz8/n8ccf57e//S0TJkyge/fuFBYWfh9+N2/eHOr19ifePNYtW7awYMGClNUhIuFREBUREZGcFi/kXH755VXqp/zw1g0bNjB27FhmzJjBzTffHFapNGvWjFq1alVoT9Z8yby8PCZNmsQFF1zAm2++ycqVK78PvyUlJSlbLCjWFi8TJkzg8MMPp2/fvhkxfFhEqkZBVERERHJarMV6DjnkEKZNm8amTZuq3e/111/P0KFDuf3225k2bVpo9a5duzZme7LmS5oZ77zzToX2srKylC4WFL3Fy7XXXsuyZcu48MILueGGGxgwYABbt25NWT0ikhgFUREREcl50Yv1zJs3j7Vr1zJo0CC++6566xWaGffddx99+vRh2LBhLFy4MJRa07HvZ6yQ+80336R9saAGDRrw5JNPctddd/Hcc89xwgknMH78eO03KpIFFERFREREonTp0oX77ruPhQsXMmrUqGr3U7t2bZ5++mk6dOjAOeecQ8uWLRMOSLGGEpsZw4cPr3adBxIr5N5///20bNkyadesLDPjuuuuY8GCBZSUlDBixAjtNyqSBRRERURERGIYMmQIl19+ObfffjvPP/98tftp1KgRl112GTt37qS0tDThgBQ9lLh58+bUq1ePe++9l3Xr1lW7zv2JFX63bt3K9u3b+eijj5Jyzao67bTTaNy4cYV27TcqkpkUREVERETimDx5Ml26dGHQoEG0atWq2k8zJ0yYkPBepeWVH0pcWlrKX//6V9asWUOvXr0Smte6v+tFLxZ02223kZ+fz8knn8zf/va30K9ZHamePysi1acgKiIiIhJH3bp1KSoqYseOHaxdu7baTzPD2Kt0f7p27cqLL77IJ598Qu/evdmyZUso/ZYXvVhQp06dWLp0KY0aNeLUU0/ltddeC/2aVZWO+bMiUj0KoiIiIiL7MWHChAptVX2amYqA1LNnT5599lk++OADunTpQps2bZK+YM/hhx/OkiVLKCwspG/fvgkNYQ5DrCHEBQUFFBcXp6kiEYlHQVRERERkP8J4mhlvr9Ju3bpVu65YzjjjDK644gqWL1/OqlWrUrJgT8uWLXnjjTfo1KkTF1xwAU2bNk3birWxhhBPnTqVoqKilNYhIgemICoiIiKyH2E8zYwOSG3atKFTp05Mnz6duXPnhlUqAC+++GKFtmQv2HPIIYcwZMgQzIxNmzaldcXa6CHECqEimUlBVERERGQ/4j3NvO6666rUT/mAVFJSwuuvv87RRx/NwIED+eCDD8IqN+nzUeO59dZbKSsr26dNK9aKSDwKoiIiXZKH6wAAH49JREFUIiL7Ef00s2XLltStW5cZM2awa9euavfbsGFD5syZQ+PGjTnzzDPjrvhaVfGe1LZu3TqU/uOJF3RLSkqSel0RyU4KoiIiIiIHUP5p5po1a3j88cdZtmwZI0aMSKjfVq1a8dJLL/HVV1/RtWvXUBYYivcE9yc/+UmFLWTCFC8AFxQUJBTYRaRmUhAVERERqaIBAwZwzTXXMHnyZJ566qmE+jrmmGMYNmwYK1asCGWBoVjzUfv06cOCBQsYNWpUQrXuT6wAnJ+fz7Zt2zj33HPZtm1b0q4tItlHQVRERESkGu644w5OOukkhgwZwocffphQX7NmzarQlsj8yuj5qC+//DJDhgzhtttu44477kio1v1dM3rF2kceeYSpU6cyf/58fv7zn/P1118n5doikn1qp7sAERERkWyUn5/PzJkz6dSpE7169SI/P5/Vq1fTpk0biouLq7Raa7IXGDIzpkyZwrfffsvvf/97GjZsyJVXXhlK3+UVFRXF/LqbNGlCUVER3bt3Z8GCBTRr1iz0a4tIdtETUREREZFqatGiBUOGDGHdunUJDasNY4uYA6lVqxaPPfYYZ599NldddRVXXHEFbdu2Tcmen/3792fOnDl88sknnHTSSdx9990pu7aIZCYFUREREZEEPPHEExXaqjqsNt4CQ2E/tczPz+fpp5+mQ4cOTJkyhZKSkpTt+dm7d29eeeUV1q5dy/Dhw1N6bRHJPAqiIiIiIgkIY1ht9PzKVq1a0bhxY6ZMmcKGDRvCKhWAevXq8e9//7tCeyr2/PzZz35G48aNK6zeq/1GRXKPgqiIiIhIAsIaVlt+gaHVq1ezcOFCSktLOffcc9mxY0cYpX5v9erVMdvDmpO6P+vWrUvbtUUkcyiIioiIiCQg1rDavLw8/vjHPybUb5cuXb7fr/Syyy4LdQ/QVMxJzcRri0jmUBAVERERSUD0sNpDDz2UsrIy/vnPfybcd//+/bntttt48sknEw625aVqTmpVrt2nT5+kX1tEMoeCqIiIiEiCyg+r3bhxI7/+9a8ZN24cL774YsJ9jxw5ksGDBzN69Gh+8IMfhLLSbHR4btmyJU2aNOHuu+/m888/T7jmqly7devWdOjQgQceeIBJkyYl9doikjkUREVERERCdvfdd9OpUycGDx7MZ599llBfZkaPHj3Iy8tj48aNoa00Wz48r1mzhiVLlrBz50569uzJmjVrEqq5KtdeuXIl7777Lueddx7XXHMNo0ePDnUYsohkJgVRERERkZDVq1ePWbNmkZeXR79+/di+fXtC/Y0ePZqysrJ92sJeabZDhw4sWLCATZs20atXr9BX692funXrMnPmTC699FLGjBnD7373uwpfr4jULAqiIiIiIknQrl07nnjiCd5//30OO+ywhIbUhrFFTGV07tyZl156iS+++ILevXuzZcuWUPvfn9q1a/PQQw9x7bXXMnnyZLp160ZhYWEoQ5FFJPMoiIqIiIgkyddff03t2rXZunVrQkNq460o27p16zDK3Mcpp5zCc889x4cffkiXLl1o06ZNysJgXl4e48ePp3///ixZsoSVK1eGNhRZRDKLgqiIiIhIktx4443s2bNnn7bqDKmNt9Lsf/7nfyZUXzx9+/Zl2LBhLF++nFWrVqU0DJoZb7/9doX2sIcii0h6KYiKiIiIJElYQ2qjV5otLCykd+/ezJ8/nwceeCCMUiuIteJvqsJgqoYii0j6KIiKiIiIJEmYQ2rLrzS7YsUK5s6dS9++fbnqqqt49dVXEy21gnSGwXh/bvHaRST7KIiKiIiIJEm8IbXdu3dPuO9atWoxY8YM2rdvT79+/fj4448T7rO8VM5LjRbrz62goIDi4uKkX1tEUkNBVERERCRJoofUtmnThmOPPZannnoq5jzIqmrUqBFz5swhPz+fM888k02bNoVQdSBeiO7UqVNo14gn1lDkqVOnUlRUlPRri0hqKIiKiIiIJFH5IbUlJSW88sortGzZkgEDBrB58+aE+2/Xrh3PP/88K1eu5OSTTw5ty5NYIbpbt27Mnj2b8ePHJ1x3Za5ffiiyQqhIzaIgKiIiIpJChxxyCM888wylpaUMHjyYsrKyhPvs2rUrv/rVr/joo49C3fIkOkS/+uqr9O/fnxEjRjB37tyE6xaR3JXVQdTMfmxmvzez18xslZntMrP1ZvaCmfWoYl9tzcz38zEjWV+HiIiI5Jbjjz+eCRMmMHfuXO64445Q+pw3b16FtrBXua1VqxbTp0+nb9++jB8/npkzZ4bWt4jklqwOosCtwFigGfAyMB5YCpwBvGZmV1ejzw+AMTE+ZoVRsIiIiAjAlVdeyYABAxg5ciTNmzdPeDhtqla5rVOnDrNmzaJjx44MGjSIZs2ahTIUWERyS+10F5Cg+cA4d3+vfKOZdQP+CtxpZs+4e2kV+nzf3UeHWKOIiIhIBWZGr169eOaZZ1i/fj3A98NpgSrPiWzTpg0lJSUx28NWUFDAiBEjuOSSS/jyyy+BxGoXkdyT1U9E3f3R6BAaaX8dWAzUAU5MdV0iIiIilXHbbbfh7vu0VXc4baxVbs2M66+/PqEa4/n6669Dq11Eck9WB9ED2B057qni+1qa2a/NbGTkeFTYhYmIiIhAuMNpo1e5bd68ObVr1+bJJ59k586diZZawa5du2K2hz0UWERqphoZRM2sEOgJbAPeqOLbewFTgOLI8QMzW2Rm4Y9rERERkZwWb9hsdYfTll/ltrS0lOnTp7N06VKGDh1a4ellourUqROzvUWLFqFeR0RqJgv7H6V0M7O6wKtAV+B6d7+zku87DPgNMBv4PNJ8FDAa6AF8Chzj7t/up4+hwFCAZs2aHTdjhhba3bp1Kw0bNkx3GVKO7knm0T3JPLonmaem3pPNmzdTUlJCWVkZO3fu5J577uHbb79l4sSJHHHEEaFc47HHHuPRRx9l6NCh/OIXvwilTwiG5n7xxRffbz9TWlrK/fffT6NGjbjvvvs49NBDQ7uWHFhN/TuSzXRPoEePHu+6e+eYJ909rR/ACsCr8DF9P33VAmZGXjeDSNBOsL7awFuRPn9X2fcdd9xxLu6LFi1KdwkSRfck8+ieZB7dk8xTk+/J9OnTvbCw0M3MW7Zs6XXr1vVTTjnFd+/eHUr/ZWVlPnDgQDczv/baa7+/VmFhoU+fPr3a/S5atGif2gsLC33MmDHesGFD79Chg2/cuDGU+qVyavLfkWyle+IOvONx8lImDM39DPi4Ch9rY3ViZrWA6UB/gjB6UeSLT4i77wGmRT49JdH+RERERMorP5x2zZo1TJs2jTfeeINbbrkllP7NjIcffph27doxceJESkpKcPfvV7lNZMuV8rWvWLGCUaNG8eKLL/Lpp59y+umn880334TyNYhIzZP2IOruPd29fRU+Kiz9Zma1gaeAgcCTwKBIgAzLhsixQYh9ioiIiFRw0UUXMWTIEG6//Xbmz58fSp/169ePuWBRMla57dGjBzNnzuTdd9/lnHPOYceOHaH2LyI1Q9qDaKLMrA4wi+BJ6OPAxe7+XciXOSFy/Hy/rxIREREJwT333MNRRx3FRRddxKpVq0Lpc+3amIPKkrLK7dlnn81jjz3G4sWLOfHEEyksLCQvL4+2bdsm9ARWRGqOrA6ikYWJngfOAR4CfunuZQd4T2Mza29mLaLafxoJtdGvPxW4NvLp9HAqFxEREYmvfv36PPPMM+zcuZOePXuGEuTCXqH3QIqKihg8eDDvvfceK1euDG04sIjUDFkdRAm2Vzkd2AisAUaZ2eioj+5R7zkP+Aj4U1T7OGCNmT1jZhMjH68SrMBbF7jZ3Zcl9asRERERiTjyyCO55JJL+OSTT0IJcsXFxRQUFOzTlp+fT3FxcVglV7B48eIKbckYDiwi2ad2ugtIULvIsSkwaj+vW1yJvp4gCKnHA32BfGA9wcJH97r7m9UvU0RERKTqXnrppQpte4NcUVFRlfra+/obb7yRkpISCgoK2LZtGw0aJG8JjHjDfpMxHFhEsktWPxF19+7ubgf4GB31nkcj7ZdGtT/k7me6e1t3b+judd29jbtfqBAqIiIi6RB2kNu7yq27s3HjRrp06cJFF13E+++/n0iZcaV6OLCIZI+sDqIiIiIiNVkyg1z9+vWZPXs2Bx98MGeffTbr1q1LuM9osYYDFxQUJHU4sIhkBwVRERERkQwVK8jVrl07tCDXokULXnjhBTZu3Mh5550X+lYrRUVFTJ06lcLCQsyMwsJCpk6dWuVhxSJS8yiIioiIiGSo6CDXsGFD9uzZQ+PGjUO7RqdOnXjiiSd46623OO2000LfamXvcOCysjJWrFihECoigIKoiIiISEYrH+Q2bNjAscceyyWXXBLqgj8XXHAB/fr1Y+nSpdpqRURSQkFUREREJEvUq1ePmTNnsnv3bi688EJ2794dWt9vv/12hTZttSIiyaIgKiIiIpJFjjjiCKZNm8Zbb73FyJEjQ+t31apVMdu11YqIJIOCqIiIiEiWGTBgAFdeeSV33XUXhx12WChzOrXVioikkoKoiIiISBbq3LkzZsaGDRtCmdMZa4XevLw8Ro8eHUK1IiL7UhAVERERyUJjxozB3fdpS2ROZ/QKvU2bNqWsrIxFixZVuI6ISKIUREVERESyULy5m4nM6YxeoXfMmDE8/vjj3HnnndXuU0QkFgVRERERkSyUijmdN998MwMGDOAPf/gDc+bMCa1fEREFUREREZEsFGtOJ8Dw4cNDu4aZ8cgjj3DccccxaNAg/vGPf4TWt4jkNgVRERERkSwUPaezZcuW++wzGpaCggJmz57NQQcdRM+ePWndujV5eXn84x//SGiVXhHJbQqiIiIiIlmq/JzONWvW8NBDD7F06VJGjRoV6nVatWrFsGHD2LBhA6tXr8bd2bVrV0Kr9IpIblMQFREREakhBg0axJAhQxg7dizz588Pte+HH364Qlsiq/SKSG5TEBURERGpQe655x46duzIxRdfzJo1a0LrNxmr9IpI7lIQFREREalB6tevz8yZM9m+fTunnXYahYWF5OXl0bZt24SG0aZilV4RyR0KoiIiIiI1TPv27bn44ov517/+xcqVK3F3SkpKEprTGWuV3oKCAoqLi8MoWURyjIKoiIiISA00b968Cm2JzOmMXqW3Tp06TJ06laKiokRLFZEcpCAqIiIiUgMlY05n+VV6O3bsqBAqItWmICoiIiJSA2lOp4hkMgVRERERkRoo1pzO/Px8zekUkYygICoiIiJSA5Wf0wnQoEED9uzZQ6tWrdJcmYiIgqiIiIhIjbV3Tqe7s27dOo488kgGDRrEl19+me7SRCTHKYiKiIiI5ICGDRsyc+ZMvvrqKy6++GLKysrSXZKI5DAFUREREZEccdRRRzFp0iQWLlzIuHHj0l2OiOQwBVERERGRHHL55ZczcOBAbrrpJpo3b05eXh5t27blL3/5S7pLE5EcoiAqIiIikkPMjFNPPRV3Z/369bg7JSUlDB06VGFURFJGQVREREQkxxQXF+Pu+7Rt27aNG2+8MU0ViUiuURAVERERyTErV66sUruISNgUREVERERyTJs2barULiISNgVRERERkRxTXFxMQUHBPm0FBQUUFxenqSIRyTUKoiIiIiI5pqioiKlTp1JYWIiZUVhYyNSpUykqKkp3aSKSI2qnuwARERERSb2ioiIFTxFJGz0RFRERERERkZTK6iBqZq3N7H4z+28zW2dmO81srZm9aWa/NLP8avR5opm9bGabzWybmf3dzK4xs1rJ+BpERERERERyTVYHUeBwoAjYAswGxgNzgELgYWChmVV6+LGZnQO8AZwCPA/cB9QBJgIzQq1cREREREQkR2X7HNFlwMHuXla+MfIkdCHQHTgfmHmgjsysEfAg8B3Q3d3fibTfDLwG9DOzge6uQCoiIiIiIpKArH4i6u67okNopH03wRNSgB9Xsrt+wA+AGXtDaKSvHcBNkU+vSKBcERERERERIcuDaDyR+ZynRz79eyXfdmrkOD/GuTeAbcCJZlY3wfJERERERERyWrYPzQXAzJoCvwGM4KlmL+AI4EngpUp28x+R4/LoE+6+x8y+ADoAPwI+SrRmERERERGRXGXunu4aEmZm7dk3HDrBwkUjI8N0K9PHcoJhvD92909jnF8KnAic6O7/FaePocBQgGbNmh03Y4amk27dupWGDRumuwwpR/ck8+ieZB7dk8yje5J5dE8yi+5H5tE9gR49erzr7p1jnUv7E1EzW0Gwym1l/cXdLyrf4O7/CrqyWkAr4Dzgj8BJZnaGu28Oo9S9l4v3AnefCkwF6Ny5s3fv3j2Ey2a3xYsXoz+HzKJ7knl0TzKP7knm0T3JPLonmUX3I/Ponuxf2oMo8BmwowqvXxvvhLt/B6wEJpnZeuApgkD6m0r0uyVybBznfKOo14mIiIiIiEg1pD2IunvPJHU9L3LsXsnXfwx0Bo4E3i1/IrIXaTtgD/B5SPWJiIiIiIjkpBq5am5Eq8hxTyVf/1rk2CfGuVOAAmCZu+9MtDAREREREZFcltVB1Mx+amYFMdobApMin86NOtfYzNqbWYuot80CNgIDzaxzudfXA26LfPrn0IoXERERERHJUVm9aq6ZzSYYevs6wdzQbUBroC/QBFgG9Hb3reXecynwCPCYu18a1d+5BIF0BzAD2AycTbC1yyxggFfyD8zMNgAl1f7iao6mBAFfMofuSebRPck8uieZR/ck8+ieZBbdj8yjewKF7v6DWCfSPkc0QQ8C3wLHEwTSAuArgjmeM4GH3b2yQ3Nx99lm1g24EbgAqAd8CgwH7qlsCI30FfMPPNeY2TvxlmyW9NA9yTy6J5lH9yTz6J5kHt2TzKL7kXl0T/Yvq4Oou88lauhtJd7zKPDofs4vBU5PqDARERERERGJK6vniIqIiIiIiEj2URCVZJua7gKkAt2TzKN7knl0TzKP7knm0T3JLLofmUf3ZD+yerEiERERERERyT56IioiIiIiIiIppSAqIiIiIiIiKaUgKilhZvlm9jsze8TM3jezXWbmZjYk3bXlAjP7oZk9bGZrzWynma0ws7vN7OB015aLzKyfmU02szfN7N+RvwvT011XrjKzQ81siJk9b2afmtl2M9tiZkvM7DIz0/fKNDCzcWb2qpmtityTzWb2npndYmaHprs+ATO7OPLvl76fp0nk+7nH+ViX7vpylZmdbGbPmllp5OeuUjNbaGbamaOcrN6+RbJKA+DuyH+vB9YBrdNXTu4ws8OBZcBhwAvAv4AuwO+APmbW1d03pbHEXHQTcDSwFVgNtE9vOTmvP/BnoBRYBKwEmgHnA9OAvmbWvyp7SUsorgX+B/gr8CXB95ETgNHAUDM7wd1Xpa+83GZmrYHJBP+ONUxzObluC//3M1Z5W1NdiICZ3QTcCmwEXiL43tIUOBboDryctuIyjIKopMo2gv1Z33f3UjMbDdyS3pJyxv0EIfRqd5+8t9HMJhD8oFcMDEtTbbnqWoIA+inQjSD8SPosB84G5rp72d5GMxsJvA1cQBBKn01PeTmrkbvviG40s2JgJHADcGXKqxLMzIBHgE3Ac8CI9FaU875299HpLkLAzPoThNBXgPPd/Zuo8/lpKSxDabiRpIS773L3ee5emu5acomZ/Qj4ObACuC/q9C3At8DFZtYgxaXlNHdf5O6f6AlbZnD319x9TvkQGmlfB0yJfNo95YXluFghNGJm5PjjVNUiFVwNnAr8kuD7iEjOi0zjGEfw8GVQdAgFcPfdKS8sg+mJqEjNdmrkuDDGD9nfmNlSgqB6AvBqqosTyQJ7f2jYk9YqpLyzIse/p7WKHGVmPwHGApPc/Q0zO/VA75Gkq2tmFwFtCH4x8HfgDXf/Lr1l5ZwTgXbALOArMzsD+H/ADuBtd/+vdBaXiRRERWq2/4gcl8c5/wlBED0SBVGRfZhZbWBw5NP56awll5nZCII5iI2BzsBJBD9oj01nXbko8nfiCYJ51CPTXI78n+YE96W8L8zsl+7+ejoKylHHR47rCea3dyx/0szeAPq5+4ZUF5apNDRXpGZrHDluiXN+b3uTFNQikm3GEvw2+2V3X5DuYnLYCIKpBNcQhND5wM/1w1xajCJYcOVSd9+e7mIECObq9iQIow0Iws8DQFtgnpkdnb7Scs5hkeMwoD5wGnAQwfeRBcApwDPpKS0zKYhKpR1gifBYH9qOIvNZ5Ki5iiLlmNnVwHUEq0xfnOZycpq7N3d3I/hB+3zgR8B7ZtYpvZXlFjPrQvAUdLyGGGYOdx8Tmee+3t23ufuH7j4MmEAQhkant8KcUityNIInn6+6+1Z3/1/gPIJFCruZ2c/SVmGG0dBcqYrPCMa5V9baZBUilbb3iWfjOOcbRb1OJOeZ2VXAJOCfQE9335zmkgRw9/XA82b2PwTTDR4neNIgSVZuSO5y4OY0lyOVM4Xgl2mnpLuQHPJV5Pi5u39Q/oS7bzezBcBlBFvo6Zc5KIhKFbh7z3TXIFX2ceR4ZJzze1edjDeHVCSnmNk1wETgQ4IQ+mWaS5Io7l5iZv8EjjGzpu6+Md015YCG/N/3kR3B7i0VPGhmDxIsYnRNyiqTePb+26VV8VNn789cX8c5vzeo1k9BLVlBQVSkZtu7P+XPzSwvao/Eg4CuwHbgrXQUJ5JJzOz3BPNC3wd6KeBktJaRo1YFTY2dwENxznUimDe6hOAHcT3pyQx7h39+ntYqcssbBCus/9jM6rj7rqjze0dwrEhpVRlMQVSkBnP3z8xsIcHKuFcBk8udHkPwm9IH3F37wElOM7ObgT8C7xIshKPhuGlkZu2BryN7uZZvzyPYLP4wYJm7fxXr/RKuyMJEQ2KdM7PRBEH0MXeflsq6cp2ZdQBKo/+9MrNC4N7Ip1qvI0XcfaOZPQ0UESzsddPec2bWC+hNMBVKq7BHKIhKypjZH4D2kU+PiRx/aWYnRf57ib6JJcWVwDLgHjPrCXwE/BToQTAk98Y01paTzOxc4NzIp80jx5+Z2aOR/97o7iNSXliOMrNLCELod8CbwNUxhh6ucPdHU1xaLusD3BnZ7uAzYBPQDOhGsFjROuDy9JUnkhH6A38ws0XAF8A3wOHAGUA94GXgrvSVl5OGE/yMdaOZnQK8DRQSLFb0HXC5u8cbuptzFEQllfoQ/BBR3omRj70UREMWeSrameAH7T7A6UApcA8wRk9+0uIY4JKoth9FPgBKCLaskNRoFznWItgiJJbXgUdTUo0AvAJMJZg+cDTBFlPfEvzy7AngHv3bJcIigv3CjyUYituAYH7iEoK/J0+4u1bFTyF3/9LMfkrwNPQ84ASCXxDMBf7k7poKVY7p/08RERERERFJJe0jKiIiIiIiIimlICoiIiIiIiIppSAqIiIiIiIiKaUgKiIiIiIiIimlICoiIiIiIiIppSAqIiIiIiIiKaUgKiIiIiIiIimlICoiIiIiIiIppSAqIiJSA5jZr8zMzaxLEq9xnZntNrP2ybqGiIjkBnP3dNcgIiIiCTCzhsBy4B13PzuJ16kPfAq85+5nJus6IiJS8+mJqIiISPa7GmgBjE3mRdx9OzAJOMPMTkzmtUREpGbTE1EREZEsZma1gC+AHe5+ZAqu1xJYCcxw94uSfT0REamZ9ERUREQkxcxsdmQ+529jnLs1cm5aJbvrBbQGno5zLTezxWbWzMweNrP1ZvatmS0zs5Mjr2lgZneaWYmZ7TSz/zWz/rH6c/e1wJtAPzNrVMkaRURE9qEgKiIiknq/IniqeKeZHbu30cx6AiOBfxIMt62M0yLHJft5TRNgKXAs8BTwLNAZWGBmRwOvAucALwGPAW2Ap83shDj9LQXqAqdUskYREZF9KIiKiIikmLtvBn4B1CIIfA3N7DBgOrATGODu2yrZ3UmR4zv7ec3RwCvAce5+jbsPBi4D6gOLgA3AUe5+lbsPBU4HDPh9nP7+FjkqiIqISLVojqiIiEiamNkfgD8BTwI/IBhme7m7V3ZYLma2Fmjq7nXinHdgG9Dc3b8p114L2AHUBg5398+j3vcFgLu3i9HnT4G3gKfdfWBlaxUREdmrdroLEBERyWHjgO7AoMjnT1UlhEYcCnx1gNcsLx9CAdz9OzNbDzSIDqERa4Cfxulvc+TYtEqVioiIRGhoroiISJp4MCzp+XJNd1ejm+1AvQO8Zkuc9j0HOBfvF9b1y11bRESkyhRERURE0sTMfgzcRfBEswyYZmYHCpXRvgQamVl+2PXtx6Hlri0iIlJlCqIiIiJpYGZ1CbZcaQAMJJgr2pGqPxX9e+T4H+FVd0DtI8f3U3hNERGpQRRERURE0uMugu1U7nD3hcAtBNui/NrMBlShn8WRY7ytVpJh77UWpfCaIiJSgyiIioiIpJiZnQv8Bvhv4CYIFg8i2NJlM/Cgmf2okt3NBr4Deieh1ArMLA/oCXzs7h+m4poiIlLzKIiKiIikkJm1AR4mWCToF+6+Z+85d18F/ApoBMwws5hbspTn7quBOcBZZnZwcqrex2lAK2BKCq4lIiI1lPYRFRERyXJmdiLBsN7h7j4xydd6FuhGsPdovBV3RURE9ktBVEREpAYws5nAKcCP3H1bkq5xDPA/wNXufm8yriEiIrlBQ3NFRERqhhEEw2XbJfEaLYCb0bBcERFJkJ6IioiIiIiISErpiaiIiIiIiIiklIKoiIiIiIiIpJSCqIiIiIiIiKSUgqiIiIiIiIiklIKoiIiIiIiIpJSCqIiIiIiIiKTU/weYHYRqe9kwPQAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "# Plot the deformed beam shapes\n", "fig= plt.subplots(1, 1, figsize=(15, 6))\n", "plt.scatter(x0,z0,c='black')\n", "plt.plot(x0,z0,c='black')\n", "\n", "for jForce in range(Nforces):\n", " plt.scatter(x1[jForce,0:N],z1[jForce,0:N],c='black')\n", " plt.plot(x1[jForce,0:N],z1[jForce,0:N],c='black')\n", "\n", "plt.axis('equal')\n", "plt.grid()\n", "plt.xlabel('x (m)')\n", "plt.ylabel('z (m)')\n", "plt.savefig(\"images/ncb1-dead-displ.eps\", format='eps', dpi=1000, bbox_inches='tight')" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " Force Tip z\n", "--------------------\n", " 100000 -0.4438\n", " 200000 -0.8672\n", " 300000 -1.2551\n", " 400000 -1.5999\n", " 500000 -1.9005\n", " 600000 -2.1597\n", " 700000 -2.3824\n", " 800000 -2.5737\n", " 900000 -2.7386\n", " 1000000 -2.8813\n" ] } ], "source": [ "print('{:>8s}{:>12s}'.format('Force','Tip z'))\n", "dash=20*'-'; print(dash)\n", "for jForce in range(Nforces):\n", " print('{:>8.0f}{:>12.4f}'.format((jForce+1)*DeltaForce,z1[jForce,N-1]))" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "model.clean_test_files(route, case_name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Problem 2: Comparing follower and dead forces" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "#Loop through all external follower forces, again applied at the tip.\n", "x2=np.zeros((Nforces,N))\n", "z2=np.zeros((Nforces,N))\n", "for jForce in range(Nforces):\n", " model.clean_test_files(route, case_name)\n", " model.generate_fem_file(route, case_name, Nelem, 0, -float(jForce+1)*DeltaForce)\n", " model.generate_solver_file(route,case_name)\n", "\n", " case_foll=sharpy.sharpy_main.main(['', route + case_name + '.sharpy'])\n", "\n", " x2[jForce,0:N]=case_foll.structure.timestep_info[0].pos[:, 0]\n", " z2[jForce,0:N]=case_foll.structure.timestep_info[0].pos[:, 2]" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "The PostScript backend does not support transparency; partially transparent artists will be rendered opaque.\n", "The PostScript backend does not support transparency; partially transparent artists will be rendered opaque.\n", "The PostScript backend does not support transparency; partially transparent artists will be rendered opaque.\n", "The PostScript backend does not support transparency; partially transparent artists will be rendered opaque.\n" ] }, { "data": { "image/png": "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Hc2SJUtISUkxjufk5ISfnx/33XcfW7duJTAwkMDAQOrWrUuZMjKXlhClSCVs86VtgdWidnd3Jy4ujlWrVtGvXz++/fZbli5dysGDB/H397/zAYQQ4gYTJkwAzIlcSkoKkZGRbN++nYyMDJo3b86iRYtylCxZvHgx7du3Z/DgwXz66ac8+OCDVKxYkYSEBA4ePEhERASbN2827np79dVXGTp0KE2aNKFPnz44ODiwY8cODh06RI8ePVi9erXF8b29vXn//fcZNWoUTZo04emnn8bNzY0NGzaQkpJCo0aNjImKRE7WfNIdA+wAvlRKjQbXxvYAACAASURBVNJap93pAUIUR/fdd5/xe40aNTh8+DBpaWkcOHDA6AH18vKibNmypKSkMGLECNzc3Lj//vt56KGHGDZsGA888ABnzpwhOjraWPbu3UtoaKiRtDo6OuLv728kn9mLr6+v9AoIUQwopdretMk7l21gLhPmBTzDP2MoC01h1KLu2bMnZ8+epXXr1uzfv5/GjRszbtw4Xn755VI3iYkQ4u5NnDgR+GeW29q1a9O/f3/69OlD586dc62rXrNmTcLCwpgxYwbLly9n0aJFZGZm4unpSYMGDXjppZcsxloOGTIEJycnpk2bxoIFC3B2dqZNmzbMmzeP5cuX50g2AV577TWqVavGhx9+yPz583F1daVLly783//9n8VtvSInlddOSqXUr5jrcDUGLmEuBJ2SS1Otte6QbxEWIn9/fx0dXeifA4qt0jxVebZTp06xdu1a9u7dy969ezl48CCXL18mLCyMpk2bsn79elauXEnTpk1RStG3b1+io6OJjIy0WOLj441jOjk5Ub9+fRo2bGiRhPr4+OT6R7akkveXdeR8WUcpFaa1Dr7HY2SRS8mQWzUHsoD+WuvF9/K8tmDN9TExMZGXX36ZZcuWoZTio48+4rXXXivgCIsW+f9ondJ8vqKiou5qrHNpnI32Xsj5Msvr+y0/rpHZrOnZNN3wezng/lu0k1tsRalRvXp1nn/+eWP92rVrREdH4+dnnvjx6NGjLFmyhC+//BKA119/nVatWrFs2TL69+9vPC4tLY2oqCgiIiKMBHTr1q0sWrTIaOPs7ExAQECOntDatWuXqiRUiCLkPczXPAW8C4QAobm0ywTOAVu01ocLLTob8fT0ZOnSpZQpU4bFixczatQoFi5c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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "#Plot results.\n", "fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(15, 6))\n", "\n", "ax0 = axs[0]\n", "ax0.plot([0,Nforces],[0, 0],linestyle=':', c='black')\n", "ax0.plot(range(Nforces+1), np.concatenate((5, x1[0:Nforces,-1]), axis=None)-5*np.ones(Nforces+1),linestyle='-', c='black')\n", "ax0.plot(range(Nforces+1), np.concatenate((5, x2[0:Nforces,-1]), axis=None)-5*np.ones(Nforces+1),linestyle='--', c='black')\n", "#ax0.axis('equal')\n", "ax0.grid()\n", "ax0.set_xlabel('Force (N) x 10^5')\n", "ax0.set_ylabel('Tip horizontal displacement (m)')\n", "ax0.set(xlim=(0, Nforces), ylim=(-6, 1))\n", "\n", "ax1 = axs[1]\n", "ax1.plot([0,Nforces],np.concatenate((0, z1[0,-1]*Nforces), axis=None),linestyle=':', c='black')\n", "ax1.plot(range(Nforces+1), np.concatenate((0, z1[0:Nforces,-1]), axis=None),linestyle='-', c='black')\n", "ax1.plot(range(Nforces+1), np.concatenate((0, z2[0:Nforces,-1]), axis=None),linestyle='--', c='black')\n", " \n", " \n", "#ax1.axis('equal')\n", "ax1.grid()\n", "ax1.set_xlabel('Force (N) x 10^5')\n", "ax1.set_ylabel('Tip vertical displacement (m)')\n", "ax1.set(xlim=(0, Nforces), ylim=(-5, 1))\n", "\n", "ax1.legend(['Linear','Dead','Follower'])\n", "\n", "fig.savefig(\"images/ncb1-foll-displ.eps\", format='eps', dpi=1000, bbox_inches=\"tight\")" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "model.clean_test_files(route, case_name)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.5" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: docs/source/content/example_notebooks/cantilever_wing.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "# A first test case with SHARPy\n", "This notebook explains how to create a prismatic cantilever and flexible wing in SHARPy. It aims to provide a big picture about the simulations available in SHARPy describing the very fundamental concepts. \n", "\n", "This notebook requires around two hours to be completed." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Generation of the aeroelastic model\n", "This section explains how to generate the input files for SHARPy including the structure, the aerodynamics and the simulation details." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# Loading of the used packages\n", "import numpy as np # basic mathematical and array functions\n", "import os # Functions related to the operating system\n", "import matplotlib.pyplot as plt # Plotting library\n", "\n", "import sharpy.sharpy_main # Run SHARPy inside jupyter notebooks\n", "import sharpy.utils.plotutils as pu # Plotting utilities\n", "from sharpy.utils.constants import deg2rad # Constant to conver degrees to radians\n", "\n", "import sharpy.utils.generate_cases as gc" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The [generate cases](https://ic-sharpy.readthedocs.io/en/main/includes/utils/generate_cases/index.html) module of SHARPy includes a number of templates for the creation of simple cases using common parameters as inputs. It also removes clutter from this notebook.\n", "\n", "The following cell configures the plotting library to show the next graphics, it is not part of SHARPy." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "%%capture\n", "! pip install plotly" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Define the problem parameters\n", "In this section, we define the basic parameters to generate a model for the wing shown in the image below. We use SI units for all variables." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%config InlineBackend.figure_format = 'svg'\n", "from IPython.display import Image\n", "url = ('https://raw.githubusercontent.com/ImperialCollegeLondon/sharpy/dev_example/docs/' + \n", " 'source/content/example_notebooks/images/simple_wing_scheme.png')\n", "Image(url=url, width=800)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# Geometry\n", "chord = 1. # Chord of the wing | Set to 1 by default\n", "aspect_ratio = 16. # Ratio between lenght and chord: aspect_ratio = length/chord | Set to 16 by default\n", "wake_length = 50 # Length of the wake in chord lengths | Set to 50 by default\n", "\n", "# Discretization\n", "num_node = 21 # Number of nodes in the structural discretisation\n", " # The number of nodes will also define the aerodynamic panels in the \n", " # spanwise direction | Set to 21 by default\n", "num_chord_panels = 4 # Number of aerodynamic panels in the chordwise direction | Set to 4 by default\n", "num_points_camber = 200 # The camber line of the wing will be defined by a series of (x,y)\n", " # coordintes. Here, we define the size of the (x,y) vectors | Set to 200 by default\n", "\n", "# Structural properties of the beam cross section\n", "mass_per_unit_length = 0.75 # Mass per unit length | Set to 0.75 by default\n", "mass_iner_x = 0.1 # Mass inertia around the local x axis | Set to 0.1 by default\n", "mass_iner_y = 0.05 # Mass inertia around the local y axis | Set to 0.05 by default\n", "mass_iner_z = 0.05 # Mass inertia around the local z axis | Set to 0.05 by default\n", "pos_cg_B = np.zeros((3)) # position of the centre of mass with respect to the elastic axis\n", "EA = 1e7 # Axial stiffness | Set to 1e7 by default\n", "GAy = 1e6 # Shear stiffness in the local y axis | Set to 1e6 by default\n", "GAz = 1e6 # Shear stiffness in the local z axis | Set to 1e6 by default\n", "GJ = 1e4 # Torsional stiffness | Set to 1e4 by default\n", "EIy = 2e4 # Bending stiffness around the flapwise direction | Set to 2e4 by default\n", "EIz = 5e6 # Bending stiffness around the edgewise direction | Set to 5e6 by default\n", "\n", "# Operation\n", "WSP = 2.0 # Wind speed | Set to 2.0 by default\n", "air_density = 0.1 # Air density | Set to 0.1 by default\n", "\n", "# Time discretization\n", "end_time = 5.0 # End time of the simulation | Set to 5.0 by default\n", "dt = chord/num_chord_panels/WSP # Always keep one timestep per panel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, we are going to compute the static equilibrium of the wing at an angle of attack (``aoa_ini_deg``). Next, we will change the angle of attack to ``aoa_end_deg`` and we will compute the dynamic response of the wing. Keep in mind we are not changing the angle of attack of the wing itself in these simulations but of the wind flowing toward it, which has the same effect." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "aoa_ini_deg = 2. # Angle of attack at the beginning of the simulation | Set to 2 by default\n", "aoa_end_deg = 1. # Angle of attack at the end of the simulation | Set to 1 by default" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For now, we keep the size of the wake panels equal to the distance covered by the flow in one time step. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Structural model\n", "\n", "This section creates a class called ``AeroelasticInformation``. This class is not directly used by SHARPy, it can be thought of as an intermediate step between common engineering inputs and the input information that SHARPy needs. \n", "\n", "It has many functionalities (rotate objects, assembly simple strucures together ...) which can be looked up in the [documentation](https://ic-sharpy.readthedocs.io/en/main/includes/utils/generate_cases/index.html).\n", "\n", "Let's initialise an Aeroelastic system that will include a ``StructuralInformation`` class and an ``AerodynamicInformation`` class:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "wing = gc.AeroelasticInformation()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The attibutes that have to be defined (not all of them are compulsory) can be displayed with the code below. They constitute the input parameters to SHARPy and their names are intuitive, however, a complete description of the input variables to SHARPy can be found in the documentation: \n", "[structural inputs](https://ic-sharpy.readthedocs.io/en/main/content/casefiles.html#fem-file) and [aerodynamic inputs](https://ic-sharpy.readthedocs.io/en/main/content/casefiles.html#aerodynamics-file)\n", "\n", "For example, the structural properties are:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dict_keys(['num_node_elem', 'num_node', 'num_elem', 'coordinates', 'connectivities', 'elem_stiffness', 'stiffness_db', 'elem_mass', 'mass_db', 'frame_of_reference_delta', 'structural_twist', 'boundary_conditions', 'beam_number', 'body_number', 'app_forces', 'lumped_mass_nodes', 'lumped_mass', 'lumped_mass_inertia', 'lumped_mass_position', 'lumped_mass_mat', 'lumped_mass_mat_nodes'])" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wing.StructuralInformation.__dict__.keys()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For example, the connectivities between the nodes required by the finite element solver are empty so far:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "None\n" ] } ], "source": [ "print(wing.StructuralInformation.connectivities)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For a list of methods that allow us to modify the structure, Check the [documentation ](https://ic-sharpy.readthedocs.io/en/main/includes/utils/generate_cases/index.html).\n", "\n", "First, we need to define the basic characteristics of the equations as the number of nodes, the number of nodes per element, the number of elements and the location of the nodes in the space:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "# Define the number of nodes and the number of nodes per element\n", "wing.StructuralInformation.num_node = num_node\n", "wing.StructuralInformation.num_node_elem = 3\n", "# Compute the number of elements assuming basic connections\n", "wing.StructuralInformation.compute_basic_num_elem()" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 0. 0. 0. ]\n", " [ 0. 0.8 0. ]\n", " [ 0. 1.6 0. ]\n", " [ 0. 2.4 0. ]\n", " [ 0. 3.2 0. ]\n", " [ 0. 4. 0. ]\n", " [ 0. 4.8 0. ]\n", " [ 0. 5.6 0. ]\n", " [ 0. 6.4 0. ]\n", " [ 0. 7.2 0. ]\n", " [ 0. 8. 0. ]\n", " [ 0. 8.8 0. ]\n", " [ 0. 9.6 0. ]\n", " [ 0. 10.4 0. ]\n", " [ 0. 11.2 0. ]\n", " [ 0. 12. 0. ]\n", " [ 0. 12.8 0. ]\n", " [ 0. 13.6 0. ]\n", " [ 0. 14.4 0. ]\n", " [ 0. 15.2 0. ]\n", " [ 0. 16. 0. ]]\n" ] } ], "source": [ "# Generate an array with the location of the nodes\n", "node_r = np.zeros((num_node, 3))\n", "node_r[:,1] = np.linspace(0, chord*aspect_ratio, num_node)\n", "print(node_r)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following function creates a uniform beam from the previous parameters. On top of assigning the previous parameters, it defines other variables such as the connectivities between nodes." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "wing.StructuralInformation.generate_uniform_beam(node_r,\n", " mass_per_unit_length,\n", " mass_iner_x,\n", " mass_iner_y,\n", " mass_iner_z,\n", " pos_cg_B,\n", " EA,\n", " GAy,\n", " GAz,\n", " GJ,\n", " EIy,\n", " EIz,\n", " num_node_elem = wing.StructuralInformation.num_node_elem,\n", " y_BFoR = 'x_AFoR',\n", " num_lumped_mass=0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we now show the connectivities between nodes, the function has created them for us. [Further information](https://ic-sharpy.readthedocs.io/en/main/content/casefiles.html?highlight=connectivities#fem-file)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 0 2 1]\n", " [ 2 4 3]\n", " [ 4 6 5]\n", " [ 6 8 7]\n", " [ 8 10 9]\n", " [10 12 11]\n", " [12 14 13]\n", " [14 16 15]\n", " [16 18 17]\n", " [18 20 19]]\n" ] } ], "source": [ "print(wing.StructuralInformation.connectivities)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's define the boundary conditions as clamped for node 0 and free for the last node (-1), where 1 denotes a clamped point and -1 denotes a free point. [Further information](https://ic-sharpy.readthedocs.io/en/main/content/casefiles.html?highlight=boundary#fem-file)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "wing.StructuralInformation.boundary_conditions[0] = 1\n", "wing.StructuralInformation.boundary_conditions[-1] = -1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Aerodynamics\n", "We need to define the number of panels in the wake (``wake_panels``) and the camber line of the wing which, in this case, is flat. [Further information](https://ic-sharpy.readthedocs.io/en/main/content/casefiles.html#aerodynamics-file)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "# Compute the number of panels in the wake (streamwise direction) based on the previous paramete\n", "wake_panels = int(wake_length*chord/dt)\n", "\n", "# Define the coordinates of the camber line of the wing\n", "wing_camber = np.zeros((1, num_points_camber, 2))\n", "wing_camber[0, :, 0] = np.linspace(0, 1, num_points_camber)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following function creates an aerodynamic surface uniform on top of the beam that we have already created." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "# Generate blade aerodynamics\n", "wing.AerodynamicInformation.create_one_uniform_aerodynamics(wing.StructuralInformation,\n", " chord = chord,\n", " twist = 0.,\n", " sweep = 0.,\n", " num_chord_panels = num_chord_panels,\n", " m_distribution = 'uniform',\n", " elastic_axis = 0.5,\n", " num_points_camber = num_points_camber,\n", " airfoil = wing_camber)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Summary\n", "Now, we have all the inputs that we need for SHARPy. In this section, a first simulation with SHARPy is run. However, it will perform no computation, it will just load the data so we can plot the system we have just created.\n", "\n", "SHARPy runs a series of [solvers](https://ic-sharpy.readthedocs.io/en/main/content/solvers.html) and [postprocessors](https://ic-sharpy.readthedocs.io/en/main/content/postproc.html) in the order indicated by the ``flow`` variable in the ``SHARPy`` dictionary.\n", "\n", "The ``generate cases`` module also allows us to show all the avaiable solvers and all the parameters they accept as inputs. To do so, we need to run the following commands:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "_BaseStructural\n", "AerogridLoader\n", "BeamLoader\n", "DynamicCoupled\n", "DynamicUVLM\n", "InitialAeroelasticLoader\n", "LinDynamicSim\n", "LinearAssembler\n", "Modal\n", "NoAero\n", "NonLinearDynamic\n", "NonLinearDynamicCoupledStep\n", "NonLinearDynamicMultibody\n", "NonLinearDynamicPrescribedStep\n", "NonLinearStatic\n", "NoStructural\n", "PrescribedUvlm\n", "RigidDynamicCoupledStep\n", "RigidDynamicPrescribedStep\n", "StaticCoupled\n", "StaticTrim\n", "StaticUvlm\n", "StepLinearUVLM\n", "StepUvlm\n", "_BaseTimeIntegrator\n", "NewmarkBeta\n", "GeneralisedAlpha\n", "Trim\n", "UpdatePickle\n", "StabilityDerivatives\n", "WriteVariablesTime\n", "SHARPy\n", "SaveData\n", "BeamLoads\n", "UDPout\n", "SaveParametricCase\n", "AerogridPlot\n", "Cleanup\n", "PlotFlowField\n", "FrequencyResponse\n", "AsymptoticStability\n", "LiftDistribution\n", "AeroForcesCalculator\n", "StallCheck\n", "BeamPlot\n", "PickleData\n", "DynamicControlSurface\n", "FloatingForces\n", "TurbVelocityField\n", "TrajectoryGenerator\n", "PolarCorrection\n", "EfficiencyCorrection\n", "ModifyStructure\n", "GustVelocityField\n", "SteadyVelocityField\n", "HelicoidalWake\n", "BumpVelocityField\n", "GridBox\n", "StraightWake\n", "TurbVelocityFieldBts\n", "ShearVelocityField\n", "BladePitchPid\n", "ControlSurfacePidController\n", "TakeOffTrajectoryController\n" ] } ], "source": [ "# Gather data about available solvers\n", "SimInfo = gc.SimulationInformation() # Initialises the SimulationInformation class\n", "SimInfo.set_default_values() # Assigns the default values to all the solvers\n", "\n", "# Print the available solvers and postprocessors\n", "for key in SimInfo.solvers.keys():\n", " print(key)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As an example, let's print the input parameters of the ``BeamLoader`` solver. This solver is in charge of loading the structural infromation." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'unsteady': True, 'orientation': [1.0, 0, 0, 0], 'for_pos': [0.0, 0, 0]}" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "SimInfo.solvers['BeamLoader']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following dictionary defines the basic inputs of SHARPy including the solvers to be run (``flow`` variable), the case name and the route in the file system. In this case we are turning off the screen output because we are not running any computation; it is useless." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader',\n", " 'AerogridLoader']\n", "\n", "SimInfo.solvers['SHARPy']['case'] = 'plot'\n", "SimInfo.solvers['SHARPy']['route'] = './'\n", "SimInfo.solvers['SHARPy']['write_screen'] = 'off'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We do not modify any of the default input paramteters in ``BeamLoader`` but we need to define the initial wake shape that we want and its size (``wake_panels``)." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "SimInfo.solvers['AerogridLoader']['unsteady'] = 'on'\n", "SimInfo.solvers['AerogridLoader']['mstar'] = wake_panels\n", "SimInfo.solvers['AerogridLoader']['freestream_dir'] = np.array([1.,0.,0.])\n", "SimInfo.solvers['AerogridLoader']['wake_shape_generator'] = 'StraightWake'\n", "SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] = {'u_inf': WSP,\n", " 'u_inf_direction' : np.array(\n", " [np.cos(aoa_ini_deg*deg2rad),\n", " 0.,\n", " np.sin(aoa_ini_deg*deg2rad)]),\n", " 'dt': dt}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following functions write the input files needed by SHARPy." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case'])\n", "wing.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case'])\n", "SimInfo.generate_solver_file()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following line of code runs SHARPy inside of jupyter notebook. It is equivalent to running this in a terminal, with: ``sharpy plot.sharpy`` being \"plot\", the case name defined above." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "fatal: not a git repository (or any of the parent directories): .git\n" ] } ], "source": [ "sharpy_output = sharpy.sharpy_main.main(['',\n", " SimInfo.solvers['SHARPy']['route'] +\n", " SimInfo.solvers['SHARPy']['case'] +\n", " '.sharpy'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's plot the case we have created. The function below provides a quick way of generating an interactive plot but it is not suitable for large cases. Luckily SHARPy has other more complex plotting capabilities.\n", "\n", "If you download the original [jupyter notebook](https://ic-sharpy.readthedocs.io/en/main/content/examples.html), change the geometric parameters above and rerun the notebook, you will see its impact on the geometry.\n", "\n", "In our simulations only the first 6 wake panels are plotted for the sake of efficiency. This can be changed by changing the number within ```minus_mstar```.\n", "\n", "Setting ```plotly``` to ```False``` will not plot a graph.\n", "\n", "```custom_scaling``` is a toggleable feature that realistically models the wing's aspect ratio and compresses the z axis. It is disabled by default.\n", "\n", "If ```custom_scaling``` is toggled on,```z_compression``` determines how compressed the z axis is. By default this is set to 0.5." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/html": [ " \n", " " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.plotly.v1+json": { "config": { "plotlyServerURL": "https://plot.ly" }, "data": [ { "line": { "color": "grey" }, "mode": "lines", "name": "Aero surface", "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ -7.126160546901204e-18, -3.054068805814802e-18, 0.8, 0.8, -7.126160546901204e-18 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 0.8, 0.8, 1.6, 1.6, 0.8 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 1.6, 1.6, 2.4000000000000004, 2.4000000000000004, 1.6 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 2.4000000000000004, 2.4000000000000004, 3.2, 3.2, 2.4000000000000004 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 3.2, 3.2, 4, 4, 3.2 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 4, 4, 4.800000000000001, 4.800000000000001, 4 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 4.800000000000001, 4.800000000000001, 5.6000000000000005, 5.6000000000000005, 4.800000000000001 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 5.6000000000000005, 5.6000000000000005, 6.4, 6.4, 5.6000000000000005 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 6.4, 6.4, 7.2, 7.2, 6.4 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 7.2, 7.2, 8, 8, 7.2 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 8, 8, 8.8, 8.8, 8 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 8.8, 8.8, 9.600000000000001, 9.600000000000001, 8.8 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 9.600000000000001, 9.600000000000001, 10.4, 10.4, 9.600000000000001 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 10.4, 10.4, 11.200000000000001, 11.200000000000001, 10.4 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 11.200000000000001, 11.200000000000001, 12, 12, 11.200000000000001 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 12, 12, 12.8, 12.8, 12 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 12.8, 12.8, 13.600000000000001, 13.600000000000001, 12.8 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 13.600000000000001, 13.600000000000001, 14.4, 14.4, 13.600000000000001 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 14.4, 14.4, 15.200000000000001, 15.200000000000001, 14.4 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.18750000000000006, -0.4375000000000001, -0.4375000000000001 ], "y": [ 15.200000000000001, 15.200000000000001, 16, 16, 15.200000000000001 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ -3.054068805814802e-18, 1.0180229352716006e-18, 0.8, 0.8, -3.054068805814802e-18 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 0.8, 0.8, 1.6, 1.6, 0.8 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 1.6, 1.6, 2.4000000000000004, 2.4000000000000004, 1.6 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 2.4000000000000004, 2.4000000000000004, 3.2, 3.2, 2.4000000000000004 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 3.2, 3.2, 4, 4, 3.2 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 4, 4, 4.800000000000001, 4.800000000000001, 4 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 4.800000000000001, 4.800000000000001, 5.6000000000000005, 5.6000000000000005, 4.800000000000001 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 5.6000000000000005, 5.6000000000000005, 6.4, 6.4, 5.6000000000000005 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 6.4, 6.4, 7.2, 7.2, 6.4 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 7.2, 7.2, 8, 8, 7.2 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 8, 8, 8.8, 8.8, 8 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 8.8, 8.8, 9.600000000000001, 9.600000000000001, 8.8 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 9.600000000000001, 9.600000000000001, 10.4, 10.4, 9.600000000000001 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 10.4, 10.4, 11.200000000000001, 11.200000000000001, 10.4 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 11.200000000000001, 11.200000000000001, 12, 12, 11.200000000000001 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 12, 12, 12.8, 12.8, 12 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 12.8, 12.8, 13.600000000000001, 13.600000000000001, 12.8 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 13.600000000000001, 13.600000000000001, 14.4, 14.4, 13.600000000000001 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 14.4, 14.4, 15.200000000000001, 15.200000000000001, 14.4 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.06250000000000001, -0.18750000000000006, -0.18750000000000006 ], "y": [ 15.200000000000001, 15.200000000000001, 16, 16, 15.200000000000001 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 5.412225351471832e-18, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 1.0180229352716006e-18, 5.090114676358003e-18, 0.8, 0.8, 1.0180229352716006e-18 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 0.8, 0.8, 1.6, 1.6, 0.8 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 1.6, 1.6, 2.4000000000000004, 2.4000000000000004, 1.6 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 2.4000000000000004, 2.4000000000000004, 3.2, 3.2, 2.4000000000000004 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 3.2, 3.2, 4, 4, 3.2 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 4, 4, 4.800000000000001, 4.800000000000001, 4 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 4.800000000000001, 4.800000000000001, 5.6000000000000005, 5.6000000000000005, 4.800000000000001 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 5.6000000000000005, 5.6000000000000005, 6.4, 6.4, 5.6000000000000005 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 6.4, 6.4, 7.2, 7.2, 6.4 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 7.2, 7.2, 8, 8, 7.2 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 8, 8, 8.8, 8.8, 8 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 8.8, 8.8, 9.600000000000001, 9.600000000000001, 8.8 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 9.600000000000001, 9.600000000000001, 10.4, 10.4, 9.600000000000001 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 10.4, 10.4, 11.200000000000001, 11.200000000000001, 10.4 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 11.200000000000001, 11.200000000000001, 12, 12, 11.200000000000001 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 12, 12, 12.8, 12.8, 12 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 12.8, 12.8, 13.600000000000001, 13.600000000000001, 12.8 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 13.600000000000001, 13.600000000000001, 14.4, 14.4, 13.600000000000001 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 14.4, 14.4, 15.200000000000001, 15.200000000000001, 14.4 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31250000000000006, 0.06250000000000001, 0.06250000000000001 ], "y": [ 15.200000000000001, 15.200000000000001, 16, 16, 15.200000000000001 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 2.706112675735916e-17, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 5.090114676358003e-18, 9.162206417444405e-18, 0.8, 0.8, 5.090114676358003e-18 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 0.8, 0.8, 1.6, 1.6, 0.8 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 1.6, 1.6, 2.4000000000000004, 2.4000000000000004, 1.6 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 2.4000000000000004, 2.4000000000000004, 3.2, 3.2, 2.4000000000000004 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 3.2, 3.2, 4, 4, 3.2 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 4, 4, 4.800000000000001, 4.800000000000001, 4 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 4.800000000000001, 4.800000000000001, 5.6000000000000005, 5.6000000000000005, 4.800000000000001 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 5.6000000000000005, 5.6000000000000005, 6.4, 6.4, 5.6000000000000005 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 6.4, 6.4, 7.2, 7.2, 6.4 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 7.2, 7.2, 8, 8, 7.2 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 8, 8, 8.8, 8.8, 8 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 8.8, 8.8, 9.600000000000001, 9.600000000000001, 8.8 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 9.600000000000001, 9.600000000000001, 10.4, 10.4, 9.600000000000001 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 10.4, 10.4, 11.200000000000001, 11.200000000000001, 10.4 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 11.200000000000001, 11.200000000000001, 12, 12, 11.200000000000001 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 12, 12, 12.8, 12.8, 12 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 12.8, 12.8, 13.600000000000001, 13.600000000000001, 12.8 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 13.600000000000001, 13.600000000000001, 14.4, 14.4, 13.600000000000001 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 14.4, 14.4, 15.200000000000001, 15.200000000000001, 14.4 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5625000000000001, 0.31250000000000006, 0.31250000000000006 ], "y": [ 15.200000000000001, 15.200000000000001, 16, 16, 15.200000000000001 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 4.871002816324649e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001, -0.4375000000000001 ], "y": [ -7.126160546901204e-18, 0.8, 1.6, 2.4000000000000004, 3.2, 4, 4.800000000000001, 5.6000000000000005, 6.4, 7.2, 8, 8.8, 9.600000000000001, 10.4, 11.200000000000001, 12, 12.8, 13.600000000000001, 14.4, 15.200000000000001, 16 ], "z": [ -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17, -3.7885577460302824e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006, -0.18750000000000006 ], "y": [ -3.054068805814802e-18, 0.8, 1.6, 2.4000000000000004, 3.2, 4, 4.800000000000001, 5.6000000000000005, 6.4, 7.2, 8, 8.8, 9.600000000000001, 10.4, 11.200000000000001, 12, 12.8, 13.600000000000001, 14.4, 15.200000000000001, 16 ], "z": [ -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17, -1.6236676054415498e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001, 0.06250000000000001 ], "y": [ 1.0180229352716006e-18, 0.8, 1.6, 2.4000000000000004, 3.2, 4, 4.800000000000001, 5.6000000000000005, 6.4, 7.2, 8, 8.8, 9.600000000000001, 10.4, 11.200000000000001, 12, 12.8, 13.600000000000001, 14.4, 15.200000000000001, 16 ], "z": [ 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18, 5.412225351471832e-18 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006, 0.31250000000000006 ], "y": [ 5.090114676358003e-18, 0.8, 1.6, 2.4000000000000004, 3.2, 4, 4.800000000000001, 5.6000000000000005, 6.4, 7.2, 8, 8.8, 9.600000000000001, 10.4, 11.200000000000001, 12, 12.8, 13.600000000000001, 14.4, 15.200000000000001, 16 ], "z": [ 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17, 2.706112675735916e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 9.162206417444405e-18, 0.8, 1.6, 2.4000000000000004, 3.2, 4, 4.800000000000001, 5.6000000000000005, 6.4, 7.2, 8, 8.8, 9.600000000000001, 10.4, 11.200000000000001, 12, 12.8, 13.600000000000001, 14.4, 15.200000000000001, 16 ], "z": [ 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ -7.126160546901204e-18, -3.054068805814802e-18, 1.0180229352716006e-18, 5.090114676358003e-18, 9.162206417444405e-18 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 0.8, 0.8, 0.8, 0.8, 0.8 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 1.6, 1.6, 1.6, 1.6, 1.6 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 2.4000000000000004, 2.4000000000000004, 2.4000000000000004, 2.4000000000000004, 2.4000000000000004 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 3.2, 3.2, 3.2, 3.2, 3.2 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 4, 4, 4, 4, 4 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 4.800000000000001, 4.800000000000001, 4.800000000000001, 4.800000000000001, 4.800000000000001 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 5.6000000000000005, 5.6000000000000005, 5.6000000000000005, 5.6000000000000005, 5.6000000000000005 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 6.4, 6.4, 6.4, 6.4, 6.4 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 7.2, 7.2, 7.2, 7.2, 7.2 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 8, 8, 8, 8, 8 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 8.8, 8.8, 8.8, 8.8, 8.8 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 9.600000000000001, 9.600000000000001, 9.600000000000001, 9.600000000000001, 9.600000000000001 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 10.4, 10.4, 10.4, 10.4, 10.4 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 11.200000000000001, 11.200000000000001, 11.200000000000001, 11.200000000000001, 11.200000000000001 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 12, 12, 12, 12, 12 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 12.8, 12.8, 12.8, 12.8, 12.8 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 13.600000000000001, 13.600000000000001, 13.600000000000001, 13.600000000000001, 13.600000000000001 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 14.4, 14.4, 14.4, 14.4, 14.4 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 15.200000000000001, 15.200000000000001, 15.200000000000001, 15.200000000000001, 15.200000000000001 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ 16, 16, 16, 16, 16 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "name": "Aero wake", "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.8, 0.8, 9.162206417444405e-18 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 0.8, 0.8, 1.6, 1.6, 0.8 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 1.6, 1.6, 2.4000000000000004, 2.4000000000000004, 1.6 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 2.4000000000000004, 2.4000000000000004, 3.2, 3.2, 2.4000000000000004 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 3.2, 3.2, 4, 4, 3.2 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 4, 4, 4.800000000000001, 4.800000000000001, 4 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 4.800000000000001, 4.800000000000001, 5.6000000000000005, 5.6000000000000005, 4.800000000000001 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 5.6000000000000005, 5.6000000000000005, 6.4, 6.4, 5.6000000000000005 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 6.4, 6.4, 7.2, 7.2, 6.4 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 7.2, 7.2, 8, 8, 7.2 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 8, 8, 8.8, 8.8, 8 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 8.8, 8.8, 9.600000000000001, 9.600000000000001, 8.8 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 9.600000000000001, 9.600000000000001, 10.4, 10.4, 9.600000000000001 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 10.4, 10.4, 11.200000000000001, 11.200000000000001, 10.4 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 11.200000000000001, 11.200000000000001, 12, 12, 11.200000000000001 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 12, 12, 12.8, 12.8, 12 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 12.8, 12.8, 13.600000000000001, 13.600000000000001, 12.8 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 13.600000000000001, 13.600000000000001, 14.4, 14.4, 13.600000000000001 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 14.4, 14.4, 15.200000000000001, 15.200000000000001, 14.4 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123477075000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 15.200000000000001, 15.200000000000001, 16, 16, 15.200000000000001 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008724875000000049, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.8, 0.8, 9.162206417444405e-18 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 0.8, 0.8, 1.6, 1.6, 0.8 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 1.6, 1.6, 2.4000000000000004, 2.4000000000000004, 1.6 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 2.4000000000000004, 2.4000000000000004, 3.2, 3.2, 2.4000000000000004 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 3.2, 3.2, 4, 4, 3.2 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 4, 4, 4.800000000000001, 4.800000000000001, 4 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 4.800000000000001, 4.800000000000001, 5.6000000000000005, 5.6000000000000005, 4.800000000000001 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 5.6000000000000005, 5.6000000000000005, 6.4, 6.4, 5.6000000000000005 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 6.4, 6.4, 7.2, 7.2, 6.4 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 7.2, 7.2, 8, 8, 7.2 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 8, 8, 8.8, 8.8, 8 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 8.8, 8.8, 9.600000000000001, 9.600000000000001, 8.8 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 9.600000000000001, 9.600000000000001, 10.4, 10.4, 9.600000000000001 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 10.4, 10.4, 11.200000000000001, 11.200000000000001, 10.4 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 11.200000000000001, 11.200000000000001, 12, 12, 11.200000000000001 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 12, 12, 12.8, 12.8, 12 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 12.8, 12.8, 13.600000000000001, 13.600000000000001, 12.8 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 13.600000000000001, 13.600000000000001, 14.4, 14.4, 13.600000000000001 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 14.4, 14.4, 15.200000000000001, 15.200000000000001, 14.4 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.0621954150000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 15.200000000000001, 15.200000000000001, 16, 16, 15.200000000000001 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01744975000000005, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.8, 0.8, 9.162206417444405e-18 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 0.8, 0.8, 1.6, 1.6, 0.8 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 1.6, 1.6, 2.4000000000000004, 2.4000000000000004, 1.6 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 2.4000000000000004, 2.4000000000000004, 3.2, 3.2, 2.4000000000000004 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 3.2, 3.2, 4, 4, 3.2 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 4, 4, 4.800000000000001, 4.800000000000001, 4 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 4.800000000000001, 4.800000000000001, 5.6000000000000005, 5.6000000000000005, 4.800000000000001 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 5.6000000000000005, 5.6000000000000005, 6.4, 6.4, 5.6000000000000005 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 6.4, 6.4, 7.2, 7.2, 6.4 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 7.2, 7.2, 8, 8, 7.2 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 8, 8, 8.8, 8.8, 8 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 8.8, 8.8, 9.600000000000001, 9.600000000000001, 8.8 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 9.600000000000001, 9.600000000000001, 10.4, 10.4, 9.600000000000001 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 10.4, 10.4, 11.200000000000001, 11.200000000000001, 10.4 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 11.200000000000001, 11.200000000000001, 12, 12, 11.200000000000001 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 12, 12, 12.8, 12.8, 12 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 12.8, 12.8, 13.600000000000001, 13.600000000000001, 12.8 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 13.600000000000001, 13.600000000000001, 14.4, 14.4, 13.600000000000001 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 14.4, 14.4, 15.200000000000001, 15.200000000000001, 14.4 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.3120431225000002, 1.0621954150000001, 1.0621954150000001 ], "y": [ 15.200000000000001, 15.200000000000001, 16, 16, 15.200000000000001 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02617462500000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.8, 0.8, 9.162206417444405e-18 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 0.8, 0.8, 1.6, 1.6, 0.8 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 1.6, 1.6, 2.4000000000000004, 2.4000000000000004, 1.6 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 2.4000000000000004, 2.4000000000000004, 3.2, 3.2, 2.4000000000000004 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 3.2, 3.2, 4, 4, 3.2 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 4, 4, 4.800000000000001, 4.800000000000001, 4 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 4.800000000000001, 4.800000000000001, 5.6000000000000005, 5.6000000000000005, 4.800000000000001 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 5.6000000000000005, 5.6000000000000005, 6.4, 6.4, 5.6000000000000005 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 6.4, 6.4, 7.2, 7.2, 6.4 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 7.2, 7.2, 8, 8, 7.2 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 8, 8, 8.8, 8.8, 8 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 8.8, 8.8, 9.600000000000001, 9.600000000000001, 8.8 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 9.600000000000001, 9.600000000000001, 10.4, 10.4, 9.600000000000001 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 10.4, 10.4, 11.200000000000001, 11.200000000000001, 10.4 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 11.200000000000001, 11.200000000000001, 12, 12, 11.200000000000001 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 12, 12, 12.8, 12.8, 12 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 12.8, 12.8, 13.600000000000001, 13.600000000000001, 12.8 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 13.600000000000001, 13.600000000000001, 14.4, 14.4, 13.600000000000001 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 14.4, 14.4, 15.200000000000001, 15.200000000000001, 14.4 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908300000003, 1.3120431225000002, 1.3120431225000002 ], "y": [ 15.200000000000001, 15.200000000000001, 16, 16, 15.200000000000001 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03489950000000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.8, 0.8, 9.162206417444405e-18 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 0.8, 0.8, 1.6, 1.6, 0.8 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 1.6, 1.6, 2.4000000000000004, 2.4000000000000004, 1.6 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 2.4000000000000004, 2.4000000000000004, 3.2, 3.2, 2.4000000000000004 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 3.2, 3.2, 4, 4, 3.2 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 4, 4, 4.800000000000001, 4.800000000000001, 4 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 4.800000000000001, 4.800000000000001, 5.6000000000000005, 5.6000000000000005, 4.800000000000001 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 5.6000000000000005, 5.6000000000000005, 6.4, 6.4, 5.6000000000000005 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 6.4, 6.4, 7.2, 7.2, 6.4 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 7.2, 7.2, 8, 8, 7.2 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 8, 8, 8.8, 8.8, 8 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 8.8, 8.8, 9.600000000000001, 9.600000000000001, 8.8 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 9.600000000000001, 9.600000000000001, 10.4, 10.4, 9.600000000000001 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 10.4, 10.4, 11.200000000000001, 11.200000000000001, 10.4 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 11.200000000000001, 11.200000000000001, 12, 12, 11.200000000000001 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 12, 12, 12.8, 12.8, 12 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 12.8, 12.8, 13.600000000000001, 13.600000000000001, 12.8 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 13.600000000000001, 13.600000000000001, 14.4, 14.4, 13.600000000000001 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 14.4, 14.4, 15.200000000000001, 15.200000000000001, 14.4 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385375000004, 1.5618908300000003, 1.5618908300000003 ], "y": [ 15.200000000000001, 15.200000000000001, 16, 16, 15.200000000000001 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04362437500000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.8, 0.8, 9.162206417444405e-18 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 0.8, 0.8, 1.6, 1.6, 0.8 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 1.6, 1.6, 2.4000000000000004, 2.4000000000000004, 1.6 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 2.4000000000000004, 2.4000000000000004, 3.2, 3.2, 2.4000000000000004 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 3.2, 3.2, 4, 4, 3.2 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 4, 4, 4.800000000000001, 4.800000000000001, 4 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 4.800000000000001, 4.800000000000001, 5.6000000000000005, 5.6000000000000005, 4.800000000000001 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 5.6000000000000005, 5.6000000000000005, 6.4, 6.4, 5.6000000000000005 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 6.4, 6.4, 7.2, 7.2, 6.4 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 7.2, 7.2, 8, 8, 7.2 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 8, 8, 8.8, 8.8, 8 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 8.8, 8.8, 9.600000000000001, 9.600000000000001, 8.8 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 9.600000000000001, 9.600000000000001, 10.4, 10.4, 9.600000000000001 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 10.4, 10.4, 11.200000000000001, 11.200000000000001, 10.4 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 11.200000000000001, 11.200000000000001, 12, 12, 11.200000000000001 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 12, 12, 12.8, 12.8, 12 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 12.8, 12.8, 13.600000000000001, 13.600000000000001, 12.8 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 13.600000000000001, 13.600000000000001, 14.4, 14.4, 13.600000000000001 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 14.4, 14.4, 15.200000000000001, 15.200000000000001, 14.4 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862450000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 15.200000000000001, 15.200000000000001, 16, 16, 15.200000000000001 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05234925000000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001, 0.5625000000000001 ], "y": [ 9.162206417444405e-18, 0.8, 1.6, 2.4000000000000004, 3.2, 4, 4.800000000000001, 5.6000000000000005, 6.4, 7.2, 8, 8.8, 9.600000000000001, 10.4, 11.200000000000001, 12, 12.8, 13.600000000000001, 14.4, 15.200000000000001, 16 ], "z": [ 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17, 4.871002816324649e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001, 0.8123477075000001 ], "y": [ 9.162206417444405e-18, 0.8, 1.6, 2.4000000000000004, 3.2, 4, 4.800000000000001, 5.6000000000000005, 6.4, 7.2, 8, 8.8, 9.600000000000001, 10.4, 11.200000000000001, 12, 12.8, 13.600000000000001, 14.4, 15.200000000000001, 16 ], "z": [ 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049, 0.008724875000000049 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001, 1.0621954150000001 ], "y": [ 9.162206417444405e-18, 0.8, 1.6, 2.4000000000000004, 3.2, 4, 4.800000000000001, 5.6000000000000005, 6.4, 7.2, 8, 8.8, 9.600000000000001, 10.4, 11.200000000000001, 12, 12.8, 13.600000000000001, 14.4, 15.200000000000001, 16 ], "z": [ 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005, 0.01744975000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002, 1.3120431225000002 ], "y": [ 9.162206417444405e-18, 0.8, 1.6, 2.4000000000000004, 3.2, 4, 4.800000000000001, 5.6000000000000005, 6.4, 7.2, 8, 8.8, 9.600000000000001, 10.4, 11.200000000000001, 12, 12.8, 13.600000000000001, 14.4, 15.200000000000001, 16 ], "z": [ 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005, 0.02617462500000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003, 1.5618908300000003 ], "y": [ 9.162206417444405e-18, 0.8, 1.6, 2.4000000000000004, 3.2, 4, 4.800000000000001, 5.6000000000000005, 6.4, 7.2, 8, 8.8, 9.600000000000001, 10.4, 11.200000000000001, 12, 12.8, 13.600000000000001, 14.4, 15.200000000000001, 16 ], "z": [ 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005, 0.03489950000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004, 1.8117385375000004 ], "y": [ 9.162206417444405e-18, 0.8, 1.6, 2.4000000000000004, 3.2, 4, 4.800000000000001, 5.6000000000000005, 6.4, 7.2, 8, 8.8, 9.600000000000001, 10.4, 11.200000000000001, 12, 12.8, 13.600000000000001, 14.4, 15.200000000000001, 16 ], "z": [ 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005, 0.04362437500000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004, 2.0615862450000004 ], "y": [ 9.162206417444405e-18, 0.8, 1.6, 2.4000000000000004, 3.2, 4, 4.800000000000001, 5.6000000000000005, 6.4, 7.2, 8, 8.8, 9.600000000000001, 10.4, 11.200000000000001, 12, 12.8, 13.600000000000001, 14.4, 15.200000000000001, 16 ], "z": [ 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 2.4000000000000004, 2.4000000000000004, 2.4000000000000004, 2.4000000000000004, 2.4000000000000004, 2.4000000000000004, 2.4000000000000004 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 3.2, 3.2, 3.2, 3.2, 3.2, 3.2, 3.2 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 4, 4, 4, 4, 4, 4, 4 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 4.800000000000001, 4.800000000000001, 4.800000000000001, 4.800000000000001, 4.800000000000001, 4.800000000000001, 4.800000000000001 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 5.6000000000000005, 5.6000000000000005, 5.6000000000000005, 5.6000000000000005, 5.6000000000000005, 5.6000000000000005, 5.6000000000000005 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 6.4, 6.4, 6.4, 6.4, 6.4, 6.4, 6.4 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 7.2, 7.2, 7.2, 7.2, 7.2, 7.2, 7.2 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 8, 8, 8, 8, 8, 8, 8 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 8.8, 8.8, 8.8, 8.8, 8.8, 8.8, 8.8 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 9.600000000000001, 9.600000000000001, 9.600000000000001, 9.600000000000001, 9.600000000000001, 9.600000000000001, 9.600000000000001 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 10.4, 10.4, 10.4, 10.4, 10.4, 10.4, 10.4 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 11.200000000000001, 11.200000000000001, 11.200000000000001, 11.200000000000001, 11.200000000000001, 11.200000000000001, 11.200000000000001 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 12, 12, 12, 12, 12, 12, 12 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 12.8, 12.8, 12.8, 12.8, 12.8, 12.8, 12.8 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 13.600000000000001, 13.600000000000001, 13.600000000000001, 13.600000000000001, 13.600000000000001, 13.600000000000001, 13.600000000000001 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 14.4, 14.4, 14.4, 14.4, 14.4, 14.4, 14.4 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 15.200000000000001, 15.200000000000001, 15.200000000000001, 15.200000000000001, 15.200000000000001, 15.200000000000001, 15.200000000000001 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 16, 16, 16, 16, 16, 16, 16 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "name": "Beam nodes", "type": "scatter3d", "x": [ 0, 0, 0 ], "y": [ 0, 0.8, 1.6 ], "z": [ 0, 0, 0 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ 0, 0, 0 ], "y": [ 1.6, 2.4000000000000004, 3.2 ], "z": [ 0, 0, 0 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ 0, 0, 0 ], "y": [ 3.2, 4, 4.800000000000001 ], "z": [ 0, 0, 0 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ 0, 0, 0 ], "y": [ 4.800000000000001, 5.6000000000000005, 6.4 ], "z": [ 0, 0, 0 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ 0, 0, 0 ], "y": [ 6.4, 7.2, 8 ], "z": [ 0, 0, 0 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ 0, 0, 0 ], "y": [ 8, 8.8, 9.600000000000001 ], "z": [ 0, 0, 0 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ 0, 0, 0 ], "y": [ 9.600000000000001, 10.4, 11.200000000000001 ], "z": [ 0, 0, 0 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ 0, 0, 0 ], "y": [ 11.200000000000001, 12, 12.8 ], "z": [ 0, 0, 0 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ 0, 0, 0 ], "y": [ 12.8, 13.600000000000001, 14.4 ], "z": [ 0, 0, 0 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ 0, 0, 0 ], "y": [ 14.4, 15.200000000000001, 16 ], "z": [ 0, 0, 0 ] } ], "layout": { "autosize": true, "scene": { "aspectmode": "auto", "aspectratio": { "x": 1, "y": 1, "z": 1 }, "camera": { "center": { "x": 0, "y": 0, "z": 0 }, "eye": { "x": -1.2315038942222687, "y": -1.577291494357572, "z": 0.8264682089122644 }, "projection": { "type": "perspective" }, "up": { "x": 0, "y": 0, "z": 1 } } }, "template": { "data": { "bar": [ { "error_x": { "color": "#2a3f5f" }, "error_y": { "color": "#2a3f5f" }, "marker": { "line": { "color": "#E5ECF6", "width": 0.5 }, "pattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 } }, "type": "bar" } ], "barpolar": [ { "marker": { "line": { "color": "#E5ECF6", "width": 0.5 }, "pattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 } }, "type": "barpolar" } ], "carpet": [ { "aaxis": { "endlinecolor": "#2a3f5f", "gridcolor": "white", "linecolor": "white", "minorgridcolor": "white", "startlinecolor": "#2a3f5f" }, "baxis": { "endlinecolor": "#2a3f5f", "gridcolor": "white", "linecolor": "white", "minorgridcolor": "white", "startlinecolor": "#2a3f5f" }, "type": "carpet" } ], "choropleth": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "type": "choropleth" } ], "contour": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "contour" } ], "contourcarpet": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "type": "contourcarpet" } ], "heatmap": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "heatmap" } ], "heatmapgl": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "heatmapgl" } ], "histogram": [ { "marker": { "pattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 } }, "type": "histogram" } ], "histogram2d": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "histogram2d" } ], "histogram2dcontour": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "histogram2dcontour" } ], "mesh3d": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "type": "mesh3d" } ], "parcoords": [ { "line": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "parcoords" } ], "pie": [ { "automargin": true, "type": "pie" } ], "scatter": [ { "fillpattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 }, "type": "scatter" } ], "scatter3d": [ { "line": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatter3d" } ], "scattercarpet": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattercarpet" } ], "scattergeo": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattergeo" } ], "scattergl": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattergl" } ], "scattermapbox": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattermapbox" } ], "scatterpolar": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatterpolar" } ], "scatterpolargl": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatterpolargl" } ], "scatterternary": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatterternary" } ], "surface": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "surface" } ], "table": [ { "cells": { "fill": { "color": "#EBF0F8" }, "line": { "color": "white" } }, "header": { "fill": { "color": "#C8D4E3" }, "line": { "color": "white" } }, "type": "table" } ] }, "layout": { "annotationdefaults": { "arrowcolor": "#2a3f5f", "arrowhead": 0, "arrowwidth": 1 }, "autotypenumbers": "strict", "coloraxis": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "colorscale": { "diverging": [ [ 0, "#8e0152" ], [ 0.1, "#c51b7d" ], [ 0.2, "#de77ae" ], [ 0.3, "#f1b6da" ], [ 0.4, "#fde0ef" ], [ 0.5, "#f7f7f7" ], [ 0.6, "#e6f5d0" ], [ 0.7, "#b8e186" ], [ 0.8, "#7fbc41" ], [ 0.9, "#4d9221" ], [ 1, "#276419" ] ], "sequential": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "sequentialminus": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ] }, "colorway": [ "#636efa", "#EF553B", "#00cc96", "#ab63fa", "#FFA15A", "#19d3f3", "#FF6692", "#B6E880", "#FF97FF", "#FECB52" ], "font": { "color": "#2a3f5f" }, "geo": { "bgcolor": "white", "lakecolor": "white", "landcolor": "#E5ECF6", "showlakes": true, "showland": true, "subunitcolor": "white" }, "hoverlabel": { "align": "left" }, "hovermode": "closest", "mapbox": { "style": "light" }, "paper_bgcolor": "white", "plot_bgcolor": "#E5ECF6", "polar": { "angularaxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" }, "bgcolor": "#E5ECF6", "radialaxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" } }, "scene": { "xaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white" }, "yaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white" }, "zaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white" } }, "shapedefaults": { "line": { "color": "#2a3f5f" } }, "ternary": { "aaxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" }, "baxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" }, "bgcolor": "#E5ECF6", "caxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" } }, "title": { "x": 0.05 }, "xaxis": { "automargin": true, "gridcolor": "white", "linecolor": "white", "ticks": "", "title": { "standoff": 15 }, "zerolinecolor": "white", "zerolinewidth": 2 }, "yaxis": { "automargin": true, "gridcolor": "white", "linecolor": "white", "ticks": "", "title": { "standoff": 15 }, "zerolinecolor": "white", "zerolinewidth": 2 } } } } }, "image/png": "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ETE5FQIIIIAAAggggAACCCCAAAIIIBAiQEATosYxCCCAAAIIIIAAAggggAACCCCAQEQBApqImJwKAQQQQAABBBBAAAEEEEAAAQQQCBEgoAlR4xgEEEAAAQQQQAABBBBAAAEEEEAgogABTURMToUAAggggAACCCCAAAIIIIAAAgiECBDQhKhxDAIIIIAAAggggAACCCCAAAIIIBBRgIAmIianQgABBBBAAAEEEEAAAQQQQAABBEIECGhC1DgGAQQQQAABBBBAAAEEEEAAAQQQiChAQBMRk1MhgAACCCCAAAIIIIAAAggggAACIQIENCFqHIMAAggggAACCCCAAAIIIIAAAghEFCCgiYjJqRBAAAEEEEAAAQQQQAABBBBAAIEQAQKaEDWOQQABBBBAAAEEEEAAAQQQQAABBCIKENBExORUCCCAAAIIIIAAAggggAACCCCAQIgAAU2IGscggAACCCCAAAIIIIAAAggggAACEQUIaCJicioEEEAAAQQQQAABBBBAAAEEEEAgRICAJkSNYxBAAAEEEEAAAQQQQAABBBBAAIGIAgQ0ETE5FQIIIIAAAggggAACCCCAAAIIIBAiQEATosYxCCCAAAIIIIAAAggggAACCCCAQEQBApqImJwKAQQQQAABBBBAAAEEEEAAAQQQCBEgoAlR4xgEEEAAAQQQQAABBBBAAAEEEEAgogABTURMToUAAggggAACCCCAAAIIIIAAAgiECBDQhKhxDAIIIIAAAggggAACCCCAAAIIIBBRgIAmIianQgABBBBAAAEEEEAAAQQQQAABBEIECGhC1DgGAQQQQAABBBBAAAEEEEAAAQQQiChAQBMRk1MhgAACCCCAAAIIIIAAAggggAACIQIENCFqHIMAAggggAACCCCAAAIIIIAAAghEFCCgiYjJqRBAAAEEEEAAAQQQQAABBBBAAIEQAQKaEDWOQQABBBBAAAEEEEAAAQQQQAABBCIKENBExORUCCCAAAIIIIAAAggggAACCCCAQIgAAU2IGscggAACCCCAAAIIIIAAAggggAACEQUIaCJicioEEEAAAQQQQAABBBBAAAEEEEAgRICAJkSNYxBAAAEEEEAAAQQQQAABBBBAAIGIAgQ0ETE5FQIIIIAAAggggAACCCCAAAIIIBAiQEATosYxCCCAAAIIIIAAAggggAACCCCAQEQBApqImJwKAQQQQAABBBBAAAEEEEAAAQQQCBEgoAlR4xgEEEAAAQQQQAABBBBAAAEEEEAgogABTURMToUAAggggAACCCCAAAIIIIAAAgiECBDQhKhxDAIIIIAAAggggAACCCCAAAIIIBBRgIAmIianQgABBBBAAAEEEEAAAQQQQAABBEIECGhC1DgGAQQQQAABBBBAAAEEEEAAAQQQiChAQBMRk1MhgAACCCCAAAIIIIAAAggggAACIQIENCFqHIMAAggggAACCCCAAAIIIIAAAghEFCCgiYjJqRBAAAEEEEAAAQQQQAABBBBAAIEQAQKaEDWOQQABBBBAAAEEEEAAAQQQQAABBCIKENBExORUCCCAAAIIIIAAAggggAACCCCAQIgAAU2IGscggAACCCCAAAIIIIAAAggggAACEQUIaCJicioEEEAAAQQQQAABBBBAAAEEEEAgRICAJkSNYxBAAAEEEEAAAQQQQAABBBBAAIGIAgQ0ETE5FQIIIIAAAggggAACCCCAAAIIIBAiQEATosYxCCCAAAIIIIAAAggggAACCCCAQEQBApqImJwKAQQQQAABBBBAAAEEEEAAAQQQCBEgoAlR4xgEEEAAAQQQQAABBBBAAAEEEEAgogABTURMToUAAggggAACCCCAAAIIIIAAAgiECBDQhKhxDAIIIIAAAggggAACCCCAAAIIIBBRgIAmIianQgABBBBAAAEEEEAAAQQQQAABBEIECGhC1DgGAQQQQAABBBBAAAEEEEAAAQQQiChAQBMRk1MhgAACCCCAAAIIIIAAAggggAACIQIENCFqHIMAAggggAACCCCAAAIIIIAAAghEFCCgiYjJqRBAAAEEEEAAAQQQQAABBBBAAIEQAQKaEDWOQQABBBBAAAEEEEAAAQQQQAABBCIKENBExORUCCCAAAIIIIAAAggggAACCCCAQIgAAU2IGscggAACCCCAAAIIIIAAAggggAACEQUIaCJicioEEEAAAQQQQAABBBBAAAEEEEAgRICAJkSNYxBAAAEEEEAAAQQQQAABBBBAAIGIAgQ0ETE5FQIIIIAAAggggAACCCCAAAIIIBAiQEATosYxCCCAAAIIIIAAAggggAACCCCAQEQBApqImJwKAQQQQAABBBBAAAEEEEAAAQQQCBH4f7Qu3dQQjEGQAAAAAElFTkSuQmCC", "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pu.plot_timestep(sharpy_output, tstep=-1, minus_mstar=(wake_panels - 6), plotly=True,custom_scaling=False,z_compression=0.5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Static simulation\n", "Next, we will run a static simulation of the previous system. We have already defined the required inputs for ``BeamLoader`` and ``AerogridLoader`` but we need to define the static solvers. We are going to use an FSI solver (``StaticCoupled``) that will include a structural solver (``NonLinearStatic``) and aerodynamic solver (``StaticUvlm``). \n", "\n", "We will use most of the default values but redefine some of them:\n", "- We assign the air density to all the solvers that need it\n", "- We turn off the gravity on the structural solver\n", "- We use a horseshoe solution without roll up for the aerodynamics\n", "- We define the velocity field against the wing\n", "- We do not use load steps in the FSI solver\n", "\n", "In the documentation you can find further information about the [modular framework](https://ic-sharpy.readthedocs.io/en/main/content/casefiles.html#modular-framework) and about the [solvers](https://ic-sharpy.readthedocs.io/en/main/content/solvers.html) and their inputs. " ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "# Define the simulation\n", "SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader',\n", " 'AerogridLoader',\n", " 'StaticCoupled']\n", "\n", "SimInfo.set_variable_all_dicts('rho', air_density)\n", "\n", "SimInfo.solvers['SHARPy']['case'] = 'static'\n", "SimInfo.solvers['SHARPy']['write_screen'] = 'on'\n", "\n", "SimInfo.solvers['NonLinearStatic']['gravity_on'] = False\n", "\n", "SimInfo.solvers['StaticUvlm']['horseshoe'] = True\n", "SimInfo.solvers['StaticUvlm']['n_rollup'] = 0\n", "SimInfo.solvers['StaticUvlm']['velocity_field_generator'] = 'SteadyVelocityField'\n", "SimInfo.solvers['StaticUvlm']['velocity_field_input'] = {'u_inf' : WSP,\n", " 'u_inf_direction' : np.array(\n", " [np.cos(aoa_ini_deg*deg2rad),\n", " 0.,\n", " np.sin(aoa_ini_deg*deg2rad)])}\n", "\n", "SimInfo.solvers['StaticCoupled']['structural_solver'] = 'NonLinearStatic'\n", "SimInfo.solvers['StaticCoupled']['structural_solver_settings'] = SimInfo.solvers['NonLinearStatic']\n", "SimInfo.solvers['StaticCoupled']['aero_solver'] = 'StaticUvlm'\n", "SimInfo.solvers['StaticCoupled']['aero_solver_settings'] = SimInfo.solvers['StaticUvlm']\n", "SimInfo.solvers['StaticCoupled']['n_load_steps'] = 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following functions create the input files required by SHARPy. For further information check:\n", "- [Configuration file](https://ic-sharpy.readthedocs.io/en/main/content/casefiles.html#solver-configuration-file)\n", "- [FEM input file](https://ic-sharpy.readthedocs.io/en/main/content/casefiles.html#fem-file)\n", "- [Aerodynamic input file](https://ic-sharpy.readthedocs.io/en/main/content/casefiles.html#aerodynamics-file)\n" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case'])\n", "SimInfo.generate_solver_file()\n", "wing.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case'])" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--------------------------------------------------------------------------------\u001b[0m\n", " ###### ## ## ### ######## ######## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ####\u001b[0m\n", " ###### ######### ## ## ######## ######## ##\u001b[0m\n", " ## ## ## ######### ## ## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ##\u001b[0m\n", " ###### ## ## ## ## ## ## ## ##\u001b[0m\n", "--------------------------------------------------------------------------------\u001b[0m\n", "Aeroelastics Lab, Aeronautics Department.\u001b[0m\n", " Copyright (c), Imperial College London.\u001b[0m\n", " All rights reserved.\u001b[0m\n", " License available at https://github.com/imperialcollegelondon/sharpy\u001b[0m\n", "\u001b[36mRunning SHARPy from /home/loca/Downloads/sharpy copy/sharpy/docs/source/content/example_notebooks\u001b[0m\n", "\u001b[36mSHARPy being run is in /home/loca/anaconda3/envs/sharpy/lib/python3.10/site-packages\u001b[0m\n", "SHARPy output folder set\u001b[0m\n", "\u001b[34m\t./output//static/\u001b[0m\n", "\u001b[36mGenerating an instance of BeamLoader\u001b[0m\n", "\u001b[36mGenerating an instance of AerogridLoader\u001b[0m\n", "Variable dx1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: -1.0\u001b[0m\n", "Variable ndx1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable r has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1.0\u001b[0m\n", "Variable dxmax has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: -1.0\u001b[0m\n", "\u001b[34mThe aerodynamic grid contains 1 surfaces\u001b[0m\n", "\u001b[34m Surface 0, M=4, N=20\u001b[0m\n", " Wake 0, M=400, N=20\u001b[0m\n", " In total: 80 bound panels\u001b[0m\n", " In total: 8000 wake panels\u001b[0m\n", " Total number of panels = 8080\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "fatal: not a git repository (or any of the parent directories): .git\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\u001b[36mGenerating an instance of StaticCoupled\u001b[0m\n", "\u001b[36mGenerating an instance of NonLinearStatic\u001b[0m\n", "\u001b[36mGenerating an instance of StaticUvlm\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "|=====|=====|============|==========|==========|==========|==========|==========|==========|\u001b[0m\n", "|iter |step | log10(res) | Fx | Fy | Fz | Mx | My | Mz |\u001b[0m\n", "|=====|=====|============|==========|==========|==========|==========|==========|==========|\u001b[0m\n", " DeltaF DeltaX Res ResRel ResFrc ResRelFrc ResMmt ResRelMmt ErX ErPos ErPsi\n", "LoadStep Subiter DeltaF DeltaX Res ResRel ResFrc ResRelFrc ResMmt ResRelMmt ErX ErPos ErPsi\n", "| 0 | 0 | 0.00000 | -0.0187 | -0.0006 | 0.6029 | 4.8229 | 0.1523 | 0.1495 |\u001b[0m\n", " DeltaF DeltaX Res ResRel ResFrc ResRelFrc ResMmt ResRelMmt ErX ErPos ErPsi\n", "LoadStep Subiter DeltaF DeltaX Res ResRel ResFrc ResRelFrc ResMmt ResRelMmt ErX ErPos ErPsi\n", "| 1 | 0 | -9.22874 | -0.0188 | -0.0006 | 0.6043 | 4.8366 | 0.1526 | 0.1504 |\u001b[0m\n", "\u001b[36mFINISHED - Elapsed time = 1.3510996 seconds\u001b[0m\n", "\u001b[36mFINISHED - CPU process time = 5.6712286 seconds\u001b[0m\n" ] } ], "source": [ "# Running SHARPy again inside jupyter\n", "sharpy_output = sharpy.sharpy_main.main(['',\n", " SimInfo.solvers['SHARPy']['route'] +\n", " SimInfo.solvers['SHARPy']['case'] +\n", " '.sharpy'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The next function will plot the static equilibirium solution for the wing:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "application/vnd.plotly.v1+json": { "config": { "plotlyServerURL": "https://plot.ly" }, "data": [ { "line": { "color": "grey" }, "mode": "lines", "name": "Aero surface", "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.1875000237228303, -0.4375000236873756, -0.4375000000000001 ], "y": [ -7.126160546901204e-18, -3.054068805814802e-18, 0.8000022258875188, 0.8000051998112413, -7.126160546901204e-18 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 7.752893514114484e-05, 8.050939904993674e-05, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000236873756, -0.1875000237228303, -0.1875000627455803, -0.43750006260819346, -0.4375000236873756 ], "y": [ 0.8000051998112413, 0.8000022258875188, 1.600004355772871, 1.6000102094000985, 0.8000051998112413 ], "z": [ 8.050939904993674e-05, 7.752893514114484e-05, 0.0002946907979488142, 0.00030055837290183433, 8.050939904993674e-05 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750006260819346, -0.1875000627455803, -0.18750011356082813, -0.43750011326728777, -0.43750006260819346 ], "y": [ 1.6000102094000985, 1.600004355772871, 2.4000063093414794, 2.400014864900074, 1.6000102094000985 ], "z": [ 0.00030055837290183433, 0.0002946907979488142, 0.0006367385904886074, 0.000645316033046242, 0.00030055837290183433 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750011326728777, -0.18750011356082813, -0.18750017381202885, -0.4375001733139123, -0.43750011326728777 ], "y": [ 2.400014864900074, 2.4000063093414794, 3.200008122887067, 3.2000192670677388, 2.400014864900074 ], "z": [ 0.000645316033046242, 0.0006367385904886074, 0.001090003018371559, 0.001101177340912812, 0.000645316033046242 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375001733139123, -0.18750017381202885, -0.18750024087202166, -0.4375002401368169, -0.4375001733139123 ], "y": [ 3.2000192670677388, 3.200008122887067, 4.000009729344271, 4.000023267460916, 3.2000192670677388 ], "z": [ 0.001101177340912812, 0.001090003018371559, 0.0016411962247617027, 0.0016547727416050344, 0.001101177340912812 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375002401368169, -0.18750024087202166, -0.1875003132785193, -0.4375003122753531, -0.4375002401368169 ], "y": [ 4.000023267460916, 4.000009729344271, 4.800011182963365, 4.800026996061957, 4.000023267460916 ], "z": [ 0.0016547727416050344, 0.0016411962247617027, 0.002278231379164863, 0.0022940910454462126, 0.0016547727416050344 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375003122753531, -0.1875003132785193, -0.1875003891153329, -0.43750038783161005, -0.4375003122753531 ], "y": [ 4.800026996061957, 4.800011182963365, 5.6000124237463025, 5.600030311089138, 4.800026996061957 ], "z": [ 0.0022940910454462126, 0.002278231379164863, 0.002989419809327907, 0.003007361504251719, 0.0022940910454462126 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750038783161005, -0.1875003891153329, -0.18750046758303424, -0.43750046600357495, -0.43750038783161005 ], "y": [ 5.600030311089138, 5.6000124237463025, 6.400013514431788, 6.4000333547312875, 5.600030311089138 ], "z": [ 0.003007361504251719, 0.002989419809327907, 0.0037643153101892014, 0.0037842173728184353, 0.003007361504251719 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750046600357495, -0.18750046758303424, -0.18750054731387217, -0.43750054544343747, -0.43750046600357495 ], "y": [ 6.4000333547312875, 6.400013514431788, 7.20001439891522, 7.200035988878993, 6.4000333547312875 ], "z": [ 0.0037842173728184353, 0.0037643153101892014, 0.004592877438281733, 0.00461453594247971, 0.0037842173728184353 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750054544343747, -0.18750054731387217, -0.187500627992497, -0.4375006258293518, -0.43750054544343747 ], "y": [ 7.200035988878993, 7.20001439891522, 8.000015143951318, 8.000038361345101, 7.200035988878993 ], "z": [ 0.00461453594247971, 0.004592877438281733, 0.005466325160662682, 0.005489617331180315, 0.00461453594247971 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375006258293518, -0.187500627992497, -0.18750070865375484, -0.4375007062171683, -0.4375006258293518 ], "y": [ 8.000038361345101, 8.000015143951318, 8.800015695169904, 8.80004033591147, 8.000038361345101 ], "z": [ 0.005489617331180315, 0.005466325160662682, 0.006376286273275938, 0.0064010072583238825, 0.005489617331180315 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375007062171683, -0.18750070865375484, -0.1875007893187073, -0.43750078661795866, -0.4375007062171683 ], "y": [ 8.80004033591147, 8.800015695169904, 9.600016120600648, 9.600042062380043, 8.80004033591147 ], "z": [ 0.0064010072583238825, 0.006376286273275938, 0.007315653004851139, 0.007341679882890364, 0.0064010072583238825 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750078661795866, -0.1875007893187073, -0.18750086930210966, -0.4375008663679509, -0.43750078661795866 ], "y": [ 9.600042062380043, 9.600016120600648, 10.40001636630499, 10.400043405634353, 9.600042062380043 ], "z": [ 0.007341679882890364, 0.007315653004851139, 0.008277726524304333, 0.008304854997333818, 0.007341679882890364 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375008663679509, -0.18750086930210966, -0.1875009488408821, -0.4375009456910473, -0.4375008663679509 ], "y": [ 10.400043405634353, 10.40001636630499, 11.200016499826141, 11.200044515189864, 10.400043405634353 ], "z": [ 0.008304854997333818, 0.008277726524304333, 0.009257068207257402, 0.009285176155695221, 0.008304854997333818 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375009456910473, -0.1875009488408821, -0.18750102743479913, -0.4375010241082931, -0.4375009456910473 ], "y": [ 11.200044515189864, 11.200016499826141, 12.000016467265384, 12.000045257535799, 11.200044515189864 ], "z": [ 0.009285176155695221, 0.009257068207257402, 0.01024864552259437, 0.0102775310435586, 0.009285176155695221 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375010241082931, -0.18750102743479913, -0.18750110544179802, -0.43750110196207914, -0.4375010241082931 ], "y": [ 12.000045257535799, 12.000016467265384, 12.800016334581887, 12.800045780422645, 12.000045257535799 ], "z": [ 0.0102775310435586, 0.01024864552259437, 0.01124866997069216, 0.011278213190779474, 0.0102775310435586 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750110196207914, -0.18750110544179802, -0.18750118248733483, -0.43750117889798307, -0.43750110196207914 ], "y": [ 12.800045780422645, 12.800016334581887, 13.600016048933623, 13.600045955079421, 12.800045780422645 ], "z": [ 0.011278213190779474, 0.01124866997069216, 0.012253751753734908, 0.012283756724918393, 0.011278213190779474 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750117889798307, -0.18750118248733483, -0.18750125898039016, -0.43750125530740736, -0.43750117889798307 ], "y": [ 13.600045955079421, 13.600016048933623, 14.400015674183493, 14.400045926789915, 13.600045955079421 ], "z": [ 0.012283756724918393, 0.012253751753734908, 0.013261704347121926, 0.013292056796790585, 0.012283756724918393 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750125530740736, -0.18750125898039016, -0.18750133467799002, -0.43750133096412286, -0.43750125530740736 ], "y": [ 14.400045926789915, 14.400015674183493, 15.200015164032955, 15.200045584583822, 14.400045926789915 ], "z": [ 0.013292056796790585, 0.013261704347121926, 0.0142707234002452, 0.014301244272439662, 0.013292056796790585 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750133096412286, -0.18750133467799002, -0.1875014100029893, -0.4375014062712665, -0.43750133096412286 ], "y": [ 15.200045584583822, 15.200015164032955, 16.000014582198226, 16.000045075811887, 15.200045584583822 ], "z": [ 0.014301244272439662, 0.0142707234002452, 0.01528008489372113, 0.015310679025836462, 0.014301244272439662 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.062499976241714925, -0.1875000237228303, -0.18750000000000006 ], "y": [ -3.054068805814802e-18, 1.0180229352716006e-18, 0.7999992519637964, 0.8000022258875188, -3.054068805814802e-18 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 7.454847123235294e-05, 7.752893514114484e-05, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.1875000237228303, 0.062499976241714925, 0.06249993711703296, -0.1875000627455803, -0.1875000237228303 ], "y": [ 0.8000022258875188, 0.7999992519637964, 1.599998502145643, 1.600004355772871, 0.8000022258875188 ], "z": [ 7.752893514114484e-05, 7.454847123235294e-05, 0.000288823222995794, 0.0002946907979488142, 7.752893514114484e-05 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.1875000627455803, 0.06249993711703296, 0.06249988614563157, -0.18750011356082813, -0.1875000627455803 ], "y": [ 1.600004355772871, 1.599998502145643, 2.3999977537828854, 2.4000063093414794, 1.600004355772871 ], "z": [ 0.0002946907979488142, 0.000288823222995794, 0.0006281611479309727, 0.0006367385904886074, 0.0002946907979488142 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750011356082813, 0.06249988614563157, 0.06249982568985464, -0.18750017381202885, -0.18750011356082813 ], "y": [ 2.4000063093414794, 2.3999977537828854, 3.199996978706395, 3.200008122887067, 2.4000063093414794 ], "z": [ 0.0006367385904886074, 0.0006281611479309727, 0.0010788286958303061, 0.001090003018371559, 0.0006367385904886074 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750017381202885, 0.06249982568985464, 0.06249975839277351, -0.18750024087202166, -0.18750017381202885 ], "y": [ 3.200008122887067, 3.199996978706395, 3.9999961912276247, 4.000009729344271, 3.200008122887067 ], "z": [ 0.001090003018371559, 0.0010788286958303061, 0.0016276197079183711, 0.0016411962247617027, 0.001090003018371559 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750024087202166, 0.06249975839277351, 0.06249968571831458, -0.1875003132785193, -0.18750024087202166 ], "y": [ 4.000009729344271, 3.9999961912276247, 4.799995369864772, 4.800011182963365, 4.000009729344271 ], "z": [ 0.0016411962247617027, 0.0016276197079183711, 0.002262371712883513, 0.002278231379164863, 0.0016411962247617027 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.1875003132785193, 0.06249968571831458, 0.06249960960094422, -0.1875003891153329, -0.1875003132785193 ], "y": [ 4.800011182963365, 4.799995369864772, 5.599994536403467, 5.6000124237463025, 4.800011182963365 ], "z": [ 0.002278231379164863, 0.002262371712883513, 0.002971478114404095, 0.002989419809327907, 0.002278231379164863 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.1875003891153329, 0.06249960960094422, 0.06249953083750652, -0.18750046758303424, -0.1875003891153329 ], "y": [ 5.6000124237463025, 5.599994536403467, 6.399993674132288, 6.400013514431788, 5.6000124237463025 ], "z": [ 0.002989419809327907, 0.002971478114404095, 0.0037444132475599676, 0.0037643153101892014, 0.002989419809327907 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750046758303424, 0.06249953083750652, 0.06249945081569314, -0.18750054731387217, -0.18750046758303424 ], "y": [ 6.400013514431788, 6.399993674132288, 7.199992808951447, 7.20001439891522, 6.400013514431788 ], "z": [ 0.0037643153101892014, 0.0037444132475599676, 0.0045712189340837575, 0.004592877438281733, 0.0037643153101892014 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750054731387217, 0.06249945081569314, 0.062499369844357826, -0.187500627992497, -0.18750054731387217 ], "y": [ 7.20001439891522, 7.199992808951447, 7.999991926557534, 8.000015143951318, 7.20001439891522 ], "z": [ 0.004592877438281733, 0.0045712189340837575, 0.005443032990145048, 0.005466325160662682, 0.004592877438281733 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.187500627992497, 0.062499369844357826, 0.06249928890965862, -0.18750070865375484, -0.187500627992497 ], "y": [ 8.000015143951318, 7.999991926557534, 8.799991054428338, 8.800015695169904, 8.000015143951318 ], "z": [ 0.005466325160662682, 0.005443032990145048, 0.006351565288227993, 0.006376286273275938, 0.005466325160662682 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750070865375484, 0.06249928890965862, 0.062499207980544064, -0.1875007893187073, -0.18750070865375484 ], "y": [ 8.800015695169904, 8.799991054428338, 9.599990178821255, 9.600016120600648, 8.800015695169904 ], "z": [ 0.006376286273275938, 0.006351565288227993, 0.007289626126811912, 0.007315653004851139, 0.006376286273275938 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.1875007893187073, 0.062499207980544064, 0.06249912776373148, -0.18750086930210966, -0.1875007893187073 ], "y": [ 9.600016120600648, 9.599990178821255, 10.399989326975627, 10.40001636630499, 9.600016120600648 ], "z": [ 0.007315653004851139, 0.007289626126811912, 0.008250598051274849, 0.008277726524304333, 0.007315653004851139 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750086930210966, 0.06249912776373148, 0.06249904800928315, -0.1875009488408821, -0.18750086930210966 ], "y": [ 10.40001636630499, 10.399989326975627, 11.19998848446242, 11.200016499826141, 10.40001636630499 ], "z": [ 0.008277726524304333, 0.008250598051274849, 0.009228960258819583, 0.009257068207257402, 0.008277726524304333 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.1875009488408821, 0.06249904800928315, 0.06249896923869486, -0.18750102743479913, -0.1875009488408821 ], "y": [ 11.200016499826141, 11.19998848446242, 11.999987676994971, 12.000016467265384, 11.200016499826141 ], "z": [ 0.009257068207257402, 0.009228960258819583, 0.010219760001630138, 0.01024864552259437, 0.009257068207257402 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750102743479913, 0.06249896923869486, 0.06249889107848312, -0.18750110544179802, -0.18750102743479913 ], "y": [ 12.000016467265384, 11.999987676994971, 12.79998688874113, 12.800016334581887, 12.000016467265384 ], "z": [ 0.01024864552259437, 0.010219760001630138, 0.011219126750604847, 0.01124866997069216, 0.01024864552259437 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750110544179802, 0.06249889107848312, 0.06249881392331345, -0.18750118248733483, -0.18750110544179802 ], "y": [ 12.800016334581887, 12.79998688874113, 13.599986142787827, 13.600016048933623, 12.800016334581887 ], "z": [ 0.01124866997069216, 0.011219126750604847, 0.012223746782551424, 0.012253751753734908, 0.01124866997069216 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750118248733483, 0.06249881392331345, 0.06249873734662704, -0.18750125898039016, -0.18750118248733483 ], "y": [ 13.600016048933623, 13.599986142787827, 14.39998542157707, 14.400015674183493, 13.600016048933623 ], "z": [ 0.012253751753734908, 0.012223746782551424, 0.013231351897453265, 0.013261704347121926, 0.012253751753734908 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750125898039016, 0.06249873734662704, 0.06249866160814282, -0.18750133467799002, -0.18750125898039016 ], "y": [ 14.400015674183493, 14.39998542157707, 15.199984743482087, 15.200015164032955, 14.400015674183493 ], "z": [ 0.013261704347121926, 0.013231351897453265, 0.01424020252805074, 0.0142707234002452, 0.013261704347121926 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750133467799002, 0.06249866160814282, 0.06249858626528794, -0.1875014100029893, -0.18750133467799002 ], "y": [ 15.200015164032955, 15.199984743482087, 15.999984088584561, 16.000014582198226, 15.200015164032955 ], "z": [ 0.0142707234002452, 0.01424020252805074, 0.015249490761605795, 0.01528008489372113, 0.0142707234002452 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.3124999762062602, 0.062499976241714925, 0.06250000000000001 ], "y": [ 1.0180229352716006e-18, 5.090114676358003e-18, 0.799996278040074, 0.7999992519637964, 1.0180229352716006e-18 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 7.156800732356103e-05, 7.454847123235294e-05, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.062499976241714925, 0.3124999762062602, 0.3124999369796462, 0.06249993711703296, 0.062499976241714925 ], "y": [ 0.7999992519637964, 0.799996278040074, 1.5999926485184153, 1.599998502145643, 0.7999992519637964 ], "z": [ 7.454847123235294e-05, 7.156800732356103e-05, 0.0002829556480427738, 0.000288823222995794, 7.454847123235294e-05 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249993711703296, 0.3124999369796462, 0.31249988585209126, 0.06249988614563157, 0.06249993711703296 ], "y": [ 1.599998502145643, 1.5999926485184153, 2.3999891982242914, 2.3999977537828854, 1.599998502145643 ], "z": [ 0.000288823222995794, 0.0002829556480427738, 0.000619583705373338, 0.0006281611479309727, 0.000288823222995794 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249988614563157, 0.31249988585209126, 0.31249982519173813, 0.06249982568985464, 0.06249988614563157 ], "y": [ 2.3999977537828854, 2.3999891982242914, 3.199985834525723, 3.199996978706395, 2.3999977537828854 ], "z": [ 0.0006281611479309727, 0.000619583705373338, 0.0010676543732890532, 0.0010788286958303061, 0.0006281611479309727 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249982568985464, 0.31249982519173813, 0.3124997576575687, 0.06249975839277351, 0.06249982568985464 ], "y": [ 3.199996978706395, 3.199985834525723, 3.999982653110979, 3.9999961912276247, 3.199996978706395 ], "z": [ 0.0010788286958303061, 0.0010676543732890532, 0.0016140431910750396, 0.0016276197079183711, 0.0010788286958303061 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249975839277351, 0.3124997576575687, 0.31249968471514844, 0.06249968571831458, 0.06249975839277351 ], "y": [ 3.9999961912276247, 3.999982653110979, 4.7999795567661785, 4.799995369864772, 3.9999961912276247 ], "z": [ 0.0016276197079183711, 0.0016140431910750396, 0.002246512046602163, 0.002262371712883513, 0.0016276197079183711 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249968571831458, 0.31249968471514844, 0.31249960831722134, 0.06249960960094422, 0.06249968571831458 ], "y": [ 4.799995369864772, 4.7999795567661785, 5.599976649060632, 5.599994536403467, 4.799995369864772 ], "z": [ 0.002262371712883513, 0.002246512046602163, 0.0029535364194802835, 0.002971478114404095, 0.002262371712883513 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249960960094422, 0.31249960831722134, 0.31249952925804725, 0.06249953083750652, 0.06249960960094422 ], "y": [ 5.599994536403467, 5.599976649060632, 6.399973833832788, 6.399993674132288, 5.599994536403467 ], "z": [ 0.002971478114404095, 0.0029535364194802835, 0.0037245111849307337, 0.0037444132475599676, 0.002971478114404095 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249953083750652, 0.31249952925804725, 0.3124994489452585, 0.06249945081569314, 0.06249953083750652 ], "y": [ 6.399993674132288, 6.399973833832788, 7.199971218987674, 7.199992808951447, 6.399993674132288 ], "z": [ 0.0037444132475599676, 0.0037245111849307337, 0.004549560429885782, 0.0045712189340837575, 0.0037444132475599676 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249945081569314, 0.3124994489452585, 0.3124993676812126, 0.062499369844357826, 0.06249945081569314 ], "y": [ 7.199992808951447, 7.199971218987674, 7.999968709163751, 7.999991926557534, 7.199992808951447 ], "z": [ 0.0045712189340837575, 0.004549560429885782, 0.005419740819627414, 0.005443032990145048, 0.0045712189340837575 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.062499369844357826, 0.3124993676812126, 0.31249928647307207, 0.06249928890965862, 0.062499369844357826 ], "y": [ 7.999991926557534, 7.999968709163751, 8.799966413686771, 8.799991054428338, 7.999991926557534 ], "z": [ 0.005443032990145048, 0.005419740819627414, 0.006326844303180048, 0.006351565288227993, 0.005443032990145048 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249928890965862, 0.31249928647307207, 0.3124992052797954, 0.062499207980544064, 0.06249928890965862 ], "y": [ 8.799991054428338, 8.799966413686771, 9.599964237041862, 9.599990178821255, 8.799991054428338 ], "z": [ 0.006351565288227993, 0.006326844303180048, 0.0072635992487726855, 0.007289626126811912, 0.006351565288227993 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.062499207980544064, 0.3124992052797954, 0.31249912482957265, 0.06249912776373148, 0.062499207980544064 ], "y": [ 9.599990178821255, 9.599964237041862, 10.399962287646263, 10.399989326975627, 9.599990178821255 ], "z": [ 0.007289626126811912, 0.0072635992487726855, 0.008223469578245364, 0.008250598051274849, 0.007289626126811912 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249912776373148, 0.31249912482957265, 0.3124990448594484, 0.06249904800928315, 0.06249912776373148 ], "y": [ 10.399989326975627, 10.399962287646263, 11.199960469098698, 11.19998848446242, 10.399989326975627 ], "z": [ 0.008250598051274849, 0.008223469578245364, 0.009200852310381764, 0.009228960258819583, 0.008250598051274849 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249904800928315, 0.3124990448594484, 0.31249896591218884, 0.06249896923869486, 0.06249904800928315 ], "y": [ 11.19998848446242, 11.199960469098698, 11.999958886724558, 11.999987676994971, 11.19998848446242 ], "z": [ 0.009228960258819583, 0.009200852310381764, 0.010190874480665907, 0.010219760001630138, 0.009228960258819583 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249896923869486, 0.31249896591218884, 0.31249888759876426, 0.06249889107848312, 0.06249896923869486 ], "y": [ 11.999987676994971, 11.999958886724558, 12.799957442900372, 12.79998688874113, 11.999987676994971 ], "z": [ 0.010219760001630138, 0.010190874480665907, 0.011189583530517534, 0.011219126750604847, 0.010219760001630138 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249889107848312, 0.31249888759876426, 0.3124988103339617, 0.06249881392331345, 0.06249889107848312 ], "y": [ 12.79998688874113, 12.799957442900372, 13.599956236642031, 13.599986142787827, 12.79998688874113 ], "z": [ 0.011219126750604847, 0.011189583530517534, 0.012193741811367939, 0.012223746782551424, 0.011219126750604847 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249881392331345, 0.3124988103339617, 0.3124987336736442, 0.06249873734662704, 0.06249881392331345 ], "y": [ 13.599986142787827, 13.599956236642031, 14.399955168970646, 14.39998542157707, 13.599986142787827 ], "z": [ 0.012223746782551424, 0.012193741811367939, 0.013200999447784604, 0.013231351897453265, 0.012223746782551424 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249873734662704, 0.3124987336736442, 0.31249865789427567, 0.06249866160814282, 0.06249873734662704 ], "y": [ 14.39998542157707, 14.399955168970646, 15.19995432293122, 15.199984743482087, 14.39998542157707 ], "z": [ 0.013231351897453265, 0.013200999447784604, 0.01420968165585628, 0.01424020252805074, 0.013231351897453265 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249866160814282, 0.31249865789427567, 0.31249858253356516, 0.06249858626528794, 0.06249866160814282 ], "y": [ 15.199984743482087, 15.19995432293122, 15.999953594970899, 15.999984088584561, 15.199984743482087 ], "z": [ 0.01424020252805074, 0.01420968165585628, 0.01521889662949046, 0.015249490761605795, 0.01424020252805074 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5624999761708054, 0.3124999762062602, 0.31250000000000006 ], "y": [ 5.090114676358003e-18, 9.162206417444405e-18, 0.7999933041163515, 0.799996278040074, 5.090114676358003e-18 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 6.858754341476913e-05, 7.156800732356103e-05, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124999762062602, 0.5624999761708054, 0.5624999368422594, 0.3124999369796462, 0.3124999762062602 ], "y": [ 0.799996278040074, 0.7999933041163515, 1.5999867948911877, 1.5999926485184153, 0.799996278040074 ], "z": [ 7.156800732356103e-05, 6.858754341476913e-05, 0.00027708807308975364, 0.0002829556480427738, 7.156800732356103e-05 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124999369796462, 0.5624999368422594, 0.562499885558551, 0.31249988585209126, 0.3124999369796462 ], "y": [ 1.5999926485184153, 1.5999867948911877, 2.399980642665697, 2.3999891982242914, 1.5999926485184153 ], "z": [ 0.0002829556480427738, 0.00027708807308975364, 0.0006110062628157034, 0.000619583705373338, 0.0002829556480427738 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249988585209126, 0.562499885558551, 0.5624998246936216, 0.31249982519173813, 0.31249988585209126 ], "y": [ 2.3999891982242914, 2.399980642665697, 3.1999746903450514, 3.199985834525723, 2.3999891982242914 ], "z": [ 0.000619583705373338, 0.0006110062628157034, 0.0010564800507478003, 0.0010676543732890532, 0.000619583705373338 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249982519173813, 0.5624998246936216, 0.5624997569223639, 0.3124997576575687, 0.31249982519173813 ], "y": [ 3.199985834525723, 3.1999746903450514, 3.999969114994333, 3.999982653110979, 3.199985834525723 ], "z": [ 0.0010676543732890532, 0.0010564800507478003, 0.0016004666742317078, 0.0016140431910750396, 0.0010676543732890532 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124997576575687, 0.5624997569223639, 0.5624996837119822, 0.31249968471514844, 0.3124997576575687 ], "y": [ 3.999982653110979, 3.999969114994333, 4.799963743667586, 4.7999795567661785, 3.999982653110979 ], "z": [ 0.0016140431910750396, 0.0016004666742317078, 0.0022306523803208135, 0.002246512046602163, 0.0016140431910750396 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249968471514844, 0.5624996837119822, 0.5624996070334984, 0.31249960831722134, 0.31249968471514844 ], "y": [ 4.7999795567661785, 4.799963743667586, 5.599958761717796, 5.599976649060632, 4.7999795567661785 ], "z": [ 0.002246512046602163, 0.0022306523803208135, 0.0029355947245564714, 0.0029535364194802835, 0.002246512046602163 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249960831722134, 0.5624996070334984, 0.562499527678588, 0.31249952925804725, 0.31249960831722134 ], "y": [ 5.599976649060632, 5.599958761717796, 6.3999539935332885, 6.399973833832788, 5.599976649060632 ], "z": [ 0.0029535364194802835, 0.0029355947245564714, 0.0037046091223015, 0.0037245111849307337, 0.0029535364194802835 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249952925804725, 0.562499527678588, 0.5624994470748238, 0.3124994489452585, 0.31249952925804725 ], "y": [ 6.399973833832788, 6.3999539935332885, 7.199949629023902, 7.199971218987674, 6.399973833832788 ], "z": [ 0.0037245111849307337, 0.0037046091223015, 0.004527901925687805, 0.004549560429885782, 0.0037245111849307337 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124994489452585, 0.5624994470748238, 0.5624993655180675, 0.3124993676812126, 0.3124994489452585 ], "y": [ 7.199971218987674, 7.199949629023902, 7.999945491769968, 7.999968709163751, 7.199971218987674 ], "z": [ 0.004549560429885782, 0.004527901925687805, 0.0053964486491097806, 0.005419740819627414, 0.004549560429885782 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124993676812126, 0.5624993655180675, 0.5624992840364855, 0.31249928647307207, 0.3124993676812126 ], "y": [ 7.999968709163751, 7.999945491769968, 8.799941772945205, 8.799966413686771, 7.999968709163751 ], "z": [ 0.005419740819627414, 0.0053964486491097806, 0.006302123318132103, 0.006326844303180048, 0.005419740819627414 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249928647307207, 0.5624992840364855, 0.5624992025790467, 0.3124992052797954, 0.31249928647307207 ], "y": [ 8.799966413686771, 8.799941772945205, 9.599938295262467, 9.599964237041862, 8.799966413686771 ], "z": [ 0.006326844303180048, 0.006302123318132103, 0.00723757237073346, 0.0072635992487726855, 0.006326844303180048 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124992052797954, 0.5624992025790467, 0.5624991218954138, 0.31249912482957265, 0.3124992052797954 ], "y": [ 9.599964237041862, 9.599938295262467, 10.3999352483169, 10.399962287646263, 9.599964237041862 ], "z": [ 0.0072635992487726855, 0.00723757237073346, 0.008196341105215879, 0.008223469578245364, 0.0072635992487726855 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249912482957265, 0.5624991218954138, 0.5624990417096136, 0.3124990448594484, 0.31249912482957265 ], "y": [ 10.399962287646263, 10.3999352483169, 11.199932453734975, 11.199960469098698, 10.399962287646263 ], "z": [ 0.008223469578245364, 0.008196341105215879, 0.009172744361943945, 0.009200852310381764, 0.008223469578245364 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124990448594484, 0.5624990417096136, 0.5624989625856828, 0.31249896591218884, 0.3124990448594484 ], "y": [ 11.199960469098698, 11.199932453734975, 11.999930096454143, 11.999958886724558, 11.199960469098698 ], "z": [ 0.009200852310381764, 0.009172744361943945, 0.010161988959701676, 0.010190874480665907, 0.009200852310381764 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249896591218884, 0.5624989625856828, 0.5624988841190455, 0.31249888759876426, 0.31249896591218884 ], "y": [ 11.999958886724558, 11.999930096454143, 12.799927997059614, 12.799957442900372, 11.999958886724558 ], "z": [ 0.010190874480665907, 0.010161988959701676, 0.01116004031043022, 0.011189583530517534, 0.010190874480665907 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249888759876426, 0.5624988841190455, 0.56249880674461, 0.3124988103339617, 0.31249888759876426 ], "y": [ 12.799957442900372, 12.799927997059614, 13.599926330496233, 13.599956236642031, 12.799957442900372 ], "z": [ 0.011189583530517534, 0.01116004031043022, 0.012163736840184455, 0.012193741811367939, 0.011189583530517534 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124988103339617, 0.56249880674461, 0.5624987300006614, 0.3124987336736442, 0.3124988103339617 ], "y": [ 13.599956236642031, 13.599926330496233, 14.399924916364224, 14.399955168970646, 13.599956236642031 ], "z": [ 0.012193741811367939, 0.012163736840184455, 0.013170646998115945, 0.013200999447784604, 0.012193741811367939 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124987336736442, 0.5624987300006614, 0.5624986541804086, 0.31249865789427567, 0.3124987336736442 ], "y": [ 14.399955168970646, 14.399924916364224, 15.199923902380352, 15.19995432293122, 14.399955168970646 ], "z": [ 0.013200999447784604, 0.013170646998115945, 0.01417916078366182, 0.01420968165585628, 0.013200999447784604 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249865789427567, 0.5624986541804086, 0.5624985788018424, 0.31249858253356516, 0.31249865789427567 ], "y": [ 15.19995432293122, 15.199923902380352, 15.999923101357235, 15.999953594970899, 15.19995432293122 ], "z": [ 0.01420968165585628, 0.01417916078366182, 0.015188302497375128, 0.01521889662949046, 0.01420968165585628 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.4375000236873756, -0.43750006260819346, -0.43750011326728777, -0.4375001733139123, -0.4375002401368169, -0.4375003122753531, -0.43750038783161005, -0.43750046600357495, -0.43750054544343747, -0.4375006258293518, -0.4375007062171683, -0.43750078661795866, -0.4375008663679509, -0.4375009456910473, -0.4375010241082931, -0.43750110196207914, -0.43750117889798307, -0.43750125530740736, -0.43750133096412286, -0.4375014062712665 ], "y": [ -7.126160546901204e-18, 0.8000051998112413, 1.6000102094000985, 2.400014864900074, 3.2000192670677388, 4.000023267460916, 4.800026996061957, 5.600030311089138, 6.4000333547312875, 7.200035988878993, 8.000038361345101, 8.80004033591147, 9.600042062380043, 10.400043405634353, 11.200044515189864, 12.000045257535799, 12.800045780422645, 13.600045955079421, 14.400045926789915, 15.200045584583822, 16.000045075811887 ], "z": [ -3.7885577460302824e-17, 8.050939904993674e-05, 0.00030055837290183433, 0.000645316033046242, 0.001101177340912812, 0.0016547727416050344, 0.0022940910454462126, 0.003007361504251719, 0.0037842173728184353, 0.00461453594247971, 0.005489617331180315, 0.0064010072583238825, 0.007341679882890364, 0.008304854997333818, 0.009285176155695221, 0.0102775310435586, 0.011278213190779474, 0.012283756724918393, 0.013292056796790585, 0.014301244272439662, 0.015310679025836462 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.18750000000000006, -0.1875000237228303, -0.1875000627455803, -0.18750011356082813, -0.18750017381202885, -0.18750024087202166, -0.1875003132785193, -0.1875003891153329, -0.18750046758303424, -0.18750054731387217, -0.187500627992497, -0.18750070865375484, -0.1875007893187073, -0.18750086930210966, -0.1875009488408821, -0.18750102743479913, -0.18750110544179802, -0.18750118248733483, -0.18750125898039016, -0.18750133467799002, -0.1875014100029893 ], "y": [ -3.054068805814802e-18, 0.8000022258875188, 1.600004355772871, 2.4000063093414794, 3.200008122887067, 4.000009729344271, 4.800011182963365, 5.6000124237463025, 6.400013514431788, 7.20001439891522, 8.000015143951318, 8.800015695169904, 9.600016120600648, 10.40001636630499, 11.200016499826141, 12.000016467265384, 12.800016334581887, 13.600016048933623, 14.400015674183493, 15.200015164032955, 16.000014582198226 ], "z": [ -1.6236676054415498e-17, 7.752893514114484e-05, 0.0002946907979488142, 0.0006367385904886074, 0.001090003018371559, 0.0016411962247617027, 0.002278231379164863, 0.002989419809327907, 0.0037643153101892014, 0.004592877438281733, 0.005466325160662682, 0.006376286273275938, 0.007315653004851139, 0.008277726524304333, 0.009257068207257402, 0.01024864552259437, 0.01124866997069216, 0.012253751753734908, 0.013261704347121926, 0.0142707234002452, 0.01528008489372113 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.06250000000000001, 0.062499976241714925, 0.06249993711703296, 0.06249988614563157, 0.06249982568985464, 0.06249975839277351, 0.06249968571831458, 0.06249960960094422, 0.06249953083750652, 0.06249945081569314, 0.062499369844357826, 0.06249928890965862, 0.062499207980544064, 0.06249912776373148, 0.06249904800928315, 0.06249896923869486, 0.06249889107848312, 0.06249881392331345, 0.06249873734662704, 0.06249866160814282, 0.06249858626528794 ], "y": [ 1.0180229352716006e-18, 0.7999992519637964, 1.599998502145643, 2.3999977537828854, 3.199996978706395, 3.9999961912276247, 4.799995369864772, 5.599994536403467, 6.399993674132288, 7.199992808951447, 7.999991926557534, 8.799991054428338, 9.599990178821255, 10.399989326975627, 11.19998848446242, 11.999987676994971, 12.79998688874113, 13.599986142787827, 14.39998542157707, 15.199984743482087, 15.999984088584561 ], "z": [ 5.412225351471832e-18, 7.454847123235294e-05, 0.000288823222995794, 0.0006281611479309727, 0.0010788286958303061, 0.0016276197079183711, 0.002262371712883513, 0.002971478114404095, 0.0037444132475599676, 0.0045712189340837575, 0.005443032990145048, 0.006351565288227993, 0.007289626126811912, 0.008250598051274849, 0.009228960258819583, 0.010219760001630138, 0.011219126750604847, 0.012223746782551424, 0.013231351897453265, 0.01424020252805074, 0.015249490761605795 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.31250000000000006, 0.3124999762062602, 0.3124999369796462, 0.31249988585209126, 0.31249982519173813, 0.3124997576575687, 0.31249968471514844, 0.31249960831722134, 0.31249952925804725, 0.3124994489452585, 0.3124993676812126, 0.31249928647307207, 0.3124992052797954, 0.31249912482957265, 0.3124990448594484, 0.31249896591218884, 0.31249888759876426, 0.3124988103339617, 0.3124987336736442, 0.31249865789427567, 0.31249858253356516 ], "y": [ 5.090114676358003e-18, 0.799996278040074, 1.5999926485184153, 2.3999891982242914, 3.199985834525723, 3.999982653110979, 4.7999795567661785, 5.599976649060632, 6.399973833832788, 7.199971218987674, 7.999968709163751, 8.799966413686771, 9.599964237041862, 10.399962287646263, 11.199960469098698, 11.999958886724558, 12.799957442900372, 13.599956236642031, 14.399955168970646, 15.19995432293122, 15.999953594970899 ], "z": [ 2.706112675735916e-17, 7.156800732356103e-05, 0.0002829556480427738, 0.000619583705373338, 0.0010676543732890532, 0.0016140431910750396, 0.002246512046602163, 0.0029535364194802835, 0.0037245111849307337, 0.004549560429885782, 0.005419740819627414, 0.006326844303180048, 0.0072635992487726855, 0.008223469578245364, 0.009200852310381764, 0.010190874480665907, 0.011189583530517534, 0.012193741811367939, 0.013200999447784604, 0.01420968165585628, 0.01521889662949046 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.5624999761708054, 0.5624999368422594, 0.562499885558551, 0.5624998246936216, 0.5624997569223639, 0.5624996837119822, 0.5624996070334984, 0.562499527678588, 0.5624994470748238, 0.5624993655180675, 0.5624992840364855, 0.5624992025790467, 0.5624991218954138, 0.5624990417096136, 0.5624989625856828, 0.5624988841190455, 0.56249880674461, 0.5624987300006614, 0.5624986541804086, 0.5624985788018424 ], "y": [ 9.162206417444405e-18, 0.7999933041163515, 1.5999867948911877, 2.399980642665697, 3.1999746903450514, 3.999969114994333, 4.799963743667586, 5.599958761717796, 6.3999539935332885, 7.199949629023902, 7.999945491769968, 8.799941772945205, 9.599938295262467, 10.3999352483169, 11.199932453734975, 11.999930096454143, 12.799927997059614, 13.599926330496233, 14.399924916364224, 15.199923902380352, 15.999923101357235 ], "z": [ 4.871002816324649e-17, 6.858754341476913e-05, 0.00027708807308975364, 0.0006110062628157034, 0.0010564800507478003, 0.0016004666742317078, 0.0022306523803208135, 0.0029355947245564714, 0.0037046091223015, 0.004527901925687805, 0.0053964486491097806, 0.006302123318132103, 0.00723757237073346, 0.008196341105215879, 0.009172744361943945, 0.010161988959701676, 0.01116004031043022, 0.012163736840184455, 0.013170646998115945, 0.01417916078366182, 0.015188302497375128 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ -7.126160546901204e-18, -3.054068805814802e-18, 1.0180229352716006e-18, 5.090114676358003e-18, 9.162206417444405e-18 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000236873756, -0.1875000237228303, 0.062499976241714925, 0.3124999762062602, 0.5624999761708054 ], "y": [ 0.8000051998112413, 0.8000022258875188, 0.7999992519637964, 0.799996278040074, 0.7999933041163515 ], "z": [ 8.050939904993674e-05, 7.752893514114484e-05, 7.454847123235294e-05, 7.156800732356103e-05, 6.858754341476913e-05 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750006260819346, -0.1875000627455803, 0.06249993711703296, 0.3124999369796462, 0.5624999368422594 ], "y": [ 1.6000102094000985, 1.600004355772871, 1.599998502145643, 1.5999926485184153, 1.5999867948911877 ], "z": [ 0.00030055837290183433, 0.0002946907979488142, 0.000288823222995794, 0.0002829556480427738, 0.00027708807308975364 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750011326728777, -0.18750011356082813, 0.06249988614563157, 0.31249988585209126, 0.562499885558551 ], "y": [ 2.400014864900074, 2.4000063093414794, 2.3999977537828854, 2.3999891982242914, 2.399980642665697 ], "z": [ 0.000645316033046242, 0.0006367385904886074, 0.0006281611479309727, 0.000619583705373338, 0.0006110062628157034 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375001733139123, -0.18750017381202885, 0.06249982568985464, 0.31249982519173813, 0.5624998246936216 ], "y": [ 3.2000192670677388, 3.200008122887067, 3.199996978706395, 3.199985834525723, 3.1999746903450514 ], "z": [ 0.001101177340912812, 0.001090003018371559, 0.0010788286958303061, 0.0010676543732890532, 0.0010564800507478003 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375002401368169, -0.18750024087202166, 0.06249975839277351, 0.3124997576575687, 0.5624997569223639 ], "y": [ 4.000023267460916, 4.000009729344271, 3.9999961912276247, 3.999982653110979, 3.999969114994333 ], "z": [ 0.0016547727416050344, 0.0016411962247617027, 0.0016276197079183711, 0.0016140431910750396, 0.0016004666742317078 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375003122753531, -0.1875003132785193, 0.06249968571831458, 0.31249968471514844, 0.5624996837119822 ], "y": [ 4.800026996061957, 4.800011182963365, 4.799995369864772, 4.7999795567661785, 4.799963743667586 ], "z": [ 0.0022940910454462126, 0.002278231379164863, 0.002262371712883513, 0.002246512046602163, 0.0022306523803208135 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750038783161005, -0.1875003891153329, 0.06249960960094422, 0.31249960831722134, 0.5624996070334984 ], "y": [ 5.600030311089138, 5.6000124237463025, 5.599994536403467, 5.599976649060632, 5.599958761717796 ], "z": [ 0.003007361504251719, 0.002989419809327907, 0.002971478114404095, 0.0029535364194802835, 0.0029355947245564714 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750046600357495, -0.18750046758303424, 0.06249953083750652, 0.31249952925804725, 0.562499527678588 ], "y": [ 6.4000333547312875, 6.400013514431788, 6.399993674132288, 6.399973833832788, 6.3999539935332885 ], "z": [ 0.0037842173728184353, 0.0037643153101892014, 0.0037444132475599676, 0.0037245111849307337, 0.0037046091223015 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750054544343747, -0.18750054731387217, 0.06249945081569314, 0.3124994489452585, 0.5624994470748238 ], "y": [ 7.200035988878993, 7.20001439891522, 7.199992808951447, 7.199971218987674, 7.199949629023902 ], "z": [ 0.00461453594247971, 0.004592877438281733, 0.0045712189340837575, 0.004549560429885782, 0.004527901925687805 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375006258293518, -0.187500627992497, 0.062499369844357826, 0.3124993676812126, 0.5624993655180675 ], "y": [ 8.000038361345101, 8.000015143951318, 7.999991926557534, 7.999968709163751, 7.999945491769968 ], "z": [ 0.005489617331180315, 0.005466325160662682, 0.005443032990145048, 0.005419740819627414, 0.0053964486491097806 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375007062171683, -0.18750070865375484, 0.06249928890965862, 0.31249928647307207, 0.5624992840364855 ], "y": [ 8.80004033591147, 8.800015695169904, 8.799991054428338, 8.799966413686771, 8.799941772945205 ], "z": [ 0.0064010072583238825, 0.006376286273275938, 0.006351565288227993, 0.006326844303180048, 0.006302123318132103 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750078661795866, -0.1875007893187073, 0.062499207980544064, 0.3124992052797954, 0.5624992025790467 ], "y": [ 9.600042062380043, 9.600016120600648, 9.599990178821255, 9.599964237041862, 9.599938295262467 ], "z": [ 0.007341679882890364, 0.007315653004851139, 0.007289626126811912, 0.0072635992487726855, 0.00723757237073346 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375008663679509, -0.18750086930210966, 0.06249912776373148, 0.31249912482957265, 0.5624991218954138 ], "y": [ 10.400043405634353, 10.40001636630499, 10.399989326975627, 10.399962287646263, 10.3999352483169 ], "z": [ 0.008304854997333818, 0.008277726524304333, 0.008250598051274849, 0.008223469578245364, 0.008196341105215879 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375009456910473, -0.1875009488408821, 0.06249904800928315, 0.3124990448594484, 0.5624990417096136 ], "y": [ 11.200044515189864, 11.200016499826141, 11.19998848446242, 11.199960469098698, 11.199932453734975 ], "z": [ 0.009285176155695221, 0.009257068207257402, 0.009228960258819583, 0.009200852310381764, 0.009172744361943945 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375010241082931, -0.18750102743479913, 0.06249896923869486, 0.31249896591218884, 0.5624989625856828 ], "y": [ 12.000045257535799, 12.000016467265384, 11.999987676994971, 11.999958886724558, 11.999930096454143 ], "z": [ 0.0102775310435586, 0.01024864552259437, 0.010219760001630138, 0.010190874480665907, 0.010161988959701676 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750110196207914, -0.18750110544179802, 0.06249889107848312, 0.31249888759876426, 0.5624988841190455 ], "y": [ 12.800045780422645, 12.800016334581887, 12.79998688874113, 12.799957442900372, 12.799927997059614 ], "z": [ 0.011278213190779474, 0.01124866997069216, 0.011219126750604847, 0.011189583530517534, 0.01116004031043022 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750117889798307, -0.18750118248733483, 0.06249881392331345, 0.3124988103339617, 0.56249880674461 ], "y": [ 13.600045955079421, 13.600016048933623, 13.599986142787827, 13.599956236642031, 13.599926330496233 ], "z": [ 0.012283756724918393, 0.012253751753734908, 0.012223746782551424, 0.012193741811367939, 0.012163736840184455 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750125530740736, -0.18750125898039016, 0.06249873734662704, 0.3124987336736442, 0.5624987300006614 ], "y": [ 14.400045926789915, 14.400015674183493, 14.39998542157707, 14.399955168970646, 14.399924916364224 ], "z": [ 0.013292056796790585, 0.013261704347121926, 0.013231351897453265, 0.013200999447784604, 0.013170646998115945 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750133096412286, -0.18750133467799002, 0.06249866160814282, 0.31249865789427567, 0.5624986541804086 ], "y": [ 15.200045584583822, 15.200015164032955, 15.199984743482087, 15.19995432293122, 15.199923902380352 ], "z": [ 0.014301244272439662, 0.0142707234002452, 0.01424020252805074, 0.01420968165585628, 0.01417916078366182 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375014062712665, -0.1875014100029893, 0.06249858626528794, 0.31249858253356516, 0.5624985788018424 ], "y": [ 16.000045075811887, 16.000014582198226, 15.999984088584561, 15.999953594970899, 15.999923101357235 ], "z": [ 0.015310679025836462, 0.01528008489372113, 0.015249490761605795, 0.01521889662949046, 0.015188302497375128 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "name": "Aero wake", "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 0.8123476837953569, 0.562499976295357, 0.5625000000000001 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.7999933212351085, 0.7999933212351085, 9.162206417444405e-18 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.008793254890310381, 6.837989031038178e-05, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.562499976295357, 0.8123476837953569, 0.8123476446826243, 0.5624999371826244, 0.562499976295357 ], "y": [ 0.7999933212351085, 0.7999933212351085, 1.5999868291304182, 1.5999868291304182, 0.7999933212351085 ], "z": [ 6.837989031038178e-05, 0.008793254890310381, 0.009001125991601577, 0.00027625099160157664, 6.837989031038178e-05 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624999371826244, 0.8123476446826243, 0.8123475936878366, 0.5624998861878366, 0.5624999371826244 ], "y": [ 1.5999868291304182, 1.5999868291304182, 2.3999806938941566, 2.3999806938941566, 1.5999868291304182 ], "z": [ 0.00027625099160157664, 0.009001125991601577, 0.00933403095937816, 0.0006091559593781608, 0.00027625099160157664 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624998861878366, 0.8123475936878366, 0.8123475331702955, 0.5624998256702956, 0.5624998861878366 ], "y": [ 2.3999806938941566, 2.3999806938941566, 3.1999747586029037, 3.1999747586029037, 2.3999806938941566 ], "z": [ 0.0006091559593781608, 0.00933403095937816, 0.009778145021025551, 0.0010532700210255516, 0.0006091559593781608 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624998256702956, 0.8123475331702955, 0.8123474657906017, 0.5624997582906017, 0.5624998256702956 ], "y": [ 3.1999747586029037, 3.1999747586029037, 3.999969200022097, 3.999969200022097, 3.1999747586029037 ], "z": [ 0.0010532700210255516, 0.009778145021025551, 0.010320461637624298, 0.001595586637624299, 0.0010532700210255516 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624997582906017, 0.8123474657906017, 0.8123473930060079, 0.5624996855060079, 0.5624997582906017 ], "y": [ 3.999969200022097, 3.999969200022097, 4.799963845425502, 4.799963845425502, 3.999969200022097 ], "z": [ 0.001595586637624299, 0.010320461637624298, 0.010948702164465246, 0.0022238271644652464, 0.001595586637624299 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624996855060079, 0.8123473930060079, 0.8123473167765953, 0.5624996092765954, 0.5624996855060079 ], "y": [ 4.799963845425502, 4.799963845425502, 5.599958879734863, 5.599958879734863, 4.799963845425502 ], "z": [ 0.0022238271644652464, 0.010948702164465246, 0.011651457730397104, 0.0029265827303971044, 0.0022238271644652464 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624996092765954, 0.8123473167765953, 0.8123472378880863, 0.5624995303880863, 0.5624996092765954 ], "y": [ 5.599958879734863, 5.599958879734863, 6.399954127618544, 6.399954127618544, 5.599958879734863 ], "z": [ 0.0029265827303971044, 0.011651457730397104, 0.012418075832735435, 0.003693200832735435, 0.0029265827303971044 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624995303880863, 0.8123472378880863, 0.8123471577598415, 0.5624994502598415, 0.5624995303880863 ], "y": [ 6.399954127618544, 6.399954127618544, 7.199949778457311, 7.199949778457311, 6.399954127618544 ], "z": [ 0.003693200832735435, 0.012418075832735435, 0.013238792957041995, 0.004513917957041995, 0.003693200832735435 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624994502598415, 0.8123471577598415, 0.8123470766849902, 0.5624993691849902, 0.5624994502598415 ], "y": [ 7.199949778457311, 7.199949778457311, 7.999945656178906, 7.999945656178906, 7.199949778457311 ], "z": [ 0.004513917957041995, 0.013238792957041995, 0.01410461293228132, 0.00537973793228132, 0.004513917957041995 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624993691849902, 0.8123470766849902, 0.8123469956855739, 0.5624992881855739, 0.5624993691849902 ], "y": [ 7.999945656178906, 7.999945656178906, 8.799941951360688, 8.799941951360688, 7.999945656178906 ], "z": [ 0.00537973793228132, 0.01410461293228132, 0.015007435798364901, 0.006282560798364902, 0.00537973793228132 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624992881855739, 0.8123469956855739, 0.8123469147102647, 0.5624992072102647, 0.5624992881855739 ], "y": [ 8.799941951360688, 8.799941951360688, 9.599938487131828, 9.599938487131828, 8.799941951360688 ], "z": [ 0.006282560798364902, 0.015007435798364901, 0.015939931915767612, 0.007215056915767613, 0.006282560798364902 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624992072102647, 0.8123469147102647, 0.8123468345040965, 0.5624991270040965, 0.5624992072102647 ], "y": [ 9.599938487131828, 9.599938487131828, 10.399935452451604, 10.399935452451604, 9.599938487131828 ], "z": [ 0.007215056915767613, 0.015939931915767612, 0.016895667922093942, 0.008170792922093943, 0.007215056915767613 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624991270040965, 0.8123468345040965, 0.8123467547925016, 0.5624990472925017, 0.5624991270040965 ], "y": [ 10.399935452451604, 10.399935452451604, 11.199932669428227, 11.199932669428227, 10.399935452451604 ], "z": [ 0.008170792922093943, 0.016895667922093942, 0.01786897769443925, 0.009144102694439252, 0.008170792922093943 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624990472925017, 0.8123467547925016, 0.8123466761359044, 0.5624989686359044, 0.5624990472925017 ], "y": [ 11.199932669428227, 11.199932669428227, 11.999930322350817, 11.999930322350817, 11.199932669428227 ], "z": [ 0.009144102694439252, 0.01786897769443925, 0.018855084300556615, 0.010130209300556614, 0.009144102694439252 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624989686359044, 0.8123466761359044, 0.8123465981321941, 0.5624988906321942, 0.5624989686359044 ], "y": [ 11.999930322350817, 11.999930322350817, 12.799928232343898, 12.799928232343898, 11.999930322350817 ], "z": [ 0.010130209300556614, 0.018855084300556615, 0.01984996696380438, 0.011125091963804382, 0.010130209300556614 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624988906321942, 0.8123465981321941, 0.8123465212134034, 0.5624988137134035, 0.5624988906321942 ], "y": [ 12.799928232343898, 12.799928232343898, 13.599926573724765, 13.599926573724765, 12.799928232343898 ], "z": [ 0.011125091963804382, 0.01984996696380438, 0.02085047502889098, 0.012125600028890982, 0.011125091963804382 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624988137134035, 0.8123465212134034, 0.8123464449208242, 0.5624987374208242, 0.5624988137134035 ], "y": [ 13.599926573724765, 13.599926573724765, 14.39992516668033, 14.39992516668033, 13.599926573724765 ], "z": [ 0.012125600028890982, 0.02085047502889098, 0.021854185403696567, 0.01312931040369657, 0.012125600028890982 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624987374208242, 0.8123464449208242, 0.8123463695456885, 0.5624986620456885, 0.5624987374208242 ], "y": [ 14.39992516668033, 14.39992516668033, 15.199924158393646, 15.199924158393646, 14.39992516668033 ], "z": [ 0.01312931040369657, 0.021854185403696567, 0.022859493721092188, 0.014134618721092188, 0.01312931040369657 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624986620456885, 0.8123463695456885, 0.8123462946092366, 0.5624985871092366, 0.5624986620456885 ], "y": [ 15.199924158393646, 15.199924158393646, 15.999923362319814, 15.999923362319814, 15.199924158393646 ], "z": [ 0.014134618721092188, 0.022859493721092188, 0.02386542772471522, 0.01514055272471522, 0.014134618721092188 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123477075000001, 1.0621954150000001, 1.062195391295357, 0.8123476837953569, 0.8123477075000001 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.7999933212351085, 0.7999933212351085, 9.162206417444405e-18 ], "z": [ 0.008724875000000049, 0.01744975000000005, 0.01751812989031038, 0.008793254890310381, 0.008724875000000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123476837953569, 1.062195391295357, 1.0621953521826244, 0.8123476446826243, 0.8123476837953569 ], "y": [ 0.7999933212351085, 0.7999933212351085, 1.5999868291304182, 1.5999868291304182, 0.7999933212351085 ], "z": [ 0.008793254890310381, 0.01751812989031038, 0.017726000991601577, 0.009001125991601577, 0.008793254890310381 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123476446826243, 1.0621953521826244, 1.0621953011878367, 0.8123475936878366, 0.8123476446826243 ], "y": [ 1.5999868291304182, 1.5999868291304182, 2.3999806938941566, 2.3999806938941566, 1.5999868291304182 ], "z": [ 0.009001125991601577, 0.017726000991601577, 0.01805890595937816, 0.00933403095937816, 0.009001125991601577 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123475936878366, 1.0621953011878367, 1.0621952406702955, 0.8123475331702955, 0.8123475936878366 ], "y": [ 2.3999806938941566, 2.3999806938941566, 3.1999747586029037, 3.1999747586029037, 2.3999806938941566 ], "z": [ 0.00933403095937816, 0.01805890595937816, 0.01850302002102555, 0.009778145021025551, 0.00933403095937816 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123475331702955, 1.0621952406702955, 1.0621951732906016, 0.8123474657906017, 0.8123475331702955 ], "y": [ 3.1999747586029037, 3.1999747586029037, 3.999969200022097, 3.999969200022097, 3.1999747586029037 ], "z": [ 0.009778145021025551, 0.01850302002102555, 0.019045336637624297, 0.010320461637624298, 0.009778145021025551 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123474657906017, 1.0621951732906016, 1.062195100506008, 0.8123473930060079, 0.8123474657906017 ], "y": [ 3.999969200022097, 3.999969200022097, 4.799963845425502, 4.799963845425502, 3.999969200022097 ], "z": [ 0.010320461637624298, 0.019045336637624297, 0.019673577164465245, 0.010948702164465246, 0.010320461637624298 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123473930060079, 1.062195100506008, 1.0621950242765954, 0.8123473167765953, 0.8123473930060079 ], "y": [ 4.799963845425502, 4.799963845425502, 5.599958879734863, 5.599958879734863, 4.799963845425502 ], "z": [ 0.010948702164465246, 0.019673577164465245, 0.020376332730397104, 0.011651457730397104, 0.010948702164465246 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123473167765953, 1.0621950242765954, 1.0621949453880863, 0.8123472378880863, 0.8123473167765953 ], "y": [ 5.599958879734863, 5.599958879734863, 6.399954127618544, 6.399954127618544, 5.599958879734863 ], "z": [ 0.011651457730397104, 0.020376332730397104, 0.021142950832735437, 0.012418075832735435, 0.011651457730397104 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123472378880863, 1.0621949453880863, 1.0621948652598414, 0.8123471577598415, 0.8123472378880863 ], "y": [ 6.399954127618544, 6.399954127618544, 7.199949778457311, 7.199949778457311, 6.399954127618544 ], "z": [ 0.012418075832735435, 0.021142950832735437, 0.021963667957041993, 0.013238792957041995, 0.012418075832735435 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123471577598415, 1.0621948652598414, 1.0621947841849901, 0.8123470766849902, 0.8123471577598415 ], "y": [ 7.199949778457311, 7.199949778457311, 7.999945656178906, 7.999945656178906, 7.199949778457311 ], "z": [ 0.013238792957041995, 0.021963667957041993, 0.02282948793228132, 0.01410461293228132, 0.013238792957041995 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123470766849902, 1.0621947841849901, 1.0621947031855739, 0.8123469956855739, 0.8123470766849902 ], "y": [ 7.999945656178906, 7.999945656178906, 8.799941951360688, 8.799941951360688, 7.999945656178906 ], "z": [ 0.01410461293228132, 0.02282948793228132, 0.0237323107983649, 0.015007435798364901, 0.01410461293228132 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123469956855739, 1.0621947031855739, 1.0621946222102647, 0.8123469147102647, 0.8123469956855739 ], "y": [ 8.799941951360688, 8.799941951360688, 9.599938487131828, 9.599938487131828, 8.799941951360688 ], "z": [ 0.015007435798364901, 0.0237323107983649, 0.024664806915767612, 0.015939931915767612, 0.015007435798364901 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123469147102647, 1.0621946222102647, 1.0621945420040966, 0.8123468345040965, 0.8123469147102647 ], "y": [ 9.599938487131828, 9.599938487131828, 10.399935452451604, 10.399935452451604, 9.599938487131828 ], "z": [ 0.015939931915767612, 0.024664806915767612, 0.025620542922093942, 0.016895667922093942, 0.015939931915767612 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123468345040965, 1.0621945420040966, 1.0621944622925017, 0.8123467547925016, 0.8123468345040965 ], "y": [ 10.399935452451604, 10.399935452451604, 11.199932669428227, 11.199932669428227, 10.399935452451604 ], "z": [ 0.016895667922093942, 0.025620542922093942, 0.02659385269443925, 0.01786897769443925, 0.016895667922093942 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123467547925016, 1.0621944622925017, 1.0621943836359045, 0.8123466761359044, 0.8123467547925016 ], "y": [ 11.199932669428227, 11.199932669428227, 11.999930322350817, 11.999930322350817, 11.199932669428227 ], "z": [ 0.01786897769443925, 0.02659385269443925, 0.027579959300556615, 0.018855084300556615, 0.01786897769443925 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123466761359044, 1.0621943836359045, 1.062194305632194, 0.8123465981321941, 0.8123466761359044 ], "y": [ 11.999930322350817, 11.999930322350817, 12.799928232343898, 12.799928232343898, 11.999930322350817 ], "z": [ 0.018855084300556615, 0.027579959300556615, 0.02857484196380438, 0.01984996696380438, 0.018855084300556615 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123465981321941, 1.062194305632194, 1.0621942287134034, 0.8123465212134034, 0.8123465981321941 ], "y": [ 12.799928232343898, 12.799928232343898, 13.599926573724765, 13.599926573724765, 12.799928232343898 ], "z": [ 0.01984996696380438, 0.02857484196380438, 0.02957535002889098, 0.02085047502889098, 0.01984996696380438 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123465212134034, 1.0621942287134034, 1.0621941524208243, 0.8123464449208242, 0.8123465212134034 ], "y": [ 13.599926573724765, 13.599926573724765, 14.39992516668033, 14.39992516668033, 13.599926573724765 ], "z": [ 0.02085047502889098, 0.02957535002889098, 0.030579060403696567, 0.021854185403696567, 0.02085047502889098 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123464449208242, 1.0621941524208243, 1.0621940770456886, 0.8123463695456885, 0.8123464449208242 ], "y": [ 14.39992516668033, 14.39992516668033, 15.199924158393646, 15.199924158393646, 14.39992516668033 ], "z": [ 0.021854185403696567, 0.030579060403696567, 0.03158436872109219, 0.022859493721092188, 0.021854185403696567 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8123463695456885, 1.0621940770456886, 1.0621940021092366, 0.8123462946092366, 0.8123463695456885 ], "y": [ 15.199924158393646, 15.199924158393646, 15.999923362319814, 15.999923362319814, 15.199924158393646 ], "z": [ 0.022859493721092188, 0.03158436872109219, 0.03259030272471522, 0.02386542772471522, 0.022859493721092188 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621954150000001, 1.3120431225000002, 1.312043098795357, 1.062195391295357, 1.0621954150000001 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.7999933212351085, 0.7999933212351085, 9.162206417444405e-18 ], "z": [ 0.01744975000000005, 0.02617462500000005, 0.02624300489031038, 0.01751812989031038, 0.01744975000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.062195391295357, 1.312043098795357, 1.3120430596826245, 1.0621953521826244, 1.062195391295357 ], "y": [ 0.7999933212351085, 0.7999933212351085, 1.5999868291304182, 1.5999868291304182, 0.7999933212351085 ], "z": [ 0.01751812989031038, 0.02624300489031038, 0.026450875991601577, 0.017726000991601577, 0.01751812989031038 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621953521826244, 1.3120430596826245, 1.3120430086878367, 1.0621953011878367, 1.0621953521826244 ], "y": [ 1.5999868291304182, 1.5999868291304182, 2.3999806938941566, 2.3999806938941566, 1.5999868291304182 ], "z": [ 0.017726000991601577, 0.026450875991601577, 0.02678378095937816, 0.01805890595937816, 0.017726000991601577 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621953011878367, 1.3120430086878367, 1.3120429481702955, 1.0621952406702955, 1.0621953011878367 ], "y": [ 2.3999806938941566, 2.3999806938941566, 3.1999747586029037, 3.1999747586029037, 2.3999806938941566 ], "z": [ 0.01805890595937816, 0.02678378095937816, 0.02722789502102555, 0.01850302002102555, 0.01805890595937816 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621952406702955, 1.3120429481702955, 1.3120428807906017, 1.0621951732906016, 1.0621952406702955 ], "y": [ 3.1999747586029037, 3.1999747586029037, 3.999969200022097, 3.999969200022097, 3.1999747586029037 ], "z": [ 0.01850302002102555, 0.02722789502102555, 0.027770211637624297, 0.019045336637624297, 0.01850302002102555 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621951732906016, 1.3120428807906017, 1.312042808006008, 1.062195100506008, 1.0621951732906016 ], "y": [ 3.999969200022097, 3.999969200022097, 4.799963845425502, 4.799963845425502, 3.999969200022097 ], "z": [ 0.019045336637624297, 0.027770211637624297, 0.028398452164465245, 0.019673577164465245, 0.019045336637624297 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.062195100506008, 1.312042808006008, 1.3120427317765955, 1.0621950242765954, 1.062195100506008 ], "y": [ 4.799963845425502, 4.799963845425502, 5.599958879734863, 5.599958879734863, 4.799963845425502 ], "z": [ 0.019673577164465245, 0.028398452164465245, 0.029101207730397104, 0.020376332730397104, 0.019673577164465245 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621950242765954, 1.3120427317765955, 1.3120426528880864, 1.0621949453880863, 1.0621950242765954 ], "y": [ 5.599958879734863, 5.599958879734863, 6.399954127618544, 6.399954127618544, 5.599958879734863 ], "z": [ 0.020376332730397104, 0.029101207730397104, 0.029867825832735437, 0.021142950832735437, 0.020376332730397104 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621949453880863, 1.3120426528880864, 1.3120425727598415, 1.0621948652598414, 1.0621949453880863 ], "y": [ 6.399954127618544, 6.399954127618544, 7.199949778457311, 7.199949778457311, 6.399954127618544 ], "z": [ 0.021142950832735437, 0.029867825832735437, 0.030688542957041993, 0.021963667957041993, 0.021142950832735437 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621948652598414, 1.3120425727598415, 1.3120424916849902, 1.0621947841849901, 1.0621948652598414 ], "y": [ 7.199949778457311, 7.199949778457311, 7.999945656178906, 7.999945656178906, 7.199949778457311 ], "z": [ 0.021963667957041993, 0.030688542957041993, 0.031554362932281324, 0.02282948793228132, 0.021963667957041993 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621947841849901, 1.3120424916849902, 1.312042410685574, 1.0621947031855739, 1.0621947841849901 ], "y": [ 7.999945656178906, 7.999945656178906, 8.799941951360688, 8.799941951360688, 7.999945656178906 ], "z": [ 0.02282948793228132, 0.031554362932281324, 0.0324571857983649, 0.0237323107983649, 0.02282948793228132 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621947031855739, 1.312042410685574, 1.3120423297102648, 1.0621946222102647, 1.0621947031855739 ], "y": [ 8.799941951360688, 8.799941951360688, 9.599938487131828, 9.599938487131828, 8.799941951360688 ], "z": [ 0.0237323107983649, 0.0324571857983649, 0.03338968191576761, 0.024664806915767612, 0.0237323107983649 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621946222102647, 1.3120423297102648, 1.3120422495040966, 1.0621945420040966, 1.0621946222102647 ], "y": [ 9.599938487131828, 9.599938487131828, 10.399935452451604, 10.399935452451604, 9.599938487131828 ], "z": [ 0.024664806915767612, 0.03338968191576761, 0.034345417922093946, 0.025620542922093942, 0.024664806915767612 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621945420040966, 1.3120422495040966, 1.3120421697925018, 1.0621944622925017, 1.0621945420040966 ], "y": [ 10.399935452451604, 10.399935452451604, 11.199932669428227, 11.199932669428227, 10.399935452451604 ], "z": [ 0.025620542922093942, 0.034345417922093946, 0.03531872769443925, 0.02659385269443925, 0.025620542922093942 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621944622925017, 1.3120421697925018, 1.3120420911359045, 1.0621943836359045, 1.0621944622925017 ], "y": [ 11.199932669428227, 11.199932669428227, 11.999930322350817, 11.999930322350817, 11.199932669428227 ], "z": [ 0.02659385269443925, 0.03531872769443925, 0.036304834300556615, 0.027579959300556615, 0.02659385269443925 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621943836359045, 1.3120420911359045, 1.3120420131321942, 1.062194305632194, 1.0621943836359045 ], "y": [ 11.999930322350817, 11.999930322350817, 12.799928232343898, 12.799928232343898, 11.999930322350817 ], "z": [ 0.027579959300556615, 0.036304834300556615, 0.03729971696380438, 0.02857484196380438, 0.027579959300556615 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.062194305632194, 1.3120420131321942, 1.3120419362134035, 1.0621942287134034, 1.062194305632194 ], "y": [ 12.799928232343898, 12.799928232343898, 13.599926573724765, 13.599926573724765, 12.799928232343898 ], "z": [ 0.02857484196380438, 0.03729971696380438, 0.03830022502889098, 0.02957535002889098, 0.02857484196380438 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621942287134034, 1.3120419362134035, 1.3120418599208243, 1.0621941524208243, 1.0621942287134034 ], "y": [ 13.599926573724765, 13.599926573724765, 14.39992516668033, 14.39992516668033, 13.599926573724765 ], "z": [ 0.02957535002889098, 0.03830022502889098, 0.03930393540369657, 0.030579060403696567, 0.02957535002889098 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621941524208243, 1.3120418599208243, 1.3120417845456886, 1.0621940770456886, 1.0621941524208243 ], "y": [ 14.39992516668033, 14.39992516668033, 15.199924158393646, 15.199924158393646, 14.39992516668033 ], "z": [ 0.030579060403696567, 0.03930393540369657, 0.04030924372109219, 0.03158436872109219, 0.030579060403696567 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0621940770456886, 1.3120417845456886, 1.3120417096092367, 1.0621940021092366, 1.0621940770456886 ], "y": [ 15.199924158393646, 15.199924158393646, 15.999923362319814, 15.999923362319814, 15.199924158393646 ], "z": [ 0.03158436872109219, 0.04030924372109219, 0.04131517772471522, 0.03259030272471522, 0.03158436872109219 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120431225000002, 1.5618908300000003, 1.5618908062953571, 1.312043098795357, 1.3120431225000002 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.7999933212351085, 0.7999933212351085, 9.162206417444405e-18 ], "z": [ 0.02617462500000005, 0.03489950000000005, 0.03496787989031038, 0.02624300489031038, 0.02617462500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.312043098795357, 1.5618908062953571, 1.5618907671826245, 1.3120430596826245, 1.312043098795357 ], "y": [ 0.7999933212351085, 0.7999933212351085, 1.5999868291304182, 1.5999868291304182, 0.7999933212351085 ], "z": [ 0.02624300489031038, 0.03496787989031038, 0.03517575099160158, 0.026450875991601577, 0.02624300489031038 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120430596826245, 1.5618907671826245, 1.5618907161878368, 1.3120430086878367, 1.3120430596826245 ], "y": [ 1.5999868291304182, 1.5999868291304182, 2.3999806938941566, 2.3999806938941566, 1.5999868291304182 ], "z": [ 0.026450875991601577, 0.03517575099160158, 0.03550865595937816, 0.02678378095937816, 0.026450875991601577 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120430086878367, 1.5618907161878368, 1.5618906556702956, 1.3120429481702955, 1.3120430086878367 ], "y": [ 2.3999806938941566, 2.3999806938941566, 3.1999747586029037, 3.1999747586029037, 2.3999806938941566 ], "z": [ 0.02678378095937816, 0.03550865595937816, 0.03595277002102555, 0.02722789502102555, 0.02678378095937816 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120429481702955, 1.5618906556702956, 1.5618905882906018, 1.3120428807906017, 1.3120429481702955 ], "y": [ 3.1999747586029037, 3.1999747586029037, 3.999969200022097, 3.999969200022097, 3.1999747586029037 ], "z": [ 0.02722789502102555, 0.03595277002102555, 0.0364950866376243, 0.027770211637624297, 0.02722789502102555 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120428807906017, 1.5618905882906018, 1.561890515506008, 1.312042808006008, 1.3120428807906017 ], "y": [ 3.999969200022097, 3.999969200022097, 4.799963845425502, 4.799963845425502, 3.999969200022097 ], "z": [ 0.027770211637624297, 0.0364950866376243, 0.037123327164465245, 0.028398452164465245, 0.027770211637624297 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.312042808006008, 1.561890515506008, 1.5618904392765955, 1.3120427317765955, 1.312042808006008 ], "y": [ 4.799963845425502, 4.799963845425502, 5.599958879734863, 5.599958879734863, 4.799963845425502 ], "z": [ 0.028398452164465245, 0.037123327164465245, 0.0378260827303971, 0.029101207730397104, 0.028398452164465245 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120427317765955, 1.5618904392765955, 1.5618903603880865, 1.3120426528880864, 1.3120427317765955 ], "y": [ 5.599958879734863, 5.599958879734863, 6.399954127618544, 6.399954127618544, 5.599958879734863 ], "z": [ 0.029101207730397104, 0.0378260827303971, 0.03859270083273544, 0.029867825832735437, 0.029101207730397104 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120426528880864, 1.5618903603880865, 1.5618902802598416, 1.3120425727598415, 1.3120426528880864 ], "y": [ 6.399954127618544, 6.399954127618544, 7.199949778457311, 7.199949778457311, 6.399954127618544 ], "z": [ 0.029867825832735437, 0.03859270083273544, 0.03941341795704199, 0.030688542957041993, 0.029867825832735437 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120425727598415, 1.5618902802598416, 1.5618901991849903, 1.3120424916849902, 1.3120425727598415 ], "y": [ 7.199949778457311, 7.199949778457311, 7.999945656178906, 7.999945656178906, 7.199949778457311 ], "z": [ 0.030688542957041993, 0.03941341795704199, 0.040279237932281324, 0.031554362932281324, 0.030688542957041993 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120424916849902, 1.5618901991849903, 1.561890118185574, 1.312042410685574, 1.3120424916849902 ], "y": [ 7.999945656178906, 7.999945656178906, 8.799941951360688, 8.799941951360688, 7.999945656178906 ], "z": [ 0.031554362932281324, 0.040279237932281324, 0.0411820607983649, 0.0324571857983649, 0.031554362932281324 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.312042410685574, 1.561890118185574, 1.5618900372102649, 1.3120423297102648, 1.312042410685574 ], "y": [ 8.799941951360688, 8.799941951360688, 9.599938487131828, 9.599938487131828, 8.799941951360688 ], "z": [ 0.0324571857983649, 0.0411820607983649, 0.04211455691576761, 0.03338968191576761, 0.0324571857983649 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120423297102648, 1.5618900372102649, 1.5618899570040967, 1.3120422495040966, 1.3120423297102648 ], "y": [ 9.599938487131828, 9.599938487131828, 10.399935452451604, 10.399935452451604, 9.599938487131828 ], "z": [ 0.03338968191576761, 0.04211455691576761, 0.043070292922093946, 0.034345417922093946, 0.03338968191576761 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120422495040966, 1.5618899570040967, 1.5618898772925018, 1.3120421697925018, 1.3120422495040966 ], "y": [ 10.399935452451604, 10.399935452451604, 11.199932669428227, 11.199932669428227, 10.399935452451604 ], "z": [ 0.034345417922093946, 0.043070292922093946, 0.04404360269443925, 0.03531872769443925, 0.034345417922093946 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120421697925018, 1.5618898772925018, 1.5618897986359046, 1.3120420911359045, 1.3120421697925018 ], "y": [ 11.199932669428227, 11.199932669428227, 11.999930322350817, 11.999930322350817, 11.199932669428227 ], "z": [ 0.03531872769443925, 0.04404360269443925, 0.045029709300556615, 0.036304834300556615, 0.03531872769443925 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120420911359045, 1.5618897986359046, 1.5618897206321942, 1.3120420131321942, 1.3120420911359045 ], "y": [ 11.999930322350817, 11.999930322350817, 12.799928232343898, 12.799928232343898, 11.999930322350817 ], "z": [ 0.036304834300556615, 0.045029709300556615, 0.04602459196380438, 0.03729971696380438, 0.036304834300556615 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120420131321942, 1.5618897206321942, 1.5618896437134036, 1.3120419362134035, 1.3120420131321942 ], "y": [ 12.799928232343898, 12.799928232343898, 13.599926573724765, 13.599926573724765, 12.799928232343898 ], "z": [ 0.03729971696380438, 0.04602459196380438, 0.04702510002889098, 0.03830022502889098, 0.03729971696380438 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120419362134035, 1.5618896437134036, 1.5618895674208244, 1.3120418599208243, 1.3120419362134035 ], "y": [ 13.599926573724765, 13.599926573724765, 14.39992516668033, 14.39992516668033, 13.599926573724765 ], "z": [ 0.03830022502889098, 0.04702510002889098, 0.04802881040369657, 0.03930393540369657, 0.03830022502889098 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120418599208243, 1.5618895674208244, 1.5618894920456887, 1.3120417845456886, 1.3120418599208243 ], "y": [ 14.39992516668033, 14.39992516668033, 15.199924158393646, 15.199924158393646, 14.39992516668033 ], "z": [ 0.03930393540369657, 0.04802881040369657, 0.04903411872109219, 0.04030924372109219, 0.03930393540369657 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3120417845456886, 1.5618894920456887, 1.5618894171092368, 1.3120417096092367, 1.3120417845456886 ], "y": [ 15.199924158393646, 15.199924158393646, 15.999923362319814, 15.999923362319814, 15.199924158393646 ], "z": [ 0.04030924372109219, 0.04903411872109219, 0.05004005272471522, 0.04131517772471522, 0.04030924372109219 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908300000003, 1.8117385375000004, 1.8117385137953572, 1.5618908062953571, 1.5618908300000003 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.7999933212351085, 0.7999933212351085, 9.162206417444405e-18 ], "z": [ 0.03489950000000005, 0.04362437500000005, 0.04369275489031038, 0.03496787989031038, 0.03489950000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618908062953571, 1.8117385137953572, 1.8117384746826246, 1.5618907671826245, 1.5618908062953571 ], "y": [ 0.7999933212351085, 0.7999933212351085, 1.5999868291304182, 1.5999868291304182, 0.7999933212351085 ], "z": [ 0.03496787989031038, 0.04369275489031038, 0.04390062599160158, 0.03517575099160158, 0.03496787989031038 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618907671826245, 1.8117384746826246, 1.8117384236878369, 1.5618907161878368, 1.5618907671826245 ], "y": [ 1.5999868291304182, 1.5999868291304182, 2.3999806938941566, 2.3999806938941566, 1.5999868291304182 ], "z": [ 0.03517575099160158, 0.04390062599160158, 0.04423353095937816, 0.03550865595937816, 0.03517575099160158 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618907161878368, 1.8117384236878369, 1.8117383631702957, 1.5618906556702956, 1.5618907161878368 ], "y": [ 2.3999806938941566, 2.3999806938941566, 3.1999747586029037, 3.1999747586029037, 2.3999806938941566 ], "z": [ 0.03550865595937816, 0.04423353095937816, 0.04467764502102555, 0.03595277002102555, 0.03550865595937816 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618906556702956, 1.8117383631702957, 1.8117382957906019, 1.5618905882906018, 1.5618906556702956 ], "y": [ 3.1999747586029037, 3.1999747586029037, 3.999969200022097, 3.999969200022097, 3.1999747586029037 ], "z": [ 0.03595277002102555, 0.04467764502102555, 0.0452199616376243, 0.0364950866376243, 0.03595277002102555 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618905882906018, 1.8117382957906019, 1.8117382230060082, 1.561890515506008, 1.5618905882906018 ], "y": [ 3.999969200022097, 3.999969200022097, 4.799963845425502, 4.799963845425502, 3.999969200022097 ], "z": [ 0.0364950866376243, 0.0452199616376243, 0.045848202164465245, 0.037123327164465245, 0.0364950866376243 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.561890515506008, 1.8117382230060082, 1.8117381467765956, 1.5618904392765955, 1.561890515506008 ], "y": [ 4.799963845425502, 4.799963845425502, 5.599958879734863, 5.599958879734863, 4.799963845425502 ], "z": [ 0.037123327164465245, 0.045848202164465245, 0.0465509577303971, 0.0378260827303971, 0.037123327164465245 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618904392765955, 1.8117381467765956, 1.8117380678880866, 1.5618903603880865, 1.5618904392765955 ], "y": [ 5.599958879734863, 5.599958879734863, 6.399954127618544, 6.399954127618544, 5.599958879734863 ], "z": [ 0.0378260827303971, 0.0465509577303971, 0.04731757583273544, 0.03859270083273544, 0.0378260827303971 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618903603880865, 1.8117380678880866, 1.8117379877598416, 1.5618902802598416, 1.5618903603880865 ], "y": [ 6.399954127618544, 6.399954127618544, 7.199949778457311, 7.199949778457311, 6.399954127618544 ], "z": [ 0.03859270083273544, 0.04731757583273544, 0.04813829295704199, 0.03941341795704199, 0.03859270083273544 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618902802598416, 1.8117379877598416, 1.8117379066849904, 1.5618901991849903, 1.5618902802598416 ], "y": [ 7.199949778457311, 7.199949778457311, 7.999945656178906, 7.999945656178906, 7.199949778457311 ], "z": [ 0.03941341795704199, 0.04813829295704199, 0.049004112932281324, 0.040279237932281324, 0.03941341795704199 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618901991849903, 1.8117379066849904, 1.811737825685574, 1.561890118185574, 1.5618901991849903 ], "y": [ 7.999945656178906, 7.999945656178906, 8.799941951360688, 8.799941951360688, 7.999945656178906 ], "z": [ 0.040279237932281324, 0.049004112932281324, 0.0499069357983649, 0.0411820607983649, 0.040279237932281324 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.561890118185574, 1.811737825685574, 1.811737744710265, 1.5618900372102649, 1.561890118185574 ], "y": [ 8.799941951360688, 8.799941951360688, 9.599938487131828, 9.599938487131828, 8.799941951360688 ], "z": [ 0.0411820607983649, 0.0499069357983649, 0.05083943191576761, 0.04211455691576761, 0.0411820607983649 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618900372102649, 1.811737744710265, 1.8117376645040968, 1.5618899570040967, 1.5618900372102649 ], "y": [ 9.599938487131828, 9.599938487131828, 10.399935452451604, 10.399935452451604, 9.599938487131828 ], "z": [ 0.04211455691576761, 0.05083943191576761, 0.051795167922093946, 0.043070292922093946, 0.04211455691576761 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618899570040967, 1.8117376645040968, 1.811737584792502, 1.5618898772925018, 1.5618899570040967 ], "y": [ 10.399935452451604, 10.399935452451604, 11.199932669428227, 11.199932669428227, 10.399935452451604 ], "z": [ 0.043070292922093946, 0.051795167922093946, 0.05276847769443925, 0.04404360269443925, 0.043070292922093946 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618898772925018, 1.811737584792502, 1.8117375061359047, 1.5618897986359046, 1.5618898772925018 ], "y": [ 11.199932669428227, 11.199932669428227, 11.999930322350817, 11.999930322350817, 11.199932669428227 ], "z": [ 0.04404360269443925, 0.05276847769443925, 0.053754584300556615, 0.045029709300556615, 0.04404360269443925 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618897986359046, 1.8117375061359047, 1.8117374281321943, 1.5618897206321942, 1.5618897986359046 ], "y": [ 11.999930322350817, 11.999930322350817, 12.799928232343898, 12.799928232343898, 11.999930322350817 ], "z": [ 0.045029709300556615, 0.053754584300556615, 0.05474946696380438, 0.04602459196380438, 0.045029709300556615 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618897206321942, 1.8117374281321943, 1.8117373512134036, 1.5618896437134036, 1.5618897206321942 ], "y": [ 12.799928232343898, 12.799928232343898, 13.599926573724765, 13.599926573724765, 12.799928232343898 ], "z": [ 0.04602459196380438, 0.05474946696380438, 0.05574997502889098, 0.04702510002889098, 0.04602459196380438 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618896437134036, 1.8117373512134036, 1.8117372749208245, 1.5618895674208244, 1.5618896437134036 ], "y": [ 13.599926573724765, 13.599926573724765, 14.39992516668033, 14.39992516668033, 13.599926573724765 ], "z": [ 0.04702510002889098, 0.05574997502889098, 0.05675368540369657, 0.04802881040369657, 0.04702510002889098 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618895674208244, 1.8117372749208245, 1.8117371995456888, 1.5618894920456887, 1.5618895674208244 ], "y": [ 14.39992516668033, 14.39992516668033, 15.199924158393646, 15.199924158393646, 14.39992516668033 ], "z": [ 0.04802881040369657, 0.05675368540369657, 0.05775899372109219, 0.04903411872109219, 0.04802881040369657 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5618894920456887, 1.8117371995456888, 1.8117371246092369, 1.5618894171092368, 1.5618894920456887 ], "y": [ 15.199924158393646, 15.199924158393646, 15.999923362319814, 15.999923362319814, 15.199924158393646 ], "z": [ 0.04903411872109219, 0.05775899372109219, 0.05876492772471522, 0.05004005272471522, 0.04903411872109219 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385375000004, 2.0615862450000004, 2.0615862212953573, 1.8117385137953572, 1.8117385375000004 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.7999933212351085, 0.7999933212351085, 9.162206417444405e-18 ], "z": [ 0.04362437500000005, 0.05234925000000005, 0.05241762989031038, 0.04369275489031038, 0.04362437500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117385137953572, 2.0615862212953573, 2.0615861821826247, 1.8117384746826246, 1.8117385137953572 ], "y": [ 0.7999933212351085, 0.7999933212351085, 1.5999868291304182, 1.5999868291304182, 0.7999933212351085 ], "z": [ 0.04369275489031038, 0.05241762989031038, 0.05262550099160158, 0.04390062599160158, 0.04369275489031038 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117384746826246, 2.0615861821826247, 2.0615861311878367, 1.8117384236878369, 1.8117384746826246 ], "y": [ 1.5999868291304182, 1.5999868291304182, 2.3999806938941566, 2.3999806938941566, 1.5999868291304182 ], "z": [ 0.04390062599160158, 0.05262550099160158, 0.05295840595937816, 0.04423353095937816, 0.04390062599160158 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117384236878369, 2.0615861311878367, 2.0615860706702955, 1.8117383631702957, 1.8117384236878369 ], "y": [ 2.3999806938941566, 2.3999806938941566, 3.1999747586029037, 3.1999747586029037, 2.3999806938941566 ], "z": [ 0.04423353095937816, 0.05295840595937816, 0.05340252002102555, 0.04467764502102555, 0.04423353095937816 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117383631702957, 2.0615860706702955, 2.0615860032906017, 1.8117382957906019, 1.8117383631702957 ], "y": [ 3.1999747586029037, 3.1999747586029037, 3.999969200022097, 3.999969200022097, 3.1999747586029037 ], "z": [ 0.04467764502102555, 0.05340252002102555, 0.0539448366376243, 0.0452199616376243, 0.04467764502102555 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117382957906019, 2.0615860032906017, 2.0615859305060082, 1.8117382230060082, 1.8117382957906019 ], "y": [ 3.999969200022097, 3.999969200022097, 4.799963845425502, 4.799963845425502, 3.999969200022097 ], "z": [ 0.0452199616376243, 0.0539448366376243, 0.054573077164465245, 0.045848202164465245, 0.0452199616376243 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117382230060082, 2.0615859305060082, 2.0615858542765957, 1.8117381467765956, 1.8117382230060082 ], "y": [ 4.799963845425502, 4.799963845425502, 5.599958879734863, 5.599958879734863, 4.799963845425502 ], "z": [ 0.045848202164465245, 0.054573077164465245, 0.0552758327303971, 0.0465509577303971, 0.045848202164465245 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117381467765956, 2.0615858542765957, 2.0615857753880866, 1.8117380678880866, 1.8117381467765956 ], "y": [ 5.599958879734863, 5.599958879734863, 6.399954127618544, 6.399954127618544, 5.599958879734863 ], "z": [ 0.0465509577303971, 0.0552758327303971, 0.05604245083273544, 0.04731757583273544, 0.0465509577303971 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117380678880866, 2.0615857753880866, 2.0615856952598417, 1.8117379877598416, 1.8117380678880866 ], "y": [ 6.399954127618544, 6.399954127618544, 7.199949778457311, 7.199949778457311, 6.399954127618544 ], "z": [ 0.04731757583273544, 0.05604245083273544, 0.05686316795704199, 0.04813829295704199, 0.04731757583273544 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117379877598416, 2.0615856952598417, 2.0615856141849904, 1.8117379066849904, 1.8117379877598416 ], "y": [ 7.199949778457311, 7.199949778457311, 7.999945656178906, 7.999945656178906, 7.199949778457311 ], "z": [ 0.04813829295704199, 0.05686316795704199, 0.057728987932281324, 0.049004112932281324, 0.04813829295704199 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117379066849904, 2.0615856141849904, 2.061585533185574, 1.811737825685574, 1.8117379066849904 ], "y": [ 7.999945656178906, 7.999945656178906, 8.799941951360688, 8.799941951360688, 7.999945656178906 ], "z": [ 0.049004112932281324, 0.057728987932281324, 0.0586318107983649, 0.0499069357983649, 0.049004112932281324 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.811737825685574, 2.061585533185574, 2.061585452210265, 1.811737744710265, 1.811737825685574 ], "y": [ 8.799941951360688, 8.799941951360688, 9.599938487131828, 9.599938487131828, 8.799941951360688 ], "z": [ 0.0499069357983649, 0.0586318107983649, 0.05956430691576761, 0.05083943191576761, 0.0499069357983649 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.811737744710265, 2.061585452210265, 2.061585372004097, 1.8117376645040968, 1.811737744710265 ], "y": [ 9.599938487131828, 9.599938487131828, 10.399935452451604, 10.399935452451604, 9.599938487131828 ], "z": [ 0.05083943191576761, 0.05956430691576761, 0.060520042922093946, 0.051795167922093946, 0.05083943191576761 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117376645040968, 2.061585372004097, 2.0615852922925018, 1.811737584792502, 1.8117376645040968 ], "y": [ 10.399935452451604, 10.399935452451604, 11.199932669428227, 11.199932669428227, 10.399935452451604 ], "z": [ 0.051795167922093946, 0.060520042922093946, 0.06149335269443925, 0.05276847769443925, 0.051795167922093946 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.811737584792502, 2.0615852922925018, 2.0615852136359045, 1.8117375061359047, 1.811737584792502 ], "y": [ 11.199932669428227, 11.199932669428227, 11.999930322350817, 11.999930322350817, 11.199932669428227 ], "z": [ 0.05276847769443925, 0.06149335269443925, 0.062479459300556615, 0.053754584300556615, 0.05276847769443925 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117375061359047, 2.0615852136359045, 2.0615851356321944, 1.8117374281321943, 1.8117375061359047 ], "y": [ 11.999930322350817, 11.999930322350817, 12.799928232343898, 12.799928232343898, 11.999930322350817 ], "z": [ 0.053754584300556615, 0.062479459300556615, 0.06347434196380439, 0.05474946696380438, 0.053754584300556615 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117374281321943, 2.0615851356321944, 2.0615850587134035, 1.8117373512134036, 1.8117374281321943 ], "y": [ 12.799928232343898, 12.799928232343898, 13.599926573724765, 13.599926573724765, 12.799928232343898 ], "z": [ 0.05474946696380438, 0.06347434196380439, 0.06447485002889097, 0.05574997502889098, 0.05474946696380438 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117373512134036, 2.0615850587134035, 2.0615849824208246, 1.8117372749208245, 1.8117373512134036 ], "y": [ 13.599926573724765, 13.599926573724765, 14.39992516668033, 14.39992516668033, 13.599926573724765 ], "z": [ 0.05574997502889098, 0.06447485002889097, 0.06547856040369657, 0.05675368540369657, 0.05574997502889098 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117372749208245, 2.0615849824208246, 2.0615849070456886, 1.8117371995456888, 1.8117372749208245 ], "y": [ 14.39992516668033, 14.39992516668033, 15.199924158393646, 15.199924158393646, 14.39992516668033 ], "z": [ 0.05675368540369657, 0.06547856040369657, 0.06648386872109219, 0.05775899372109219, 0.05675368540369657 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8117371995456888, 2.0615849070456886, 2.061584832109237, 1.8117371246092369, 1.8117371995456888 ], "y": [ 15.199924158393646, 15.199924158393646, 15.999923362319814, 15.999923362319814, 15.199924158393646 ], "z": [ 0.05775899372109219, 0.06648386872109219, 0.06748980272471522, 0.05876492772471522, 0.05775899372109219 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.562499976295357, 0.5624999371826244, 0.5624998861878366, 0.5624998256702956, 0.5624997582906017, 0.5624996855060079, 0.5624996092765954, 0.5624995303880863, 0.5624994502598415, 0.5624993691849902, 0.5624992881855739, 0.5624992072102647, 0.5624991270040965, 0.5624990472925017, 0.5624989686359044, 0.5624988906321942, 0.5624988137134035, 0.5624987374208242, 0.5624986620456885, 0.5624985871092366 ], "y": [ 9.162206417444405e-18, 0.7999933212351085, 1.5999868291304182, 2.3999806938941566, 3.1999747586029037, 3.999969200022097, 4.799963845425502, 5.599958879734863, 6.399954127618544, 7.199949778457311, 7.999945656178906, 8.799941951360688, 9.599938487131828, 10.399935452451604, 11.199932669428227, 11.999930322350817, 12.799928232343898, 13.599926573724765, 14.39992516668033, 15.199924158393646, 15.999923362319814 ], "z": [ 4.871002816324649e-17, 6.837989031038178e-05, 0.00027625099160157664, 0.0006091559593781608, 0.0010532700210255516, 0.001595586637624299, 0.0022238271644652464, 0.0029265827303971044, 0.003693200832735435, 0.004513917957041995, 0.00537973793228132, 0.006282560798364902, 0.007215056915767613, 0.008170792922093943, 0.009144102694439252, 0.010130209300556614, 0.011125091963804382, 0.012125600028890982, 0.01312931040369657, 0.014134618721092188, 0.01514055272471522 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.8123477075000001, 0.8123476837953569, 0.8123476446826243, 0.8123475936878366, 0.8123475331702955, 0.8123474657906017, 0.8123473930060079, 0.8123473167765953, 0.8123472378880863, 0.8123471577598415, 0.8123470766849902, 0.8123469956855739, 0.8123469147102647, 0.8123468345040965, 0.8123467547925016, 0.8123466761359044, 0.8123465981321941, 0.8123465212134034, 0.8123464449208242, 0.8123463695456885, 0.8123462946092366 ], "y": [ 9.162206417444405e-18, 0.7999933212351085, 1.5999868291304182, 2.3999806938941566, 3.1999747586029037, 3.999969200022097, 4.799963845425502, 5.599958879734863, 6.399954127618544, 7.199949778457311, 7.999945656178906, 8.799941951360688, 9.599938487131828, 10.399935452451604, 11.199932669428227, 11.999930322350817, 12.799928232343898, 13.599926573724765, 14.39992516668033, 15.199924158393646, 15.999923362319814 ], "z": [ 0.008724875000000049, 0.008793254890310381, 0.009001125991601577, 0.00933403095937816, 0.009778145021025551, 0.010320461637624298, 0.010948702164465246, 0.011651457730397104, 0.012418075832735435, 0.013238792957041995, 0.01410461293228132, 0.015007435798364901, 0.015939931915767612, 0.016895667922093942, 0.01786897769443925, 0.018855084300556615, 0.01984996696380438, 0.02085047502889098, 0.021854185403696567, 0.022859493721092188, 0.02386542772471522 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 1.0621954150000001, 1.062195391295357, 1.0621953521826244, 1.0621953011878367, 1.0621952406702955, 1.0621951732906016, 1.062195100506008, 1.0621950242765954, 1.0621949453880863, 1.0621948652598414, 1.0621947841849901, 1.0621947031855739, 1.0621946222102647, 1.0621945420040966, 1.0621944622925017, 1.0621943836359045, 1.062194305632194, 1.0621942287134034, 1.0621941524208243, 1.0621940770456886, 1.0621940021092366 ], "y": [ 9.162206417444405e-18, 0.7999933212351085, 1.5999868291304182, 2.3999806938941566, 3.1999747586029037, 3.999969200022097, 4.799963845425502, 5.599958879734863, 6.399954127618544, 7.199949778457311, 7.999945656178906, 8.799941951360688, 9.599938487131828, 10.399935452451604, 11.199932669428227, 11.999930322350817, 12.799928232343898, 13.599926573724765, 14.39992516668033, 15.199924158393646, 15.999923362319814 ], "z": [ 0.01744975000000005, 0.01751812989031038, 0.017726000991601577, 0.01805890595937816, 0.01850302002102555, 0.019045336637624297, 0.019673577164465245, 0.020376332730397104, 0.021142950832735437, 0.021963667957041993, 0.02282948793228132, 0.0237323107983649, 0.024664806915767612, 0.025620542922093942, 0.02659385269443925, 0.027579959300556615, 0.02857484196380438, 0.02957535002889098, 0.030579060403696567, 0.03158436872109219, 0.03259030272471522 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 1.3120431225000002, 1.312043098795357, 1.3120430596826245, 1.3120430086878367, 1.3120429481702955, 1.3120428807906017, 1.312042808006008, 1.3120427317765955, 1.3120426528880864, 1.3120425727598415, 1.3120424916849902, 1.312042410685574, 1.3120423297102648, 1.3120422495040966, 1.3120421697925018, 1.3120420911359045, 1.3120420131321942, 1.3120419362134035, 1.3120418599208243, 1.3120417845456886, 1.3120417096092367 ], "y": [ 9.162206417444405e-18, 0.7999933212351085, 1.5999868291304182, 2.3999806938941566, 3.1999747586029037, 3.999969200022097, 4.799963845425502, 5.599958879734863, 6.399954127618544, 7.199949778457311, 7.999945656178906, 8.799941951360688, 9.599938487131828, 10.399935452451604, 11.199932669428227, 11.999930322350817, 12.799928232343898, 13.599926573724765, 14.39992516668033, 15.199924158393646, 15.999923362319814 ], "z": [ 0.02617462500000005, 0.02624300489031038, 0.026450875991601577, 0.02678378095937816, 0.02722789502102555, 0.027770211637624297, 0.028398452164465245, 0.029101207730397104, 0.029867825832735437, 0.030688542957041993, 0.031554362932281324, 0.0324571857983649, 0.03338968191576761, 0.034345417922093946, 0.03531872769443925, 0.036304834300556615, 0.03729971696380438, 0.03830022502889098, 0.03930393540369657, 0.04030924372109219, 0.04131517772471522 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 1.5618908300000003, 1.5618908062953571, 1.5618907671826245, 1.5618907161878368, 1.5618906556702956, 1.5618905882906018, 1.561890515506008, 1.5618904392765955, 1.5618903603880865, 1.5618902802598416, 1.5618901991849903, 1.561890118185574, 1.5618900372102649, 1.5618899570040967, 1.5618898772925018, 1.5618897986359046, 1.5618897206321942, 1.5618896437134036, 1.5618895674208244, 1.5618894920456887, 1.5618894171092368 ], "y": [ 9.162206417444405e-18, 0.7999933212351085, 1.5999868291304182, 2.3999806938941566, 3.1999747586029037, 3.999969200022097, 4.799963845425502, 5.599958879734863, 6.399954127618544, 7.199949778457311, 7.999945656178906, 8.799941951360688, 9.599938487131828, 10.399935452451604, 11.199932669428227, 11.999930322350817, 12.799928232343898, 13.599926573724765, 14.39992516668033, 15.199924158393646, 15.999923362319814 ], "z": [ 0.03489950000000005, 0.03496787989031038, 0.03517575099160158, 0.03550865595937816, 0.03595277002102555, 0.0364950866376243, 0.037123327164465245, 0.0378260827303971, 0.03859270083273544, 0.03941341795704199, 0.040279237932281324, 0.0411820607983649, 0.04211455691576761, 0.043070292922093946, 0.04404360269443925, 0.045029709300556615, 0.04602459196380438, 0.04702510002889098, 0.04802881040369657, 0.04903411872109219, 0.05004005272471522 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 1.8117385375000004, 1.8117385137953572, 1.8117384746826246, 1.8117384236878369, 1.8117383631702957, 1.8117382957906019, 1.8117382230060082, 1.8117381467765956, 1.8117380678880866, 1.8117379877598416, 1.8117379066849904, 1.811737825685574, 1.811737744710265, 1.8117376645040968, 1.811737584792502, 1.8117375061359047, 1.8117374281321943, 1.8117373512134036, 1.8117372749208245, 1.8117371995456888, 1.8117371246092369 ], "y": [ 9.162206417444405e-18, 0.7999933212351085, 1.5999868291304182, 2.3999806938941566, 3.1999747586029037, 3.999969200022097, 4.799963845425502, 5.599958879734863, 6.399954127618544, 7.199949778457311, 7.999945656178906, 8.799941951360688, 9.599938487131828, 10.399935452451604, 11.199932669428227, 11.999930322350817, 12.799928232343898, 13.599926573724765, 14.39992516668033, 15.199924158393646, 15.999923362319814 ], "z": [ 0.04362437500000005, 0.04369275489031038, 0.04390062599160158, 0.04423353095937816, 0.04467764502102555, 0.0452199616376243, 0.045848202164465245, 0.0465509577303971, 0.04731757583273544, 0.04813829295704199, 0.049004112932281324, 0.0499069357983649, 0.05083943191576761, 0.051795167922093946, 0.05276847769443925, 0.053754584300556615, 0.05474946696380438, 0.05574997502889098, 0.05675368540369657, 0.05775899372109219, 0.05876492772471522 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 2.0615862450000004, 2.0615862212953573, 2.0615861821826247, 2.0615861311878367, 2.0615860706702955, 2.0615860032906017, 2.0615859305060082, 2.0615858542765957, 2.0615857753880866, 2.0615856952598417, 2.0615856141849904, 2.061585533185574, 2.061585452210265, 2.061585372004097, 2.0615852922925018, 2.0615852136359045, 2.0615851356321944, 2.0615850587134035, 2.0615849824208246, 2.0615849070456886, 2.061584832109237 ], "y": [ 9.162206417444405e-18, 0.7999933212351085, 1.5999868291304182, 2.3999806938941566, 3.1999747586029037, 3.999969200022097, 4.799963845425502, 5.599958879734863, 6.399954127618544, 7.199949778457311, 7.999945656178906, 8.799941951360688, 9.599938487131828, 10.399935452451604, 11.199932669428227, 11.999930322350817, 12.799928232343898, 13.599926573724765, 14.39992516668033, 15.199924158393646, 15.999923362319814 ], "z": [ 0.05234925000000005, 0.05241762989031038, 0.05262550099160158, 0.05295840595937816, 0.05340252002102555, 0.0539448366376243, 0.054573077164465245, 0.0552758327303971, 0.05604245083273544, 0.05686316795704199, 0.057728987932281324, 0.0586318107983649, 0.05956430691576761, 0.060520042922093946, 0.06149335269443925, 0.062479459300556615, 0.06347434196380439, 0.06447485002889097, 0.06547856040369657, 0.06648386872109219, 0.06748980272471522 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8123477075000001, 1.0621954150000001, 1.3120431225000002, 1.5618908300000003, 1.8117385375000004, 2.0615862450000004 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18 ], "z": [ 4.871002816324649e-17, 0.008724875000000049, 0.01744975000000005, 0.02617462500000005, 0.03489950000000005, 0.04362437500000005, 0.05234925000000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.562499976295357, 0.8123476837953569, 1.062195391295357, 1.312043098795357, 1.5618908062953571, 1.8117385137953572, 2.0615862212953573 ], "y": [ 0.7999933212351085, 0.7999933212351085, 0.7999933212351085, 0.7999933212351085, 0.7999933212351085, 0.7999933212351085, 0.7999933212351085 ], "z": [ 6.837989031038178e-05, 0.008793254890310381, 0.01751812989031038, 0.02624300489031038, 0.03496787989031038, 0.04369275489031038, 0.05241762989031038 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624999371826244, 0.8123476446826243, 1.0621953521826244, 1.3120430596826245, 1.5618907671826245, 1.8117384746826246, 2.0615861821826247 ], "y": [ 1.5999868291304182, 1.5999868291304182, 1.5999868291304182, 1.5999868291304182, 1.5999868291304182, 1.5999868291304182, 1.5999868291304182 ], "z": [ 0.00027625099160157664, 0.009001125991601577, 0.017726000991601577, 0.026450875991601577, 0.03517575099160158, 0.04390062599160158, 0.05262550099160158 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624998861878366, 0.8123475936878366, 1.0621953011878367, 1.3120430086878367, 1.5618907161878368, 1.8117384236878369, 2.0615861311878367 ], "y": [ 2.3999806938941566, 2.3999806938941566, 2.3999806938941566, 2.3999806938941566, 2.3999806938941566, 2.3999806938941566, 2.3999806938941566 ], "z": [ 0.0006091559593781608, 0.00933403095937816, 0.01805890595937816, 0.02678378095937816, 0.03550865595937816, 0.04423353095937816, 0.05295840595937816 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624998256702956, 0.8123475331702955, 1.0621952406702955, 1.3120429481702955, 1.5618906556702956, 1.8117383631702957, 2.0615860706702955 ], "y": [ 3.1999747586029037, 3.1999747586029037, 3.1999747586029037, 3.1999747586029037, 3.1999747586029037, 3.1999747586029037, 3.1999747586029037 ], "z": [ 0.0010532700210255516, 0.009778145021025551, 0.01850302002102555, 0.02722789502102555, 0.03595277002102555, 0.04467764502102555, 0.05340252002102555 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624997582906017, 0.8123474657906017, 1.0621951732906016, 1.3120428807906017, 1.5618905882906018, 1.8117382957906019, 2.0615860032906017 ], "y": [ 3.999969200022097, 3.999969200022097, 3.999969200022097, 3.999969200022097, 3.999969200022097, 3.999969200022097, 3.999969200022097 ], "z": [ 0.001595586637624299, 0.010320461637624298, 0.019045336637624297, 0.027770211637624297, 0.0364950866376243, 0.0452199616376243, 0.0539448366376243 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624996855060079, 0.8123473930060079, 1.062195100506008, 1.312042808006008, 1.561890515506008, 1.8117382230060082, 2.0615859305060082 ], "y": [ 4.799963845425502, 4.799963845425502, 4.799963845425502, 4.799963845425502, 4.799963845425502, 4.799963845425502, 4.799963845425502 ], "z": [ 0.0022238271644652464, 0.010948702164465246, 0.019673577164465245, 0.028398452164465245, 0.037123327164465245, 0.045848202164465245, 0.054573077164465245 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624996092765954, 0.8123473167765953, 1.0621950242765954, 1.3120427317765955, 1.5618904392765955, 1.8117381467765956, 2.0615858542765957 ], "y": [ 5.599958879734863, 5.599958879734863, 5.599958879734863, 5.599958879734863, 5.599958879734863, 5.599958879734863, 5.599958879734863 ], "z": [ 0.0029265827303971044, 0.011651457730397104, 0.020376332730397104, 0.029101207730397104, 0.0378260827303971, 0.0465509577303971, 0.0552758327303971 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624995303880863, 0.8123472378880863, 1.0621949453880863, 1.3120426528880864, 1.5618903603880865, 1.8117380678880866, 2.0615857753880866 ], "y": [ 6.399954127618544, 6.399954127618544, 6.399954127618544, 6.399954127618544, 6.399954127618544, 6.399954127618544, 6.399954127618544 ], "z": [ 0.003693200832735435, 0.012418075832735435, 0.021142950832735437, 0.029867825832735437, 0.03859270083273544, 0.04731757583273544, 0.05604245083273544 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624994502598415, 0.8123471577598415, 1.0621948652598414, 1.3120425727598415, 1.5618902802598416, 1.8117379877598416, 2.0615856952598417 ], "y": [ 7.199949778457311, 7.199949778457311, 7.199949778457311, 7.199949778457311, 7.199949778457311, 7.199949778457311, 7.199949778457311 ], "z": [ 0.004513917957041995, 0.013238792957041995, 0.021963667957041993, 0.030688542957041993, 0.03941341795704199, 0.04813829295704199, 0.05686316795704199 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624993691849902, 0.8123470766849902, 1.0621947841849901, 1.3120424916849902, 1.5618901991849903, 1.8117379066849904, 2.0615856141849904 ], "y": [ 7.999945656178906, 7.999945656178906, 7.999945656178906, 7.999945656178906, 7.999945656178906, 7.999945656178906, 7.999945656178906 ], "z": [ 0.00537973793228132, 0.01410461293228132, 0.02282948793228132, 0.031554362932281324, 0.040279237932281324, 0.049004112932281324, 0.057728987932281324 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624992881855739, 0.8123469956855739, 1.0621947031855739, 1.312042410685574, 1.561890118185574, 1.811737825685574, 2.061585533185574 ], "y": [ 8.799941951360688, 8.799941951360688, 8.799941951360688, 8.799941951360688, 8.799941951360688, 8.799941951360688, 8.799941951360688 ], "z": [ 0.006282560798364902, 0.015007435798364901, 0.0237323107983649, 0.0324571857983649, 0.0411820607983649, 0.0499069357983649, 0.0586318107983649 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624992072102647, 0.8123469147102647, 1.0621946222102647, 1.3120423297102648, 1.5618900372102649, 1.811737744710265, 2.061585452210265 ], "y": [ 9.599938487131828, 9.599938487131828, 9.599938487131828, 9.599938487131828, 9.599938487131828, 9.599938487131828, 9.599938487131828 ], "z": [ 0.007215056915767613, 0.015939931915767612, 0.024664806915767612, 0.03338968191576761, 0.04211455691576761, 0.05083943191576761, 0.05956430691576761 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624991270040965, 0.8123468345040965, 1.0621945420040966, 1.3120422495040966, 1.5618899570040967, 1.8117376645040968, 2.061585372004097 ], "y": [ 10.399935452451604, 10.399935452451604, 10.399935452451604, 10.399935452451604, 10.399935452451604, 10.399935452451604, 10.399935452451604 ], "z": [ 0.008170792922093943, 0.016895667922093942, 0.025620542922093942, 0.034345417922093946, 0.043070292922093946, 0.051795167922093946, 0.060520042922093946 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624990472925017, 0.8123467547925016, 1.0621944622925017, 1.3120421697925018, 1.5618898772925018, 1.811737584792502, 2.0615852922925018 ], "y": [ 11.199932669428227, 11.199932669428227, 11.199932669428227, 11.199932669428227, 11.199932669428227, 11.199932669428227, 11.199932669428227 ], "z": [ 0.009144102694439252, 0.01786897769443925, 0.02659385269443925, 0.03531872769443925, 0.04404360269443925, 0.05276847769443925, 0.06149335269443925 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624989686359044, 0.8123466761359044, 1.0621943836359045, 1.3120420911359045, 1.5618897986359046, 1.8117375061359047, 2.0615852136359045 ], "y": [ 11.999930322350817, 11.999930322350817, 11.999930322350817, 11.999930322350817, 11.999930322350817, 11.999930322350817, 11.999930322350817 ], "z": [ 0.010130209300556614, 0.018855084300556615, 0.027579959300556615, 0.036304834300556615, 0.045029709300556615, 0.053754584300556615, 0.062479459300556615 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624988906321942, 0.8123465981321941, 1.062194305632194, 1.3120420131321942, 1.5618897206321942, 1.8117374281321943, 2.0615851356321944 ], "y": [ 12.799928232343898, 12.799928232343898, 12.799928232343898, 12.799928232343898, 12.799928232343898, 12.799928232343898, 12.799928232343898 ], "z": [ 0.011125091963804382, 0.01984996696380438, 0.02857484196380438, 0.03729971696380438, 0.04602459196380438, 0.05474946696380438, 0.06347434196380439 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624988137134035, 0.8123465212134034, 1.0621942287134034, 1.3120419362134035, 1.5618896437134036, 1.8117373512134036, 2.0615850587134035 ], "y": [ 13.599926573724765, 13.599926573724765, 13.599926573724765, 13.599926573724765, 13.599926573724765, 13.599926573724765, 13.599926573724765 ], "z": [ 0.012125600028890982, 0.02085047502889098, 0.02957535002889098, 0.03830022502889098, 0.04702510002889098, 0.05574997502889098, 0.06447485002889097 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624987374208242, 0.8123464449208242, 1.0621941524208243, 1.3120418599208243, 1.5618895674208244, 1.8117372749208245, 2.0615849824208246 ], "y": [ 14.39992516668033, 14.39992516668033, 14.39992516668033, 14.39992516668033, 14.39992516668033, 14.39992516668033, 14.39992516668033 ], "z": [ 0.01312931040369657, 0.021854185403696567, 0.030579060403696567, 0.03930393540369657, 0.04802881040369657, 0.05675368540369657, 0.06547856040369657 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624986620456885, 0.8123463695456885, 1.0621940770456886, 1.3120417845456886, 1.5618894920456887, 1.8117371995456888, 2.0615849070456886 ], "y": [ 15.199924158393646, 15.199924158393646, 15.199924158393646, 15.199924158393646, 15.199924158393646, 15.199924158393646, 15.199924158393646 ], "z": [ 0.014134618721092188, 0.022859493721092188, 0.03158436872109219, 0.04030924372109219, 0.04903411872109219, 0.05775899372109219, 0.06648386872109219 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624985871092366, 0.8123462946092366, 1.0621940021092366, 1.3120417096092367, 1.5618894171092368, 1.8117371246092369, 2.061584832109237 ], "y": [ 15.999923362319814, 15.999923362319814, 15.999923362319814, 15.999923362319814, 15.999923362319814, 15.999923362319814, 15.999923362319814 ], "z": [ 0.01514055272471522, 0.02386542772471522, 0.03259030272471522, 0.04131517772471522, 0.05004005272471522, 0.05876492772471522, 0.06748980272471522 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "name": "Beam nodes", "type": "scatter3d", "x": [ 0, -2.374942138688192e-08, -6.284862035108256e-08 ], "y": [ 0, 0.799999995444727, 1.59999996555245 ], "z": [ 0, 7.529358720955091e-05, 0.00029029011673404904 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -6.284862035108256e-08, -1.1378098335723172e-07, -1.7418561622652579e-07 ], "y": [ 1.59999996555245, 2.399999892672534, 3.199999764751563 ], "z": [ 0.00029029011673404904, 0.0006303055085703814, 0.0010816222764656192 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -1.7418561622652579e-07, -2.414234252892935e-07, -3.1403089387878787e-07 ], "y": [ 3.199999764751563, 3.999999575756786, 4.79999932313942 ], "z": [ 0.0010816222764656192, 0.001631013837129204, 0.0022663366294538505 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -3.1403089387878787e-07, -3.900781250590501e-07, -4.6876762866440916e-07 ], "y": [ 4.79999932313942, 5.599999008239176, 6.399998634207163 ], "z": [ 0.0022663366294538505, 0.002975963538135048, 0.003749388763217276 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -4.6876762866440916e-07, -5.487166981842684e-07, -6.296148558767214e-07 ], "y": [ 6.399998634207163, 7.19999820644239, 7.99999773090598 ], "z": [ 0.003749388763217276, 0.0045766335601332515, 0.005448856032774456 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -6.296148558767214e-07, -7.104811947455406e-07, -7.913442687833645e-07 ], "y": [ 7.99999773090598, 8.799997214613729, 9.599996664266103 ], "z": [ 0.005448856032774456, 0.006357745534489979, 0.007296132846321719 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -7.913442687833645e-07, -8.715027288124392e-07, -9.512032581544789e-07 ], "y": [ 9.599996664266103, 10.399996086807967, 11.199995488303351 ], "z": [ 0.007296132846321719, 0.00825738016953222, 0.009235987245929039 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -9.512032581544789e-07, -1.029929678635961e-06, -1.1080515871694994e-06 ], "y": [ 11.199995488303351, 11.999994874562574, 12.799994250201319 ], "z": [ 0.009235987245929039, 0.010226981381871196, 0.011226512555626676 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -1.1080515871694994e-06, -1.1851793486233463e-06, -1.261735127261516e-06 ], "y": [ 12.799994250201319, 13.599993619324277, 14.399992984728675 ], "z": [ 0.011226512555626676, 0.012231248025347296, 0.01323894000987043 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -1.261735127261516e-06, -1.3374633903919913e-06, -1.412801781371022e-06 ], "y": [ 14.399992984728675, 15.199992348619805, 15.999991711987978 ], "z": [ 0.01323894000987043, 0.014247832746099356, 0.015257139294634629 ] } ], "layout": { "autosize": true, "scene": { "aspectmode": "auto", "aspectratio": { "x": 1, "y": 1, "z": 1 }, "camera": { "center": { "x": 0, "y": 0, "z": 0 }, "eye": { "x": -1.2559758030610624, "y": -1.4581970864212204, "z": 0.9918094773079071 }, "projection": { "type": "perspective" }, "up": { "x": 0, "y": 0, "z": 1 } } }, "template": { "data": { "bar": [ { "error_x": { "color": "#2a3f5f" }, "error_y": { "color": "#2a3f5f" }, "marker": { "line": { "color": "#E5ECF6", "width": 0.5 }, "pattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 } }, "type": "bar" } ], "barpolar": [ { "marker": { "line": { "color": "#E5ECF6", "width": 0.5 }, "pattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 } }, "type": "barpolar" } ], "carpet": [ { "aaxis": { "endlinecolor": "#2a3f5f", "gridcolor": "white", "linecolor": "white", "minorgridcolor": "white", "startlinecolor": "#2a3f5f" }, "baxis": { "endlinecolor": "#2a3f5f", "gridcolor": "white", "linecolor": "white", "minorgridcolor": "white", "startlinecolor": "#2a3f5f" }, "type": "carpet" } ], "choropleth": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "type": "choropleth" } ], "contour": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "contour" } ], "contourcarpet": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "type": "contourcarpet" } ], "heatmap": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "heatmap" } ], "heatmapgl": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "heatmapgl" } ], "histogram": [ { "marker": { "pattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 } }, "type": "histogram" } ], "histogram2d": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "histogram2d" } ], "histogram2dcontour": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "histogram2dcontour" } ], "mesh3d": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "type": "mesh3d" } ], "parcoords": [ { "line": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "parcoords" } ], "pie": [ { "automargin": true, "type": "pie" } ], "scatter": [ { "fillpattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 }, "type": "scatter" } ], "scatter3d": [ { "line": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatter3d" } ], "scattercarpet": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattercarpet" } ], "scattergeo": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattergeo" } ], "scattergl": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattergl" } ], "scattermapbox": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattermapbox" } ], "scatterpolar": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatterpolar" } ], "scatterpolargl": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatterpolargl" } ], "scatterternary": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatterternary" } ], "surface": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "surface" } ], "table": [ { "cells": { "fill": { "color": "#EBF0F8" }, "line": { "color": "white" } }, "header": { "fill": { "color": "#C8D4E3" }, "line": { "color": "white" } }, "type": "table" } ] }, "layout": { "annotationdefaults": { "arrowcolor": "#2a3f5f", "arrowhead": 0, "arrowwidth": 1 }, "autotypenumbers": "strict", "coloraxis": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "colorscale": { "diverging": [ [ 0, "#8e0152" ], [ 0.1, "#c51b7d" ], [ 0.2, "#de77ae" ], [ 0.3, "#f1b6da" ], [ 0.4, "#fde0ef" ], [ 0.5, "#f7f7f7" ], [ 0.6, "#e6f5d0" ], [ 0.7, "#b8e186" ], [ 0.8, "#7fbc41" ], [ 0.9, "#4d9221" ], [ 1, "#276419" ] ], "sequential": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "sequentialminus": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ] }, "colorway": [ "#636efa", "#EF553B", "#00cc96", "#ab63fa", "#FFA15A", "#19d3f3", "#FF6692", "#B6E880", "#FF97FF", "#FECB52" ], "font": { "color": "#2a3f5f" }, "geo": { "bgcolor": "white", "lakecolor": "white", "landcolor": "#E5ECF6", "showlakes": true, "showland": true, "subunitcolor": "white" }, "hoverlabel": { "align": "left" }, "hovermode": "closest", "mapbox": { "style": "light" }, "paper_bgcolor": "white", "plot_bgcolor": "#E5ECF6", "polar": { "angularaxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" }, "bgcolor": "#E5ECF6", "radialaxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" } }, "scene": { "xaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white" }, "yaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white" }, "zaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white" } }, "shapedefaults": { "line": { "color": "#2a3f5f" } }, "ternary": { "aaxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" }, "baxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" }, "bgcolor": "#E5ECF6", "caxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" } }, "title": { "x": 0.05 }, "xaxis": { "automargin": true, "gridcolor": "white", "linecolor": "white", "ticks": "", "title": { "standoff": 15 }, "zerolinecolor": "white", "zerolinewidth": 2 }, "yaxis": { "automargin": true, "gridcolor": "white", "linecolor": "white", "ticks": "", "title": { "standoff": 15 }, "zerolinecolor": "white", "zerolinewidth": 2 } } } } }, "image/png": "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9sKlbnXPru7s+UtX9a7Zf/NKl+1Oy1hpsnzip692GU1uESBAgAABAgS2k4CAZjs9LfdKgAABAtedQMKZhx55qvtkGa39aGkMPGmlOiRVIbX3TN0WtUyQ9UKabGe688Bqd+HiWvf3b527clvLDGdqZVF/MtYyfVyLAAECBAgQINAiIKBp0XMsAQIECBCYg8BQc9of/NXL3f0fG+4xk9dnElSCiGWvoZCmv81p9+4but27dnW/OHWu23fT8ipn0lj5yJeOdS9+9Wh36EN3LZvF9QgQIECAAAECzQICmmZCJyBAgAABAgQIECBAgAABAgQItAkIaNr8HE2AAAECBAgQIECAAAECBAgQaBYQ0DQTOgEBAgQIECBAgAABAgQIECBAoE1AQNPm52gCBAgQIECAAAECBAgQIECAQLOAgKaZ0AkIECBAgAABAgQIECBAgAABAm0CApo2P0cTIECAAAECBAgQIECAAAECBJoFBDTNhE5AgAABAgQIECBAgAABAgQIEGgTENC0+TmaAAECBAgQIECAAAECBAgQINAsIKBpJnQCAgQIECBAgAABAgQIECBAgECbgICmzc/RBAgQIECAAAECBAgQIECAAIFmAQFNM6ETECBAgAABAgQIECBAgAABAgTaBAQ0bX6OJkCAAAECBAgQIECAAAECBAg0CwhomgmdgAABAgQIECBAgAABAgQIECDQJiCgafNzNAECBAgQIECAAAECBAgQIECgWUBA00zoBAQIECBAgAABAgQIECBAgACBNgEBTZufowkQIECAAAECBAgQIECAAAECzQICmmZCJyBAgAABAgQIECBAgAABAgQItAkIaNr8HE2AAAECBAgQIECAAAECBAgQaBYQ0DQTOgEBAgQIECBAgAABAgQIECBAoE1AQNPm52gCBAgQIECAAAECBAgQIECAQLOAgKaZ0AkIECBAgAABAgQIECBAgAABAm0CApo2P0cTIECAAAECBAgQIECAAAECBJoFBDTNhE5AgAABAgQIECBAgAABAgQIEGgTENC0+TmaAAECBAgQIECAAAECBAgQINAsIKBpJnQCAgQIECBAgAABAgQIECBAgECbgICmzc/RBAgQIECAAAECBAgQIECAAIFmAQFNM6ETECBAgAABAgQIECBAgAABAgTaBAQ0bX6OJkCAAAECBAgQIECAAAECBAg0CwhomgmdgAABAgQIECBAgAABAgQIECDQJiCgafNzNAECBAgQIECAAAECBAgQIECgWUBA00zoBAQIECBAgAABAgQIECBAgACBNgEBTZufowkQIECAAAECBAgQIECAAAECzQICmmZCJyBAgAABAgQIECBAgAABAgQItAkIaNr8HE2AAAECBAgQIECAAAECBAgQaBYQ0DQTOgEBAgQIECBAgAABAgQIECBAoE1AQNPm52gCBAgQIECAAAECBAgQIECAQLOAgKaZ0AkIECBAgAABAgQIECBAgAABAm0CApo2P0cTIECAAAECBAgQIECAAAECBJoFBDTNhE5AgAABAgQIECBAgAABAgQIEGgTENC0+TmaAAECBAgQIECAAAECBAgQINAsIKBpJnQCAgQIECBAgAABAgQIECBAgECbgICmzc/RBAgQIECAAAECBAgQIECAAIFmAQFNM6ETECBAgAABAgQIECBAgAABAgTaBAQ0bX6OJkCAAAECBAgQIECAAAECBAg0CwhomgmdgAABAgQIECBAgAABAgQIECDQJiCgafNzNAECBAgQIECAAAECBAgQIECgWUBA00zoBAQIECBAgAABAgQIECBAgACBNgEBTZufowkQIECAAAECBAgQIECAAAECzQICmmZCJyBAgAABAgQIECBAgAABAgQItAkIaNr8HE2AAAECBAgQIECAAAECBAgQaBYQ0DQTOgEBAgQIECBAgAABAgQIECBAoE1AQNPm52gCBAgQIECAAAECBAgQIECAQLOAgKaZ0AkIECBAgAABAgQIECBAgAABAm0CApo2P0cTIECAAAECBAgQIECAAAECBJoFBDTNhE5AgAABAgQIECBAgAABAgQIEGgTENC0+TmaAAECBAgQIECAAAECBAgQINAsIKBpJnQCAgQIECBAgAABAgQIECBAgECbgICmzc/RBAgQIECAAAECBAgQIECAAIFmAQFNM6ETECBAgAABAgQIECBAgAABAgTaBAQ0bX6OJkCAAAECBAgQIECAAAECBAg0CwhomgmdgAABAgQIECBAgAABAgQIECDQJiCgafNzNAECBAgQIECAAAECBAgQIECgWUBA00zoBAQIECBAgAABAgQIECBAgACBNgEBTZufowkQIECAAAECBAgQIECAAAECzQICmmZCJyBAgAABAgQIECBAgAABAgQItAkIaNr8HE2AAAECBAgQIECAAAECBAgQaBYQ0DQTOgEBAgQIECBAgAABAgQIECBAoE1AQNPm52gCBAgQIECAAAECBAgQIECAQLOAgKaZ0AkIECBAgAABAgQIECBAgAABAm0CApo2P0cTIECAAAECBAgQIECAAAECBJoFBDTNhE5AgAABAgQIECBAgAABAgQIEGgTENC0+TmaAAECBAgQIECAAAECBAgQINAsIKBpJnQCAgQIECBAgAABAgQIECBAgECbgICmzc/RBAgQIECAAAECBAgQIECAAIFmAQFNM6ETECBAgAABAgQIECBAgAABAgTaBAQ0bX6OJkCAAAECBAgQIECAAAECBAg0CwhomgmdgAABAgQIECBAgAABAgQIECDQJiCgafNzNAECBAgQIECAAAECBAgQIECgWUBA00zoBAQIECBAgAABAgQIECBAgACBNgEBTZufowkQIECAAAECBAgQIECAAAECzQL/H8m9dNSRcRxkAAAAAElFTkSuQmCC", "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pu.plot_timestep(sharpy_output, tstep=-1, minus_mstar=(wake_panels - 6), plotly=True,custom_scaling=False,z_compression=0.5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dynamic simulation\n", "Finally, we will run a dynamic simulation after the previous static one. With that in mind, we will now include a dynamic FSI solver (``DynamicCoupled``) in the simulation." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader',\n", " 'AerogridLoader',\n", " 'StaticCoupled',\n", " 'DynamicCoupled']\n", "\n", "SimInfo.solvers['SHARPy']['route'] = './'\n", "SimInfo.solvers['SHARPy']['case'] = 'dynamic'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We need information about the time step and the number of iterations:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "# Compute the number of time steps needed based on the previous parameters\n", "time_steps = int(end_time/dt)\n", "\n", "# Define the time step and the number of time steps in every solver that requires them as input\n", "SimInfo.set_variable_all_dicts('dt', dt)\n", "SimInfo.define_num_steps(time_steps)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We define the approximation we are going to use for the wake convection (``convection_scheme``), the velocity field and deactivate the gravity.\n", "\n", "We are going to use an FSI solver (``DynamicCoupled``) that couples a structual solver (``NonLinearDynamicPrescribedStep``) and aerodynamic solver (``StepUvlm``).\n", "\n", "Moreover, the FSI solver runs a series of postprocessors after each time step. Specifically, it will save a plot of the structure (``BeamPlot``) and of the aerodynamic surfaces (``AerogridPlot``)." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "SimInfo.solvers['StepUvlm']['convection_scheme'] = 2\n", "SimInfo.solvers['StaticUvlm']['velocity_field_generator'] = 'SteadyVelocityField'\n", "SimInfo.solvers['StepUvlm']['velocity_field_input'] = {'u_inf' : WSP,\n", " 'u_inf_direction' : np.array(\n", " [np.cos(aoa_end_deg*deg2rad),\n", " 0.,\n", " np.sin(aoa_end_deg*deg2rad)])}\n", "\n", "SimInfo.solvers['NonLinearDynamicPrescribedStep']['gravity_on'] = False\n", "\n", "SimInfo.solvers['DynamicCoupled']['structural_solver'] = 'NonLinearDynamicPrescribedStep'\n", "SimInfo.solvers['DynamicCoupled']['structural_solver_settings'] = SimInfo.solvers['NonLinearDynamicPrescribedStep']\n", "SimInfo.solvers['DynamicCoupled']['aero_solver'] = 'StepUvlm'\n", "SimInfo.solvers['DynamicCoupled']['aero_solver_settings'] = SimInfo.solvers['StepUvlm']\n", "SimInfo.solvers['DynamicCoupled']['postprocessors'] = ['BeamPlot', 'AerogridPlot']\n", "SimInfo.solvers['DynamicCoupled']['postprocessors_settings'] = {'BeamPlot': SimInfo.solvers['BeamPlot'],\n", " 'AerogridPlot': SimInfo.solvers['AerogridPlot']}\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we need to define the movement of the whole wing through its velocity and acceleration. In this case we want a clamped structure so we will set every thing to zero.\n", "\n", "Moreover, we can define forces applied together with the aerodynamic ones. In this case they will be set to zero." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "SimInfo.with_forced_vel = True\n", "SimInfo.for_vel = np.zeros((time_steps,6), dtype=float)\n", "SimInfo.for_acc = np.zeros((time_steps,6), dtype=float)\n", "SimInfo.with_dynamic_forces = True\n", "SimInfo.dynamic_forces = np.zeros((time_steps,wing.StructuralInformation.num_node,6),\n", " dtype=float)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We write the input files required by SHARPy again:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [], "source": [ "gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case'])\n", "wing.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case'])\n", "SimInfo.generate_solver_file()\n", "SimInfo.generate_dyn_file(time_steps)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And we run the simulation:" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--------------------------------------------------------------------------------\u001b[0m\n", " ###### ## ## ### ######## ######## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ####\u001b[0m\n", " ###### ######### ## ## ######## ######## ##\u001b[0m\n", " ## ## ## ######### ## ## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ##\u001b[0m\n", " ###### ## ## ## ## ## ## ## ##\u001b[0m\n", "--------------------------------------------------------------------------------\u001b[0m\n", "Aeroelastics Lab, Aeronautics Department.\u001b[0m\n", " Copyright (c), Imperial College London.\u001b[0m\n", " All rights reserved.\u001b[0m\n", " License available at https://github.com/imperialcollegelondon/sharpy\u001b[0m\n", "\u001b[36mRunning SHARPy from /home/loca/Downloads/sharpy copy/sharpy/docs/source/content/example_notebooks\u001b[0m\n", "\u001b[36mSHARPy being run is in /home/loca/anaconda3/envs/sharpy/lib/python3.10/site-packages\u001b[0m\n", "SHARPy output folder set\u001b[0m\n", "\u001b[34m\t./output//dynamic/\u001b[0m\n", "\u001b[36mGenerating an instance of BeamLoader\u001b[0m\n", "\u001b[36mGenerating an instance of AerogridLoader\u001b[0m\n", "Variable dx1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: -1.0\u001b[0m\n", "Variable ndx1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable r has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1.0\u001b[0m\n", "Variable dxmax has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: -1.0\u001b[0m\n", "\u001b[34mThe aerodynamic grid contains 1 surfaces\u001b[0m\n", "\u001b[34m Surface 0, M=4, N=20\u001b[0m\n", " Wake 0, M=400, N=20\u001b[0m\n", " In total: 80 bound panels\u001b[0m\n", " In total: 8000 wake panels\u001b[0m\n", " Total number of panels = 8080\u001b[0m\n", "\u001b[36mGenerating an instance of StaticCoupled\u001b[0m\n", "\u001b[36mGenerating an instance of NonLinearStatic\u001b[0m\n", "\u001b[36mGenerating an instance of StaticUvlm\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "fatal: not a git repository (or any of the parent directories): .git\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "|=====|=====|============|==========|==========|==========|==========|==========|==========|\u001b[0m\n", "|iter |step | log10(res) | Fx | Fy | Fz | Mx | My | Mz |\u001b[0m\n", "|=====|=====|============|==========|==========|==========|==========|==========|==========|\u001b[0m\n", " DeltaF DeltaX Res ResRel ResFrc ResRelFrc ResMmt ResRelMmt ErX ErPos ErPsi\n", "LoadStep Subiter DeltaF DeltaX Res ResRel ResFrc ResRelFrc ResMmt ResRelMmt ErX ErPos ErPsi\n", "| 0 | 0 | 0.00000 | -0.0187 | -0.0006 | 0.6029 | 4.8229 | 0.1523 | 0.1495 |\u001b[0m\n", " DeltaF DeltaX Res ResRel ResFrc ResRelFrc ResMmt ResRelMmt ErX ErPos ErPsi\n", "LoadStep Subiter DeltaF DeltaX Res ResRel ResFrc ResRelFrc ResMmt ResRelMmt ErX ErPos ErPsi\n", "| 1 | 0 | -9.22874 | -0.0188 | -0.0006 | 0.6043 | 4.8366 | 0.1526 | 0.1504 |\u001b[0m\n", "\u001b[36mGenerating an instance of DynamicCoupled\u001b[0m\n", "\u001b[36mGenerating an instance of NonLinearDynamicPrescribedStep\u001b[0m\n", "\u001b[36mGenerating an instance of StepUvlm\u001b[0m\n", "\u001b[36mGenerating an instance of BeamPlot\u001b[0m\n", "\u001b[36mGenerating an instance of AerogridPlot\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "|=======|========|======|==============|==============|==============|==============|==============|\u001b[0m\n", "| ts | t | iter | struc ratio | iter time | residual vel | FoR_vel(x) | FoR_vel(z) |\u001b[0m\n", "|=======|========|======|==============|==============|==============|==============|==============|\u001b[0m\n", "| 1 | 0.1250 | 4 | 0.406694 | 2.468390 | -5.501499 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 2 | 0.2500 | 3 | 0.488530 | 2.521077 | -5.442787 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 3 | 0.3750 | 3 | 0.341496 | 1.847399 | -5.640521 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 4 | 0.5000 | 3 | 0.307099 | 1.716782 | -5.645651 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 5 | 0.6250 | 3 | 0.387260 | 1.940912 | -5.889684 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 6 | 0.7500 | 3 | 0.304108 | 1.657786 | -5.914635 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 7 | 0.8750 | 3 | 0.071527 | 1.164022 | -6.040009 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 8 | 1.0000 | 3 | 0.069586 | 1.161729 | -5.925998 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 9 | 1.1250 | 3 | 0.068879 | 1.164142 | -5.836958 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 10 | 1.2500 | 3 | 0.068450 | 1.162696 | -5.632144 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 11 | 1.3750 | 3 | 0.068106 | 1.162670 | -5.513670 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 12 | 1.5000 | 3 | 0.077652 | 1.170546 | -5.471589 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 13 | 1.6250 | 3 | 0.070499 | 1.172093 | -5.423647 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 14 | 1.7500 | 3 | 0.066050 | 1.159820 | -5.451292 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 15 | 1.8750 | 3 | 0.066831 | 1.159918 | -5.503924 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 16 | 2.0000 | 3 | 0.076779 | 1.171578 | -5.521127 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 17 | 2.1250 | 3 | 0.069862 | 1.168248 | -5.649248 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 18 | 2.2500 | 3 | 0.295345 | 1.482039 | -5.852560 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 19 | 2.3750 | 3 | 0.065010 | 1.166991 | -6.169248 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 20 | 2.5000 | 3 | 0.088990 | 1.190061 | -6.501434 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 21 | 2.6250 | 3 | 0.070542 | 1.172962 | -5.911316 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 22 | 2.7500 | 3 | 0.073701 | 1.205343 | -5.844249 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 23 | 2.8750 | 3 | 0.069573 | 1.208120 | -5.662014 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 24 | 3.0000 | 3 | 0.065514 | 1.184485 | -5.623002 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 25 | 3.1250 | 3 | 0.068776 | 1.164661 | -5.695734 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 26 | 3.2500 | 3 | 0.154359 | 1.276498 | -5.679340 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 27 | 3.3750 | 3 | 0.070216 | 1.205452 | -5.742054 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 28 | 3.5000 | 3 | 0.067111 | 1.184687 | -5.934912 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 29 | 3.6250 | 3 | 0.081985 | 1.188525 | -6.170404 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 30 | 3.7500 | 3 | 0.075176 | 1.174604 | -6.498959 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 31 | 3.8750 | 3 | 0.068256 | 1.167637 | -6.054012 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 32 | 4.0000 | 3 | 0.073072 | 1.176508 | -6.023555 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 33 | 4.1250 | 3 | 0.070665 | 1.172659 | -5.782342 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 34 | 4.2500 | 3 | 0.073685 | 1.172562 | -5.673256 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 35 | 4.3750 | 3 | 0.073392 | 1.185722 | -5.733463 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 36 | 4.5000 | 3 | 0.077823 | 1.177345 | -5.705716 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 37 | 4.6250 | 3 | 0.065806 | 1.168674 | -5.751720 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 38 | 4.7500 | 3 | 0.155288 | 1.272752 | -5.851236 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 39 | 4.8750 | 3 | 0.065315 | 1.167344 | -5.920499 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 40 | 5.0000 | 3 | 0.065910 | 1.165901 | -6.297188 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "\u001b[34m...Finished\u001b[0m\n", "\u001b[36mFINISHED - Elapsed time = 61.3302536 seconds\u001b[0m\n", "\u001b[36mFINISHED - CPU process time = 205.6064506 seconds\u001b[0m\n" ] } ], "source": [ "sharpy_output = sharpy.sharpy_main.main(['',\n", " SimInfo.solvers['SHARPy']['route'] +\n", " SimInfo.solvers['SHARPy']['case'] +\n", " '.sharpy'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is the plot of the wing at the end of the simulation:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "application/vnd.plotly.v1+json": { "config": { "plotlyServerURL": "https://plot.ly" }, "data": [ { "line": { "color": "grey" }, "mode": "lines", "name": "Aero surface", "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, -0.187500005737461, -0.4375000057285399, -0.4375000000000001 ], "y": [ -7.126160546901204e-18, -3.054068805814802e-18, 0.8000011183147504, 0.8000026109241081, -7.126160546901204e-18 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 3.884345648756862e-05, 4.033766384433025e-05, -3.7885577460302824e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000057285399, -0.187500005737461, -0.18750001518488343, -0.43750001515032233, -0.4375000057285399 ], "y": [ 0.8000026109241081, 0.8000011183147504, 1.6000021946752327, 1.6000051324015534, 0.8000026109241081 ], "z": [ 4.033766384433025e-05, 3.884345648756862e-05, 0.0001474587562785521, 0.00015039990075090066, 4.033766384433025e-05 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750001515032233, -0.18750001518488343, -0.18750002748442468, -0.4375000274105985, -0.43750001515032233 ], "y": [ 1.6000051324015534, 1.6000021946752327, 2.4000031932873562, 2.400007486707306, 1.6000051324015534 ], "z": [ 0.00015039990075090066, 0.0001474587562785521, 0.00031820666645609595, 0.00032250546002824353, 0.00015039990075090066 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000274105985, -0.18750002748442468, -0.18750004207304757, -0.4375000419477604, -0.4375000274105985 ], "y": [ 2.400007486707306, 2.4000031932873562, 3.2000041361397518, 3.2000097290216205, 2.400007486707306 ], "z": [ 0.00032250546002824353, 0.00031820666645609595, 0.0005440116524442518, 0.0005496119456658349, 0.00032250546002824353 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000419477604, -0.18750004207304757, -0.18750005831539146, -0.4375000581303961, -0.4375000419477604 ], "y": [ 3.2000097290216205, 3.2000041361397518, 4.000004991724207, 4.000011787646406, 3.2000097290216205 ], "z": [ 0.0005496119456658349, 0.0005440116524442518, 0.0008180063787988016, 0.0008248117514201825, 0.0005496119456658349 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000581303961, -0.18750005831539146, -0.18750007587297496, -0.4375000756203353, -0.4375000581303961 ], "y": [ 4.000011787646406, 4.000004991724207, 4.800005788831466, 4.800013730415652, 4.000011787646406 ], "z": [ 0.0008248117514201825, 0.0008180063787988016, 0.0011339697660142335, 0.0011419228184735238, 0.0008248117514201825 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375000756203353, -0.18750007587297496, -0.1875000942880858, -0.43750009396440015, -0.4375000756203353 ], "y": [ 4.800013730415652, 4.800005788831466, 5.600006497238266, 5.600015486172148, 4.800013730415652 ], "z": [ 0.0011419228184735238, 0.0011339697660142335, 0.0014859053575590015, 0.0014949076836219992, 0.0011419228184735238 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750009396440015, -0.1875000942880858, -0.1875001133847934, -0.4375001129859722, -0.43750009396440015 ], "y": [ 5.600015486172148, 5.600006497238266, 6.400007147597506, 6.400017125235562, 5.600015486172148 ], "z": [ 0.0014949076836219992, 0.0014859053575590015, 0.0018684959356252715, 0.0018784887981949942, 0.0014949076836219992 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375001129859722, -0.1875001133847934, -0.1875001328395648, -0.4375001323665614, -0.4375001129859722 ], "y": [ 6.400017125235562, 6.400007147597506, 7.200007709974395, 7.200018575842047, 6.400017125235562 ], "z": [ 0.0018784887981949942, 0.0018684959356252715, 0.0022766574780168644, 0.0022875402460654105, 0.0018784887981949942 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375001323665614, -0.1875001328395648, -0.1875001525902163, -0.43750015204242715, -0.4375001323665614 ], "y": [ 7.200018575842047, 7.200007709974395, 8.000008215603518, 8.000019908825728, 7.200018575842047 ], "z": [ 0.0022875402460654105, 0.0022766574780168644, 0.0027059959238166756, 0.002717707587989787, 0.0022875402460654105 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750015204242715, -0.1875001525902163, -0.18750017240775796, -0.43750017178994344, -0.43750015204242715 ], "y": [ 8.000019908825728, 8.000008215603518, 8.800008634998948, 8.80002105303595, 8.000019908825728 ], "z": [ 0.002717707587989787, 0.0027059959238166756, 0.00315235047156843, 0.003164788301163934, 0.002717707587989787 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750017178994344, -0.18750017240775796, -0.18750019230338255, -0.4375001916179239, -0.43750017178994344 ], "y": [ 8.80002105303595, 8.800008634998948, 9.600008999668942, 9.600022079802349, 8.80002105303595 ], "z": [ 0.003164788301163934, 0.00315235047156843, 0.003612244321978627, 0.003625345445617321, 0.003164788301163934 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375001916179239, -0.18750019230338255, -0.18750021210963969, -0.4375002113643574, -0.4375001916179239 ], "y": [ 9.600022079802349, 9.600008999668942, 10.400009280849583, 10.400022919780158, 9.600022079802349 ], "z": [ 0.003625345445617321, 0.003612244321978627, 0.004082429194450335, 0.004096090111835357, 0.003625345445617321 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375002113643574, -0.18750021210963969, -0.18750023187928208, -0.43750023107885805, -0.4375002113643574 ], "y": [ 10.400022919780158, 10.400009280849583, 11.200009509987185, 11.200023644449196, 10.400022919780158 ], "z": [ 0.004096090111835357, 0.004082429194450335, 0.004560325318252977, 0.0045744826124137754, 0.004096090111835357 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750023107885805, -0.18750023187928208, -0.18750025148252614, -0.43750025063692877, -0.43750023107885805 ], "y": [ 11.200023644449196, 11.200009509987185, 12.000009659246636, 12.000024187077601, 11.200023644449196 ], "z": [ 0.0045744826124137754, 0.004560325318252977, 0.005043573110761696, 0.005058124427915424, 0.0045744826124137754 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750025063692877, -0.18750025148252614, -0.18750027099142436, -0.4375002701068412, -0.43750025063692877 ], "y": [ 12.000024187077601, 12.000009659246636, 12.800009759600563, 12.800024618563835, 12.000024187077601 ], "z": [ 0.005058124427915424, 0.005043573110761696, 0.0055304507811946265, 0.005545333750792834, 0.005058124427915424 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375002701068412, -0.18750027099142436, -0.18750029030227897, -0.43750028938977437, -0.4375002701068412 ], "y": [ 12.800024618563835, 12.800009759600563, 13.600009784797404, 13.600024876458574, 12.800024618563835 ], "z": [ 0.005545333750792834, 0.0055304507811946265, 0.0060194392546856455, 0.006034555275549564, 0.005545333750792834 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750028938977437, -0.18750029030227897, -0.18750030949419816, -0.4375003085605567, -0.43750028938977437 ], "y": [ 13.600024876458574, 13.600009784797404, 14.400009765078664, 14.400025030543393, 13.600024876458574 ], "z": [ 0.006034555275549564, 0.0060194392546856455, 0.006509601017991055, 0.006524891089689876, 0.006034555275549564 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.4375003085605567, -0.18750030949419816, -0.18750032849661877, -0.43750032755255314, -0.4375003085605567 ], "y": [ 14.400025030543393, 14.400009765078664, 15.20000967846006, 15.200025028918631, 14.400025030543393 ], "z": [ 0.006524891089689876, 0.006509601017991055, 0.0070001736447586945, 0.00701554882735514, 0.006524891089689876 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.43750032755255314, -0.18750032849661877, -0.18750034739363916, -0.4375003464451198, -0.43750032755255314 ], "y": [ 15.200025028918631, 15.20000967846006, 16.000009555123633, 16.00002494175388, 15.200025028918631 ], "z": [ 0.00701554882735514, 0.0070001736447586945, 0.007490874433673721, 0.007506285835269044, 0.00701554882735514 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750000000000006, 0.06250000000000001, 0.062499994253617984, -0.187500005737461, -0.18750000000000006 ], "y": [ -3.054068805814802e-18, 1.0180229352716006e-18, 0.7999996257053927, 0.8000011183147504, -3.054068805814802e-18 ], "z": [ -1.6236676054415498e-17, 5.412225351471832e-18, 3.734924913080698e-05, 3.884345648756862e-05, -1.6236676054415498e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.187500005737461, 0.062499994253617984, 0.062499984780555455, -0.18750001518488343, -0.187500005737461 ], "y": [ 0.8000011183147504, 0.7999996257053927, 1.5999992569489119, 1.6000021946752327, 0.8000011183147504 ], "z": [ 3.884345648756862e-05, 3.734924913080698e-05, 0.00014451761180620353, 0.0001474587562785521, 3.884345648756862e-05 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750001518488343, 0.062499984780555455, 0.06249997244174921, -0.18750002748442468, -0.18750001518488343 ], "y": [ 1.6000021946752327, 1.5999992569489119, 2.3999988998674064, 2.4000031932873562, 1.6000021946752327 ], "z": [ 0.0001474587562785521, 0.00014451761180620353, 0.0003139078728839484, 0.00031820666645609595, 0.0001474587562785521 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750002748442468, 0.06249997244174921, 0.0624999578016652, -0.18750004207304757, -0.18750002748442468 ], "y": [ 2.4000031932873562, 2.3999988998674064, 3.199998543257883, 3.2000041361397518, 2.4000031932873562 ], "z": [ 0.00031820666645609595, 0.0003139078728839484, 0.0005384113592226688, 0.0005440116524442518, 0.00031820666645609595 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750004207304757, 0.0624999578016652, 0.062499941499613264, -0.18750005831539146, -0.18750004207304757 ], "y": [ 3.2000041361397518, 3.199998543257883, 3.9999981958020085, 4.000004991724207, 3.2000041361397518 ], "z": [ 0.0005440116524442518, 0.0005384113592226688, 0.0008112010061774206, 0.0008180063787988016, 0.0005440116524442518 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750005831539146, 0.062499941499613264, 0.062499923874385396, -0.18750007587297496, -0.18750005831539146 ], "y": [ 4.000004991724207, 3.9999981958020085, 4.799997847247279, 4.800005788831466, 4.000004991724207 ], "z": [ 0.0008180063787988016, 0.0008112010061774206, 0.0011260167135549429, 0.0011339697660142335, 0.0008180063787988016 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750007587297496, 0.062499923874385396, 0.06249990538822854, -0.1875000942880858, -0.18750007587297496 ], "y": [ 4.800005788831466, 4.799997847247279, 5.5999975083043845, 5.600006497238266, 4.800005788831466 ], "z": [ 0.0011339697660142335, 0.0011260167135549429, 0.001476903031496004, 0.0014859053575590015, 0.0011339697660142335 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.1875000942880858, 0.06249990538822854, 0.0624998862163855, -0.1875001133847934, -0.1875000942880858 ], "y": [ 5.600006497238266, 5.5999975083043845, 6.39999716995945, 6.400007147597506, 5.600006497238266 ], "z": [ 0.0014859053575590015, 0.001476903031496004, 0.0018585030730555484, 0.0018684959356252715, 0.0014859053575590015 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.1875001133847934, 0.0624998862163855, 0.06249986668743177, -0.1875001328395648, -0.1875001133847934 ], "y": [ 6.400007147597506, 6.39999716995945, 7.199996844106742, 7.200007709974395, 6.400007147597506 ], "z": [ 0.0018684959356252715, 0.0018585030730555484, 0.0022657747099683184, 0.0022766574780168644, 0.0018684959356252715 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.1875001328395648, 0.06249986668743177, 0.06249984686199458, -0.1875001525902163, -0.1875001328395648 ], "y": [ 7.200007709974395, 7.199996844106742, 7.999996522381308, 8.000008215603518, 7.200007709974395 ], "z": [ 0.0022766574780168644, 0.0022657747099683184, 0.002694284259643564, 0.0027059959238166756, 0.0022766574780168644 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.1875001525902163, 0.06249984686199458, 0.062499826974427505, -0.18750017240775796, -0.1875001525902163 ], "y": [ 8.000008215603518, 7.999996522381308, 8.799996216961945, 8.800008634998948, 8.000008215603518 ], "z": [ 0.0027059959238166756, 0.002694284259643564, 0.003139912641972926, 0.00315235047156843, 0.0027059959238166756 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750017240775796, 0.062499826974427505, 0.062499807011158884, -0.18750019230338255, -0.18750017240775796 ], "y": [ 8.800008634998948, 8.799996216961945, 9.599995919535536, 9.600008999668942, 8.800008634998948 ], "z": [ 0.00315235047156843, 0.003139912641972926, 0.003599143198339933, 0.003612244321978627, 0.00315235047156843 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750019230338255, 0.062499807011158884, 0.06249978714507812, -0.18750021210963969, -0.18750019230338255 ], "y": [ 9.600008999668942, 9.599995919535536, 10.399995641919006, 10.400009280849583, 9.600008999668942 ], "z": [ 0.003612244321978627, 0.003599143198339933, 0.004068768277065312, 0.004082429194450335, 0.003612244321978627 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750021210963969, 0.06249978714507812, 0.062499767320293845, -0.18750023187928208, -0.18750021210963969 ], "y": [ 10.400009280849583, 10.399995641919006, 11.199995375525171, 11.200009509987185, 10.400009280849583 ], "z": [ 0.004082429194450335, 0.004068768277065312, 0.00454616802409218, 0.004560325318252977, 0.004082429194450335 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750023187928208, 0.062499767320293845, 0.062499747671876424, -0.18750025148252614, -0.18750023187928208 ], "y": [ 11.200009509987185, 11.199995375525171, 11.99999513141567, 12.000009659246636, 11.200009509987185 ], "z": [ 0.004560325318252977, 0.00454616802409218, 0.005029021793607967, 0.005043573110761696, 0.004560325318252977 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750025148252614, 0.062499747671876424, 0.06249972812399248, -0.18750027099142436, -0.18750025148252614 ], "y": [ 12.000009659246636, 11.99999513141567, 12.79999490063729, 12.800009759600563, 12.000009659246636 ], "z": [ 0.005043573110761696, 0.005029021793607967, 0.00551556781159642, 0.0055304507811946265, 0.005043573110761696 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750027099142436, 0.06249972812399248, 0.06249970878521643, -0.18750029030227897, -0.18750027099142436 ], "y": [ 12.800009759600563, 12.79999490063729, 13.599994693136232, 13.600009784797404, 12.800009759600563 ], "z": [ 0.0055304507811946265, 0.00551556781159642, 0.0060043232338217265, 0.0060194392546856455, 0.0055304507811946265 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750029030227897, 0.06249970878521643, 0.06249968957216046, -0.18750030949419816, -0.18750029030227897 ], "y": [ 13.600009784797404, 13.599994693136232, 14.399994499613936, 14.400009765078664, 13.600009784797404 ], "z": [ 0.0060194392546856455, 0.0060043232338217265, 0.006494310946292235, 0.006509601017991055, 0.0060194392546856455 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750030949419816, 0.06249968957216046, 0.06249967055931569, -0.18750032849661877, -0.18750030949419816 ], "y": [ 14.400009765078664, 14.399994499613936, 15.199994328001486, 15.20000967846006, 14.400009765078664 ], "z": [ 0.006509601017991055, 0.006494310946292235, 0.00698479846216225, 0.0070001736447586945, 0.006509601017991055 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ -0.18750032849661877, 0.06249967055931569, 0.062499651657841424, -0.18750034739363916, -0.18750032849661877 ], "y": [ 15.20000967846006, 15.199994328001486, 15.999994168493387, 16.000009555123633, 15.20000967846006 ], "z": [ 0.0070001736447586945, 0.00698479846216225, 0.007475463032078398, 0.007490874433673721, 0.0070001736447586945 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06250000000000001, 0.31250000000000006, 0.31249999424469693, 0.062499994253617984, 0.06250000000000001 ], "y": [ 1.0180229352716006e-18, 5.090114676358003e-18, 0.7999981330960351, 0.7999996257053927, 1.0180229352716006e-18 ], "z": [ 5.412225351471832e-18, 2.706112675735916e-17, 3.585504177404534e-05, 3.734924913080698e-05, 5.412225351471832e-18 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.062499994253617984, 0.31249999424469693, 0.31249998474599433, 0.062499984780555455, 0.062499994253617984 ], "y": [ 0.7999996257053927, 0.7999981330960351, 1.599996319222591, 1.5999992569489119, 0.7999996257053927 ], "z": [ 3.734924913080698e-05, 3.585504177404534e-05, 0.00014157646733385497, 0.00014451761180620353, 3.734924913080698e-05 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.062499984780555455, 0.31249998474599433, 0.3124999723679231, 0.06249997244174921, 0.062499984780555455 ], "y": [ 1.5999992569489119, 1.599996319222591, 2.3999946064474567, 2.3999988998674064, 1.5999992569489119 ], "z": [ 0.00014451761180620353, 0.00014157646733385497, 0.0003096090793118008, 0.0003139078728839484, 0.00014451761180620353 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249997244174921, 0.3124999723679231, 0.31249995767637795, 0.0624999578016652, 0.06249997244174921 ], "y": [ 2.3999988998674064, 2.3999946064474567, 3.1999929503760143, 3.199998543257883, 2.3999988998674064 ], "z": [ 0.0003139078728839484, 0.0003096090793118008, 0.0005328110660010858, 0.0005384113592226688, 0.0003139078728839484 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.0624999578016652, 0.31249995767637795, 0.31249994131461795, 0.062499941499613264, 0.0624999578016652 ], "y": [ 3.199998543257883, 3.1999929503760143, 3.9999913998798102, 3.9999981958020085, 3.199998543257883 ], "z": [ 0.0005384113592226688, 0.0005328110660010858, 0.0008043956335560395, 0.0008112010061774206, 0.0005384113592226688 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.062499941499613264, 0.31249994131461795, 0.3124999236217457, 0.062499923874385396, 0.062499941499613264 ], "y": [ 3.9999981958020085, 3.9999913998798102, 4.799989905663092, 4.799997847247279, 3.9999981958020085 ], "z": [ 0.0008112010061774206, 0.0008043956335560395, 0.0011180636610956523, 0.0011260167135549429, 0.0008112010061774206 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.062499923874385396, 0.3124999236217457, 0.31249990506454295, 0.06249990538822854, 0.062499923874385396 ], "y": [ 4.799997847247279, 4.799989905663092, 5.599988519370503, 5.5999975083043845, 4.799997847247279 ], "z": [ 0.0011260167135549429, 0.0011180636610956523, 0.0014679007054330066, 0.001476903031496004, 0.0011260167135549429 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249990538822854, 0.31249990506454295, 0.3124998858175643, 0.0624998862163855, 0.06249990538822854 ], "y": [ 5.5999975083043845, 5.599988519370503, 6.399987192321394, 6.39999716995945, 5.5999975083043845 ], "z": [ 0.001476903031496004, 0.0014679007054330066, 0.0018485102104858254, 0.0018585030730555484, 0.001476903031496004 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.0624998862163855, 0.3124998858175643, 0.3124998662144284, 0.06249986668743177, 0.0624998862163855 ], "y": [ 6.39999716995945, 6.399987192321394, 7.19998597823909, 7.199996844106742, 6.39999716995945 ], "z": [ 0.0018585030730555484, 0.0018485102104858254, 0.0022548919419197724, 0.0022657747099683184, 0.0018585030730555484 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249986668743177, 0.3124998662144284, 0.31249984631420546, 0.06249984686199458, 0.06249986668743177 ], "y": [ 7.199996844106742, 7.19998597823909, 7.999984829159098, 7.999996522381308, 7.199996844106742 ], "z": [ 0.0022657747099683184, 0.0022548919419197724, 0.0026825725954704523, 0.002694284259643564, 0.0022657747099683184 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249984686199458, 0.31249984631420546, 0.312499826356613, 0.062499826974427505, 0.06249984686199458 ], "y": [ 7.999996522381308, 7.999984829159098, 8.799983798924943, 8.799996216961945, 7.999996522381308 ], "z": [ 0.002694284259643564, 0.0026825725954704523, 0.003127474812377422, 0.003139912641972926, 0.002694284259643564 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.062499826974427505, 0.312499826356613, 0.3124998063257003, 0.062499807011158884, 0.062499826974427505 ], "y": [ 8.799996216961945, 8.799983798924943, 9.599982839402129, 9.599995919535536, 8.799996216961945 ], "z": [ 0.003139912641972926, 0.003127474812377422, 0.003586042074701239, 0.003599143198339933, 0.003139912641972926 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.062499807011158884, 0.3124998063257003, 0.3124997863997959, 0.06249978714507812, 0.062499807011158884 ], "y": [ 9.599995919535536, 9.599982839402129, 10.399982002988429, 10.399995641919006, 9.599995919535536 ], "z": [ 0.003599143198339933, 0.003586042074701239, 0.0040551073596802895, 0.004068768277065312, 0.003599143198339933 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249978714507812, 0.3124997863997959, 0.31249976651986977, 0.062499767320293845, 0.06249978714507812 ], "y": [ 10.399995641919006, 10.399982002988429, 11.199981241063158, 11.199995375525171, 10.399995641919006 ], "z": [ 0.004068768277065312, 0.0040551073596802895, 0.004532010729931383, 0.00454616802409218, 0.004068768277065312 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.062499767320293845, 0.31249976651986977, 0.312499746826279, 0.062499747671876424, 0.062499767320293845 ], "y": [ 11.199995375525171, 11.199981241063158, 11.999980603584703, 11.99999513141567, 11.199995375525171 ], "z": [ 0.00454616802409218, 0.004532010729931383, 0.005014470476454239, 0.005029021793607967, 0.00454616802409218 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.062499747671876424, 0.312499746826279, 0.31249972723940933, 0.06249972812399248, 0.062499747671876424 ], "y": [ 11.99999513141567, 11.999980603584703, 12.799980041674019, 12.79999490063729, 11.99999513141567 ], "z": [ 0.005029021793607967, 0.005014470476454239, 0.005500684841998214, 0.00551556781159642, 0.005029021793607967 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249972812399248, 0.31249972723940933, 0.3124997078727118, 0.06249970878521643, 0.06249972812399248 ], "y": [ 12.79999490063729, 12.799980041674019, 13.59997960147506, 13.599994693136232, 12.79999490063729 ], "z": [ 0.00551556781159642, 0.005500684841998214, 0.0059892072129578075, 0.0060043232338217265, 0.00551556781159642 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249970878521643, 0.3124997078727118, 0.31249968863851907, 0.06249968957216046, 0.06249970878521643 ], "y": [ 13.599994693136232, 13.59997960147506, 14.399979234149207, 14.399994499613936, 13.599994693136232 ], "z": [ 0.0060043232338217265, 0.0059892072129578075, 0.006479020874593415, 0.006494310946292235, 0.0060043232338217265 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249968957216046, 0.31249968863851907, 0.3124996696152501, 0.06249967055931569, 0.06249968957216046 ], "y": [ 14.399994499613936, 14.399979234149207, 15.199978977542912, 15.199994328001486, 14.399994499613936 ], "z": [ 0.006494310946292235, 0.006479020874593415, 0.006969423279565805, 0.00698479846216225, 0.006494310946292235 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.06249967055931569, 0.3124996696152501, 0.312499650709322, 0.062499651657841424, 0.06249967055931569 ], "y": [ 15.199994328001486, 15.199978977542912, 15.999978781863144, 15.999994168493387, 15.199994328001486 ], "z": [ 0.00698479846216225, 0.006969423279565805, 0.007460051630483075, 0.007475463032078398, 0.00698479846216225 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31250000000000006, 0.5625000000000001, 0.5624999942357759, 0.31249999424469693, 0.31250000000000006 ], "y": [ 5.090114676358003e-18, 9.162206417444405e-18, 0.7999966404866774, 0.7999981330960351, 5.090114676358003e-18 ], "z": [ 2.706112675735916e-17, 4.871002816324649e-17, 3.4360834417283705e-05, 3.585504177404534e-05, 2.706112675735916e-17 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249999424469693, 0.5624999942357759, 0.5624999847114331, 0.31249998474599433, 0.31249999424469693 ], "y": [ 0.7999981330960351, 0.7999966404866774, 1.5999933814962704, 1.599996319222591, 0.7999981330960351 ], "z": [ 3.585504177404534e-05, 3.4360834417283705e-05, 0.0001386353228615064, 0.00014157646733385497, 3.585504177404534e-05 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249998474599433, 0.5624999847114331, 0.562499972294097, 0.3124999723679231, 0.31249998474599433 ], "y": [ 1.599996319222591, 1.5999933814962704, 2.399990313027507, 2.3999946064474567, 1.599996319222591 ], "z": [ 0.00014157646733385497, 0.0001386353228615064, 0.00030531028573965323, 0.0003096090793118008, 0.00014157646733385497 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124999723679231, 0.562499972294097, 0.5624999575510907, 0.31249995767637795, 0.3124999723679231 ], "y": [ 2.3999946064474567, 2.399990313027507, 3.1999873574941455, 3.1999929503760143, 2.3999946064474567 ], "z": [ 0.0003096090793118008, 0.00030531028573965323, 0.0005272107727795027, 0.0005328110660010858, 0.0003096090793118008 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249995767637795, 0.5624999575510907, 0.5624999411296226, 0.31249994131461795, 0.31249995767637795 ], "y": [ 3.1999929503760143, 3.1999873574941455, 3.999984603957612, 3.9999913998798102, 3.1999929503760143 ], "z": [ 0.0005328110660010858, 0.0005272107727795027, 0.0007975902609346586, 0.0008043956335560395, 0.0005328110660010858 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249994131461795, 0.5624999411296226, 0.5624999233691061, 0.3124999236217457, 0.31249994131461795 ], "y": [ 3.9999913998798102, 3.999984603957612, 4.799981964078905, 4.799989905663092, 3.9999913998798102 ], "z": [ 0.0008043956335560395, 0.0007975902609346586, 0.0011101106086363619, 0.0011180636610956523, 0.0008043956335560395 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124999236217457, 0.5624999233691061, 0.5624999047408573, 0.31249990506454295, 0.3124999236217457 ], "y": [ 4.799989905663092, 4.799981964078905, 5.599979530436621, 5.599988519370503, 4.799989905663092 ], "z": [ 0.0011180636610956523, 0.0011101106086363619, 0.0014588983793700089, 0.0014679007054330066, 0.0011180636610956523 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249990506454295, 0.5624999047408573, 0.5624998854187432, 0.3124998858175643, 0.31249990506454295 ], "y": [ 5.599988519370503, 5.599979530436621, 6.399977214683338, 6.399987192321394, 5.599988519370503 ], "z": [ 0.0014679007054330066, 0.0014588983793700089, 0.0018385173479161026, 0.0018485102104858254, 0.0014679007054330066 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124998858175643, 0.5624998854187432, 0.562499865741425, 0.3124998662144284, 0.3124998858175643 ], "y": [ 6.399987192321394, 6.399977214683338, 7.199975112371438, 7.19998597823909, 6.399987192321394 ], "z": [ 0.0018485102104858254, 0.0018385173479161026, 0.0022440091738712264, 0.0022548919419197724, 0.0018485102104858254 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124998662144284, 0.562499865741425, 0.5624998457664163, 0.31249984631420546, 0.3124998662144284 ], "y": [ 7.19998597823909, 7.199975112371438, 7.999973135936888, 7.999984829159098, 7.19998597823909 ], "z": [ 0.0022548919419197724, 0.0022440091738712264, 0.002670860931297341, 0.0026825725954704523, 0.0022548919419197724 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249984631420546, 0.5624998457664163, 0.5624998257387984, 0.312499826356613, 0.31249984631420546 ], "y": [ 7.999984829159098, 7.999973135936888, 8.799971380887941, 8.799983798924943, 7.999984829159098 ], "z": [ 0.0026825725954704523, 0.002670860931297341, 0.003115036982781918, 0.003127474812377422, 0.0026825725954704523 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.312499826356613, 0.5624998257387984, 0.5624998056402417, 0.3124998063257003, 0.312499826356613 ], "y": [ 8.799983798924943, 8.799971380887941, 9.599969759268722, 9.599982839402129, 8.799983798924943 ], "z": [ 0.003127474812377422, 0.003115036982781918, 0.003572940951062545, 0.003586042074701239, 0.003127474812377422 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124998063257003, 0.5624998056402417, 0.5624997856545136, 0.3124997863997959, 0.3124998063257003 ], "y": [ 9.599982839402129, 9.599969759268722, 10.399968364057854, 10.399982002988429, 9.599982839402129 ], "z": [ 0.003586042074701239, 0.003572940951062545, 0.004041446442295267, 0.0040551073596802895, 0.003586042074701239 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124997863997959, 0.5624997856545136, 0.5624997657194457, 0.31249976651986977, 0.3124997863997959 ], "y": [ 10.399982002988429, 10.399968364057854, 11.199967106601147, 11.199981241063158, 10.399982002988429 ], "z": [ 0.0040551073596802895, 0.004041446442295267, 0.004517853435770585, 0.004532010729931383, 0.0040551073596802895 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249976651986977, 0.5624997657194457, 0.5624997459806815, 0.312499746826279, 0.31249976651986977 ], "y": [ 11.199981241063158, 11.199967106601147, 11.999966075753738, 11.999980603584703, 11.199981241063158 ], "z": [ 0.004532010729931383, 0.004517853435770585, 0.0049999191593005105, 0.005014470476454239, 0.004532010729931383 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.312499746826279, 0.5624997459806815, 0.5624997263548261, 0.31249972723940933, 0.312499746826279 ], "y": [ 11.999980603584703, 11.999966075753738, 12.799965182710746, 12.799980041674019, 11.999980603584703 ], "z": [ 0.005014470476454239, 0.0049999191593005105, 0.005485801872400007, 0.005500684841998214, 0.005014470476454239 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249972723940933, 0.5624997263548261, 0.5624997069602071, 0.3124997078727118, 0.31249972723940933 ], "y": [ 12.799980041674019, 12.799965182710746, 13.599964509813889, 13.59997960147506, 12.799980041674019 ], "z": [ 0.005500684841998214, 0.005485801872400007, 0.005974091192093889, 0.0059892072129578075, 0.005500684841998214 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124997078727118, 0.5624997069602071, 0.5624996877048777, 0.31249968863851907, 0.3124997078727118 ], "y": [ 13.59997960147506, 13.599964509813889, 14.399963968684478, 14.399979234149207, 13.59997960147506 ], "z": [ 0.0059892072129578075, 0.005974091192093889, 0.0064637308028945946, 0.006479020874593415, 0.0059892072129578075 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.31249968863851907, 0.5624996877048777, 0.5624996686711845, 0.3124996696152501, 0.31249968863851907 ], "y": [ 14.399979234149207, 14.399963968684478, 15.19996362708434, 15.199978977542912, 14.399979234149207 ], "z": [ 0.006479020874593415, 0.0064637308028945946, 0.00695404809696936, 0.006969423279565805, 0.006479020874593415 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.3124996696152501, 0.5624996686711845, 0.5624996497608026, 0.312499650709322, 0.3124996696152501 ], "y": [ 15.199978977542912, 15.19996362708434, 15.999963395232898, 15.999978781863144, 15.199978977542912 ], "z": [ 0.006969423279565805, 0.00695404809696936, 0.007444640228887752, 0.007460051630483075, 0.006969423279565805 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.4375000057285399, -0.43750001515032233, -0.4375000274105985, -0.4375000419477604, -0.4375000581303961, -0.4375000756203353, -0.43750009396440015, -0.4375001129859722, -0.4375001323665614, -0.43750015204242715, -0.43750017178994344, -0.4375001916179239, -0.4375002113643574, -0.43750023107885805, -0.43750025063692877, -0.4375002701068412, -0.43750028938977437, -0.4375003085605567, -0.43750032755255314, -0.4375003464451198 ], "y": [ -7.126160546901204e-18, 0.8000026109241081, 1.6000051324015534, 2.400007486707306, 3.2000097290216205, 4.000011787646406, 4.800013730415652, 5.600015486172148, 6.400017125235562, 7.200018575842047, 8.000019908825728, 8.80002105303595, 9.600022079802349, 10.400022919780158, 11.200023644449196, 12.000024187077601, 12.800024618563835, 13.600024876458574, 14.400025030543393, 15.200025028918631, 16.00002494175388 ], "z": [ -3.7885577460302824e-17, 4.033766384433025e-05, 0.00015039990075090066, 0.00032250546002824353, 0.0005496119456658349, 0.0008248117514201825, 0.0011419228184735238, 0.0014949076836219992, 0.0018784887981949942, 0.0022875402460654105, 0.002717707587989787, 0.003164788301163934, 0.003625345445617321, 0.004096090111835357, 0.0045744826124137754, 0.005058124427915424, 0.005545333750792834, 0.006034555275549564, 0.006524891089689876, 0.00701554882735514, 0.007506285835269044 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.18750000000000006, -0.187500005737461, -0.18750001518488343, -0.18750002748442468, -0.18750004207304757, -0.18750005831539146, -0.18750007587297496, -0.1875000942880858, -0.1875001133847934, -0.1875001328395648, -0.1875001525902163, -0.18750017240775796, -0.18750019230338255, -0.18750021210963969, -0.18750023187928208, -0.18750025148252614, -0.18750027099142436, -0.18750029030227897, -0.18750030949419816, -0.18750032849661877, -0.18750034739363916 ], "y": [ -3.054068805814802e-18, 0.8000011183147504, 1.6000021946752327, 2.4000031932873562, 3.2000041361397518, 4.000004991724207, 4.800005788831466, 5.600006497238266, 6.400007147597506, 7.200007709974395, 8.000008215603518, 8.800008634998948, 9.600008999668942, 10.400009280849583, 11.200009509987185, 12.000009659246636, 12.800009759600563, 13.600009784797404, 14.400009765078664, 15.20000967846006, 16.000009555123633 ], "z": [ -1.6236676054415498e-17, 3.884345648756862e-05, 0.0001474587562785521, 0.00031820666645609595, 0.0005440116524442518, 0.0008180063787988016, 0.0011339697660142335, 0.0014859053575590015, 0.0018684959356252715, 0.0022766574780168644, 0.0027059959238166756, 0.00315235047156843, 0.003612244321978627, 0.004082429194450335, 0.004560325318252977, 0.005043573110761696, 0.0055304507811946265, 0.0060194392546856455, 0.006509601017991055, 0.0070001736447586945, 0.007490874433673721 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.06250000000000001, 0.062499994253617984, 0.062499984780555455, 0.06249997244174921, 0.0624999578016652, 0.062499941499613264, 0.062499923874385396, 0.06249990538822854, 0.0624998862163855, 0.06249986668743177, 0.06249984686199458, 0.062499826974427505, 0.062499807011158884, 0.06249978714507812, 0.062499767320293845, 0.062499747671876424, 0.06249972812399248, 0.06249970878521643, 0.06249968957216046, 0.06249967055931569, 0.062499651657841424 ], "y": [ 1.0180229352716006e-18, 0.7999996257053927, 1.5999992569489119, 2.3999988998674064, 3.199998543257883, 3.9999981958020085, 4.799997847247279, 5.5999975083043845, 6.39999716995945, 7.199996844106742, 7.999996522381308, 8.799996216961945, 9.599995919535536, 10.399995641919006, 11.199995375525171, 11.99999513141567, 12.79999490063729, 13.599994693136232, 14.399994499613936, 15.199994328001486, 15.999994168493387 ], "z": [ 5.412225351471832e-18, 3.734924913080698e-05, 0.00014451761180620353, 0.0003139078728839484, 0.0005384113592226688, 0.0008112010061774206, 0.0011260167135549429, 0.001476903031496004, 0.0018585030730555484, 0.0022657747099683184, 0.002694284259643564, 0.003139912641972926, 0.003599143198339933, 0.004068768277065312, 0.00454616802409218, 0.005029021793607967, 0.00551556781159642, 0.0060043232338217265, 0.006494310946292235, 0.00698479846216225, 0.007475463032078398 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.31250000000000006, 0.31249999424469693, 0.31249998474599433, 0.3124999723679231, 0.31249995767637795, 0.31249994131461795, 0.3124999236217457, 0.31249990506454295, 0.3124998858175643, 0.3124998662144284, 0.31249984631420546, 0.312499826356613, 0.3124998063257003, 0.3124997863997959, 0.31249976651986977, 0.312499746826279, 0.31249972723940933, 0.3124997078727118, 0.31249968863851907, 0.3124996696152501, 0.312499650709322 ], "y": [ 5.090114676358003e-18, 0.7999981330960351, 1.599996319222591, 2.3999946064474567, 3.1999929503760143, 3.9999913998798102, 4.799989905663092, 5.599988519370503, 6.399987192321394, 7.19998597823909, 7.999984829159098, 8.799983798924943, 9.599982839402129, 10.399982002988429, 11.199981241063158, 11.999980603584703, 12.799980041674019, 13.59997960147506, 14.399979234149207, 15.199978977542912, 15.999978781863144 ], "z": [ 2.706112675735916e-17, 3.585504177404534e-05, 0.00014157646733385497, 0.0003096090793118008, 0.0005328110660010858, 0.0008043956335560395, 0.0011180636610956523, 0.0014679007054330066, 0.0018485102104858254, 0.0022548919419197724, 0.0026825725954704523, 0.003127474812377422, 0.003586042074701239, 0.0040551073596802895, 0.004532010729931383, 0.005014470476454239, 0.005500684841998214, 0.0059892072129578075, 0.006479020874593415, 0.006969423279565805, 0.007460051630483075 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.5624999942357759, 0.5624999847114331, 0.562499972294097, 0.5624999575510907, 0.5624999411296226, 0.5624999233691061, 0.5624999047408573, 0.5624998854187432, 0.562499865741425, 0.5624998457664163, 0.5624998257387984, 0.5624998056402417, 0.5624997856545136, 0.5624997657194457, 0.5624997459806815, 0.5624997263548261, 0.5624997069602071, 0.5624996877048777, 0.5624996686711845, 0.5624996497608026 ], "y": [ 9.162206417444405e-18, 0.7999966404866774, 1.5999933814962704, 2.399990313027507, 3.1999873574941455, 3.999984603957612, 4.799981964078905, 5.599979530436621, 6.399977214683338, 7.199975112371438, 7.999973135936888, 8.799971380887941, 9.599969759268722, 10.399968364057854, 11.199967106601147, 11.999966075753738, 12.799965182710746, 13.599964509813889, 14.399963968684478, 15.19996362708434, 15.999963395232898 ], "z": [ 4.871002816324649e-17, 3.4360834417283705e-05, 0.0001386353228615064, 0.00030531028573965323, 0.0005272107727795027, 0.0007975902609346586, 0.0011101106086363619, 0.0014588983793700089, 0.0018385173479161026, 0.0022440091738712264, 0.002670860931297341, 0.003115036982781918, 0.003572940951062545, 0.004041446442295267, 0.004517853435770585, 0.0049999191593005105, 0.005485801872400007, 0.005974091192093889, 0.0064637308028945946, 0.00695404809696936, 0.007444640228887752 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000000000001, -0.18750000000000006, 0.06250000000000001, 0.31250000000000006, 0.5625000000000001 ], "y": [ -7.126160546901204e-18, -3.054068805814802e-18, 1.0180229352716006e-18, 5.090114676358003e-18, 9.162206417444405e-18 ], "z": [ -3.7885577460302824e-17, -1.6236676054415498e-17, 5.412225351471832e-18, 2.706112675735916e-17, 4.871002816324649e-17 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000057285399, -0.187500005737461, 0.062499994253617984, 0.31249999424469693, 0.5624999942357759 ], "y": [ 0.8000026109241081, 0.8000011183147504, 0.7999996257053927, 0.7999981330960351, 0.7999966404866774 ], "z": [ 4.033766384433025e-05, 3.884345648756862e-05, 3.734924913080698e-05, 3.585504177404534e-05, 3.4360834417283705e-05 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750001515032233, -0.18750001518488343, 0.062499984780555455, 0.31249998474599433, 0.5624999847114331 ], "y": [ 1.6000051324015534, 1.6000021946752327, 1.5999992569489119, 1.599996319222591, 1.5999933814962704 ], "z": [ 0.00015039990075090066, 0.0001474587562785521, 0.00014451761180620353, 0.00014157646733385497, 0.0001386353228615064 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000274105985, -0.18750002748442468, 0.06249997244174921, 0.3124999723679231, 0.562499972294097 ], "y": [ 2.400007486707306, 2.4000031932873562, 2.3999988998674064, 2.3999946064474567, 2.399990313027507 ], "z": [ 0.00032250546002824353, 0.00031820666645609595, 0.0003139078728839484, 0.0003096090793118008, 0.00030531028573965323 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000419477604, -0.18750004207304757, 0.0624999578016652, 0.31249995767637795, 0.5624999575510907 ], "y": [ 3.2000097290216205, 3.2000041361397518, 3.199998543257883, 3.1999929503760143, 3.1999873574941455 ], "z": [ 0.0005496119456658349, 0.0005440116524442518, 0.0005384113592226688, 0.0005328110660010858, 0.0005272107727795027 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000581303961, -0.18750005831539146, 0.062499941499613264, 0.31249994131461795, 0.5624999411296226 ], "y": [ 4.000011787646406, 4.000004991724207, 3.9999981958020085, 3.9999913998798102, 3.999984603957612 ], "z": [ 0.0008248117514201825, 0.0008180063787988016, 0.0008112010061774206, 0.0008043956335560395, 0.0007975902609346586 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375000756203353, -0.18750007587297496, 0.062499923874385396, 0.3124999236217457, 0.5624999233691061 ], "y": [ 4.800013730415652, 4.800005788831466, 4.799997847247279, 4.799989905663092, 4.799981964078905 ], "z": [ 0.0011419228184735238, 0.0011339697660142335, 0.0011260167135549429, 0.0011180636610956523, 0.0011101106086363619 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750009396440015, -0.1875000942880858, 0.06249990538822854, 0.31249990506454295, 0.5624999047408573 ], "y": [ 5.600015486172148, 5.600006497238266, 5.5999975083043845, 5.599988519370503, 5.599979530436621 ], "z": [ 0.0014949076836219992, 0.0014859053575590015, 0.001476903031496004, 0.0014679007054330066, 0.0014588983793700089 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375001129859722, -0.1875001133847934, 0.0624998862163855, 0.3124998858175643, 0.5624998854187432 ], "y": [ 6.400017125235562, 6.400007147597506, 6.39999716995945, 6.399987192321394, 6.399977214683338 ], "z": [ 0.0018784887981949942, 0.0018684959356252715, 0.0018585030730555484, 0.0018485102104858254, 0.0018385173479161026 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375001323665614, -0.1875001328395648, 0.06249986668743177, 0.3124998662144284, 0.562499865741425 ], "y": [ 7.200018575842047, 7.200007709974395, 7.199996844106742, 7.19998597823909, 7.199975112371438 ], "z": [ 0.0022875402460654105, 0.0022766574780168644, 0.0022657747099683184, 0.0022548919419197724, 0.0022440091738712264 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750015204242715, -0.1875001525902163, 0.06249984686199458, 0.31249984631420546, 0.5624998457664163 ], "y": [ 8.000019908825728, 8.000008215603518, 7.999996522381308, 7.999984829159098, 7.999973135936888 ], "z": [ 0.002717707587989787, 0.0027059959238166756, 0.002694284259643564, 0.0026825725954704523, 0.002670860931297341 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750017178994344, -0.18750017240775796, 0.062499826974427505, 0.312499826356613, 0.5624998257387984 ], "y": [ 8.80002105303595, 8.800008634998948, 8.799996216961945, 8.799983798924943, 8.799971380887941 ], "z": [ 0.003164788301163934, 0.00315235047156843, 0.003139912641972926, 0.003127474812377422, 0.003115036982781918 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375001916179239, -0.18750019230338255, 0.062499807011158884, 0.3124998063257003, 0.5624998056402417 ], "y": [ 9.600022079802349, 9.600008999668942, 9.599995919535536, 9.599982839402129, 9.599969759268722 ], "z": [ 0.003625345445617321, 0.003612244321978627, 0.003599143198339933, 0.003586042074701239, 0.003572940951062545 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375002113643574, -0.18750021210963969, 0.06249978714507812, 0.3124997863997959, 0.5624997856545136 ], "y": [ 10.400022919780158, 10.400009280849583, 10.399995641919006, 10.399982002988429, 10.399968364057854 ], "z": [ 0.004096090111835357, 0.004082429194450335, 0.004068768277065312, 0.0040551073596802895, 0.004041446442295267 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750023107885805, -0.18750023187928208, 0.062499767320293845, 0.31249976651986977, 0.5624997657194457 ], "y": [ 11.200023644449196, 11.200009509987185, 11.199995375525171, 11.199981241063158, 11.199967106601147 ], "z": [ 0.0045744826124137754, 0.004560325318252977, 0.00454616802409218, 0.004532010729931383, 0.004517853435770585 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750025063692877, -0.18750025148252614, 0.062499747671876424, 0.312499746826279, 0.5624997459806815 ], "y": [ 12.000024187077601, 12.000009659246636, 11.99999513141567, 11.999980603584703, 11.999966075753738 ], "z": [ 0.005058124427915424, 0.005043573110761696, 0.005029021793607967, 0.005014470476454239, 0.0049999191593005105 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375002701068412, -0.18750027099142436, 0.06249972812399248, 0.31249972723940933, 0.5624997263548261 ], "y": [ 12.800024618563835, 12.800009759600563, 12.79999490063729, 12.799980041674019, 12.799965182710746 ], "z": [ 0.005545333750792834, 0.0055304507811946265, 0.00551556781159642, 0.005500684841998214, 0.005485801872400007 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750028938977437, -0.18750029030227897, 0.06249970878521643, 0.3124997078727118, 0.5624997069602071 ], "y": [ 13.600024876458574, 13.600009784797404, 13.599994693136232, 13.59997960147506, 13.599964509813889 ], "z": [ 0.006034555275549564, 0.0060194392546856455, 0.0060043232338217265, 0.0059892072129578075, 0.005974091192093889 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375003085605567, -0.18750030949419816, 0.06249968957216046, 0.31249968863851907, 0.5624996877048777 ], "y": [ 14.400025030543393, 14.400009765078664, 14.399994499613936, 14.399979234149207, 14.399963968684478 ], "z": [ 0.006524891089689876, 0.006509601017991055, 0.006494310946292235, 0.006479020874593415, 0.0064637308028945946 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.43750032755255314, -0.18750032849661877, 0.06249967055931569, 0.3124996696152501, 0.5624996686711845 ], "y": [ 15.200025028918631, 15.20000967846006, 15.199994328001486, 15.199978977542912, 15.19996362708434 ], "z": [ 0.00701554882735514, 0.0070001736447586945, 0.00698479846216225, 0.006969423279565805, 0.00695404809696936 ] }, { "line": { "color": "black" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ -0.4375003464451198, -0.18750034739363916, 0.062499651657841424, 0.312499650709322, 0.5624996497608026 ], "y": [ 16.00002494175388, 16.000009555123633, 15.999994168493387, 15.999978781863144, 15.999963395232898 ], "z": [ 0.007506285835269044, 0.007490874433673721, 0.007475463032078398, 0.007460051630483075, 0.007444640228887752 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "name": "Aero wake", "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8124619250000001, 0.8124619134459421, 0.5624999942343187, 0.5625000000000001 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.7999973557508343, 0.7999966402495885, 9.162206417444405e-18 ], "z": [ 4.871002816324649e-17, 0.004363102500000049, 0.0043955753776063365, 3.4360542762473186e-05, 4.871002816324649e-17 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624999942343187, 0.8124619134459421, 0.8124618930303587, 0.5624999847072422, 0.5624999942343187 ], "y": [ 0.7999966402495885, 0.7999973557508343, 1.599994811000482, 1.5999933810227123, 0.7999966402495885 ], "z": [ 3.4360542762473186e-05, 0.0043955753776063365, 0.0044931958770315856, 0.00013863466028518693, 3.4360542762473186e-05 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624999847072422, 0.8124618930303587, 0.8124618649474605, 0.5624999722860239, 0.5624999847072422 ], "y": [ 1.5999933810227123, 1.599994811000482, 2.399992454598478, 2.399990312319138, 1.5999933810227123 ], "z": [ 0.00013863466028518693, 0.0044931958770315856, 0.004649043770950182, 0.0003053092113602273, 0.00013863466028518693 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624999722860239, 0.8124618649474605, 0.812461830109957, 0.5624999575381, 0.5624999722860239 ], "y": [ 2.399990312319138, 2.399992454598478, 3.1999902063390535, 3.1999873565514116, 2.399990312319138 ], "z": [ 0.0003053092113602273, 0.004649043770950182, 0.004856514630134907, 0.0005272092806341323, 0.0003053092113602273 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624999575381, 0.812461830109957, 0.8124617895303606, 0.5624999411107972, 0.5624999575381 ], "y": [ 3.1999873565514116, 3.1999902063390535, 3.9999881562546817, 3.9999846027816304, 3.1999873565514116 ], "z": [ 0.0005272092806341323, 0.004856514630134907, 0.005109416600361549, 0.0007975883806602837, 0.0005272092806341323 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624999411107972, 0.8124617895303606, 0.8124617439231214, 0.5624999233436245, 0.5624999411107972 ], "y": [ 3.9999846027816304, 3.9999881562546817, 4.799986214012396, 4.799981962669224, 3.9999846027816304 ], "z": [ 0.0007975883806602837, 0.005109416600361549, 0.005401911948040305, 0.0011101083990921862, 0.0007975883806602837 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624999233436245, 0.8124617439231214, 0.8124616941475412, 0.5624999047080059, 0.5624999233436245 ], "y": [ 4.799981962669224, 4.799986214012396, 5.599984470124919, 5.599979528793419, 4.799981962669224 ], "z": [ 0.0011101083990921862, 0.005401911948040305, 0.005728596222836626, 0.001458895928473685, 0.0011101083990921862 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624999047080059, 0.8124616941475412, 0.812461640764451, 0.56249988537789, 0.5624999047080059 ], "y": [ 5.599979528793419, 5.599984470124919, 6.399982829358027, 6.399977212805474, 5.599979528793419 ], "z": [ 0.001458895928473685, 0.005728596222836626, 0.006084428857723916, 0.0018385147642435145, 0.001458895928473685 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.56249988537789, 0.812461640764451, 0.8124615845040528, 0.5624998656920386, 0.56249988537789 ], "y": [ 6.399977212805474, 6.399982829358027, 7.199981380818826, 7.199975110259148, 6.399977212805474 ], "z": [ 0.0018385147642435145, 0.006084428857723916, 0.006464810371519351, 0.00224400658612019, 0.0018385147642435145 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624998656920386, 0.8124615845040528, 0.8124615258019251, 0.5624998457080345, 0.5624998656920386 ], "y": [ 7.199975110259148, 7.199981380818826, 7.999980027286746, 7.999973133589262, 7.199975110259148 ], "z": [ 0.00224400658612019, 0.006464810371519351, 0.00686551272588112, 0.0026708584775881213, 0.00224400658612019 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624998457080345, 0.8124615258019251, 0.8124614652767873, 0.5624998256710535, 0.5624998457080345 ], "y": [ 7.999973133589262, 7.999980027286746, 8.799978857160102, 8.799971378307317, 7.999973133589262 ], "z": [ 0.0026708584775881213, 0.00686551272588112, 0.007282754507096603, 0.0031150348110584127, 0.0026708584775881213 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624998256710535, 0.8124614652767873, 0.8124614032576343, 0.5624998055628219, 0.5624998256710535 ], "y": [ 8.799971378307317, 8.799978857160102, 9.59997777383543, 9.59996975645606, 8.799971378307317 ], "z": [ 0.0031150348110584127, 0.007282754507096603, 0.007713132552295366, 0.0035729392050654492, 0.0031150348110584127 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624998055628219, 0.8124614032576343, 0.8124613402623917, 0.5624997855672028, 0.5624998055628219 ], "y": [ 9.59996975645606, 9.59997777383543, 10.399976865445812, 10.39996836102129, 9.59996975645606 ], "z": [ 0.0035729392050654492, 0.007713132552295366, 0.0081536905087987, 0.004041445265628865, 0.0035729392050654492 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624997855672028, 0.8124613402623917, 0.8124612765247785, 0.5624997656220664, 0.5624997855672028 ], "y": [ 10.39996836102129, 10.399976865445812, 11.199976037253185, 11.199967103346411, 10.39996836102129 ], "z": [ 0.004041445265628865, 0.0081536905087987, 0.00860184653090365, 0.004517852953449566, 0.004041445265628865 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624997656220664, 0.8124612765247785, 0.812461212465948, 0.5624997458731538, 0.5624997656220664 ], "y": [ 11.199967103346411, 11.199976037253185, 11.999975376131477, 11.999966072300438, 11.199967103346411 ], "z": [ 0.004517852953449566, 0.00860184653090365, 0.00905545614957633, 0.0049999194902928795, 0.004517852953449566 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624997458731538, 0.812461212465948, 0.8124611482303272, 0.5624997262370887, 0.5624997458731538 ], "y": [ 11.999966072300438, 11.999975376131477, 12.799974789445866, 12.799965179073068, 11.999966072300438 ], "z": [ 0.0049999194902928795, 0.00905545614957633, 0.009512733392875675, 0.005485803105934123, 0.0049999194902928795 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624997262370887, 0.8124611482303272, 0.8124610841393545, 0.562499706832297, 0.5624997262370887 ], "y": [ 12.799965179073068, 12.799974789445866, 13.599974359937981, 13.599964506028565, 12.799965179073068 ], "z": [ 0.005485803105934123, 0.009512733392875675, 0.009972311299224186, 0.005974093414724043, 0.005485803105934123 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.562499706832297, 0.8124610841393545, 0.8124610202506753, 0.5624996875668204, 0.562499706832297 ], "y": [ 13.599964506028565, 13.599974359937981, 14.399973996920059, 14.399963964776944, 13.599964506028565 ], "z": [ 0.005974093414724043, 0.009972311299224186, 0.010433151853577634, 0.0064637340689429204, 0.005974093414724043 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624996875668204, 0.8124610202506753, 0.8124609567699135, 0.5624996685230919, 0.5624996875668204 ], "y": [ 14.399963964776944, 14.399973996920059, 15.199973770005858, 15.199963623110254, 14.399963964776944 ], "z": [ 0.0064637340689429204, 0.010433151853577634, 0.010894598567877162, 0.0069540524735901225, 0.0064637340689429204 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.5624996685230919, 0.8124609567699135, 0.812460893666179, 0.5624996496027337, 0.5624996685230919 ], "y": [ 15.199963623110254, 15.199973770005858, 15.999973587313422, 15.99996339122673, 15.199963623110254 ], "z": [ 0.0069540524735901225, 0.010894598567877162, 0.011356252256599593, 0.007444645756118045, 0.0069540524735901225 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124619250000001, 1.06242385, 1.0624238509998334, 0.8124619134459421, 0.8124619250000001 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.8000041100488657, 0.7999973557508343, 9.162206417444405e-18 ], "z": [ 0.004363102500000049, 0.00872620500000005, 0.008754989921580773, 0.0043955753776063365, 0.004363102500000049 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124619134459421, 1.0624238509998334, 1.0624238543968563, 0.8124618930303587, 0.8124619134459421 ], "y": [ 0.7999973557508343, 0.8000041100488657, 1.6000081028650377, 1.599994811000482, 0.7999973557508343 ], "z": [ 0.0043955753776063365, 0.008754989921580773, 0.00884477941056367, 0.0044931958770315856, 0.0043955753776063365 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124618930303587, 1.0624238543968563, 1.0624238606253473, 0.8124618649474605, 0.8124618930303587 ], "y": [ 1.599994811000482, 1.6000081028650377, 2.4000118748560357, 2.399992454598478, 1.599994811000482 ], "z": [ 0.0044931958770315856, 0.00884477941056367, 0.008989074926814278, 0.004649043770950182, 0.0044931958770315856 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124618649474605, 1.0624238606253473, 1.0624238698131283, 0.812461830109957, 0.8124618649474605 ], "y": [ 2.399992454598478, 2.4000118748560357, 3.2000154932795244, 3.1999902063390535, 2.399992454598478 ], "z": [ 0.004649043770950182, 0.008989074926814278, 0.009181700030089264, 0.004856514630134907, 0.004649043770950182 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.812461830109957, 1.0624238698131283, 1.0624238821664975, 0.8124617895303606, 0.812461830109957 ], "y": [ 3.1999902063390535, 3.2000154932795244, 4.000018860900871, 3.9999881562546817, 3.1999902063390535 ], "z": [ 0.004856514630134907, 0.009181700030089264, 0.009416892386339146, 0.005109416600361549, 0.004856514630134907 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124617895303606, 1.0624238821664975, 1.0624238975798306, 0.8124617439231214, 0.8124617895303606 ], "y": [ 3.9999881562546817, 4.000018860900871, 4.800022061050457, 4.799986214012396, 3.9999881562546817 ], "z": [ 0.005109416600361549, 0.009416892386339146, 0.009689248635280041, 0.005401911948040305, 0.005109416600361549 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124617439231214, 1.0624238975798306, 1.0624239160825706, 0.8124616941475412, 0.8124617439231214 ], "y": [ 4.799986214012396, 4.800022061050457, 5.600024997008804, 5.599984470124919, 4.799986214012396 ], "z": [ 0.005401911948040305, 0.009689248635280041, 0.009993797411684078, 0.005728596222836626, 0.005401911948040305 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124616941475412, 1.0624239160825706, 1.062423937405997, 0.812461640764451, 0.8124616941475412 ], "y": [ 5.599984470124919, 5.600024997008804, 6.4000277557939675, 6.399982829358027, 5.599984470124919 ], "z": [ 0.005728596222836626, 0.009993797411684078, 0.01032591960212853, 0.006084428857723916, 0.005728596222836626 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.812461640764451, 1.062423937405997, 1.0624239614547102, 0.8124615845040528, 0.812461640764451 ], "y": [ 6.399982829358027, 6.4000277557939675, 7.200030239978838, 7.199981380818826, 6.399982829358027 ], "z": [ 0.006084428857723916, 0.01032591960212853, 0.010681425124526835, 0.006464810371519351, 0.006084428857723916 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124615845040528, 1.0624239614547102, 1.0624239878559636, 0.8124615258019251, 0.8124615845040528 ], "y": [ 7.199981380818826, 7.200030239978838, 8.000032537843335, 7.999980027286746, 7.199981380818826 ], "z": [ 0.006464810371519351, 0.010681425124526835, 0.01105645551949636, 0.00686551272588112, 0.006464810371519351 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124615258019251, 1.0624239878559636, 1.0624240164391061, 0.8124614652767873, 0.8124615258019251 ], "y": [ 7.999980027286746, 8.000032537843335, 8.800034552976555, 8.799978857160102, 7.999980027286746 ], "z": [ 0.00686551272588112, 0.01105645551949636, 0.011447569094086897, 0.007282754507096603, 0.00686551272588112 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124614652767873, 1.0624240164391061, 1.062424046779832, 0.8124614032576343, 0.8124614652767873 ], "y": [ 8.799978857160102, 8.800034552976555, 9.600036375170923, 9.59997777383543, 8.799978857160102 ], "z": [ 0.007282754507096603, 0.011447569094086897, 0.011851630739051535, 0.007713132552295366, 0.007282754507096603 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124614032576343, 1.062424046779832, 1.0624240786772026, 0.8124613402623917, 0.8124614032576343 ], "y": [ 9.59997777383543, 9.600036375170923, 10.400037910905114, 10.399976865445812, 9.59997777383543 ], "z": [ 0.007713132552295366, 0.011851630739051535, 0.012265908780960077, 0.0081536905087987, 0.007713132552295366 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124613402623917, 1.0624240786772026, 1.0624241117021795, 0.8124612765247785, 0.8124613402623917 ], "y": [ 10.399976865445812, 10.400037910905114, 11.200039251394067, 11.199976037253185, 10.399976865445812 ], "z": [ 0.0081536905087987, 0.012265908780960077, 0.012687962171243582, 0.00860184653090365, 0.0081536905087987 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124612765247785, 1.0624241117021795, 1.0624241456626615, 0.812461212465948, 0.8124612765247785 ], "y": [ 11.199976037253185, 11.200039251394067, 12.000040307290025, 11.999975376131477, 11.199976037253185 ], "z": [ 0.00860184653090365, 0.012687962171243582, 0.013115745021976807, 0.00905545614957633, 0.00860184653090365 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.812461212465948, 1.0624241456626615, 1.0624241801663046, 0.8124611482303272, 0.812461212465948 ], "y": [ 11.999975376131477, 12.000040307290025, 12.800041170384995, 12.799974789445866, 11.999975376131477 ], "z": [ 0.00905545614957633, 0.013115745021976807, 0.01354749833673766, 0.009512733392875675, 0.00905545614957633 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124611482303272, 1.0624241801663046, 1.0624242150604906, 0.8124610841393545, 0.8124611482303272 ], "y": [ 12.799974789445866, 12.800041170384995, 13.600041757575765, 13.599974359937981, 12.799974789445866 ], "z": [ 0.009512733392875675, 0.01354749833673766, 0.013981850987710955, 0.009972311299224186, 0.009512733392875675 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124610841393545, 1.0624242150604906, 1.062424250024925, 0.8124610202506753, 0.8124610841393545 ], "y": [ 13.599974359937981, 13.600041757575765, 14.40004216025581, 14.399973996920059, 13.599974359937981 ], "z": [ 0.009972311299224186, 0.013981850987710955, 0.01441771892949864, 0.010433151853577634, 0.009972311299224186 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124610202506753, 1.062424250024925, 1.062424284959919, 0.8124609567699135, 0.8124610202506753 ], "y": [ 14.399973996920059, 14.40004216025581, 15.200042309462798, 15.199973770005858, 14.399973996920059 ], "z": [ 0.010433151853577634, 0.01441771892949864, 0.014854388055001192, 0.010894598567877162, 0.010433151853577634 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 0.8124609567699135, 1.062424284959919, 1.0624243196435175, 0.812460893666179, 0.8124609567699135 ], "y": [ 15.199973770005858, 15.200042309462798, 16.0000422977623, 15.999973587313422, 15.199973770005858 ], "z": [ 0.010894598567877162, 0.014854388055001192, 0.015291409655183566, 0.011356252256599593, 0.010894598567877162 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.06242385, 1.312385775, 1.312385765590729, 1.0624238509998334, 1.06242385 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.7999967708512715, 0.8000041100488657, 9.162206417444405e-18 ], "z": [ 0.00872620500000005, 0.01308930750000005, 0.013117808415149604, 0.008754989921580773, 0.00872620500000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624238509998334, 1.312385765590729, 1.3123857492560815, 1.0624238543968563, 1.0624238509998334 ], "y": [ 0.8000041100488657, 0.7999967708512715, 1.5999936447026821, 1.6000081028650377, 0.8000041100488657 ], "z": [ 0.008754989921580773, 0.013117808415149604, 0.013205038226600496, 0.00884477941056367, 0.008754989921580773 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624238543968563, 1.3123857492560815, 1.312385727066829, 1.0624238606253473, 1.0624238543968563 ], "y": [ 1.6000081028650377, 1.5999936447026821, 2.399990713499301, 2.4000118748560357, 1.6000081028650377 ], "z": [ 0.00884477941056367, 0.013205038226600496, 0.013344567263095089, 0.008989074926814278, 0.00884477941056367 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624238606253473, 1.312385727066829, 1.312385699803704, 1.0624238698131283, 1.0624238606253473 ], "y": [ 2.4000118748560357, 2.399990713499301, 3.1999878994597233, 3.2000154932795244, 2.4000118748560357 ], "z": [ 0.008989074926814278, 0.013344567263095089, 0.013530285209410336, 0.009181700030089264, 0.008989074926814278 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624238698131283, 1.312385699803704, 1.3123856683437078, 1.0624238821664975, 1.0624238698131283 ], "y": [ 3.2000154932795244, 3.1999878994597233, 3.999985295937126, 4.000018860900871, 3.2000154932795244 ], "z": [ 0.009181700030089264, 0.013530285209410336, 0.013756498247063131, 0.009416892386339146, 0.009181700030089264 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624238821664975, 1.3123856683437078, 1.3123856332589672, 1.0624238975798306, 1.0624238821664975 ], "y": [ 4.000018860900871, 3.999985295937126, 4.799982815059392, 4.800022061050457, 4.000018860900871 ], "z": [ 0.009416892386339146, 0.013756498247063131, 0.014017868983988494, 0.009689248635280041, 0.009416892386339146 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624238975798306, 1.3123856332589672, 1.312385595267562, 1.0624239160825706, 1.0624238975798306 ], "y": [ 4.800022061050457, 4.799982815059392, 5.599980551570029, 5.600024997008804, 4.800022061050457 ], "z": [ 0.009689248635280041, 0.014017868983988494, 0.014309495114931086, 0.009993797411684078, 0.009689248635280041 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624239160825706, 1.312385595267562, 1.3123855547838665, 1.062423937405997, 1.0624239160825706 ], "y": [ 5.600024997008804, 5.599980551570029, 6.399978412712719, 6.4000277557939675, 5.600024997008804 ], "z": [ 0.009993797411684078, 0.014309495114931086, 0.014626828084469745, 0.01032591960212853, 0.009993797411684078 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.062423937405997, 1.3123855547838665, 1.3123855123976538, 1.0624239614547102, 1.062423937405997 ], "y": [ 6.4000277557939675, 6.399978412712719, 7.199976492431211, 7.200030239978838, 6.4000277557939675 ], "z": [ 0.01032591960212853, 0.014626828084469745, 0.014965753979210595, 0.010681425124526835, 0.01032591960212853 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624239614547102, 1.3123855123976538, 1.312385468400212, 1.0624239878559636, 1.0624239614547102 ], "y": [ 7.200030239978838, 7.199976492431211, 7.999974695521935, 8.000032537843335, 7.200030239978838 ], "z": [ 0.010681425124526835, 0.014965753979210595, 0.015322499672438088, 0.01105645551949636, 0.010681425124526835 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624239878559636, 1.312385468400212, 1.3123854232768732, 1.0624240164391061, 1.0624239878559636 ], "y": [ 8.000032537843335, 7.999974695521935, 8.799973115398142, 8.800034552976555, 8.000032537843335 ], "z": [ 0.01105645551949636, 0.015322499672438088, 0.015693722215994754, 0.011447569094086897, 0.01105645551949636 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624240164391061, 1.3123854232768732, 1.3123853772210174, 1.062424046779832, 1.0624240164391061 ], "y": [ 8.800034552976555, 8.799973115398142, 9.599971656847169, 9.600036375170923, 8.800034552976555 ], "z": [ 0.011447569094086897, 0.015693722215994754, 0.01607640837769345, 0.011851630739051535, 0.011447569094086897 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.062424046779832, 1.3123853772210174, 1.312385330630465, 1.0624240786772026, 1.062424046779832 ], "y": [ 9.600036375170923, 9.599971656847169, 10.39997041301708, 10.400037910905114, 9.600036375170923 ], "z": [ 0.011851630739051535, 0.01607640837769345, 0.01646796933785412, 0.012265908780960077, 0.011851630739051535 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624240786772026, 1.312385330630465, 1.3123852836188357, 1.0624241117021795, 1.0624240786772026 ], "y": [ 10.400037910905114, 10.39997041301708, 11.19996929003901, 11.200039251394067, 10.400037910905114 ], "z": [ 0.012265908780960077, 0.01646796933785412, 0.01686613334272428, 0.012687962171243582, 0.012265908780960077 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624241117021795, 1.3123852836188357, 1.3123852365066258, 1.0624241456626615, 1.0624241117021795 ], "y": [ 11.200039251394067, 11.19996929003901, 11.999968379665505, 12.000040307290025, 11.200039251394067 ], "z": [ 0.012687962171243582, 0.01686613334272428, 0.01726904179198468, 0.013115745021976807, 0.012687962171243582 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624241456626615, 1.3123852365066258, 1.312385189340325, 1.0624241801663046, 1.0624241456626615 ], "y": [ 12.000040307290025, 11.999968379665505, 12.799967589645137, 12.800041170384995, 12.000040307290025 ], "z": [ 0.013115745021976807, 0.01726904179198468, 0.01767513419496066, 0.01354749833673766, 0.013115745021976807 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624241801663046, 1.312385189340325, 1.3123851423655943, 1.0624242150604906, 1.0624241801663046 ], "y": [ 12.800041170384995, 12.799967589645137, 13.599967007102007, 13.600041757575765, 12.800041170384995 ], "z": [ 0.01354749833673766, 0.01767513419496066, 0.018083244240899084, 0.013981850987710955, 0.01354749833673766 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.0624242150604906, 1.3123851423655943, 1.312385095570038, 1.062424250024925, 1.0624242150604906 ], "y": [ 13.600041757575765, 13.599967007102007, 14.399966541012066, 14.40004216025581, 13.600041757575765 ], "z": [ 0.013981850987710955, 0.018083244240899084, 0.018492473641254375, 0.01441771892949864, 0.013981850987710955 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.062424250024925, 1.312385095570038, 1.3123850491119522, 1.062424284959919, 1.062424250024925 ], "y": [ 14.40004216025581, 14.399966541012066, 15.199966264105328, 15.200042309462798, 14.40004216025581 ], "z": [ 0.01441771892949864, 0.018492473641254375, 0.01890228052834785, 0.014854388055001192, 0.01441771892949864 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.062424284959919, 1.3123850491119522, 1.3123850029222204, 1.0624243196435175, 1.062424284959919 ], "y": [ 15.200042309462798, 15.199966264105328, 15.99996608299767, 16.0000422977623, 15.200042309462798 ], "z": [ 0.014854388055001192, 0.01890228052834785, 0.019312320316468137, 0.015291409655183566, 0.014854388055001192 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.312385775, 1.5623477000000001, 1.562347690821025, 1.312385765590729, 1.312385775 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.799997122848918, 0.7999967708512715, 9.162206417444405e-18 ], "z": [ 0.01308930750000005, 0.01745241000000005, 0.017480923114039832, 0.013117808415149604, 0.01308930750000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.312385765590729, 1.562347690821025, 1.562347674799373, 1.3123857492560815, 1.312385765590729 ], "y": [ 0.7999967708512715, 0.799997122848918, 1.5999943470993463, 1.5999936447026821, 0.7999967708512715 ], "z": [ 0.013117808415149604, 0.017480923114039832, 0.017567366251003295, 0.013205038226600496, 0.013117808415149604 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3123857492560815, 1.562347674799373, 1.5623476529459288, 1.312385727066829, 1.3123857492560815 ], "y": [ 1.5999936447026821, 1.5999943470993463, 2.3999917639131514, 2.399990713499301, 1.5999936447026821 ], "z": [ 0.013205038226600496, 0.017567366251003295, 0.017705147752285617, 0.013344567263095089, 0.013205038226600496 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.312385727066829, 1.5623476529459288, 1.5623476259926226, 1.312385699803704, 1.312385727066829 ], "y": [ 2.399990713499301, 2.3999917639131514, 3.1999892952201012, 3.1999878994597233, 2.399990713499301 ], "z": [ 0.013344567263095089, 0.017705147752285617, 0.017888002109101492, 0.013530285209410336, 0.013344567263095089 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.312385699803704, 1.5623476259926226, 1.5623475947770735, 1.3123856683437078, 1.312385699803704 ], "y": [ 3.1999878994597233, 3.1999892952201012, 3.9999870303856278, 3.999985295937126, 3.1999878994597233 ], "z": [ 0.013530285209410336, 0.017888002109101492, 0.01811008298364314, 0.013756498247063131, 0.013530285209410336 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3123856683437078, 1.5623475947770735, 1.5623475598403873, 1.3123856332589672, 1.3123856683437078 ], "y": [ 3.999985295937126, 3.9999870303856278, 4.799984878111898, 4.799982815059392, 3.999985295937126 ], "z": [ 0.013756498247063131, 0.01811008298364314, 0.018365920918453814, 0.014017868983988494, 0.013756498247063131 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3123856332589672, 1.5623475598403873, 1.562347521877352, 1.312385595267562, 1.3123856332589672 ], "y": [ 4.799982815059392, 4.799984878111898, 5.59998292970836, 5.599980551570029, 4.799982815059392 ], "z": [ 0.014017868983988494, 0.018365920918453814, 0.018650494977104467, 0.014309495114931086, 0.014017868983988494 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.312385595267562, 1.562347521877352, 1.5623474812848825, 1.3123855547838665, 1.312385595267562 ], "y": [ 5.599980551570029, 5.59998292970836, 6.399981091454103, 6.399978412712719, 5.599980551570029 ], "z": [ 0.014309495114931086, 0.018650494977104467, 0.01895918067700987, 0.014626828084469745, 0.014309495114931086 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3123855547838665, 1.5623474812848825, 1.5623474386424068, 1.3123855123976538, 1.3123855547838665 ], "y": [ 6.399978412712719, 6.399981091454103, 7.199979457022128, 7.199976492431211, 6.399978412712719 ], "z": [ 0.014626828084469745, 0.01895918067700987, 0.019287816111895742, 0.014965753979210595, 0.014626828084469745 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3123855123976538, 1.5623474386424068, 1.5623473942355086, 1.312385468400212, 1.3123855123976538 ], "y": [ 7.199976492431211, 7.199979457022128, 7.999977932154519, 7.999974695521935, 7.199976492431211 ], "z": [ 0.014965753979210595, 0.019287816111895742, 0.019632644031693732, 0.015322499672438088, 0.014965753979210595 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.312385468400212, 1.5623473942355086, 1.562347348551272, 1.3123854232768732, 1.312385468400212 ], "y": [ 7.999974695521935, 7.999977932154519, 8.799976610851648, 8.799973115398142, 7.999974695521935 ], "z": [ 0.015322499672438088, 0.019632644031693732, 0.019990371655679427, 0.015693722215994754, 0.015322499672438088 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3123854232768732, 1.562347348551272, 1.5623473017883505, 1.3123853772210174, 1.3123854232768732 ], "y": [ 8.799973115398142, 8.799976610851648, 9.599975397732855, 9.599971656847169, 8.799973115398142 ], "z": [ 0.015693722215994754, 0.019990371655679427, 0.020358098718006947, 0.01607640837769345, 0.015693722215994754 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3123853772210174, 1.5623473017883505, 1.562347254357797, 1.312385330630465, 1.3123853772210174 ], "y": [ 9.599971656847169, 9.599975397732855, 10.399974384962816, 10.39997041301708, 9.599971656847169 ], "z": [ 0.01607640837769345, 0.020358098718006947, 0.020733377306197856, 0.01646796933785412, 0.01607640837769345 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.312385330630465, 1.562347254357797, 1.5623472063875883, 1.3123852836188357, 1.312385330630465 ], "y": [ 10.39997041301708, 10.399974384962816, 11.199973475852698, 11.19996929003901, 10.39997041301708 ], "z": [ 0.01646796933785412, 0.020733377306197856, 0.021114119572776275, 0.01686613334272428, 0.01646796933785412 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3123852836188357, 1.5623472063875883, 1.5623471582205557, 1.3123852365066258, 1.3123852836188357 ], "y": [ 11.19996929003901, 11.199973475852698, 11.999972759265718, 11.999968379665505, 11.19996929003901 ], "z": [ 0.01686613334272428, 0.021114119572776275, 0.021498666720439867, 0.01726904179198468, 0.01686613334272428 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3123852365066258, 1.5623471582205557, 1.5623471099223738, 1.312385189340325, 1.3123852365066258 ], "y": [ 11.999968379665505, 11.999972759265718, 12.79997213800772, 12.799967589645137, 11.999968379665505 ], "z": [ 0.01726904179198468, 0.021498666720439867, 0.02188566981939099, 0.01767513419496066, 0.01726904179198468 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.312385189340325, 1.5623471099223738, 1.5623470617648778, 1.3123851423655943, 1.312385189340325 ], "y": [ 12.799967589645137, 12.79997213800772, 13.599971695647595, 13.599967007102007, 12.799967589645137 ], "z": [ 0.01767513419496066, 0.02188566981939099, 0.022274172724010707, 0.018083244240899084, 0.01767513419496066 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3123851423655943, 1.5623470617648778, 1.5623470137527669, 1.312385095570038, 1.3123851423655943 ], "y": [ 13.599967007102007, 13.599971695647595, 14.399971335726036, 14.399966541012066, 13.599967007102007 ], "z": [ 0.018083244240899084, 0.022274172724010707, 0.022663458304022048, 0.018492473641254375, 0.018083244240899084 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.312385095570038, 1.5623470137527669, 1.562346966066091, 1.3123850491119522, 1.312385095570038 ], "y": [ 14.399966541012066, 14.399971335726036, 15.199971128670821, 15.199966264105328, 14.399966541012066 ], "z": [ 0.018492473641254375, 0.022663458304022048, 0.023053144166224608, 0.01890228052834785, 0.018492473641254375 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.3123850491119522, 1.562346966066091, 1.5623469186425232, 1.3123850029222204, 1.3123850491119522 ], "y": [ 15.199966264105328, 15.199971128670821, 15.999970977392403, 15.99996608299767, 15.199966264105328 ], "z": [ 0.01890228052834785, 0.023053144166224608, 0.023442975805592004, 0.019312320316468137, 0.01890228052834785 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5623477000000001, 1.8123096250000001, 1.8123096252793973, 1.562347690821025, 1.5623477000000001 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.8000042711855221, 0.799997122848918, 9.162206417444405e-18 ], "z": [ 0.01745241000000005, 0.02181551250000005, 0.021842245662087735, 0.017480923114039832, 0.01745241000000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.562347690821025, 1.8123096252793973, 1.8123096273998411, 1.562347674799373, 1.562347690821025 ], "y": [ 0.799997122848918, 0.8000042711855221, 1.6000084254430789, 1.5999943470993463, 0.799997122848918 ], "z": [ 0.017480923114039832, 0.021842245662087735, 0.021926181197280452, 0.017567366251003295, 0.017480923114039832 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.562347674799373, 1.8123096273998411, 1.812309631841271, 1.5623476529459288, 1.562347674799373 ], "y": [ 1.5999943470993463, 1.6000084254430789, 2.4000123599611722, 2.3999917639131514, 1.5999943470993463 ], "z": [ 0.017567366251003295, 0.021926181197280452, 0.02206079417356827, 0.017705147752285617, 0.017567366251003295 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5623476529459288, 1.812309631841271, 1.812309638767255, 1.5623476259926226, 1.5623476529459288 ], "y": [ 2.3999917639131514, 2.4000123599611722, 3.2000161441788317, 3.1999892952201012, 2.3999917639131514 ], "z": [ 0.017705147752285617, 0.02206079417356827, 0.02223989477813403, 0.017888002109101492, 0.017705147752285617 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5623476259926226, 1.812309638767255, 1.8123096484183707, 1.5623475947770735, 1.5623476259926226 ], "y": [ 3.1999892952201012, 3.2000161441788317, 4.000019677718904, 3.9999870303856278, 3.1999892952201012 ], "z": [ 0.017888002109101492, 0.02223989477813403, 0.022457724482343875, 0.01811008298364314, 0.017888002109101492 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5623475947770735, 1.8123096484183707, 1.8123096607152773, 1.5623475598403873, 1.5623475947770735 ], "y": [ 3.9999870303856278, 4.000019677718904, 4.800023043050683, 4.799984878111898, 3.9999870303856278 ], "z": [ 0.01811008298364314, 0.022457724482343875, 0.022708920175087563, 0.018365920918453814, 0.01811008298364314 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5623475598403873, 1.8123096607152773, 1.8123096757177453, 1.562347521877352, 1.5623475598403873 ], "y": [ 4.799984878111898, 4.800023043050683, 5.600026139930303, 5.59998292970836, 4.799984878111898 ], "z": [ 0.018365920918453814, 0.022708920175087563, 0.022988579886379087, 0.018650494977104467, 0.018365920918453814 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.562347521877352, 1.8123096757177453, 1.8123096931809952, 1.5623474812848825, 1.562347521877352 ], "y": [ 5.59998292970836, 5.600026139930303, 6.400029057167697, 6.399981091454103, 5.59998292970836 ], "z": [ 0.018650494977104467, 0.022988579886379087, 0.023292206961162812, 0.01895918067700987, 0.018650494977104467 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5623474812848825, 1.8123096931809952, 1.8123097130428614, 1.5623474386424068, 1.5623474812848825 ], "y": [ 6.399981091454103, 6.400029057167697, 7.200031695838008, 7.199979457022128, 6.399981091454103 ], "z": [ 0.01895918067700987, 0.023292206961162812, 0.023615767898237508, 0.019287816111895742, 0.01895918067700987 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5623474386424068, 1.8123097130428614, 1.8123097349564945, 1.5623473942355086, 1.5623474386424068 ], "y": [ 7.199979457022128, 7.200031695838008, 8.000034148296033, 7.999977932154519, 7.199979457022128 ], "z": [ 0.019287816111895742, 0.023615767898237508, 0.023955616210629194, 0.019632644031693732, 0.019287816111895742 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5623473942355086, 1.8123097349564945, 1.8123097587908352, 1.562347348551272, 1.5623473942355086 ], "y": [ 7.999977932154519, 8.000034148296033, 8.800036316222785, 8.799976610851648, 7.999977932154519 ], "z": [ 0.019632644031693732, 0.023955616210629194, 0.024308558183696316, 0.019990371655679427, 0.019632644031693732 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.562347348551272, 1.8123097587908352, 1.812309784150342, 1.5623473017883505, 1.562347348551272 ], "y": [ 8.799976610851648, 8.800036316222785, 9.600038292975295, 9.599975397732855, 8.799976610851648 ], "z": [ 0.019990371655679427, 0.024308558183696316, 0.02467175825347697, 0.020358098718006947, 0.019990371655679427 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5623473017883505, 1.812309784150342, 1.8123098108800397, 1.562347254357797, 1.5623473017883505 ], "y": [ 9.599975397732855, 9.600038292975295, 10.400039980171375, 10.399974384962816, 9.599975397732855 ], "z": [ 0.020358098718006947, 0.02467175825347697, 0.025042818714815958, 0.020733377306197856, 0.020358098718006947 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.562347254357797, 1.8123098108800397, 1.8123098385802803, 1.5623472063875883, 1.562347254357797 ], "y": [ 10.399974384962816, 10.400039980171375, 11.200041470745928, 11.199973475852698, 10.399974384962816 ], "z": [ 0.020733377306197856, 0.025042818714815958, 0.02541966496156141, 0.021114119572776275, 0.020733377306197856 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5623472063875883, 1.8123098385802803, 1.812309867107981, 1.5623471582205557, 1.5623472063875883 ], "y": [ 11.199973475852698, 11.200041470745928, 12.000042667086927, 11.999972759265718, 11.199973475852698 ], "z": [ 0.021114119572776275, 0.02541966496156141, 0.025800639193156966, 0.021498666720439867, 0.021114119572776275 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5623471582205557, 1.812309867107981, 1.8123098960957866, 1.5623471099223738, 1.5623471582205557 ], "y": [ 11.999972759265718, 12.000042667086927, 12.800043661532847, 12.79997213800772, 11.999972759265718 ], "z": [ 0.021498666720439867, 0.025800639193156966, 0.026184363669349748, 0.02188566981939099, 0.021498666720439867 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5623471099223738, 1.8123098960957866, 1.8123099254371609, 1.5623470617648778, 1.5623471099223738 ], "y": [ 12.79997213800772, 12.800043661532847, 13.60004436051208, 13.599971695647595, 12.79997213800772 ], "z": [ 0.02188566981939099, 0.026184363669349748, 0.026569848578482658, 0.022274172724010707, 0.02188566981939099 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5623470617648778, 1.8123099254371609, 1.8123099548260293, 1.5623470137527669, 1.5623470617648778 ], "y": [ 13.599971695647595, 13.60004436051208, 14.40004485653229, 14.399971335726036, 13.599971695647595 ], "z": [ 0.022274172724010707, 0.026569848578482658, 0.026956330992660843, 0.022663458304022048, 0.022274172724010707 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.5623470137527669, 1.8123099548260293, 1.812309984197048, 1.562346966066091, 1.5623470137527669 ], "y": [ 14.399971335726036, 14.40004485653229, 15.200045070532308, 15.199971128670821, 14.399971335726036 ], "z": [ 0.022663458304022048, 0.026956330992660843, 0.02734338968094543, 0.023053144166224608, 0.022663458304022048 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.562346966066091, 1.812309984197048, 1.8123100133243386, 1.5623469186425232, 1.562346966066091 ], "y": [ 15.199971128670821, 15.200045070532308, 16.00004509958812, 15.999970977392403, 15.199971128670821 ], "z": [ 0.023053144166224608, 0.02734338968094543, 0.027730739956343724, 0.023442975805592004, 0.023053144166224608 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123096250000001, 2.06227155, 2.062271537819961, 1.8123096252793973, 1.8123096250000001 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 0.7999968504395711, 0.8000042711855221, 9.162206417444405e-18 ], "z": [ 0.02181551250000005, 0.02617861500000005, 0.026206145672870378, 0.021842245662087735, 0.02181551250000005 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123096252793973, 2.062271537819961, 2.0622715161497642, 1.8123096273998411, 1.8123096252793973 ], "y": [ 0.8000042711855221, 0.7999968504395711, 1.5999938047112736, 1.6000084254430789, 0.8000042711855221 ], "z": [ 0.021842245662087735, 0.026206145672870378, 0.02629050138818445, 0.021926181197280452, 0.021842245662087735 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123096273998411, 2.0622715161497642, 2.062271486229936, 1.812309631841271, 1.8123096273998411 ], "y": [ 1.6000084254430789, 1.5999938047112736, 2.399990956607656, 2.4000123599611722, 1.6000084254430789 ], "z": [ 0.021926181197280452, 0.02629050138818445, 0.026425515849181777, 0.02206079417356827, 0.021926181197280452 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.812309631841271, 2.062271486229936, 2.062271449019728, 1.812309638767255, 1.812309631841271 ], "y": [ 2.4000123599611722, 2.399990956607656, 3.199988230166699, 3.2000161441788317, 2.4000123599611722 ], "z": [ 0.02206079417356827, 0.026425515849181777, 0.02660533252262167, 0.02223989477813403, 0.02206079417356827 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.812309638767255, 2.062271449019728, 2.062271405583596, 1.8123096484183707, 1.812309638767255 ], "y": [ 3.2000161441788317, 3.199988230166699, 3.999985717099869, 4.000019677718904, 3.2000161441788317 ], "z": [ 0.02223989477813403, 0.02660533252262167, 0.026824499204128972, 0.022457724482343875, 0.02223989477813403 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123096484183707, 2.062271405583596, 2.062271356683286, 1.8123096607152773, 1.8123096484183707 ], "y": [ 4.000019677718904, 3.999985717099869, 4.799983327325806, 4.800023043050683, 4.000019677718904 ], "z": [ 0.022457724482343875, 0.026824499204128972, 0.027077892142435816, 0.022708920175087563, 0.022457724482343875 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123096607152773, 2.062271356683286, 2.062271303232613, 1.8123096757177453, 1.8123096607152773 ], "y": [ 4.800023043050683, 4.799983327325806, 5.59998115379444, 5.600026139930303, 4.800023043050683 ], "z": [ 0.022708920175087563, 0.027077892142435816, 0.027360804287995796, 0.022988579886379087, 0.022708920175087563 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123096757177453, 2.062271303232613, 2.0622712458356625, 1.8123096931809952, 1.8123096757177453 ], "y": [ 5.600026139930303, 5.59998115379444, 6.399979102912118, 6.400029057167697, 5.600026139930303 ], "z": [ 0.022988579886379087, 0.027360804287995796, 0.02766884734164822, 0.023292206961162812, 0.022988579886379087 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123096931809952, 2.0622712458356625, 2.062271185276541, 1.8123097130428614, 1.8123096931809952 ], "y": [ 6.400029057167697, 6.399979102912118, 7.199977269743877, 7.200031695838008, 6.400029057167697 ], "z": [ 0.023292206961162812, 0.02766884734164822, 0.02799805272182345, 0.023615767898237508, 0.023292206961162812 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123097130428614, 2.062271185276541, 2.06227112202711, 1.8123097349564945, 1.8123097130428614 ], "y": [ 7.200031695838008, 7.199977269743877, 7.999975559466232, 8.000034148296033, 7.200031695838008 ], "z": [ 0.023615767898237508, 0.02799805272182345, 0.028344763852801838, 0.023955616210629194, 0.023615767898237508 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123097349564945, 2.06227112202711, 2.0622710567581843, 1.8123097587908352, 1.8123097349564945 ], "y": [ 8.000034148296033, 7.999975559466232, 8.79997406786439, 8.800036316222785, 8.000034148296033 ], "z": [ 0.023955616210629194, 0.028344763852801838, 0.028705743908251077, 0.024308558183696316, 0.023955616210629194 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123097587908352, 2.0622710567581843, 2.0622709898272076, 1.812309784150342, 1.8123097587908352 ], "y": [ 8.800036316222785, 8.79997406786439, 9.599972698536526, 9.600038292975295, 8.800036316222785 ], "z": [ 0.024308558183696316, 0.028705743908251077, 0.02907805787516107, 0.02467175825347697, 0.024308558183696316 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.812309784150342, 2.0622709898272076, 2.062270921802514, 1.8123098108800397, 1.812309784150342 ], "y": [ 9.600038292975295, 9.599972698536526, 10.399971546128839, 10.400039980171375, 9.600038292975295 ], "z": [ 0.02467175825347697, 0.02907805787516107, 0.029459184992631983, 0.025042818714815958, 0.02467175825347697 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123098108800397, 2.062270921802514, 2.062270852938604, 1.8123098385802803, 1.8123098108800397 ], "y": [ 10.400039980171375, 10.399971546128839, 11.19997051240222, 11.200041470745928, 10.400039980171375 ], "z": [ 0.025042818714815958, 0.029459184992631983, 0.029846892176743862, 0.02541966496156141, 0.025042818714815958 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123098385802803, 2.062270852938604, 2.0622707837054923, 1.812309867107981, 1.8123098385802803 ], "y": [ 11.200041470745928, 11.19997051240222, 11.999969689420412, 12.000042667086927, 11.200041470745928 ], "z": [ 0.02541966496156141, 0.029846892176743862, 0.030239352581490794, 0.025800639193156966, 0.02541966496156141 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.812309867107981, 2.0622707837054923, 2.062270714260684, 1.8123098960957866, 1.812309867107981 ], "y": [ 12.000042667086927, 11.999969689420412, 12.799968977747897, 12.800043661532847, 12.000042667086927 ], "z": [ 0.025800639193156966, 0.030239352581490794, 0.03063501180854282, 0.026184363669349748, 0.025800639193156966 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123098960957866, 2.062270714260684, 2.062270644971381, 1.8123099254371609, 1.8123098960957866 ], "y": [ 12.800043661532847, 12.799968977747897, 13.59996846484284, 13.60004436051208, 12.800043661532847 ], "z": [ 0.026184363669349748, 0.03063501180854282, 0.031032708986708006, 0.026569848578482658, 0.026184363669349748 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123099254371609, 2.062270644971381, 2.0622705758989097, 1.8123099548260293, 1.8123099254371609 ], "y": [ 13.60004436051208, 13.59996846484284, 14.399968051251916, 14.40004485653229, 13.60004436051208 ], "z": [ 0.026569848578482658, 0.031032708986708006, 0.031431532185545494, 0.026956330992660843, 0.026569848578482658 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.8123099548260293, 2.0622705758989097, 2.0622705072851923, 1.812309984197048, 1.8123099548260293 ], "y": [ 14.40004485653229, 14.399968051251916, 15.199967811736276, 15.200045070532308, 14.40004485653229 ], "z": [ 0.026956330992660843, 0.031431532185545494, 0.03183093248246228, 0.02734338968094543, 0.026956330992660843 ] }, { "line": { "color": "lightskyblue" }, "mode": "lines", "showlegend": false, "surfaceaxis": 2, "type": "scatter3d", "x": [ 1.812309984197048, 2.0622705072851923, 2.062270439088722, 1.8123100133243386, 1.812309984197048 ], "y": [ 15.200045070532308, 15.199967811736276, 15.999967645165848, 16.00004509958812, 15.200045070532308 ], "z": [ 0.02734338968094543, 0.03183093248246228, 0.03223054948213675, 0.027730739956343724, 0.02734338968094543 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.5624999942343187, 0.5624999847072422, 0.5624999722860239, 0.5624999575381, 0.5624999411107972, 0.5624999233436245, 0.5624999047080059, 0.56249988537789, 0.5624998656920386, 0.5624998457080345, 0.5624998256710535, 0.5624998055628219, 0.5624997855672028, 0.5624997656220664, 0.5624997458731538, 0.5624997262370887, 0.562499706832297, 0.5624996875668204, 0.5624996685230919, 0.5624996496027337 ], "y": [ 9.162206417444405e-18, 0.7999966402495885, 1.5999933810227123, 2.399990312319138, 3.1999873565514116, 3.9999846027816304, 4.799981962669224, 5.599979528793419, 6.399977212805474, 7.199975110259148, 7.999973133589262, 8.799971378307317, 9.59996975645606, 10.39996836102129, 11.199967103346411, 11.999966072300438, 12.799965179073068, 13.599964506028565, 14.399963964776944, 15.199963623110254, 15.99996339122673 ], "z": [ 4.871002816324649e-17, 3.4360542762473186e-05, 0.00013863466028518693, 0.0003053092113602273, 0.0005272092806341323, 0.0007975883806602837, 0.0011101083990921862, 0.001458895928473685, 0.0018385147642435145, 0.00224400658612019, 0.0026708584775881213, 0.0031150348110584127, 0.0035729392050654492, 0.004041445265628865, 0.004517852953449566, 0.0049999194902928795, 0.005485803105934123, 0.005974093414724043, 0.0064637340689429204, 0.0069540524735901225, 0.007444645756118045 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.8124619250000001, 0.8124619134459421, 0.8124618930303587, 0.8124618649474605, 0.812461830109957, 0.8124617895303606, 0.8124617439231214, 0.8124616941475412, 0.812461640764451, 0.8124615845040528, 0.8124615258019251, 0.8124614652767873, 0.8124614032576343, 0.8124613402623917, 0.8124612765247785, 0.812461212465948, 0.8124611482303272, 0.8124610841393545, 0.8124610202506753, 0.8124609567699135, 0.812460893666179 ], "y": [ 9.162206417444405e-18, 0.7999973557508343, 1.599994811000482, 2.399992454598478, 3.1999902063390535, 3.9999881562546817, 4.799986214012396, 5.599984470124919, 6.399982829358027, 7.199981380818826, 7.999980027286746, 8.799978857160102, 9.59997777383543, 10.399976865445812, 11.199976037253185, 11.999975376131477, 12.799974789445866, 13.599974359937981, 14.399973996920059, 15.199973770005858, 15.999973587313422 ], "z": [ 0.004363102500000049, 0.0043955753776063365, 0.0044931958770315856, 0.004649043770950182, 0.004856514630134907, 0.005109416600361549, 0.005401911948040305, 0.005728596222836626, 0.006084428857723916, 0.006464810371519351, 0.00686551272588112, 0.007282754507096603, 0.007713132552295366, 0.0081536905087987, 0.00860184653090365, 0.00905545614957633, 0.009512733392875675, 0.009972311299224186, 0.010433151853577634, 0.010894598567877162, 0.011356252256599593 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 1.06242385, 1.0624238509998334, 1.0624238543968563, 1.0624238606253473, 1.0624238698131283, 1.0624238821664975, 1.0624238975798306, 1.0624239160825706, 1.062423937405997, 1.0624239614547102, 1.0624239878559636, 1.0624240164391061, 1.062424046779832, 1.0624240786772026, 1.0624241117021795, 1.0624241456626615, 1.0624241801663046, 1.0624242150604906, 1.062424250024925, 1.062424284959919, 1.0624243196435175 ], "y": [ 9.162206417444405e-18, 0.8000041100488657, 1.6000081028650377, 2.4000118748560357, 3.2000154932795244, 4.000018860900871, 4.800022061050457, 5.600024997008804, 6.4000277557939675, 7.200030239978838, 8.000032537843335, 8.800034552976555, 9.600036375170923, 10.400037910905114, 11.200039251394067, 12.000040307290025, 12.800041170384995, 13.600041757575765, 14.40004216025581, 15.200042309462798, 16.0000422977623 ], "z": [ 0.00872620500000005, 0.008754989921580773, 0.00884477941056367, 0.008989074926814278, 0.009181700030089264, 0.009416892386339146, 0.009689248635280041, 0.009993797411684078, 0.01032591960212853, 0.010681425124526835, 0.01105645551949636, 0.011447569094086897, 0.011851630739051535, 0.012265908780960077, 0.012687962171243582, 0.013115745021976807, 0.01354749833673766, 0.013981850987710955, 0.01441771892949864, 0.014854388055001192, 0.015291409655183566 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 1.312385775, 1.312385765590729, 1.3123857492560815, 1.312385727066829, 1.312385699803704, 1.3123856683437078, 1.3123856332589672, 1.312385595267562, 1.3123855547838665, 1.3123855123976538, 1.312385468400212, 1.3123854232768732, 1.3123853772210174, 1.312385330630465, 1.3123852836188357, 1.3123852365066258, 1.312385189340325, 1.3123851423655943, 1.312385095570038, 1.3123850491119522, 1.3123850029222204 ], "y": [ 9.162206417444405e-18, 0.7999967708512715, 1.5999936447026821, 2.399990713499301, 3.1999878994597233, 3.999985295937126, 4.799982815059392, 5.599980551570029, 6.399978412712719, 7.199976492431211, 7.999974695521935, 8.799973115398142, 9.599971656847169, 10.39997041301708, 11.19996929003901, 11.999968379665505, 12.799967589645137, 13.599967007102007, 14.399966541012066, 15.199966264105328, 15.99996608299767 ], "z": [ 0.01308930750000005, 0.013117808415149604, 0.013205038226600496, 0.013344567263095089, 0.013530285209410336, 0.013756498247063131, 0.014017868983988494, 0.014309495114931086, 0.014626828084469745, 0.014965753979210595, 0.015322499672438088, 0.015693722215994754, 0.01607640837769345, 0.01646796933785412, 0.01686613334272428, 0.01726904179198468, 0.01767513419496066, 0.018083244240899084, 0.018492473641254375, 0.01890228052834785, 0.019312320316468137 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 1.5623477000000001, 1.562347690821025, 1.562347674799373, 1.5623476529459288, 1.5623476259926226, 1.5623475947770735, 1.5623475598403873, 1.562347521877352, 1.5623474812848825, 1.5623474386424068, 1.5623473942355086, 1.562347348551272, 1.5623473017883505, 1.562347254357797, 1.5623472063875883, 1.5623471582205557, 1.5623471099223738, 1.5623470617648778, 1.5623470137527669, 1.562346966066091, 1.5623469186425232 ], "y": [ 9.162206417444405e-18, 0.799997122848918, 1.5999943470993463, 2.3999917639131514, 3.1999892952201012, 3.9999870303856278, 4.799984878111898, 5.59998292970836, 6.399981091454103, 7.199979457022128, 7.999977932154519, 8.799976610851648, 9.599975397732855, 10.399974384962816, 11.199973475852698, 11.999972759265718, 12.79997213800772, 13.599971695647595, 14.399971335726036, 15.199971128670821, 15.999970977392403 ], "z": [ 0.01745241000000005, 0.017480923114039832, 0.017567366251003295, 0.017705147752285617, 0.017888002109101492, 0.01811008298364314, 0.018365920918453814, 0.018650494977104467, 0.01895918067700987, 0.019287816111895742, 0.019632644031693732, 0.019990371655679427, 0.020358098718006947, 0.020733377306197856, 0.021114119572776275, 0.021498666720439867, 0.02188566981939099, 0.022274172724010707, 0.022663458304022048, 0.023053144166224608, 0.023442975805592004 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 1.8123096250000001, 1.8123096252793973, 1.8123096273998411, 1.812309631841271, 1.812309638767255, 1.8123096484183707, 1.8123096607152773, 1.8123096757177453, 1.8123096931809952, 1.8123097130428614, 1.8123097349564945, 1.8123097587908352, 1.812309784150342, 1.8123098108800397, 1.8123098385802803, 1.812309867107981, 1.8123098960957866, 1.8123099254371609, 1.8123099548260293, 1.812309984197048, 1.8123100133243386 ], "y": [ 9.162206417444405e-18, 0.8000042711855221, 1.6000084254430789, 2.4000123599611722, 3.2000161441788317, 4.000019677718904, 4.800023043050683, 5.600026139930303, 6.400029057167697, 7.200031695838008, 8.000034148296033, 8.800036316222785, 9.600038292975295, 10.400039980171375, 11.200041470745928, 12.000042667086927, 12.800043661532847, 13.60004436051208, 14.40004485653229, 15.200045070532308, 16.00004509958812 ], "z": [ 0.02181551250000005, 0.021842245662087735, 0.021926181197280452, 0.02206079417356827, 0.02223989477813403, 0.022457724482343875, 0.022708920175087563, 0.022988579886379087, 0.023292206961162812, 0.023615767898237508, 0.023955616210629194, 0.024308558183696316, 0.02467175825347697, 0.025042818714815958, 0.02541966496156141, 0.025800639193156966, 0.026184363669349748, 0.026569848578482658, 0.026956330992660843, 0.02734338968094543, 0.027730739956343724 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 2.06227155, 2.062271537819961, 2.0622715161497642, 2.062271486229936, 2.062271449019728, 2.062271405583596, 2.062271356683286, 2.062271303232613, 2.0622712458356625, 2.062271185276541, 2.06227112202711, 2.0622710567581843, 2.0622709898272076, 2.062270921802514, 2.062270852938604, 2.0622707837054923, 2.062270714260684, 2.062270644971381, 2.0622705758989097, 2.0622705072851923, 2.062270439088722 ], "y": [ 9.162206417444405e-18, 0.7999968504395711, 1.5999938047112736, 2.399990956607656, 3.199988230166699, 3.999985717099869, 4.799983327325806, 5.59998115379444, 6.399979102912118, 7.199977269743877, 7.999975559466232, 8.79997406786439, 9.599972698536526, 10.399971546128839, 11.19997051240222, 11.999969689420412, 12.799968977747897, 13.59996846484284, 14.399968051251916, 15.199967811736276, 15.999967645165848 ], "z": [ 0.02617861500000005, 0.026206145672870378, 0.02629050138818445, 0.026425515849181777, 0.02660533252262167, 0.026824499204128972, 0.027077892142435816, 0.027360804287995796, 0.02766884734164822, 0.02799805272182345, 0.028344763852801838, 0.028705743908251077, 0.02907805787516107, 0.029459184992631983, 0.029846892176743862, 0.030239352581490794, 0.03063501180854282, 0.031032708986708006, 0.031431532185545494, 0.03183093248246228, 0.03223054948213675 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5625000000000001, 0.8124619250000001, 1.06242385, 1.312385775, 1.5623477000000001, 1.8123096250000001, 2.06227155 ], "y": [ 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18, 9.162206417444405e-18 ], "z": [ 4.871002816324649e-17, 0.004363102500000049, 0.00872620500000005, 0.01308930750000005, 0.01745241000000005, 0.02181551250000005, 0.02617861500000005 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624999942343187, 0.8124619134459421, 1.0624238509998334, 1.312385765590729, 1.562347690821025, 1.8123096252793973, 2.062271537819961 ], "y": [ 0.7999966402495885, 0.7999973557508343, 0.8000041100488657, 0.7999967708512715, 0.799997122848918, 0.8000042711855221, 0.7999968504395711 ], "z": [ 3.4360542762473186e-05, 0.0043955753776063365, 0.008754989921580773, 0.013117808415149604, 0.017480923114039832, 0.021842245662087735, 0.026206145672870378 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624999847072422, 0.8124618930303587, 1.0624238543968563, 1.3123857492560815, 1.562347674799373, 1.8123096273998411, 2.0622715161497642 ], "y": [ 1.5999933810227123, 1.599994811000482, 1.6000081028650377, 1.5999936447026821, 1.5999943470993463, 1.6000084254430789, 1.5999938047112736 ], "z": [ 0.00013863466028518693, 0.0044931958770315856, 0.00884477941056367, 0.013205038226600496, 0.017567366251003295, 0.021926181197280452, 0.02629050138818445 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624999722860239, 0.8124618649474605, 1.0624238606253473, 1.312385727066829, 1.5623476529459288, 1.812309631841271, 2.062271486229936 ], "y": [ 2.399990312319138, 2.399992454598478, 2.4000118748560357, 2.399990713499301, 2.3999917639131514, 2.4000123599611722, 2.399990956607656 ], "z": [ 0.0003053092113602273, 0.004649043770950182, 0.008989074926814278, 0.013344567263095089, 0.017705147752285617, 0.02206079417356827, 0.026425515849181777 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624999575381, 0.812461830109957, 1.0624238698131283, 1.312385699803704, 1.5623476259926226, 1.812309638767255, 2.062271449019728 ], "y": [ 3.1999873565514116, 3.1999902063390535, 3.2000154932795244, 3.1999878994597233, 3.1999892952201012, 3.2000161441788317, 3.199988230166699 ], "z": [ 0.0005272092806341323, 0.004856514630134907, 0.009181700030089264, 0.013530285209410336, 0.017888002109101492, 0.02223989477813403, 0.02660533252262167 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624999411107972, 0.8124617895303606, 1.0624238821664975, 1.3123856683437078, 1.5623475947770735, 1.8123096484183707, 2.062271405583596 ], "y": [ 3.9999846027816304, 3.9999881562546817, 4.000018860900871, 3.999985295937126, 3.9999870303856278, 4.000019677718904, 3.999985717099869 ], "z": [ 0.0007975883806602837, 0.005109416600361549, 0.009416892386339146, 0.013756498247063131, 0.01811008298364314, 0.022457724482343875, 0.026824499204128972 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624999233436245, 0.8124617439231214, 1.0624238975798306, 1.3123856332589672, 1.5623475598403873, 1.8123096607152773, 2.062271356683286 ], "y": [ 4.799981962669224, 4.799986214012396, 4.800022061050457, 4.799982815059392, 4.799984878111898, 4.800023043050683, 4.799983327325806 ], "z": [ 0.0011101083990921862, 0.005401911948040305, 0.009689248635280041, 0.014017868983988494, 0.018365920918453814, 0.022708920175087563, 0.027077892142435816 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624999047080059, 0.8124616941475412, 1.0624239160825706, 1.312385595267562, 1.562347521877352, 1.8123096757177453, 2.062271303232613 ], "y": [ 5.599979528793419, 5.599984470124919, 5.600024997008804, 5.599980551570029, 5.59998292970836, 5.600026139930303, 5.59998115379444 ], "z": [ 0.001458895928473685, 0.005728596222836626, 0.009993797411684078, 0.014309495114931086, 0.018650494977104467, 0.022988579886379087, 0.027360804287995796 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.56249988537789, 0.812461640764451, 1.062423937405997, 1.3123855547838665, 1.5623474812848825, 1.8123096931809952, 2.0622712458356625 ], "y": [ 6.399977212805474, 6.399982829358027, 6.4000277557939675, 6.399978412712719, 6.399981091454103, 6.400029057167697, 6.399979102912118 ], "z": [ 0.0018385147642435145, 0.006084428857723916, 0.01032591960212853, 0.014626828084469745, 0.01895918067700987, 0.023292206961162812, 0.02766884734164822 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624998656920386, 0.8124615845040528, 1.0624239614547102, 1.3123855123976538, 1.5623474386424068, 1.8123097130428614, 2.062271185276541 ], "y": [ 7.199975110259148, 7.199981380818826, 7.200030239978838, 7.199976492431211, 7.199979457022128, 7.200031695838008, 7.199977269743877 ], "z": [ 0.00224400658612019, 0.006464810371519351, 0.010681425124526835, 0.014965753979210595, 0.019287816111895742, 0.023615767898237508, 0.02799805272182345 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624998457080345, 0.8124615258019251, 1.0624239878559636, 1.312385468400212, 1.5623473942355086, 1.8123097349564945, 2.06227112202711 ], "y": [ 7.999973133589262, 7.999980027286746, 8.000032537843335, 7.999974695521935, 7.999977932154519, 8.000034148296033, 7.999975559466232 ], "z": [ 0.0026708584775881213, 0.00686551272588112, 0.01105645551949636, 0.015322499672438088, 0.019632644031693732, 0.023955616210629194, 0.028344763852801838 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624998256710535, 0.8124614652767873, 1.0624240164391061, 1.3123854232768732, 1.562347348551272, 1.8123097587908352, 2.0622710567581843 ], "y": [ 8.799971378307317, 8.799978857160102, 8.800034552976555, 8.799973115398142, 8.799976610851648, 8.800036316222785, 8.79997406786439 ], "z": [ 0.0031150348110584127, 0.007282754507096603, 0.011447569094086897, 0.015693722215994754, 0.019990371655679427, 0.024308558183696316, 0.028705743908251077 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624998055628219, 0.8124614032576343, 1.062424046779832, 1.3123853772210174, 1.5623473017883505, 1.812309784150342, 2.0622709898272076 ], "y": [ 9.59996975645606, 9.59997777383543, 9.600036375170923, 9.599971656847169, 9.599975397732855, 9.600038292975295, 9.599972698536526 ], "z": [ 0.0035729392050654492, 0.007713132552295366, 0.011851630739051535, 0.01607640837769345, 0.020358098718006947, 0.02467175825347697, 0.02907805787516107 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624997855672028, 0.8124613402623917, 1.0624240786772026, 1.312385330630465, 1.562347254357797, 1.8123098108800397, 2.062270921802514 ], "y": [ 10.39996836102129, 10.399976865445812, 10.400037910905114, 10.39997041301708, 10.399974384962816, 10.400039980171375, 10.399971546128839 ], "z": [ 0.004041445265628865, 0.0081536905087987, 0.012265908780960077, 0.01646796933785412, 0.020733377306197856, 0.025042818714815958, 0.029459184992631983 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624997656220664, 0.8124612765247785, 1.0624241117021795, 1.3123852836188357, 1.5623472063875883, 1.8123098385802803, 2.062270852938604 ], "y": [ 11.199967103346411, 11.199976037253185, 11.200039251394067, 11.19996929003901, 11.199973475852698, 11.200041470745928, 11.19997051240222 ], "z": [ 0.004517852953449566, 0.00860184653090365, 0.012687962171243582, 0.01686613334272428, 0.021114119572776275, 0.02541966496156141, 0.029846892176743862 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624997458731538, 0.812461212465948, 1.0624241456626615, 1.3123852365066258, 1.5623471582205557, 1.812309867107981, 2.0622707837054923 ], "y": [ 11.999966072300438, 11.999975376131477, 12.000040307290025, 11.999968379665505, 11.999972759265718, 12.000042667086927, 11.999969689420412 ], "z": [ 0.0049999194902928795, 0.00905545614957633, 0.013115745021976807, 0.01726904179198468, 0.021498666720439867, 0.025800639193156966, 0.030239352581490794 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624997262370887, 0.8124611482303272, 1.0624241801663046, 1.312385189340325, 1.5623471099223738, 1.8123098960957866, 2.062270714260684 ], "y": [ 12.799965179073068, 12.799974789445866, 12.800041170384995, 12.799967589645137, 12.79997213800772, 12.800043661532847, 12.799968977747897 ], "z": [ 0.005485803105934123, 0.009512733392875675, 0.01354749833673766, 0.01767513419496066, 0.02188566981939099, 0.026184363669349748, 0.03063501180854282 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.562499706832297, 0.8124610841393545, 1.0624242150604906, 1.3123851423655943, 1.5623470617648778, 1.8123099254371609, 2.062270644971381 ], "y": [ 13.599964506028565, 13.599974359937981, 13.600041757575765, 13.599967007102007, 13.599971695647595, 13.60004436051208, 13.59996846484284 ], "z": [ 0.005974093414724043, 0.009972311299224186, 0.013981850987710955, 0.018083244240899084, 0.022274172724010707, 0.026569848578482658, 0.031032708986708006 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624996875668204, 0.8124610202506753, 1.062424250024925, 1.312385095570038, 1.5623470137527669, 1.8123099548260293, 2.0622705758989097 ], "y": [ 14.399963964776944, 14.399973996920059, 14.40004216025581, 14.399966541012066, 14.399971335726036, 14.40004485653229, 14.399968051251916 ], "z": [ 0.0064637340689429204, 0.010433151853577634, 0.01441771892949864, 0.018492473641254375, 0.022663458304022048, 0.026956330992660843, 0.031431532185545494 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624996685230919, 0.8124609567699135, 1.062424284959919, 1.3123850491119522, 1.562346966066091, 1.812309984197048, 2.0622705072851923 ], "y": [ 15.199963623110254, 15.199973770005858, 15.200042309462798, 15.199966264105328, 15.199971128670821, 15.200045070532308, 15.199967811736276 ], "z": [ 0.0069540524735901225, 0.010894598567877162, 0.014854388055001192, 0.01890228052834785, 0.023053144166224608, 0.02734338968094543, 0.03183093248246228 ] }, { "line": { "color": "grey" }, "mode": "lines", "showlegend": false, "type": "scatter3d", "x": [ 0.5624996496027337, 0.812460893666179, 1.0624243196435175, 1.3123850029222204, 1.5623469186425232, 1.8123100133243386, 2.062270439088722 ], "y": [ 15.99996339122673, 15.999973587313422, 16.0000422977623, 15.99996608299767, 15.999970977392403, 16.00004509958812, 15.999967645165848 ], "z": [ 0.007444645756118045, 0.011356252256599593, 0.015291409655183566, 0.019312320316468137, 0.023442975805592004, 0.027730739956343724, 0.03223054948213675 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "name": "Beam nodes", "type": "scatter3d", "x": [ 0, -5.744151761391617e-09, -1.5210804262961445e-08 ], "y": [ 0, 0.7999999988577322, 1.599999991380492 ], "z": [ 0, 3.772280096999739e-05, 0.00014525289792429068 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -1.5210804262961445e-08, -2.7539794264483134e-08, -4.216701299251646e-08 ], "y": [ 1.599999991380492, 2.399999973222394, 3.1999999414783504 ], "z": [ 0.00014525289792429068, 0.00031498257127698524, 0.0005398114325280645 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -4.216701299251646e-08, -5.845413791416317e-08, -7.606245469415831e-08 ], "y": [ 3.1999999414783504, 3.999999894782558, 4.7999998326433255 ], "z": [ 0.0005398114325280645, 0.0008129023493327658, 0.0011280049766697654 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -7.606245469415831e-08, -9.453085004776112e-08, -1.1368390921713924e-07 ], "y": [ 4.7999998326433255, 5.599999755537855, 6.399999664368964 ], "z": [ 0.0011280049766697654, 0.0014791536130117535, 0.001861001288697979 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -1.1368390921713924e-07, -1.3319431737429714e-07, -1.530010581380602e-07 ], "y": [ 6.399999664368964, 7.199999560573655, 7.999999445686861 ], "z": [ 0.001861001288697979, 0.0022684954019804547, 0.002697212175686842 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -1.530010581380602e-07, -1.7287111886294816e-07, -1.9281747647171713e-07 ], "y": [ 7.999999445686861, 8.799999321471196, 9.599999189568887 ], "z": [ 0.002697212175686842, 0.003143022099371802, 0.0036024184792496066 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -1.9281747647171713e-07, -2.1266860132434275e-07, -2.3247960013933078e-07 ], "y": [ 9.599999189568887, 10.39999905165165, 11.199998909140675 ], "z": [ 0.0036024184792496066, 0.004072183506411567, 0.0045497073476323795 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -2.3247960013933078e-07, -2.5211672422140153e-07, -2.716548617367966e-07 ], "y": [ 11.199998909140675, 11.99999876337341, 12.79999861537811 ], "z": [ 0.0045497073476323795, 0.0050326596228963995, 0.005519288553995972 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -2.716548617367966e-07, -2.909866574188866e-07, -3.1019442919001244e-07 ], "y": [ 12.79999861537811, 13.599998466051524, 14.399998315980117 ], "z": [ 0.005519288553995972, 0.006008102239037706, 0.006498133464216941 ] }, { "line": { "color": "blue", "width": 4 }, "marker": { "color": "blue", "size": 2 }, "showlegend": false, "type": "scatter3d", "x": [ -3.1019442919001244e-07, -3.292046679170043e-07, -3.481050287225777e-07 ], "y": [ 14.399998315980117, 15.19999816561613, 15.999998015150949 ], "z": [ 0.006498133464216941, 0.0069886422578113614, 0.007479315882477229 ] } ], "layout": { "autosize": true, "scene": { "aspectmode": "auto", "aspectratio": { "x": 1, "y": 1, "z": 1 }, "camera": { "center": { "x": 0, "y": 0, "z": 0 }, "eye": { "x": -1.3300269636921596, "y": -1.3537526742580583, "z": 1.042056607287229 }, "projection": { "type": "perspective" }, "up": { "x": 0, "y": 0, "z": 1 } } }, "template": { "data": { "bar": [ { "error_x": { "color": "#2a3f5f" }, "error_y": { "color": "#2a3f5f" }, "marker": { "line": { "color": "#E5ECF6", "width": 0.5 }, "pattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 } }, "type": "bar" } ], "barpolar": [ { "marker": { "line": { "color": "#E5ECF6", "width": 0.5 }, "pattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 } }, "type": "barpolar" } ], "carpet": [ { "aaxis": { "endlinecolor": "#2a3f5f", "gridcolor": "white", "linecolor": "white", "minorgridcolor": "white", "startlinecolor": "#2a3f5f" }, "baxis": { "endlinecolor": "#2a3f5f", "gridcolor": "white", "linecolor": "white", "minorgridcolor": "white", "startlinecolor": "#2a3f5f" }, "type": "carpet" } ], "choropleth": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "type": "choropleth" } ], "contour": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "contour" } ], "contourcarpet": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "type": "contourcarpet" } ], "heatmap": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "heatmap" } ], "heatmapgl": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "heatmapgl" } ], "histogram": [ { "marker": { "pattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 } }, "type": "histogram" } ], "histogram2d": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "histogram2d" } ], "histogram2dcontour": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "histogram2dcontour" } ], "mesh3d": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "type": "mesh3d" } ], "parcoords": [ { "line": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "parcoords" } ], "pie": [ { "automargin": true, "type": "pie" } ], "scatter": [ { "fillpattern": { "fillmode": "overlay", "size": 10, "solidity": 0.2 }, "type": "scatter" } ], "scatter3d": [ { "line": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatter3d" } ], "scattercarpet": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattercarpet" } ], "scattergeo": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattergeo" } ], "scattergl": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattergl" } ], "scattermapbox": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scattermapbox" } ], "scatterpolar": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatterpolar" } ], "scatterpolargl": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatterpolargl" } ], "scatterternary": [ { "marker": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "type": "scatterternary" } ], "surface": [ { "colorbar": { "outlinewidth": 0, "ticks": "" }, "colorscale": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "type": "surface" } ], "table": [ { "cells": { "fill": { "color": "#EBF0F8" }, "line": { "color": "white" } }, "header": { "fill": { "color": "#C8D4E3" }, "line": { "color": "white" } }, "type": "table" } ] }, "layout": { "annotationdefaults": { "arrowcolor": "#2a3f5f", "arrowhead": 0, "arrowwidth": 1 }, "autotypenumbers": "strict", "coloraxis": { "colorbar": { "outlinewidth": 0, "ticks": "" } }, "colorscale": { "diverging": [ [ 0, "#8e0152" ], [ 0.1, "#c51b7d" ], [ 0.2, "#de77ae" ], [ 0.3, "#f1b6da" ], [ 0.4, "#fde0ef" ], [ 0.5, "#f7f7f7" ], [ 0.6, "#e6f5d0" ], [ 0.7, "#b8e186" ], [ 0.8, "#7fbc41" ], [ 0.9, "#4d9221" ], [ 1, "#276419" ] ], "sequential": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ], "sequentialminus": [ [ 0, "#0d0887" ], [ 0.1111111111111111, "#46039f" ], [ 0.2222222222222222, "#7201a8" ], [ 0.3333333333333333, "#9c179e" ], [ 0.4444444444444444, "#bd3786" ], [ 0.5555555555555556, "#d8576b" ], [ 0.6666666666666666, "#ed7953" ], [ 0.7777777777777778, "#fb9f3a" ], [ 0.8888888888888888, "#fdca26" ], [ 1, "#f0f921" ] ] }, "colorway": [ "#636efa", "#EF553B", "#00cc96", "#ab63fa", "#FFA15A", "#19d3f3", "#FF6692", "#B6E880", "#FF97FF", "#FECB52" ], "font": { "color": "#2a3f5f" }, "geo": { "bgcolor": "white", "lakecolor": "white", "landcolor": "#E5ECF6", "showlakes": true, "showland": true, "subunitcolor": "white" }, "hoverlabel": { "align": "left" }, "hovermode": "closest", "mapbox": { "style": "light" }, "paper_bgcolor": "white", "plot_bgcolor": "#E5ECF6", "polar": { "angularaxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" }, "bgcolor": "#E5ECF6", "radialaxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" } }, "scene": { "xaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white" }, "yaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white" }, "zaxis": { "backgroundcolor": "#E5ECF6", "gridcolor": "white", "gridwidth": 2, "linecolor": "white", "showbackground": true, "ticks": "", "zerolinecolor": "white" } }, "shapedefaults": { "line": { "color": "#2a3f5f" } }, "ternary": { "aaxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" }, "baxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" }, "bgcolor": "#E5ECF6", "caxis": { "gridcolor": "white", "linecolor": "white", "ticks": "" } }, "title": { "x": 0.05 }, "xaxis": { "automargin": true, "gridcolor": "white", "linecolor": "white", "ticks": "", "title": { "standoff": 15 }, "zerolinecolor": "white", "zerolinewidth": 2 }, "yaxis": { "automargin": true, "gridcolor": "white", "linecolor": "white", "ticks": "", "title": { "standoff": 15 }, "zerolinecolor": "white", "zerolinewidth": 2 } } } } }, "image/png": "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De5f6gd/+3J378eHZ8zk9dkElSBi3cdQSNNvc9q797rLs2cO3rC+ypm6LvuFrxzvjnzkjnWzuB8BAgQIECBAoFlAQNNM6AIECBAgQIAAAQIECBAgQIAAgTYBAU2bn7MJECBAgAABAgQIECBAgAABAs0CAppmQhcgQIAAAQIECBAgQIAAAQIECLQJCGja/JxNgAABAgQIECBAgAABAgQIEGgWENA0E7oAAQIECBAgQIAAAQIECBAgQKBNQEDT5udsAgQIECBAgAABAgQIECBAgECzgICmmdAFCBAgQIAAAQIECBAgQIAAAQJtAgKaNj9nEyBAgAABAgQIECBAgAABAgSaBQQ0zYQuQIAAAQIECBAgQIAAAQIECBBoExDQtPk5mwABAgQIECBAgAABAgQIECDQLCCgaSZ0AQIECBAgQIAAAQIECBAgQIBAm4CAps3P2QQIECBAgAABAgQIECBAgACBZgEBTTOhCxAgQIAAAQIECBAgQIAAAQIE2gQENG1+ziZAgAABAgQIECBAgAABAgQINAsIaJoJXYAAAQIECBAgQIAAAQIECBAg0CYgoGnzczYBAgQIECBAgAABAgQIECBAoFlAQNNM6AIECBAgQIAAAQIECBAgQIAAgTYBAU2bn7MJECBAgAABAgQIECBAgAABAs0CAppmQhcgQIAAAQIECBAgQIAAAQIECLQJCGja/JxNgAABAgQIECBAgAABAgQIEGgWENA0E7oAAQIECBAgQIAAAQIECBAgQKBNQEDT5udsAgQIECBAgAABAgQIECBAgECzgICmmdAFCBAgQIAAAQIECBAgQIAAAQJtAgKaNj9nEyBAgAABAgQIECBAgAABAgSaBQQ0zYQuQIAAAQIECBAgQIAAAQIECBBoExDQtPk5mwABAgQIECBAgAABAgQIECDQLCCgaSZ0AQIECBAgQIAAAQIECBAgQIBAm4CAps3P2QQIECBAgAABAgQIECBAgACBZgEBTTOhCxAgQIAAAQIECBAgQIAAAQIE2gQENG1+ziZAgAABAgQIECBAgAABAgQINAsIaJoJXYAAAQIECBAgQIAAAQIECBAg0CYgoGnzczYBAgQIECBAgAABAgQIECBAoFlAQNNM6AIECBAgQIAAAQIECBAgQIAAgTYBAU2bn7MJECBAgAABAgQIECBAgAABAs0CAppmQhcgQIAAAQIECBAgQIAAAQIECLQJCGja/JxNgAABAgQIECBAgAABAgQIEGgWENA0E7oAAQIECBAgQIAAAQIECBAgQKBNQEDT5udsAgQIECBAgAABAgQIECBAgECzgICmmdAFCBAgQIAAAQIECBAgQIAAAQJtAgKaNj9nEyBAgAABAgQIECBAgAABAgSaBQQ0zYQuQIAAAQIECBAgQIAAAQIECBBoExDQtPk5mwABAgQIECBAgAABAgQIECDQLCCgaSZ0AQIECBAgQIAAAQIECBAgQIBAm4CAps3P2QQIECBAgAABAgQIECBAgACBZgEBTTOhCxAgQIAAAQIECBAgQIAAAQIE2gQENG1+ziZAgAABAgQIECBAgAABAgQINAsIaJoJXYAAAQIECBAgQIAAAQIECBAg0CYgoGnzczYBAgQIECBAgAABAgQIECBAoFlAQNNM6AIECBAgQIAAAQIECBAgQIAAgTYBAU2bn7MJECBAgAABAgQIECBAgAABAs0CAppmQhcgQIAAAQIECBAgQIAAAQIECLQJCGja/JxNgAABAgQIECBAgAABAgQIEGgWENA0E7oAAQIECBAgQIAAAQIECBAgQKBNQEDT5udsAgQIECBAgAABAgQIECBAgECzgICmmdAFCBAgQIAAAQIECBAgQIAAAQJtAgKaNj9nEyBAgAABAgQIECBAgAABAgSaBQQ0zYQuQIAAAQIECBAgQIAAAQIECBBoExDQtPk5mwABAgQIECBAgAABAgQIECDQLCCgaSZ0AQIECBAgQIAAAQIECBAgQIBAm4CAps3P2QQIECBAgAABAgQIECBAgACBZgEBTTOhCxAgQIAAAQIECBAgQIAAAQIE2gQENG1+ziZAgAABAgQIECBAgAABAgQINAsIaJoJXYAAAQIECBAgQIAAAQIECBAg0CYgoGnzczYBAgQIECBAgAABAgQIECBAoFlAQNNM6AIECBAgQIAAAQIECBAgQIAAgTYBAU2bn7MJECBAgAABAgQIECBAgAABAs0CAppmQhcgQIAAAQIECBAgQIAAAQIECLQJCGja/JxNgAABAgQIECBAgAABAgQIEGgWENA0E7oAAQIECBAgQIAAAQIECBAgQKBNQEDT5udsAgQIECBAgAABAgQIECBAgECzgICmmdAFCBAgQIAAAQIECBAgQIAAAQJtAgKaNj9nEyBAgAABAgQIECBAgAABAgSaBQQ0zYQuQIAAAQIECBAgQIAAAQIECBBoExDQtPk5mwABAgQIECBAgAABAgQIECDQLCCgaSZ0AQIECBAgQIAAAQIECBAgQIBAm4CAps3P2QQIECBAgAABAgQIECBAgACBZgEBTTOhCxAgQIAAAQIECBAgQIAAAQIE2gQENG1+ziZAgAABAgQIECBAgAABAgQINAsIaJoJXYAAAQIECBAgQIAAAQIECBAg0CYgoGnzczYBAgQIECBAgAABAgQIECBAoFng/wN2enHUOC/W8AAAAABJRU5ErkJggg==", "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pu.plot_timestep(sharpy_output, tstep=-1, minus_mstar=(wake_panels - 6), plotly=True,custom_scaling=False,z_compression=0.5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Postprocessing\n", "As an example, we are going to plot the evolution of the tip position along time:" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2023-06-30T10:27:03.096903\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.7.1, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "time = np.linspace(0, dt*time_steps, time_steps)\n", "tip_pos = np.zeros((time_steps))\n", "for it in range(time_steps):\n", " tip_pos[it] = sharpy_output.structure.timestep_info[it].pos[-1, 2]\n", "\n", "fig, plots = plt.subplots(1, 1, figsize=(6, 3))\n", "\n", "plots.grid()\n", "plots.set_xlabel(\"time [s]\")\n", "plots.set_ylabel(\"tip position [m]\")\n", "plots.plot(time, tip_pos, '-')\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Conclusions\n", "This notebook provides the big picture of the simulations with SHARPy. We recommend to go through the [documentation](https://ic-sharpy.readthedocs.io/en/main/) with special attention to the [SHARPy file description](https://ic-sharpy.readthedocs.io/en/main/content/casefiles.html) and the [examples](https://ic-sharpy.readthedocs.io/en/main/content/examples.html)." ] } ], "metadata": { "kernelspec": { "display_name": "sharpy env)", "language": "python", "name": "sharpy" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.12" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: docs/source/content/example_notebooks/linear_goland_flutter.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "\n", "# Flutter Analysis of Goland's Wing\n", "\n", "## Description\n", "This is simple rectangular wing with constant properties originally proposed by Martin Goland in 1945 as a flutter benchmark case. This example generates a beam/UVLM model with the original properties in SHARPy and computes flutter around an arbitrary equilibrium point using the following general process:\n", "\n", "* Calculate steady aerodynamic forces and deflections for a given angle of attack using a nonlinear solver\n", "\n", "* Linearise the dynamic aeroelastic equations about this reference condition\n", "\n", "* Reduce the size of the (linearized) structural subsystem using modal projection\n", "\n", "* Reduce the size of the (linearized) aerodyanic subsystem using a Krylov projection\n", "\n", "* Evaluate the stability of the linearised aeroelastic system at different velocities and plot the results.\n", "\n", "## Wing properties\n", "Span: b=20 ft., chord: c=6 ft., weight: 4 psi., radius of gyration about CG: .25c, elastic axis: 0.33c, centre of gravity: 0.43c\n", "\n", "### References\n", "\n", "Goland, M. (1945). The Flutter o a Uniorm Cantilever Wing. Journal of Applied Mechanics,12(4):197-208.\n", "\n", "Maraniello, S., & Palacios, R. (2019). State-Space Realizations and Internal Balancing in Potential-Flow Aerodynamics with Arbitrary Kinematics. AIAA Journal, 57(6):1–14. https://doi.org/10.2514/1.J058153\n", "\n", "#### Latest update, RPN 14.05.2023\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Required Packages" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import os\n", "import sys\n", "import sharpy.cases.templates.flying_wings as wings # See this package for the Goland wing structural and aerodynamic definition\n", "import sharpy.sharpy_main # used to run SHARPy from Jupyter" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Problem Set-up" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Operatig conditions\n", "\n", "The UVLM is assembled in normalised time at a velocity of $1 m/s$. The only matrices that need updating then with free stream velocity are the structural matrices, which is significantly cheaper to do than to update the UVLM." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "u_inf = 1.\n", "alpha_deg = 2. # Define angle of attack for static aeroelastic analysis.\n", "rho = 1.02 # Air density." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Discretisation\n", "\n", "Note: To achieve convergence of the flutter results with the ones found in the literature, a fine grid is needed. If you are running this notebook for the first time, set `M = 4` initially to verify that your system can perform!" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "M = 16 # Number of chordwise panels\n", "N = 32 # Number of spanwise panels\n", "M_star_fact = 10 # Length of the wake in chords.\n", "num_modes = 8 # Number of vibration modes retained in the structural model." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ROM\n", "\n", "A moment-matching (Krylov subspace) model order reduction technique is employed. This ROM method offers the ability to interpolate the transfer functions at a desired point in the complex plane. See the ROM documentation pages for more info. Note that this ROM method matches the transfer function but does not guarantee stability. Therefore the resulting system may be unstable. These unstable modes may appear far in the right hand plane but will not affect the flutter speed calculations." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "c_ref = 1.8288 # Goland wing reference chord. Used for reduced frequency normalisation\n", "rom_settings = dict()\n", "rom_settings['algorithm'] = 'mimo_rational_arnoldi' # reduction algorithm\n", "rom_settings['r'] = 6 # Krylov subspace order\n", "frequency_continuous_k = np.array([0.]) # Interpolation point in the complex plane with reduced frequency units\n", "frequency_continuous_w = 2 * u_inf * frequency_continuous_k / c_ref\n", "rom_settings['frequency'] = frequency_continuous_w" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Case Admin" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The case to run will be: goland_csM16N32Ms10_nmodes8rom_MIMORA_r6_sig0000_0000j\n", "Case files will be saved in ./cases/goland_csM16N32Ms10_nmodes8rom_MIMORA_r6_sig0000_0000j\n", "Output files will be saved in ./output/goland_csM16N32Ms10_nmodes8rom_MIMORA_r6_sig0000_0000j/\n" ] } ], "source": [ "case_name = 'goland_cs'\n", "case_nlin_info = 'M%dN%dMs%d_nmodes%d' % (M, N, M_star_fact, num_modes)\n", "case_rom_info = 'rom_MIMORA_r%d_sig%04d_%04dj' % (rom_settings['r'], frequency_continuous_k[-1].real * 100,\n", " frequency_continuous_k[-1].imag * 100)\n", "\n", "case_name += case_nlin_info + case_rom_info\n", "route_test_dir = os.path.abspath('')\n", "\n", "print('The case to run will be: %s' % case_name)\n", "print('Case files will be saved in ./cases/%s' %case_name)\n", "print('Output files will be saved in ./output/%s/' %case_name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Simulation Set-Up" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Goland Wing\n", "\n", "`ws` is an instance of a Goland wing with a control surface. Reference the template file `sharpy.cases.templates.flying_wings.GolandControlSurface` for more info on the geometrical, structural and aerodynamic definition of the Goland wing here used." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "ws = wings.GolandControlSurface(M=M,\n", " N=N,\n", " Mstar_fact=M_star_fact,\n", " u_inf=u_inf,\n", " alpha=alpha_deg,\n", " cs_deflection=[0, 0],\n", " rho=rho,\n", " sweep=0,\n", " physical_time=2,\n", " n_surfaces=2,\n", " route=route_test_dir + '/cases',\n", " case_name=case_name)\n", "\n", "ws.clean_test_files()\n", "ws.update_derived_params()\n", "ws.set_default_config_dict()\n", "\n", "ws.generate_aero_file()\n", "ws.generate_fem_file()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Simulation Settings\n", "\n", "The settings for each of the solvers are now set. For a detailed description on them please reference their respective documentation pages" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### SHARPy Settings\n", "\n", "The most important setting is the `flow` list. It tells SHARPy which solvers to run and in which order." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "ws.config['SHARPy'] = {\n", " 'flow':\n", " ['BeamLoader', 'AerogridLoader',\n", " 'StaticCoupled',\n", " 'AerogridPlot',\n", " 'BeamPlot',\n", " 'Modal',\n", " 'LinearAssembler',\n", " 'AsymptoticStability',\n", " ],\n", " 'case': ws.case_name, 'route': ws.route,\n", " 'write_screen': 'off', 'write_log': 'on', # Change to 'on' as neded.\n", " 'log_folder': route_test_dir + '/output/',\n", " 'log_file': ws.case_name + '.log'}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Beam Loader Settings" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "ws.config['BeamLoader'] = {\n", " 'unsteady': 'off',\n", " 'orientation': ws.quat}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Aerogrid Loader Settings" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "ws.config['AerogridLoader'] = {\n", " 'unsteady': 'off',\n", " 'aligned_grid': 'on',\n", " 'mstar': ws.Mstar_fact * ws.M,\n", " 'freestream_dir': ws.u_inf_direction,\n", " 'wake_shape_generator': 'StraightWake',\n", " 'wake_shape_generator_input': {'u_inf': ws.u_inf,\n", " 'u_inf_direction': ws.u_inf_direction,\n", " 'dt': ws.dt}}\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Static Coupled Solver" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "ws.config['StaticCoupled'] = {\n", " 'print_info': 'on',\n", " 'max_iter': 200,\n", " 'n_load_steps': 1,\n", " 'tolerance': 1e-10,\n", " 'relaxation_factor': 0.,\n", " 'aero_solver': 'StaticUvlm',\n", " 'aero_solver_settings': {\n", " 'rho': ws.rho,\n", " 'print_info': 'off',\n", " 'horseshoe': 'off',\n", " 'num_cores': 4,\n", " 'n_rollup': 0,\n", " 'rollup_dt': ws.dt,\n", " 'rollup_aic_refresh': 1,\n", " 'rollup_tolerance': 1e-4,\n", " 'velocity_field_generator': 'SteadyVelocityField',\n", " 'velocity_field_input': {\n", " 'u_inf': ws.u_inf,\n", " 'u_inf_direction': ws.u_inf_direction}},\n", " 'structural_solver': 'NonLinearStatic',\n", " 'structural_solver_settings': {'print_info': 'off',\n", " 'max_iterations': 150,\n", " 'num_load_steps': 4,\n", " 'delta_curved': 1e-1,\n", " 'min_delta': 1e-10,\n", " 'gravity_on': 'on',\n", " 'gravity': 9.81}}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### AerogridPlot Settings" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "ws.config['AerogridPlot'] = {'include_rbm': 'off',\n", " 'include_applied_forces': 'on',\n", " 'minus_m_star': 0}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### BeamPlot Settings" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "ws.config['BeamPlot'] = {'include_rbm': 'off',\n", " 'include_applied_forces': 'on'}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Modal Solver Settings" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "ws.config['Modal'] = {'NumLambda': 20,\n", " 'rigid_body_modes': 'off',\n", " 'print_matrices': 'on', # 'keep_linear_matrices': 'on', 'write_dat': 'off',\n", " 'rigid_modes_cg': 'off',\n", " 'continuous_eigenvalues': 'off',\n", " 'dt': 0,\n", " 'plot_eigenvalues': False,\n", " 'max_rotation_deg': 15.,\n", " 'max_displacement': 0.15,\n", " 'write_modes_vtk': True,\n", " 'use_undamped_modes': True}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Linear System Assembly Settings" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "ws.config['LinearAssembler'] = {'linear_system': 'LinearAeroelastic',\n", " 'linear_system_settings': {\n", " 'beam_settings': {'modal_projection': 'on',\n", " 'inout_coords': 'modes',\n", " 'discrete_time': 'on',\n", " 'newmark_damp': 0.5e-4,\n", " 'discr_method': 'newmark',\n", " 'dt': ws.dt,\n", " 'proj_modes': 'undamped',\n", " 'use_euler': 'off',\n", " 'num_modes': num_modes,\n", " 'print_info': 'on',\n", " 'gravity': 'on',\n", " 'remove_sym_modes': 'on',\n", " 'remove_dofs': []},\n", " 'aero_settings': {'dt': ws.dt,\n", " 'ScalingDict': {'length': 0.5 * ws.c_ref,\n", " 'speed': u_inf,\n", " 'density': rho},\n", " 'integr_order': 2,\n", " 'density': ws.rho,\n", " 'remove_predictor': 'on',\n", " 'use_sparse': 'on',\n", " 'remove_inputs': ['u_gust'],\n", " 'rom_method': ['Krylov'],\n", " 'rom_method_settings': {'Krylov': rom_settings}},\n", " }}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Asymptotic Stability Analysis Settings" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "ws.config['AsymptoticStability'] = {'print_info': True,\n", " 'velocity_analysis': [100, 180, 81],\n", " 'modes_to_plot': []}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Write solver settings config file" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "ws.config.write()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Run SHARPy" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "fatal: not a git repository (or any of the parent directories): .git\n", "/home/rpalacio/anaconda3/envs/sharpy/lib/python3.10/site-packages/scipy/sparse/_index.py:146: SparseEfficiencyWarning: Changing the sparsity structure of a csc_matrix is expensive. lil_matrix is more efficient.\n", " self._set_arrayXarray(i, j, x)\n", "/home/rpalacio/anaconda3/envs/sharpy/lib/python3.10/site-packages/sharpy/linear/src/lingebm.py:313: UserWarning: Euler parametrisation not implemented - Either rigid body modes are not being used or this method has already been called.\n", " warnings.warn('Euler parametrisation not implemented - Either rigid body modes are not being used or this '\n", "/home/rpalacio/anaconda3/envs/sharpy/lib/python3.10/site-packages/sharpy/rom/krylov.py:242: UserWarning: Reduced Order Model Unstable\n", " warn.warn('Reduced Order Model Unstable')\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sharpy.sharpy_main.main(['', ws.route + ws.case_name + '.sharpy'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Analysis" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Nonlinear equilibrium\n", "\n", "The nonlinear equilibrium condition can be visualised and analysed by opening, with Paraview, the files in the `/output//aero` and `/output//beam` folders to see the deflection and aerodynamic forces acting on the wing." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Stability \n", "\n", "The stability of the Goland wing is now analysed under changing free stream velocity. The flutter modes involves the two lowest frequency modes near the imaginary axis (1st bending and 1st torsion if aerodynamics is removed). The two modes are seen quite separated at 100 m/s. As speed is increased, the damping of the torsion mode decreases until it crosses the imaginary axis onto the right hand plane and flutter begins." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "file_name = './output/%s/stability/velocity_analysis_min1000_max1800_nvel0081.dat' % case_name\n", "\n", "velocity_analysis = np.loadtxt(file_name)\n", "u_inf = velocity_analysis[:, 0]\n", "eigs_r = velocity_analysis[:, 1]\n", "eigs_i = velocity_analysis[:, 2]" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkMAAAG4CAYAAABGhOPcAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAACDp0lEQVR4nO3deXxU1f038M+5d7YsJJAEskhAEFQQRIWKUEQim6mAioKKCii2WgvKEqk7qAgttqAGdxEUH4utCmr1J8SWVSpKhLJZFGUnMQIhe2a59zx/3JnJTPaZSUKG+bx9XTNzl3PPPSSZb84qpJQSRERERBFKOdMZICIiIjqTGAwRERFRRGMwRERERBGNwRARERFFNAZDREREFNEYDBEREVFEYzBEREREEY3BEBEREUU0BkNEREQU0RgMERERUURrVcHQyy+/jIsvvhhxcXGIi4vDgAED8H//93/e41JKzJ07F2lpaYiKisKQIUOwZ88evzTsdjumTZuGpKQkxMTEYMyYMTh69GhLPwoREVHY27hxI0aPHo20tDQIIbB69Wq/46WlpZg6dSo6duyIqKgo9OjRAy+//LLfOeHwudyqgqGOHTviT3/6E7Zt24Zt27bh6quvxnXXXecNeBYuXIhFixZhyZIl+Oabb5CSkoLhw4ejpKTEm8b06dOxatUqrFy5Eps3b0ZpaSlGjRoFTdPO1GMRERGFpbKyMvTp0wdLliyp9fiMGTPw+eef45133sF3332HGTNmYNq0afjoo4+854TF57Js5dq1ayffeOMNqeu6TElJkX/605+8xyorK2V8fLx85ZVXpJRSnj59WprNZrly5UrvOceOHZOKosjPP/+8xfNORER0tgAgV61a5bfvoosukk899ZTfvssuu0w+9thjUsrw+Vw2nelgrC6apuEf//gHysrKMGDAABw4cAD5+fkYMWKE9xyr1YqrrroKW7ZswT333IPc3Fw4nU6/c9LS0tCrVy9s2bIFI0eOrPVedrsddrvd+17XdZw6dQqJiYkQQjTfQxIRUdiTUqKkpARpaWlQlOZrcKmsrITD4Qg5HSlljc82q9UKq9UacFqDBg3Cxx9/jLvuugtpaWlYv349vv/+ezz//PMAEPTncktrdcHQrl27MGDAAFRWViI2NharVq1Cz549sWXLFgBAcnKy3/nJyck4dOgQACA/Px8WiwXt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iKimSA6G9u7dix07dmD79u345ptv8Oqrr+LUqVOIiorCRRddhK1btwaddsDB0COPPILrr78el19+OVJSUrzD3T0uv/zyoDNDREREVJsLL7wQF154IW655RYARteazz//HNOmTcPQoUNDSjvgofV5eXkYNWoUUlNT8bvf/Q6ffvqp31IWRERE1Dw8HahD2c4WQghkZmbinXfewfHjx0NKK+BgaNmyZfj555/x97//HW3btsWsWbOQlJSEsWPHYvny5Thx4kRIGSIiIqLaeZrJQtnCla7rte6/4oorsH79+pDSDmrSRSEErrzySixcuBD/+9//8PXXX+OKK67A66+/jrS0NAwePBh/+ctfcOzYsZAyR0RERGfOxo0bMXr0aKSlpUEIgdWrV/sdnzx5co1g64orrvA7x263Y9q0aUhKSkJMTAzGjBmDo0ePBpyX2NhYXH755fjd736HF198EV9++SVOnDiBzz77DKWlpaE8ZvAzUPvq0aMHZs+ejS+//BLHjh3D5MmTsWnTJvztb39riuSJiIgILd9MVlZWhj59+mDJkiV1nnPNNdcgLy/Pu3322Wd+x6dPn45Vq1Zh5cqV2Lx5M0pLSzFq1ChomhZQXj788EPceOONKC0txYsvvoghQ4YgOTkZY8aMwaxZswJ7sGoC6kDtu5hpQz766KOAM0NERER1a6rRZNUXl61rjc7MzExkZmbWm6bVaq0xmMqjqKgIS5cuxYoVK7yrQLzzzjtIT0/HF198gZEjRzY679dccw2uueYa7/vKykr8+OOPSExMrPP+jRVQMLR9+3a/97m5udA0DRdccAEA4Pvvv4eqqujbt29ImSIiIqKaBEKcgdr9NT093W//nDlz6p3MuD7r169Hhw4d0LZtW1x11VV45pln0KFDBwBGnOB0Ov0WcE9LS0OvXr2wZcuWgIKh6mw2Gy666KKgr/cVUDC0bt067+tFixahTZs2eOutt7yrwxcWFuLOO+/ElVde2SSZIyIioqZ35MgRv9UiaqsVaozMzEyMGzcOnTt3xoEDB/D444/j6quvRm5uLqxWK/Lz82GxWLxxgkdycrJ3/dHWIOhJF//6179i7dq1fg/Yrl07zJs3DyNGjAi5/Y6IiIj8KUJACaFqyHNtXFxc0Etn+br55pu9r3v16oV+/fqhc+fO+PTTTzF27Ng6r5NStqqRbUF3oC4u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a/HEE0+gd+/e6N69O1RVDSlDREREVLtIW47D17Zt2zBy5EhERUXh8ssvh5QSixYtwjPPPIO1a9fisssuCzrtgEaTvf/++8jOzkZpaSmOHz+OqVOnYu3atfjmm2/QuXNnBkJERETULGbMmIExY8bg4MGD+PDDD7Fq1SocOHAAo0aNwvTp00NKO6Bg6MEHH8TEiRNhs9kQGxuL559/Hl9++SXWrVuHnj174vPPPw8pM0RERFQ3RYiQt3C1bds2/PGPf4TJVNWoZTKZMHv2bGzbti2ktAMKhp5++mnExsb67evbty++/vprTJ8+HTfffDMmTJiAX375JaRMERERUU1ChL6Fq7i4OBw+fLjG/iNHjqBNmzYhpd0kky4KIfDAAw9g7969sNvttU6XTURERKGJ5OU4br75ZkyZMgXvvfcejhw5gqNHj2LlypW4++67ceutt4aUdpPOM3TOOefggw8+wKefftqUyRIREVGE+8tf/gIhBCZOnAiXywUAMJvN+P3vf48//elPIaXdLJMuXnvttc2RLBERUUQLtakrjCuGYLFY8Pzzz2PBggX48ccfIaVEt27dEB0dHXLanIGaiIgoTITaCTqcO1B7REdHN/l8hq1qodaNGzdi9OjRSEtLgxACq1ev9js+efLkGm2fV1xxhd85drsd06ZNQ1JSEmJiYjBmzBgcPXq0BZ+CiIjo7NDQ57Kve+65B0IIPPfcc377Q/lcnjlzZqO3ULSqmqGysjL06dMHd955J2688cZaz7nmmmuwbNky73uLxeJ3fPr06fjkk0+wcuVKJCYmYtasWRg1ahRyc3M5DxIREYW1lm4ma8znMgCsXr0aW7durXX1+FA+l7dv396ofIbaMbxVBUOZmZnIzMys9xyr1YqUlJRajxUVFWHp0qVYsWIFhg0bBgB45513kJ6eji+++AIjR45s8jwTERG1lJZejqMxn8vHjh3D1KlTsWbNmhp9hkP9XF63bl1A+Q1WswRDiqJgyJAhePbZZ9G3b98mTXv9+vXo0KED2rZti6uuugrPPPMMOnToAADIzc2F0+nEiBEjvOenpaWhV69e2LJlS52FbrfbYbfbve+Li4sBAE6nE06ns0nzfzbwlAnLpnYsn/qxfOrH8mlYayuj1pKPQHg+5zysVmtQC57quo477rgDDz74IC666KIax4P9XG5pzRIMvfnmmzh0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669x/vnnh5RBIiIiMkTypIuapiE2NhYAkJSUhOPHjwMAOnfujH379oWUdtB9hl599VXMmDEDt9xyC1wul5GYyYRJkyZ5Jz668MIL8cYbb4SUQSIiIqJevXph586d6Nq1K/r374+FCxfCYrHgtddeQ9euXUNKO+iaodjYWLz++us4efKkd2TZyZMn8dprr3kjt0suuQSXXHJJSBkkIiIig6cDdShbIDZu3IjRo0cjLS0NQgisXr3a7/jcuXNx4YUXIiYmBu3atcOwYcOwdetWv3PsdjumTZuGpKQkxMTEYMyYMTh69GjAz/7YY49B13UAwNNPP41Dhw7hyiuvxGeffYYXXngh4PR8hTzp4uHDh3H8+HE4HA4cOnTIuz+UIW5ERERUk4LQmroCHVpfVlaGPn364M4778SNN95Y4/j555+PJUuWoGvXrqioqMDixYsxYsQI7N+/H+3btwcATJ8+HZ988glWrlyJxMREzJo1C6NGjUJubi5UVW10XkaOHOl9fd5552Hv3r04deoU2rVrd+ZmoG7OIW5ERERUU0sPrc/MzERmZmadxydMmOD3ftGiRVi6dCl27tyJoUOHoqioCEuXLsWKFSswbNgwAMA777yD9PR0fPHFF34BTmNs2rQJr776Kn788Ue8//77OOecc7BixQp06dIFgwYNCuzhfATdTFZ9iNvu3bubbIgbERERNZ/i4mK/zW63h5ymw+HAa6+9hvj4ePTp0wcAkJubC6fTiREjRnjPS0tLQ69evbBly5aA0v/ggw8wcuRIREVFYfv27d48l5SUYP78+SHlPehgqPoQN1VVm2yIGxEREdWkNMEGAOnp6YiPj/duCxYsCDpP//znPxEbGwubzYbFixcjJycHSUlJAID8/HxYLBa0a9fO75rk5GTk5+cHdJ958+bhlVdeweuvv+63Yv3AgQPx7bffBp1/IIRmstqGuF1wwQVNMsSNiIiIahJChNQ/xnPtkSNH/CZNtlqtQaeZkZGBHTt24MSJE3j99dcxfvx4bN26FR06dKjzGillwM+xb98+DB48uMb+uLg4nD59OtBs+wm6ZsgzxA2Ad4jbl19+iaeeeirkIW5ERETUfOLi4vy2UIKhmJgYdOvWDVdccQWWLl0Kk8mEpUuXAgBSUlLgcDhQWFjod01BQQGSk5MDuk9qair2799fY//mzZvP3NB63yFu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E3v37kWnTp3OdPYonJ2BxWFbtc6dO8vFixfXefyzzz6TiqLIY8eOeff97W9/k1arVRYVFbVADs+8zz77TF544YVyz549EoDcvn2737FILx9fH330kRRCSIfDIaVk+VS3cOFC2aVLF+97lo9h2bJlMj4+vsb+SC+fyy+/XN57771++y688EL50EMPnaEc0dmCzWS1+POf/4zExERccskleOaZZ/yqoP/zn/+gV69efqsTjxw5Ena7Hbm5uWciuy3q559/xm9/+1usWLEC0dHRNY5Hevn4OnXqFP7f//t/GDhwIMxmMwCWT3VFRUVISEjwvmf51C+Sy8fhcCA3NxcjRozw2z9ixAhvzStRsBgMVfPAAw9g5cqVWLduHaZOnYrnnnsO9913n/d4fn5+jcXl2rVrB4vFUmOxurONlBKTJ0/Gvffei379+tV6TiSXj8cf//hHxMTEIDExEYcPH8ZHH33kPcbyqfLjjz8iOzvbb5kclk/9Irl8Tpw4AU3Tajx/cnLyWf/s1PwiIhiaO3dujU7R1bdt27YBAGbMmIGrrroKF198Me6++2688sorWLp0KU6ePOlNTwhR4x5Sylr3h4PGlk92djaKi4vx8MMP15tepJaPx4MPPojt27dj7dq1UFUVEydOhPSZ6D3SywcAjh8/jmuuuQbjxo3D3Xff7XeM5VO/s618AlX9OSPp2an5REQH6qlTp+KWW26p95xzzz231v1XXHEFAGD//v1ITExESkoKtm7d6ndOYWEhnE5njb9YwkVjy2fevHn46quvaiyO2K9fP9x222146623Irp8PJKSkpCUlITzzz8fPXr0QHp6Or766isMGDCA5QMjEMrIyPAuouyL5VO/s7F8GispKQmqqtaoBSooKDjrn51awJnrrhQePvnkEwlAHjp0SEpZ1YHx+PHj3nNWrlwZER0YDx06JHft2uXd1qxZIwHI999/Xx45ckRKGdnlU5vDhw9LAHLdunVSSpbP0aNHZffu3eUtt9wiXS5XjeORXj4eDXWgjtTyufzyy+Xvf/97v309evRgB2oKGYMhH1u2bJGLFi2S27dvlz/99JN87733ZFpamhwzZoz3HJfLJXv16iWHDh0qv/32W/nFF1/Ijh07yqlTp57BnJ8ZBw4cqDGaLJLLZ+vWrTI7O1tu375dHjx4UP773/+WgwYNkuedd56srKyU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HkRsbCwSEhKgKPX/7XT//fcjMzMT1157LQDAZDKhR48eTRYMNeZ5YmNjMWXKFDz44INITExEcnIyHn30UW/eCwoK8M033/iNuNu6dSv+9a9/YcSIEejQoQO2bt2KX375BT169GiSfBNR68eaIaImMnPmTLz++us4cuQInn76aTzxxBNYsGABevTogZEjR+KTTz7xNrsoioKVK1ciNzcXvXr1wowZM/Dss882aX5CuUdWVhZUVUXPnj3Rvn17b5+c5cuXQwhR4/x//vOf+Pe//43nn3/eb3/v3r2bLBhq7PM8++yzGDx4MMaMGYNhw4Zh0KBB6Nu3LwDgk08+Qf/+/dGhQwfv+XFxcdi4cSN+85vf4Pzzz8djjz2Gv/71r8jMzGySfBNR6ydkYxrZiYgAzJ07F+vXr8f69eubLM0hQ4bgkksuwXPPPddkadZlzJgxGDRoEGbPnh3wtUIIrFq1Ctdff33TZ4yIzijWDBFRo61ZswYLFy5s8nRfeuklxMbGNnrG6mANGjQIt956a0DX3HvvvYiNjW2mHBFRa8CaISI6o44dO4aKigoAQKdOnWCxWM5wjvwVFBSguLgYAJCamtqko++IqHVgMEREREQRjc1kREREFNEYDBEREVFEYzBEREREEY3BEBEREUU0BkNEREQU0RgMERERUURjMEREREQRjcEQERERRTQGQ0RERBTR/j9N034c0UoO3QAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig = plt.figure()\n", "plt.scatter(eigs_r, eigs_i, c=u_inf, cmap='Blues')\n", "cbar = plt.colorbar()\n", "cbar.set_label('Free Stream Velocity, $u_\\infty$ [m/s]')\n", "\n", "plt.grid()\n", "plt.xlim(-50, 5)\n", "plt.ylim(0, 300)\n", "plt.xlabel('Real Part, $\\lambda$ [rad/s]')\n", "plt.ylabel('Imag Part, $\\lambda$ [rad/s]');" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkcAAAG0CAYAAAA4rYPdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABCg0lEQVR4nO3de3RTVf7//1cKvUGhAm2BYikwyEW5KSiCqKC0wAxUEQVFuVk6owwiVFFRUcQZQFQu6oDQ4aJLYMT5KoJ0gIogKAjKZUBhoR8Ey9UiIhUobWjP7w9+zSRt0jZpkuaE52MtluRk52S/09q+2HuffSyGYRgCAACAJCmkqjsAAAAQSAhHAAAAdghHAAAAdghHAAAAdghHAAAAdghHAAAAdghHAAAAdqpXdQfMpqioSMePH1etWrVksViqujsAAKACDMPQ77//rvj4eIWElD02RDhy0/Hjx5WQkFDV3QAAAB44cuSIrr766jLbEI7cVKtWLUmXP9zatWt79dxWq1Xr1q1TcnKyQkNDvXruQEB95hfsNVKf+QV7jdTnudzcXCUkJNh+j5eFcOSm4qm02rVr+yQc1ahRQ7Vr1w7ab3rqM7dgr5H6zC/Ya6S+yqvIkhgWZAMAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANghHAEAANgxfTiaM2eOmjZtqoiICHXs2FGbN2922fbDDz9UUlKSYmNjVbt2bXXp0kVr1671Y28BAECgM3U4ev/99zV27Fg999xz2rVrl2699Vb16dNH2dnZTttv2rRJSUlJyszM1I4dO9SjRw/169dPu3bt8nPPAQBAoDJ1OJoxY4ZSU1M1cuRItW7dWrNmzVJCQoLmzp3rtP2sWbP01FNP6cYbb9Q111yjKVOm6JprrtGqVav83HMAABCoqld1BzxVUFCgHTt26JlnnnE4npycrC1btlToHEVFRfr9999Vt25dl23y8/OVn59ve5ybmytJslqtslqtHvTcteLzefu8gYL6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kvnrKyoq0pAhQzR+/Hhdd911pZ43e33FNm7cqLi4OLVo0UJpaWnKycmxPWfmGouKirR69Wq1aNFCvXr1UlxcnDp37uwwbWPm+kr6+eeftXr1aqWmptqOBUN93bp108qVK3Xs2DEZhqENGzbo+++/V69evST5v0bCURU6efKkJKl+/foOx+vXr2977uTJkwoLC1OdOnVctjGzYKvv4sWLeuaZZzR48GDbTRODocZPPvlEUVFRioiI0MyZM5WVlaWYmBhJ5q/vlVdeUfXq1TVmzBinz5u9Pknq06ePlixZos8++0yvv/66vv76a91xxx3Kz8+XZO4ac3JydO7cOU2bNk29e/fWunXr1L9/f91zzz36/PPPJZm7vpLeeecd1apVS/fcc4/tWDDU98Ybb+jaa6/V1VdfrbCwMPXu3Vtz5sxRt27dJPm/xupePyPcZrFYHB4bhlHqWEkVaWNmZqzParXq/vvvV1FRkebMmVNuezPV2KNHD+3evVu//PKLMjIyNHDgQG3btk1xcXEuX2OG+nbs2KHZs2dr586dbvfVDPUVGzRokO3vbdq0UadOnZSYmKjVq1c7/JItyQw1FhUVSZLuuusujRs3TpLUoUMHbdmyRW+//bZuv/12l681Q30lLVy4UA8++KDDeipXzFTfG2+8oa+++korV65UYmKiNm3apFGjRqlhw4bq2bOny9f5qkZGjqpQgwYNJKlU6s3JybGNJjVo0EAFBQU6c+aMyzZmFiz1Wa1WDRw4UIcOHVJWVpZt1EgKjhpr1qyp5s2b6+abb9aCBQtUvXp1LViwQJK569u8ebNycnLUuHFjVa9eXdWrV9dPP/2kJ554Qk2aNJFk7vpcadiwoRITE/XDDz9IMneNMTExql69uq699lqH461bt7ZdrWbm+uxt3rxZBw4c0MiRIx2Om72+vLw8Pfvss5oxY4b69eundu3aafTo0Ro0aJBee+01Sf6vkXBUhZo2baoGDRooKyvLdqygoECff/65unbtKknq2LGjQkNDHdqcOHFC3377ra2NmQVDfcXB6IcfftCnn36qevXqOTwfDDWWZBiGbUrGzPUNGTJEe/bs0e7du21/4uPjNX78eK1du1aSuetz5fTp0zpy5IgaNmwoydw1hoWF6cYbb9SBAwccjn///fdKTEyUZO767C1YsEAdO3a0rfcrZvb6rFarrFarQkIcI0m1atVsI4P+rpFpNR87d+6c/u///s/2+NChQ9q9e7fq1q2rxo0ba+zYsZoyZYquueYaXXPNNZoyZYpq1KihwYMHS5Kio6OVmpqqJ554QvXq1VPdunX15JNPqm3btmUONfpLefX9+uuvys7Otu37U/wDrEGDBmrQoEHA1yeVXWN8fLzuvfde7dy5U5988okKCwttI4F169ZVWFhYwNdYVn316tXT3//+d6WkpKhhw4Y6ffq05syZo6NHj+q+++6TZP7v0ZJhNjQ0VA0aNFDLli0lBX59Utk11q1bV5MmTdKAAQPUsGFDHT58WM8++6xiYmLUv39/SYFfY3lfw/Hjx2vQoEG67bbb1KNHD61Zs0arVq3Sxo0bJZm/PknKzc3VBx98oNdff73U6wO9Pqn8Gm+//XaNHz9ekZGRSkxM1Oeff653331XM2bMkFQFNXr9+jc42LBhgyGp1J9hw4YZhnH5cv4XX3zRaNCggREeHm7cdtttxt69ex3OkZeXZ4wePdqoW7euERkZafTt29fIzs6ugmpKK6++RYsWOX3+xRdftJ0jkOszjLJrLN6iwNmfDRs22M4RyDWWVV9eXp7Rv39/Iz4+3ggLCzMaNmxopKSkGNu3b3c4h1nrc6bkpfyGEdj1GUbZNV64cMFITk42YmNjjdDQUKNx48bGsGHDSvU/kGusyNdwwYIFRvPmzY2IiAijffv2xooVKxzOYfb65s2bZ0RGRhq//fab03MEcn2GUX6NJ06cMIYPH27Ex8cbERERRsuWLY3XX3/dKCoqsp3DnzVaDMMwvBe1AAAAzI01RwAAAHYIRwAAAHYIRwAAAHYIRwAAAHYIRwAAAHYIRwAAAHYIRwAAAHYIRwAAAHYIRwAAAHYIRwAAAHYIRwDgQvfu3TV27NiAPZ+/de/eXRaLRRaLRbt37/brew8fPtz23itWrPDre+PKQzgC/Mj+B7z9H/u7VftLTk6O/vKXv6hx48YKDw9XgwYN1KtXL23dutXWxoy/zPv16+fyLt1bt26VxWLRzp07/dyryz788EO9/PLLtsdm/HzT0tJ04sQJtWnTxivnGz58uJ555ply282ePVsnTpzwynsC5ale1R0ArjS9e/fWokWLHI7FxsaWaldQUKCwsDCf9WPAgAGyWq1655131KxZM/38889av369fv31V7fO4+t+uis1NVX33HOPfvrpJyUmJjo8t3DhQnXo0EE33HBDlfStbt26VfK+3lSjRg01aNDAK+cqKirS6tWrtXLlynLbRkdHKzo62ivvC5SHkSPAz4pHaez/VKtWTd27d9fo0aOVnp6umJgYJSUlSZIMw9D06dPVrFkzRUZGqn379vr3v//tcM6KtLH322+/6YsvvtArr7yiHj16KDExUTfddJMmTJigP/3pT5Iu/4v+888/1+zZs20jXIcPH/a4n2vWrFG3bt101VVXqV69eurbt68OHjzo0K/u3bvrscce09ixY1WnTh3Vr19f8+fP1/nz5zVixAjVqlVLf/jDH/Sf//zHZW19+/ZVXFycFi9e7HD8woULev/995WamurRZyZJ+fn5GjNmjOLi4hQREaFu3brp66+/tj1fVFSkV155Rc2bN1d4eLgaN26sv//97w71FY8UOft8J0+erHr16ik/P9/hfQcMGKChQ4eW2Td7a9asUWRkpC5dumQ7tn//flksFv3yyy8VPk9FVOZr9uWXXyokJESdO3fWv//9b7Vt21aRkZGqV6+eevbsqfPnz3u1r0BFEY6AAPLOO++oevXq+vLLLzVv3jxJ0vPPP69FixZp7ty5+u677zRu3Dg99NBD+vzzz22vq0gbe1FRUYqKitKKFStK/SIuNnv2bHXp0sU2jXLixAklJCR43M/z588rPT1dX3/9tdavX6+QkBD1799fRUVFpT6DmJgYbd++XY899pgeffRR3Xffferatat27typXr16aciQIbpw4YLTflevXl1Dhw7V4sWLZRiG7fgHH3yggoICPfjggx59ZpL01FNP6f/9v/+nd955Rzt37lTz5s3Vq1cv22jbhAkT9Morr2jixInat2+fli5dqvr161f4833iiSdUWFjoMJLyyy+/6JNPPtGIESNc9quk3bt367rrrlP16tUdjjVq1EgxMTEVPk9Fefo1W7lypfr166eff/5ZDzzwgB5++GHt379fGzdu1D333OPw9QP8ygDgN8OGDTOqVatm1KxZ0/bn3nvvNQzDMG6//XajQ4cODu3PnTtnREREGFu2bHE4npqaajzwwAMVbuPMv//9b6NOnTpGRESE0bVrV2PChAnGf//7X4c2t99+u/H444+XOuZJP0vKyckxJBl79+51OHe3bt1sjy9dumTUrFnTGDJkiO3YiRMnDEnG1q1bXda2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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig = plt.figure()\n", "natural_frequency = np.sqrt(eigs_r ** 2 + eigs_i ** 2)\n", "damping_ratio = eigs_r / natural_frequency\n", "cond = (eigs_r>-25) * (eigs_r<10) * (natural_frequency<100) # filter unwanted eigenvalues for this plot (mostly aero modes)\n", "\n", "plt.scatter(u_inf[cond], damping_ratio[cond], color='k', marker='s', s=9)\n", "\n", "plt.grid()\n", "plt.ylim(-0.25, 0.25)\n", "plt.xlabel('Free Stream Velocity, $u_\\infty$ [m/s]')\n", "plt.ylabel('Damping Ratio, $\\zeta$ [-]');" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "tags": [] }, "outputs": [ { "data": { "image/png": "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EZGo9A4/O3bsQHh4OGQyGU6dOoWKigoAQElJCRITE0UbIBEREZGY9A4/y5Ytw7p165Camqr2YLJ+/frh1KlTogyOiIiISGx6h59Lly5h0KBB9dpbtmyJe/fuGTImIiIiIqPRO/z4+Pjg119/rdd+9OhRBAYGGjQoIiIiImPRO/zMnDkTc+fOxfHjxyGRSHDz5k188skniI2NxaxZs8QcIxEREZFo9N7qHhcXh6KiIgwZMgR//vknBg0aBKlUitjYWMyePVvMMRIRERGJRu/wAwBvv/02Fi1ahAsXLqC6uhpdunSBq6urWGMjIiIiEp1et72USiWGDBmCy5cvw9nZGX369EHfvn0ZfIiIiMjs6RV+HB0dce7cOUgkErHHQ0RERGRUei94njJlCjZu3CjmWIiIiIiMTu81P5WVlfjoo4+QlZWFPn361HumV0pKisGDIyIiIhKb3uHn3Llz6NWrFwDg8uXLau/xdhgRERGZK73Dz6FDh8QcBxEREZFJ6Lzm57fffoMgCMYYCxEREZHR6Rx+goKCcPv2bdXrCRMm4NatW6IOioiIiMhYdA4/dWd99u7di9LSUtEGRERERGRMem91JyIiIrJEOocfiURSbzcXd3cRERGRpdB5t5cgCJg6dSqkUikA4M8//8SLL75Y75yfL774QpwREhEREYlI5/ATFRWl9nrSpEmiDYaIiIjI2HQOP2lpacYYBxEREZFJcMEzERER2RSGHyIiIrIpDD9ERERkUxh+iIiIyKYw/BAREZFN0Wm31/z585t8bUpKis6DISIiIjI2ncLP6dOnm3QdT3wmIiIic6VT+Dl06JCxxkFERERkEha35ufGjRuYNGkSPDw84OzsjB49euCnn35SvS8IAhISEuDr6wuZTIbQ0FCcP3++GUdMRERE5kTnE57runDhAq5evYrKykq19pEjRxradT2FhYXo378/hgwZgv/85z/w8vLClStX8NBDD6muSU5ORkpKCjZv3oyOHTti2bJlUCgUuHTpEtzc3EQfExEREVkWvcPPb7/9hjFjxuDs2bOQSCQQBAHA/633qaqqEmeEtaxYsQJ+fn5qj9ho37696veCIGD16tVYtGgRxo4dCwBIT0+Ht7c3MjIyMHPmTNHHRERERJZF79tec+fORUBAAG7dugVnZ2ecP38e3377Lfr06YPDhw+LOMT/s3v3bvTp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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig = plt.figure()\n", "cond = (eigs_r>-25) * (eigs_r<10) # filter unwanted eigenvalues for this plot (mostly aero modes)\n", "plt.scatter(u_inf[cond], natural_frequency[cond], color='k', marker='s', s=9)\n", "\n", "plt.grid()\n", "plt.ylim(40, 100)\n", "plt.xlabel('Free Stream Velocity, $u_\\infty$ [m/s]')\n", "plt.ylabel('Natural Frequency, $\\omega_n$ [rad/s]');" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.11" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: docs/source/content/example_notebooks/linear_horten.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Asymptotic Stability of a Flying Wing in Cruise Trimmed Conditions\n", "\n", "A Horten flying wing is analysed. The nonlinear trim condition is found and the system is linearised. The eigenvalues of the linearised system are then used to evaluate the stability at the cruise trimmed flight conditions." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# required packages\n", "import sharpy.utils.algebra as algebra\n", "import sharpy.sharpy_main\n", "from sharpy.cases.hangar.richards_wing import Baseline\n", "import numpy as np\n", "import configobj\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Flight Conditions\n", "\n", "Initial flight conditions. The values for angle of attack ``alpha``, control surface deflection ``cs_deflection`` and ``thrust`` are only initial values. The values required for trim will be calculated by the ``StaticTrim`` routine" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "u_inf = 28\n", "alpha_deg = 4.5135\n", "cs_deflection = 0.1814\n", "thrust = 5.5129" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Discretisation" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "M = 4 # chordwise panels\n", "N = 11 # spanwise panels\n", "Msf = 5 # wake length in chord numbers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create Horten Wing" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "Section Mass: 11.88 \n", "Linear Mass: 11.88\n", "Section Ixx: 1.8777\n", "Section Iyy: 1.0137\n", "Section Izz: 2.5496\n", "Linear Ixx: 1.88\n", "1\n", "Section Mass: 10.99 \n", "Linear Mass: 10.99\n", "Section Ixx: 1.4694\n", "Section Iyy: 0.9345\n", "Section Izz: 2.1501\n", "Linear Ixx: 1.74\n", "2\n", "Section Mass: 10.10 \n", "Linear Mass: 10.10\n", "Section Ixx: 1.1257\n", "Section Iyy: 0.8561\n", "Section Izz: 1.7993\n", "Linear Ixx: 1.60\n", "3\n", "Section Mass: 9.21 \n", "Linear Mass: 9.21\n", "Section Ixx: 0.8410\n", "Section Iyy: 0.7783\n", "Section Izz: 1.4933\n", "Linear Ixx: 1.46\n", "4\n", "Section Mass: 8.32 \n", "Linear Mass: 8.32\n", "Section Ixx: 0.6096\n", "Section Iyy: 0.7011\n", "Section Izz: 1.2280\n", "Linear Ixx: 1.31\n", "5\n", "Section Mass: 7.43 \n", "Linear Mass: 7.43\n", "Section Ixx: 0.4260\n", "Section Iyy: 0.6246\n", "Section Izz: 0.9996\n", "Linear Ixx: 1.17\n", "6\n", "Section Mass: 6.54 \n", "Linear Mass: 6.54\n", "Section Ixx: 0.2845\n", "Section Iyy: 0.5485\n", "Section Izz: 0.8040\n", "Linear Ixx: 1.03\n", "7\n", "Section Mass: 5.64 \n", "Linear Mass: 5.64\n", "Section Ixx: 0.1796\n", "Section Iyy: 0.4728\n", "Section Izz: 0.6374\n", "Linear Ixx: 0.89\n", "8\n", "Section Mass: 4.75 \n", "Linear Mass: 4.75\n", "Section Ixx: 0.1055\n", "Section Iyy: 0.3975\n", "Section Izz: 0.4959\n", "Linear Ixx: 0.75\n", "9\n", "Section Mass: 3.86 \n", "Linear Mass: 3.86\n", "Section Ixx: 0.0567\n", "Section Iyy: 0.3226\n", "Section Izz: 0.3753\n", "Linear Ixx: 0.61\n", "10\n", "Section Mass: 2.97 \n", "Linear Mass: 2.97\n", "Section Ixx: 0.0275\n", "Section Iyy: 0.2479\n", "Section Izz: 0.2719\n", "Linear Ixx: 0.47\n" ] } ], "source": [ "ws = Baseline(M=M,\n", " N=N,\n", " Mstarfactor=Msf,\n", " u_inf=u_inf,\n", " rho=1.02,\n", " alpha_deg=alpha_deg,\n", " roll_deg=0,\n", " cs_deflection_deg=cs_deflection,\n", " thrust=thrust,\n", " physical_time=20,\n", " case_name='horten',\n", " case_name_format=4,\n", " case_remarks='M%gN%gMsf%g' % (M, N, Msf))\n", "\n", "ws.set_properties()\n", "ws.initialise()\n", "ws.clean_test_files()\n", "\n", "ws.update_mass_stiffness(sigma=1., sigma_mass=2.5)\n", "ws.update_fem_prop()\n", "ws.generate_fem_file()\n", "ws.update_aero_properties()\n", "ws.generate_aero_file()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Simulation Information\n", "\n", "The ``flow`` setting tells SHARPy which solvers to run and in which order. You may be stranged by the presence of the ``DynamicCoupled`` solver but it is necessary to give an initial speed to the structure. This will allow proper linearisation of the structural and rigid body equations." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "flow = ['BeamLoader',\n", " 'AerogridLoader',\n", " 'StaticTrim',\n", " 'BeamPlot',\n", " 'AerogridPlot',\n", " 'AeroForcesCalculator',\n", " 'DynamicCoupled',\n", " 'Modal',\n", " 'LinearAssembler',\n", " 'AsymptoticStability',\n", " ]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### SHARPy Settings" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "settings = dict()\n", "settings['SHARPy'] = {'case': ws.case_name,\n", " 'route': ws.case_route,\n", " 'flow': flow,\n", " 'write_screen': 'on',\n", " 'write_log': 'on',\n", " 'log_folder': './output/',\n", " 'log_file': ws.case_name + '.log'}\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Loaders" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "settings['BeamLoader'] = {'unsteady': 'off',\n", " 'orientation': algebra.euler2quat(np.array([ws.roll,\n", " ws.alpha,\n", " ws.beta]))}\n", "settings['AerogridLoader'] = {'unsteady': 'off',\n", " 'aligned_grid': 'on',\n", " 'mstar': int(ws.M * ws.Mstarfactor),\n", " 'freestream_dir': ['1', '0', '0'],\n", " 'control_surface_deflection': [''],\n", " 'wake_shape_generator': 'StraightWake',\n", " 'wake_shape_generator_input': {'u_inf': ws.u_inf,\n", " 'u_inf_direction': ['1', '0', '0'],\n", " 'dt': ws.dt}}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### StaticCoupled Solver" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "settings['StaticCoupled'] = {'print_info': 'on',\n", " 'structural_solver': 'NonLinearStatic',\n", " 'structural_solver_settings': {'print_info': 'off',\n", " 'max_iterations': 200,\n", " 'num_load_steps': 1,\n", " 'delta_curved': 1e-5,\n", " 'min_delta': ws.tolerance,\n", " 'gravity_on': 'on',\n", " 'gravity': 9.81},\n", " 'aero_solver': 'StaticUvlm',\n", " 'aero_solver_settings': {'print_info': 'on',\n", " 'horseshoe': ws.horseshoe,\n", " 'num_cores': 4,\n", " 'n_rollup': int(0),\n", " 'rollup_dt': ws.dt,\n", " 'rollup_aic_refresh': 1,\n", " 'rollup_tolerance': 1e-4,\n", " 'velocity_field_generator': 'SteadyVelocityField',\n", " 'velocity_field_input': {'u_inf': ws.u_inf,\n", " 'u_inf_direction': [1., 0, 0]},\n", " 'rho': ws.rho},\n", " 'max_iter': 200,\n", " 'n_load_steps': 1,\n", " 'tolerance': ws.tolerance,\n", " 'relaxation_factor': 0.2}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Trim solver" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "settings['StaticTrim'] = {'solver': 'StaticCoupled',\n", " 'solver_settings': settings['StaticCoupled'],\n", " 'thrust_nodes': ws.thrust_nodes,\n", " 'initial_alpha': ws.alpha,\n", " 'initial_deflection': ws.cs_deflection,\n", " 'initial_thrust': ws.thrust,\n", " 'max_iter': 200,\n", " 'fz_tolerance': 1e-2,\n", " 'fx_tolerance': 1e-2,\n", " 'm_tolerance': 1e-2}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Nonlinear Equilibrium Post-process" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "settings['AerogridPlot'] = {\n", " 'include_rbm': 'off',\n", " 'include_applied_forces': 'on',\n", " 'minus_m_star': 0,\n", " 'u_inf': ws.u_inf\n", " }\n", "settings['AeroForcesCalculator'] = {\n", " 'write_text_file': 'off',\n", " 'text_file_name': ws.case_name + '_aeroforces.csv',\n", " 'screen_output': 'on',\n", " 'coefficients': True,\n", " 'q_ref': 0.5 * ws.rho * ws.u_inf ** 2,\n", " 'S_ref': 12.809,\n", " }\n", "\n", "settings['BeamPlot'] = {\n", " 'include_rbm': 'on',\n", " 'include_applied_forces': 'on',\n", " 'include_FoR': 'on'}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### DynamicCoupled Solver\n", "\n", "As mentioned before, a single time step of ``DynamicCoupled`` is required to give the structure the velocity required for the linearisation of the rigid body equations to be correct. Hence `n_time_steps = 1`" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "struct_solver_settings = {'print_info': 'off',\n", " 'initial_velocity_direction': [-1., 0., 0.],\n", " 'max_iterations': 950,\n", " 'delta_curved': 1e-6,\n", " 'min_delta': ws.tolerance,\n", " 'newmark_damp': 5e-3,\n", " 'gravity_on': True,\n", " 'gravity': 9.81,\n", " 'num_steps': ws.n_tstep,\n", " 'dt': ws.dt,\n", " 'initial_velocity': ws.u_inf * 1}\n", "\n", "step_uvlm_settings = {'print_info': 'on',\n", " 'num_cores': 4,\n", " 'convection_scheme': ws.wake_type,\n", " 'velocity_field_generator': 'SteadyVelocityField',\n", " 'velocity_field_input': {'u_inf': ws.u_inf * 0,\n", " 'u_inf_direction': [1., 0., 0.]},\n", " 'rho': ws.rho,\n", " 'n_time_steps': ws.n_tstep,\n", " 'dt': ws.dt,\n", " 'gamma_dot_filtering': 3}\n", "\n", "settings['DynamicCoupled'] = {'print_info': 'on',\n", " 'structural_solver': 'NonLinearDynamicCoupledStep',\n", " 'structural_solver_settings': struct_solver_settings,\n", " 'aero_solver': 'StepUvlm',\n", " 'aero_solver_settings': step_uvlm_settings,\n", " 'fsi_substeps': 200,\n", " 'fsi_tolerance': ws.fsi_tolerance,\n", " 'relaxation_factor': ws.relaxation_factor,\n", " 'minimum_steps': 1,\n", " 'relaxation_steps': 150,\n", " 'final_relaxation_factor': 0.5,\n", " 'n_time_steps': 1,\n", " 'dt': ws.dt,\n", " 'include_unsteady_force_contribution': 'off',\n", " }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Modal Solver Settings" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "settings['Modal'] = {'print_info': True,\n", " 'use_undamped_modes': True,\n", " 'NumLambda': 30,\n", " 'rigid_body_modes': True,\n", " 'write_modes_vtk': 'on',\n", " 'continuous_eigenvalues': 'off',\n", " 'dt': ws.dt,\n", " 'plot_eigenvalues': False,\n", " 'rigid_modes_cg': False}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Linear Assembler Settings\n", "\n", "Note that for the assembly of the linear system, we replace the parametrisation of the orientation with Euler angles instead of quaternions." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "settings['LinearAssembler'] = {'linear_system': 'LinearAeroelastic',\n", " 'linear_system_settings': {\n", " 'beam_settings': {'modal_projection': 'off',\n", " 'inout_coords': 'modes',\n", " 'discrete_time': True,\n", " 'newmark_damp': 0.5e-2,\n", " 'discr_method': 'newmark',\n", " 'dt': ws.dt,\n", " 'proj_modes': 'undamped',\n", " 'num_modes': 9,\n", " 'print_info': 'on',\n", " 'gravity': 'on',\n", " 'remove_dofs': []},\n", " 'aero_settings': {'dt': ws.dt,\n", " 'integr_order': 2,\n", " 'density': ws.rho,\n", " 'remove_predictor': 'off',\n", " 'use_sparse': 'off',\n", " 'remove_inputs': ['u_gust']},\n", " 'track_body': 'on',\n", " 'use_euler': 'on',\n", " }}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Asymptotic Stability Post-processor" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "settings['AsymptoticStability'] = {\n", " 'print_info': 'on',\n", " 'frequency_cutoff': 0,\n", " 'export_eigenvalues': 'on',\n", " 'num_evals': 100,\n", " }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Write solver file" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "config = configobj.ConfigObj()\n", "np.set_printoptions(precision=16)\n", "file_name = ws.case_route + '/' + ws.case_name + '.sharpy'\n", "config.filename = file_name\n", "for k, v in settings.items():\n", " config[k] = v\n", "config.write()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Run Simulation" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--------------------------------------------------------------------------------\u001b[0m\n", " ###### ## ## ### ######## ######## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ####\u001b[0m\n", " ###### ######### ## ## ######## ######## ##\u001b[0m\n", " ## ## ## ######### ## ## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ##\u001b[0m\n", " ###### ## ## ## ## ## ## ## ##\u001b[0m\n", "--------------------------------------------------------------------------------\u001b[0m\n", "Aeroelastics Lab, Aeronautics Department.\u001b[0m\n", " Copyright (c), Imperial College London.\u001b[0m\n", " All rights reserved.\u001b[0m\n", " License available at https://github.com/imperialcollegelondon/sharpy\u001b[0m\n", "\u001b[36mRunning SHARPy from /home/ng213/2TB/pazy_code/pazy-sharpy/lib/sharpy/docs/source/content/example_notebooks\u001b[0m\n", "\u001b[36mSHARPy being run is in /home/ng213/2TB/pazy_code/pazy-sharpy/lib/sharpy\u001b[0m\n", "\u001b[36mThe branch being run is dev_setting_error\u001b[0m\n", "\u001b[36mThe version and commit hash are: v1.2.1-344-g0239644-0239644\u001b[0m\n", "SHARPy output folder set\u001b[0m\n", "\u001b[34m\t./output//horten_u_inf2800_M4N11Msf5/\u001b[0m\n", "\u001b[36mGenerating an instance of BeamLoader\u001b[0m\n", "Variable for_pos has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0, 0]\u001b[0m\n", "\u001b[36mGenerating an instance of AerogridLoader\u001b[0m\n", "Variable control_surface_deflection_generator_settings has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable dx1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: -1.0\u001b[0m\n", "Variable ndx1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable r has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1.0\u001b[0m\n", "Variable dxmax has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: -1.0\u001b[0m\n", "\u001b[34mThe aerodynamic grid contains 4 surfaces\u001b[0m\n", "\u001b[34m Surface 0, M=4, N=2\u001b[0m\n", " Wake 0, M=20, N=2\u001b[0m\n", "\u001b[34m Surface 1, M=4, N=22\u001b[0m\n", " Wake 1, M=20, N=22\u001b[0m\n", "\u001b[34m Surface 2, M=4, N=2\u001b[0m\n", " Wake 2, M=20, N=2\u001b[0m\n", "\u001b[34m Surface 3, M=4, N=22\u001b[0m\n", " Wake 3, M=20, N=22\u001b[0m\n", " In total: 192 bound panels\u001b[0m\n", " In total: 960 wake panels\u001b[0m\n", " Total number of panels = 1152\u001b[0m\n", "\u001b[36mGenerating an instance of StaticTrim\u001b[0m\n", "Variable print_info has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable tail_cs_index has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable initial_angle_eps has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.05\u001b[0m\n", "Variable initial_thrust_eps has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 2.0\u001b[0m\n", "Variable relaxation_factor has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.2\u001b[0m\n", "Variable save_info has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "\u001b[36mGenerating an instance of StaticCoupled\u001b[0m\n", "Variable correct_forces_method has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable runtime_generators has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "\u001b[36mGenerating an instance of NonLinearStatic\u001b[0m\n", "Variable newmark_damp has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0001\u001b[0m\n", "Variable gravity_dir has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 1.0]\u001b[0m\n", "Variable relaxation_factor has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.3\u001b[0m\n", "Variable dt has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.01\u001b[0m\n", "Variable num_steps has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 500\u001b[0m\n", "Variable initial_position has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0. 0. 0.]\u001b[0m\n", "\u001b[36mGenerating an instance of StaticUvlm\u001b[0m\n", "Variable iterative_solver has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable iterative_tol has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0001\u001b[0m\n", "Variable iterative_precond has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable cfl1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable vortex_radius has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "Variable vortex_radius_wake_ind has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "Variable rbm_vel_g has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]\u001b[0m\n", "Variable centre_rot_g has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 0.0]\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "|=====|=====|============|==========|==========|==========|==========|==========|==========|\u001b[0m\n", "|iter |step | log10(res) | Fx | Fy | Fz | Mx | My | Mz |\u001b[0m\n", "|=====|=====|============|==========|==========|==========|==========|==========|==========|\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "|==========|==========|==========|==========|==========|==========|==========|==========|==========|==========|\u001b[0m\n", "| iter |alpha[deg]|elev[deg] | thrust | Fx | Fy | Fz | Mx | My | Mz |\u001b[0m\n", "|==========|==========|==========|==========|==========|==========|==========|==========|==========|==========|\u001b[0m\n", "| 0 | 0 | 0.00000 | -0.1051 | -0.0000 | 0.0598 | -0.0000 | 1.0837 | -0.0000 |\u001b[0m\n", "| 1 | 0 | -7.62384 | -0.1284 | -0.0000 | 0.1276 | -0.0000 | 0.0045 | -0.0000 |\u001b[0m\n", "| 2 | 0 | -8.33392 | -0.1190 | -0.0000 | 0.0397 | -0.0000 | -0.0774 | -0.0000 |\u001b[0m\n", "| 3 | 0 | -9.30379 | -0.1133 | 0.0000 | 0.0011 | -0.0000 | -0.0070 | 0.0000 |\u001b[0m\n", "| 4 | 0 | -10.71602 | -0.1136 | -0.0000 | 0.0032 | 0.0000 | -0.0100 | -0.0000 |\u001b[0m\n", "| 5 | 0 | -10.88827 | -0.1138 | -0.0000 | 0.0043 | 0.0000 | -0.0119 | -0.0000 |\u001b[0m\n", "| 6 | 0 | -11.66331 | -0.1138 | -0.0000 | 0.0042 | 0.0000 | -0.0116 | -0.0000 |\u001b[0m\n", "| 7 | 0 | -12.88496 | -0.1138 | -0.0000 | 0.0041 | 0.0000 | -0.0116 | 0.0000 |\u001b[0m\n", "| 0 | 4.5135 | 0.1814 | 5.5129 | -0.1138 | -0.0000 | 0.0041 | 0.0000 | -0.0116 | 0.0000 |\u001b[0m\n", "| 0 | 0 | 0.00000 |-116.9870 | -0.0000 | 994.8063 | -0.0000 |-882.4104 | -0.0000 |\u001b[0m\n", "| 1 | 0 | -5.79178 | -72.3841 | 0.0000 | 944.6140 | 0.0000 |-802.5912 | 0.0000 |\u001b[0m\n", "| 2 | 0 | -6.63730 | -62.4378 | 0.0000 | 937.6662 | 0.0000 |-792.1259 | -0.0000 |\u001b[0m\n", "| 3 | 0 | -7.22937 | -62.8923 | -0.0000 | 939.7866 | -0.0000 |-795.7093 | -0.0000 |\u001b[0m\n", "| 4 | 0 | -8.65323 | -62.8757 | -0.0000 | 939.7100 | -0.0000 |-795.5764 | -0.0000 |\u001b[0m\n", "| 5 | 0 | -8.81438 | -62.8640 | -0.0000 | 939.6554 | -0.0000 |-795.4837 | -0.0000 |\u001b[0m\n", "| 6 | 0 | -9.59386 | -62.8660 | 0.0000 | 939.6644 | -0.0000 |-795.4991 | 0.0000 |\u001b[0m\n", "| 7 | 0 | -10.80422 | -62.8661 | 0.0000 | 939.6650 | -0.0000 |-795.5000 | 0.0000 |\u001b[0m\n", "| 8 | 0 | -11.01365 | -62.8660 | -0.0000 | 939.6647 | -0.0000 |-795.4994 | -0.0000 |\u001b[0m\n", "| 9 | 0 | -12.15197 | -62.8660 | 0.0000 | 939.6647 | -0.0000 |-795.4995 | 0.0000 |\u001b[0m\n", "| 0 | 7.3783 | -2.6834 | 5.5129 | -62.8660 | 0.0000 | 939.6647 | -0.0000 |-795.4995 | 0.0000 |\u001b[0m\n", "| 0 | 0 | 0.00000 | -32.9132 | -0.0000 | 371.4715 | -0.0000 |-902.7953 | -0.0000 |\u001b[0m\n", "| 1 | 0 | -5.48409 | -8.7241 | -0.0000 | 298.8484 | 0.0000 |-777.2938 | 0.0000 |\u001b[0m\n", "| 2 | 0 | -6.39387 | -4.0957 | -0.0000 | 289.8092 | -0.0000 |-761.4855 | -0.0000 |\u001b[0m\n", "| 3 | 0 | -6.85613 | -4.6263 | -0.0000 | 293.2407 | -0.0000 |-767.3376 | 0.0000 |\u001b[0m\n", "| 4 | 0 | -8.25962 | -4.6052 | -0.0000 | 293.1048 | -0.0000 |-767.1066 | 0.0000 |\u001b[0m\n", "| 5 | 0 | -8.44065 | -4.5914 | -0.0000 | 293.0156 | 0.0000 |-766.9545 | 0.0000 |\u001b[0m\n", "| 6 | 0 | -9.20968 | -4.5937 | -0.0000 | 293.0308 | -0.0000 |-766.9804 | -0.0000 |\u001b[0m\n", "| 7 | 0 | -10.44736 | -4.5939 | -0.0000 | 293.0316 | -0.0000 |-766.9819 | -0.0000 |\u001b[0m\n", "| 8 | 0 | -10.63000 | -4.5938 | -0.0000 | 293.0311 | 0.0000 |-766.9809 | -0.0000 |\u001b[0m\n", "| 9 | 0 | -11.74670 | -4.5938 | 0.0000 | 293.0311 | 0.0000 |-766.9810 | 0.0000 |\u001b[0m\n", "| 10 | 0 | -12.29943 | -4.5938 | -0.0000 | 293.0311 | 0.0000 |-766.9810 | 0.0000 |\u001b[0m\n", "| 0 | 4.5135 | 3.0462 | 5.5129 | -4.5938 | -0.0000 | 293.0311 | 0.0000 |-766.9810 | 0.0000 |\u001b[0m\n", "| 0 | 0 | 0.00000 | -4.1051 | -0.0000 | 0.0598 | -0.0000 | 1.0834 | -0.0000 |\u001b[0m\n", "| 1 | 0 | -7.62384 | -4.1284 | -0.0000 | 0.1276 | -0.0000 | 0.0042 | -0.0000 |\u001b[0m\n", "| 2 | 0 | -8.33392 | -4.1190 | -0.0000 | 0.0397 | 0.0000 | -0.0778 | -0.0000 |\u001b[0m\n", "| 3 | 0 | -9.30379 | -4.1133 | -0.0000 | 0.0011 | 0.0000 | -0.0074 | -0.0000 |\u001b[0m\n", "| 4 | 0 | -10.71602 | -4.1136 | 0.0000 | 0.0032 | -0.0000 | -0.0104 | 0.0000 |\u001b[0m\n", "| 5 | 0 | -10.88827 | -4.1138 | 0.0000 | 0.0043 | 0.0000 | -0.0123 | 0.0000 |\u001b[0m\n", "| 6 | 0 | -11.66331 | -4.1138 | -0.0000 | 0.0042 | 0.0000 | -0.0119 | -0.0000 |\u001b[0m\n", "| 7 | 0 | -12.88496 | -4.1138 | -0.0000 | 0.0041 | 0.0000 | -0.0119 | -0.0000 |\u001b[0m\n", "| 0 | 4.5135 | 0.1814 | 7.5129 | -4.1138 | -0.0000 | 0.0041 | 0.0000 | -0.0119 | -0.0000 |\u001b[0m\n", "| 0 | 0 | 0.00000 | 0.0095 | -0.0000 | 0.0498 | 0.0000 | 1.1013 | -0.0000 |\u001b[0m\n", "| 1 | 0 | -7.62357 | -0.0140 | 0.0000 | 0.1189 | 0.0000 | 0.0198 | 0.0000 |\u001b[0m\n", "| 2 | 0 | -8.33375 | -0.0046 | -0.0000 | 0.0312 | -0.0000 | -0.0624 | 0.0000 |\u001b[0m\n", "| 3 | 0 | -9.30318 | 0.0010 | -0.0000 | -0.0075 | 0.0000 | 0.0081 | -0.0000 |\u001b[0m\n", "| 4 | 0 | -10.71542 | 0.0007 | -0.0000 | -0.0054 | 0.0000 | 0.0051 | 0.0000 |\u001b[0m\n", "| 5 | 0 | -10.88766 | 0.0006 | -0.0000 | -0.0043 | 0.0000 | 0.0032 | -0.0000 |\u001b[0m\n", "| 6 | 0 | -11.66271 | 0.0006 | 0.0000 | -0.0044 | -0.0000 | 0.0035 | 0.0000 |\u001b[0m\n", "| 7 | 0 | -12.88441 | 0.0006 | -0.0000 | -0.0045 | 0.0000 | 0.0035 | -0.0000 |\u001b[0m\n", "| 1 | 4.5135 | 0.1814 | 5.4560 | 0.0006 | -0.0000 | -0.0045 | 0.0000 | 0.0035 | -0.0000 |\u001b[0m\n", "\u001b[36mGenerating an instance of BeamPlot\u001b[0m\n", "Variable include_applied_moments has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable name_prefix has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable output_rbm has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "\u001b[34m...Finished\u001b[0m\n", "\u001b[36mGenerating an instance of AerogridPlot\u001b[0m\n", "Variable include_forward_motion has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable include_unsteady_applied_forces has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable name_prefix has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable dt has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0\u001b[0m\n", "Variable include_velocities has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable include_incidence_angle has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable num_cores has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable vortex_radius has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "\u001b[34m...Finished\u001b[0m\n", "\u001b[36mGenerating an instance of AeroForcesCalculator\u001b[0m\n", "--------------------------------------------------------------------------------\u001b[0m\n", "\u001b[34mtstep | fx_g | fy_g | fz_g | Cfx_g | Cfy_g | Cfz_g \u001b[0m\n", "\u001b[34m 0 | 1.088e+01 | -4.476e-13 | 1.835e+03 | 2.124e-03 | -8.740e-17 | 3.583e-01\u001b[0m\n", "\u001b[34m...Finished\u001b[0m\n", "\u001b[36mGenerating an instance of DynamicCoupled\u001b[0m\n", "Variable structural_substeps has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable dynamic_relaxation has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable postprocessors has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: []\u001b[0m\n", "Variable postprocessors_settings has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable controller_id has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable controller_settings has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable cleanup_previous_solution has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable steps_without_unsteady_force has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable pseudosteps_ramp_unsteady_force has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable correct_forces_method has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable network_settings has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable runtime_generators has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "\u001b[36mGenerating an instance of NonLinearDynamicCoupledStep\u001b[0m\n", "Variable num_load_steps has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable gravity_dir has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 1.0]\u001b[0m\n", "Variable relaxation_factor has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.3\u001b[0m\n", "Variable balancing has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "\u001b[36mGenerating an instance of StepUvlm\u001b[0m\n", "Variable iterative_solver has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable iterative_tol has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0001\u001b[0m\n", "Variable iterative_precond has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable cfl1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable vortex_radius has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "Variable vortex_radius_wake_ind has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "Variable interp_coords has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable filter_method has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable interp_method has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable yaw_slerp has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0\u001b[0m\n", "Variable centre_rot has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 0.0]\u001b[0m\n", "Variable quasi_steady has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "|=======|========|======|==============|==============|==============|==============|==============|\u001b[0m\n", "| ts | t | iter | struc ratio | iter time | residual vel | FoR_vel(x) | FoR_vel(z) |\u001b[0m\n", "|=======|========|======|==============|==============|==============|==============|==============|\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/home/ng213/2TB/pazy_code/pazy-sharpy/lib/sharpy/sharpy/aero/utils/uvlmlib.py:264: RuntimeWarning: invalid value encountered in true_divide\n", " flightconditions.uinf_direction = np.ctypeslib.as_ctypes(ts_info.u_ext[0][:, 0, 0]/flightconditions.uinf)\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "| 1 | 0.0089 | 3 | 0.877319 | 0.593232 | -10.598250 |-2.791317e+01 |-2.203426e+00 |\u001b[0m\n", "\u001b[34m...Finished\u001b[0m\n", "\u001b[36mGenerating an instance of Modal\u001b[0m\n", "Variable keep_linear_matrices has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable write_dat has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable delta_curved has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.01\u001b[0m\n", "Variable max_rotation_deg has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 15.0\u001b[0m\n", "Variable max_displacement has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.15\u001b[0m\n", "Variable use_custom_timestep has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: -1\u001b[0m\n", "Structural eigenvalues\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "|==============|==============|==============|==============|==============|==============|==============|\u001b[0m\n", "| mode | eval_real | eval_imag | freq_n (Hz) | freq_d (Hz) | damping | period (s) |\u001b[0m\n", "|==============|==============|==============|==============|==============|==============|==============|\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/home/ng213/2TB/pazy_code/pazy-sharpy/lib/sharpy/sharpy/solvers/modal.py:265: UserWarning: Projecting a system with damping on undamped modal shapes\n", " warnings.warn('Projecting a system with damping on undamped modal shapes')\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "| 0 | -0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 1 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 2 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 3 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 4 | -0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 5 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 6 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 7 | -0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 8 | -0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 9 | -0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 10 | 0.000000 | 28.293939 | 4.503120 | 4.503120 | -0.000000 | 0.222068 |\u001b[0m\n", "| 11 | 0.000000 | 29.271318 | 4.658675 | 4.658675 | -0.000000 | 0.214653 |\u001b[0m\n", "| 12 | 0.000000 | 54.780234 | 8.718545 | 8.718545 | -0.000000 | 0.114698 |\u001b[0m\n", "| 13 | 0.000000 | 58.999779 | 9.390106 | 9.390106 | -0.000000 | 0.106495 |\u001b[0m\n", "| 14 | 0.000000 | 70.520741 | 11.223724 | 11.223724 | -0.000000 | 0.089097 |\u001b[0m\n", "| 15 | 0.000000 | 76.917111 | 12.241738 | 12.241738 | -0.000000 | 0.081688 |\u001b[0m\n", "| 16 | 0.000000 | 87.324076 | 13.898058 | 13.898058 | -0.000000 | 0.071952 |\u001b[0m\n", "| 17 | 0.000000 | 108.035577 | 17.194396 | 17.194396 | -0.000000 | 0.058158 |\u001b[0m\n", "| 18 | 0.000000 | 119.692139 | 19.049596 | 19.049596 | -0.000000 | 0.052495 |\u001b[0m\n", "| 19 | 0.000000 | 133.495187 | 21.246419 | 21.246419 | -0.000000 | 0.047067 |\u001b[0m\n", "| 20 | 0.000000 | 134.444788 | 21.397553 | 21.397553 | -0.000000 | 0.046734 |\u001b[0m\n", "| 21 | 0.000000 | 151.060442 | 24.042016 | 24.042016 | -0.000000 | 0.041594 |\u001b[0m\n", "| 22 | 0.000000 | 159.369020 | 25.364367 | 25.364367 | -0.000000 | 0.039425 |\u001b[0m\n", "| 23 | 0.000000 | 171.256102 | 27.256255 | 27.256255 | -0.000000 | 0.036689 |\u001b[0m\n", "| 24 | 0.000000 | 173.895881 | 27.676389 | 27.676389 | -0.000000 | 0.036132 |\u001b[0m\n", "| 25 | 0.000000 | 199.016557 | 31.674469 | 31.674469 | -0.000000 | 0.031571 |\u001b[0m\n", "| 26 | 0.000000 | 205.412581 | 32.692428 | 32.692428 | -0.000000 | 0.030588 |\u001b[0m\n", "| 27 | 0.000000 | 205.419531 | 32.693534 | 32.693534 | -0.000000 | 0.030587 |\u001b[0m\n", "| 28 | 0.000000 | 223.563796 | 35.581283 | 35.581283 | -0.000000 | 0.028105 |\u001b[0m\n", "| 29 | 0.000000 | 227.924750 | 36.275351 | 36.275351 | -0.000000 | 0.027567 |\u001b[0m\n", "\u001b[36mGenerating an instance of LinearAssembler\u001b[0m\n", "Variable linearisation_tstep has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: -1\u001b[0m\n", "Variable modal_tstep has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: -1\u001b[0m\n", "Variable inout_coordinates has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable retain_inputs has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: []\u001b[0m\n", "Variable retain_outputs has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: []\u001b[0m\n", "\u001b[36mGenerating an instance of LinearAeroelastic\u001b[0m\n", "Variable uvlm_filename has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "\u001b[36mGenerating an instance of LinearUVLM\u001b[0m\n", "Variable ScalingDict has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable gust_assembler has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable rom_method has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: []\u001b[0m\n", "Variable rom_method_settings has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable vortex_radius has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "Variable cfl1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable length has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1.0\u001b[0m\n", "Variable speed has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1.0\u001b[0m\n", "Variable density has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1.0\u001b[0m\n", "Variable velocity_field_generator has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: SteadyVelocityField\u001b[0m\n", "Variable velocity_field_input has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable physical_model has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable track_body has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable track_body_number has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: -1\u001b[0m\n", "Initialising Static linear UVLM solver class...\u001b[0m\n", "\t\t\t...done in 0.39 sec\u001b[0m\n", "State-space realisation of UVLM equations started...\u001b[0m\n", "\u001b[34mComputing wake propagation matrix with CFL1=True\u001b[0m\n", "\u001b[34m\tstate-space model produced in form:\u001b[0m\n", "\u001b[34m\tx_{n+1} = A x_{n} + Bp u_{n+1}\u001b[0m\n", "\t\t\t...done in 2.43 sec\u001b[0m\n", "\u001b[36mGenerating an instance of LinearBeam\u001b[0m\n", "Variable remove_sym_modes has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Warning, projecting system with damping onto undamped modes\u001b[0m\n", "\u001b[0m\n", "Linearising gravity terms...\u001b[0m\n", "\u001b[34m\tM = 187.12 kg\u001b[0m\n", "\u001b[34m\tX_CG A -> 1.19 0.00 0.01\u001b[0m\n", "\u001b[36mNode 1 \t-> B 0.000 -0.089 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 0.089 0.206 0.000\u001b[0m\n", "\u001b[36m\t\t\t-> G 0.089 0.206 -0.007\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 2.6141\u001b[0m\n", "\u001b[36mNode 2 \t-> B -0.010 -0.019 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 0.019 0.403 0.000\u001b[0m\n", "\u001b[36m\t\t\t-> G 0.019 0.403 -0.001\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 7.3672\u001b[0m\n", "\u001b[36mNode 3 \t-> B -0.019 -0.087 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 0.234 0.800 0.000\u001b[0m\n", "\u001b[36m\t\t\t-> G 0.234 0.800 -0.018\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 5.8780\u001b[0m\n", "\u001b[36mNode 4 \t-> B -0.019 -0.084 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 0.390 1.238 0.001\u001b[0m\n", "\u001b[36m\t\t\t-> G 0.389 1.238 -0.030\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 2.8288\u001b[0m\n", "\u001b[36mNode 5 \t-> B -0.018 -0.081 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 0.546 1.676 0.001\u001b[0m\n", "\u001b[36m\t\t\t-> G 0.544 1.676 -0.041\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 5.4372\u001b[0m\n", "\u001b[36mNode 6 \t-> B -0.017 -0.078 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 0.702 2.113 0.002\u001b[0m\n", "\u001b[36m\t\t\t-> G 0.700 2.113 -0.053\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 2.6084\u001b[0m\n", "\u001b[36mNode 7 \t-> B -0.016 -0.074 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 0.857 2.551 0.003\u001b[0m\n", "\u001b[36m\t\t\t-> G 0.855 2.551 -0.064\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 4.9963\u001b[0m\n", "\u001b[36mNode 8 \t-> B -0.016 -0.071 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 1.013 2.988 0.004\u001b[0m\n", "\u001b[36m\t\t\t-> G 1.010 2.988 -0.076\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 2.3879\u001b[0m\n", "\u001b[36mNode 9 \t-> B -0.015 -0.068 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 1.169 3.426 0.005\u001b[0m\n", "\u001b[36m\t\t\t-> G 1.166 3.426 -0.087\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 4.5555\u001b[0m\n", "\u001b[36mNode 10 \t-> B -0.014 -0.065 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 1.325 3.863 0.006\u001b[0m\n", "\u001b[36m\t\t\t-> G 1.321 3.863 -0.098\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 2.1675\u001b[0m\n", "\u001b[36mNode 11 \t-> B -0.013 -0.061 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 1.480 4.301 0.007\u001b[0m\n", "\u001b[36m\t\t\t-> G 1.476 4.301 -0.109\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 4.1146\u001b[0m\n", "\u001b[36mNode 12 \t-> B -0.013 -0.058 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 1.636 4.739 0.009\u001b[0m\n", "\u001b[36m\t\t\t-> G 1.632 4.739 -0.120\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 1.9471\u001b[0m\n", "\u001b[36mNode 13 \t-> B -0.012 -0.055 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 1.792 5.176 0.010\u001b[0m\n", "\u001b[36m\t\t\t-> G 1.787 5.176 -0.131\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 3.6738\u001b[0m\n", "\u001b[36mNode 14 \t-> B -0.011 -0.052 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 1.948 5.614 0.011\u001b[0m\n", "\u001b[36m\t\t\t-> G 1.943 5.614 -0.142\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 1.7267\u001b[0m\n", "\u001b[36mNode 15 \t-> B -0.011 -0.048 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 2.104 6.052 0.012\u001b[0m\n", "\u001b[36m\t\t\t-> G 2.098 6.052 -0.153\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 3.2329\u001b[0m\n", "\u001b[36mNode 16 \t-> B -0.010 -0.045 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 2.260 6.489 0.014\u001b[0m\n", "\u001b[36m\t\t\t-> G 2.254 6.489 -0.164\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 1.5062\u001b[0m\n", "\u001b[36mNode 17 \t-> B -0.009 -0.042 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 2.415 6.927 0.015\u001b[0m\n", "\u001b[36m\t\t\t-> G 2.409 6.927 -0.175\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 2.7921\u001b[0m\n", "\u001b[36mNode 18 \t-> B -0.008 -0.039 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 2.571 7.364 0.016\u001b[0m\n", "\u001b[36m\t\t\t-> G 2.564 7.364 -0.186\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 1.2858\u001b[0m\n", "\u001b[36mNode 19 \t-> B -0.008 -0.035 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 2.727 7.802 0.017\u001b[0m\n", "\u001b[36m\t\t\t-> G 2.720 7.802 -0.197\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 2.3512\u001b[0m\n", "\u001b[36mNode 20 \t-> B -0.007 -0.032 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 2.883 8.239 0.019\u001b[0m\n", "\u001b[36m\t\t\t-> G 2.875 8.239 -0.208\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 1.0654\u001b[0m\n", "\u001b[36mNode 21 \t-> B -0.006 -0.028 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 3.038 8.677 0.020\u001b[0m\n", "\u001b[36m\t\t\t-> G 3.030 8.677 -0.219\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 1.9104\u001b[0m\n", "\u001b[36mNode 22 \t-> B -0.006 -0.026 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 3.194 9.114 0.021\u001b[0m\n", "\u001b[36m\t\t\t-> G 3.186 9.114 -0.230\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 0.8450\u001b[0m\n", "\u001b[36mNode 23 \t-> B -0.005 -0.022 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 3.350 9.552 0.023\u001b[0m\n", "\u001b[36m\t\t\t-> G 3.341 9.552 -0.241\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 1.4695\u001b[0m\n", "\u001b[36mNode 24 \t-> B -0.005 -0.022 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 3.508 9.988 0.024\u001b[0m\n", "\u001b[36m\t\t\t-> G 3.499 9.988 -0.252\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 0.3674\u001b[0m\n", "\u001b[36mNode 25 \t-> B 0.000 0.089 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 0.089 -0.206 0.000\u001b[0m\n", "\u001b[36m\t\t\t-> G 0.089 -0.206 -0.007\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 2.6141\u001b[0m\n", "\u001b[36mNode 26 \t-> B -0.010 0.019 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 0.019 -0.403 0.000\u001b[0m\n", "\u001b[36m\t\t\t-> G 0.019 -0.403 -0.001\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 7.3672\u001b[0m\n", "\u001b[36mNode 27 \t-> B -0.019 0.087 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 0.234 -0.800 0.000\u001b[0m\n", "\u001b[36m\t\t\t-> G 0.234 -0.800 -0.018\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 5.8780\u001b[0m\n", "\u001b[36mNode 28 \t-> B -0.019 0.084 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 0.390 -1.238 0.001\u001b[0m\n", "\u001b[36m\t\t\t-> G 0.389 -1.238 -0.030\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 2.8288\u001b[0m\n", "\u001b[36mNode 29 \t-> B -0.018 0.081 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 0.546 -1.676 0.001\u001b[0m\n", "\u001b[36m\t\t\t-> G 0.544 -1.676 -0.041\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 5.4372\u001b[0m\n", "\u001b[36mNode 30 \t-> B -0.017 0.078 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 0.702 -2.113 0.002\u001b[0m\n", "\u001b[36m\t\t\t-> G 0.700 -2.113 -0.053\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 2.6084\u001b[0m\n", "\u001b[36mNode 31 \t-> B -0.016 0.074 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 0.857 -2.551 0.003\u001b[0m\n", "\u001b[36m\t\t\t-> G 0.855 -2.551 -0.064\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 4.9963\u001b[0m\n", "\u001b[36mNode 32 \t-> B -0.016 0.071 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 1.013 -2.988 0.004\u001b[0m\n", "\u001b[36m\t\t\t-> G 1.010 -2.988 -0.076\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 2.3879\u001b[0m\n", "\u001b[36mNode 33 \t-> B -0.015 0.068 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 1.169 -3.426 0.005\u001b[0m\n", "\u001b[36m\t\t\t-> G 1.166 -3.426 -0.087\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 4.5555\u001b[0m\n", "\u001b[36mNode 34 \t-> B -0.014 0.065 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 1.325 -3.863 0.006\u001b[0m\n", "\u001b[36m\t\t\t-> G 1.321 -3.863 -0.098\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 2.1675\u001b[0m\n", "\u001b[36mNode 35 \t-> B -0.013 0.061 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 1.480 -4.301 0.007\u001b[0m\n", "\u001b[36m\t\t\t-> G 1.476 -4.301 -0.109\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 4.1146\u001b[0m\n", "\u001b[36mNode 36 \t-> B -0.013 0.058 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 1.636 -4.739 0.009\u001b[0m\n", "\u001b[36m\t\t\t-> G 1.632 -4.739 -0.120\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 1.9471\u001b[0m\n", "\u001b[36mNode 37 \t-> B -0.012 0.055 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 1.792 -5.176 0.010\u001b[0m\n", "\u001b[36m\t\t\t-> G 1.787 -5.176 -0.131\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 3.6738\u001b[0m\n", "\u001b[36mNode 38 \t-> B -0.011 0.052 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 1.948 -5.614 0.011\u001b[0m\n", "\u001b[36m\t\t\t-> G 1.943 -5.614 -0.142\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 1.7267\u001b[0m\n", "\u001b[36mNode 39 \t-> B -0.011 0.048 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 2.104 -6.052 0.012\u001b[0m\n", "\u001b[36m\t\t\t-> G 2.098 -6.052 -0.153\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 3.2329\u001b[0m\n", "\u001b[36mNode 40 \t-> B -0.010 0.045 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 2.260 -6.489 0.014\u001b[0m\n", "\u001b[36m\t\t\t-> G 2.254 -6.489 -0.164\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 1.5062\u001b[0m\n", "\u001b[36mNode 41 \t-> B -0.009 0.042 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 2.415 -6.927 0.015\u001b[0m\n", "\u001b[36m\t\t\t-> G 2.409 -6.927 -0.175\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 2.7921\u001b[0m\n", "\u001b[36mNode 42 \t-> B -0.008 0.039 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 2.571 -7.364 0.016\u001b[0m\n", "\u001b[36m\t\t\t-> G 2.564 -7.364 -0.186\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 1.2858\u001b[0m\n", "\u001b[36mNode 43 \t-> B -0.008 0.035 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 2.727 -7.802 0.017\u001b[0m\n", "\u001b[36m\t\t\t-> G 2.720 -7.802 -0.197\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 2.3512\u001b[0m\n", "\u001b[36mNode 44 \t-> B -0.007 0.032 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 2.883 -8.239 0.019\u001b[0m\n", "\u001b[36m\t\t\t-> G 2.875 -8.239 -0.208\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 1.0654\u001b[0m\n", "\u001b[36mNode 45 \t-> B -0.006 0.028 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 3.038 -8.677 0.020\u001b[0m\n", "\u001b[36m\t\t\t-> G 3.030 -8.677 -0.219\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 1.9104\u001b[0m\n", "\u001b[36mNode 46 \t-> B -0.006 0.026 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 3.194 -9.114 0.021\u001b[0m\n", "\u001b[36m\t\t\t-> G 3.186 -9.114 -0.230\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 0.8450\u001b[0m\n", "\u001b[36mNode 47 \t-> B -0.005 0.022 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 3.350 -9.552 0.023\u001b[0m\n", "\u001b[36m\t\t\t-> G 3.341 -9.552 -0.241\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 1.4695\u001b[0m\n", "\u001b[36mNode 48 \t-> B -0.005 0.022 -0.000\u001b[0m\n", "\u001b[36m\t\t\t-> A 3.508 -9.988 0.024\u001b[0m\n", "\u001b[36m\t\t\t-> G 3.499 -9.988 -0.252\u001b[0m\n", "\u001b[36m\tNode mass:\u001b[0m\n", "\u001b[36m\t\tMatrix: 0.3674\u001b[0m\n", " Updated the beam C, modal C and K matrices with the terms from the\n", "gravity linearisation\u001b[0m\n", "\u001b[0m\n", "Aeroelastic system assembled:\u001b[0m\n", "\u001b[34m\tAerodynamic states: 1536\u001b[0m\n", "\u001b[34m\tStructural states: 594\u001b[0m\n", "\u001b[34m\tTotal states: 2130\u001b[0m\n", "\u001b[34m\tInputs: 893\u001b[0m\n", "\u001b[34m\tOutputs: 891\u001b[0m\n", "\u001b[34mFinal system is:\u001b[0m\n", "\u001b[34mState-space system\u001b[0m\n", "\u001b[34mStates: 2130\u001b[0m\n", "\u001b[34mInputs: 893\u001b[0m\n", "\u001b[34mOutputs: 891\u001b[0m\n", "\u001b[34m\u001b[0m\n", "\u001b[36mGenerating an instance of AsymptoticStability\u001b[0m\n", "Variable reference_velocity has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1.0\u001b[0m\n", "Variable display_root_locus has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable velocity_analysis has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: []\u001b[0m\n", "Variable iterative_eigvals has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable modes_to_plot has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: []\u001b[0m\n", "Variable postprocessors has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: []\u001b[0m\n", "Variable postprocessors_settings has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Dynamical System Eigenvalues\u001b[0m\n", "Calculating eigenvalues using direct method\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "|==============|==============|==============|==============|==============|==============|==============|\u001b[0m\n", "| mode | eval_real | eval_imag | freq_n (Hz) | freq_d (Hz) | damping | period (s) |\u001b[0m\n", "|==============|==============|==============|==============|==============|==============|==============|\u001b[0m\n", "| 0 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 1 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 2 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 3 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 4 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 5 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 6 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 7 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 8 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 9 | -0.000000 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 10 | -0.000884 | -0.321712 | 0.051202 | 0.051202 | 0.002747 | 19.530439 |\u001b[0m\n", "| 11 | -0.000884 | 0.321712 | 0.051202 | 0.051202 | 0.002747 | 19.530439 |\u001b[0m\n", "| 12 | -0.008470 | -0.391287 | 0.062290 | 0.062275 | 0.021642 | 16.057731 |\u001b[0m\n", "| 13 | -0.008470 | 0.391287 | 0.062290 | 0.062275 | 0.021642 | 16.057731 |\u001b[0m\n", "| 14 | -0.022506 | 0.000000 | 0.003582 | 0.000000 | 1.000000 | inf |\u001b[0m\n", "| 15 | -0.064808 | -53.732514 | 8.551801 | 8.551795 | 0.001206 | 0.116935 |\u001b[0m\n", "| 16 | -0.064808 | 53.732514 | 8.551801 | 8.551795 | 0.001206 | 0.116935 |\u001b[0m\n", "| 17 | -0.101946 | 68.319141 | 10.873341 | 10.873329 | 0.001492 | 0.091968 |\u001b[0m\n", "| 18 | -0.101946 | -68.319141 | 10.873341 | 10.873329 | 0.001492 | 0.091968 |\u001b[0m\n", "| 19 | -0.147587 | 83.265282 | 13.252102 | 13.252081 | 0.001772 | 0.075460 |\u001b[0m\n", "| 20 | -0.147587 | -83.265282 | 13.252102 | 13.252081 | 0.001772 | 0.075460 |\u001b[0m\n", "| 21 | -0.248703 | -109.925782 | 17.495276 | 17.495232 | 0.002262 | 0.057158 |\u001b[0m\n", "| 22 | -0.248703 | 109.925782 | 17.495276 | 17.495232 | 0.002262 | 0.057158 |\u001b[0m\n", "| 23 | -0.293472 | 120.387631 | 19.160344 | 19.160287 | 0.002438 | 0.052191 |\u001b[0m\n", "| 24 | -0.293472 | -120.387631 | 19.160344 | 19.160287 | 0.002438 | 0.052191 |\u001b[0m\n", "| 25 | -0.350319 | 132.903280 | 21.152288 | 21.152214 | 0.002636 | 0.047276 |\u001b[0m\n", "| 26 | -0.350319 | -132.903280 | 21.152288 | 21.152214 | 0.002636 | 0.047276 |\u001b[0m\n", "| 27 | -0.376401 | 138.516954 | 22.045739 | 22.045658 | 0.002717 | 0.045360 |\u001b[0m\n", "| 28 | -0.376401 | -138.516954 | 22.045739 | 22.045658 | 0.002717 | 0.045360 |\u001b[0m\n", "| 29 | -0.494445 | 162.714232 | 25.896894 | 25.896774 | 0.003039 | 0.038615 |\u001b[0m\n", "| 30 | -0.494445 | -162.714232 | 25.896894 | 25.896774 | 0.003039 | 0.038615 |\u001b[0m\n", "| 31 | -0.511651 | -166.238306 | 26.457773 | 26.457648 | 0.003078 | 0.037796 |\u001b[0m\n", "| 32 | -0.511651 | 166.238306 | 26.457773 | 26.457648 | 0.003078 | 0.037796 |\u001b[0m\n", "| 33 | -0.559180 | 175.709816 | 27.965227 | 27.965086 | 0.003182 | 0.035759 |\u001b[0m\n", "| 34 | -0.559180 | -175.709816 | 27.965227 | 27.965086 | 0.003182 | 0.035759 |\u001b[0m\n", "| 35 | -0.569755 | 177.873274 | 28.309556 | 28.309411 | 0.003203 | 0.035324 |\u001b[0m\n", "| 36 | -0.569755 | -177.873274 | 28.309556 | 28.309411 | 0.003203 | 0.035324 |\u001b[0m\n", "| 37 | -0.669914 | -197.999020 | 31.512703 | 31.512523 | 0.003383 | 0.031733 |\u001b[0m\n", "| 38 | -0.669914 | 197.999020 | 31.512703 | 31.512523 | 0.003383 | 0.031733 |\u001b[0m\n", "| 39 | -0.678424 | 199.782714 | 31.796590 | 31.796406 | 0.003396 | 0.031450 |\u001b[0m\n", "| 40 | -0.678424 | -199.782714 | 31.796590 | 31.796406 | 0.003396 | 0.031450 |\u001b[0m\n", "| 41 | -0.715684 | 207.440562 | 33.015387 | 33.015191 | 0.003450 | 0.030289 |\u001b[0m\n", "| 42 | -0.715684 | -207.440562 | 33.015387 | 33.015191 | 0.003450 | 0.030289 |\u001b[0m\n", "| 43 | -0.721193 | -208.623018 | 33.203583 | 33.203385 | 0.003457 | 0.030117 |\u001b[0m\n", "| 44 | -0.721193 | 208.623018 | 33.203583 | 33.203385 | 0.003457 | 0.030117 |\u001b[0m\n", "| 45 | -0.796838 | -224.809928 | 35.779836 | 35.779611 | 0.003544 | 0.027949 |\u001b[0m\n", "| 46 | -0.796838 | 224.809928 | 35.779836 | 35.779611 | 0.003544 | 0.027949 |\u001b[0m\n", "| 47 | -0.801462 | -225.851223 | 35.945565 | 35.945339 | 0.003549 | 0.027820 |\u001b[0m\n", "| 48 | -0.801462 | 225.851223 | 35.945565 | 35.945339 | 0.003549 | 0.027820 |\u001b[0m\n", "| 49 | -0.823218 | 257.880058 | 41.043095 | 41.042886 | 0.003192 | 0.024365 |\u001b[0m\n", "| 50 | -0.823218 | -257.880058 | 41.043095 | 41.042886 | 0.003192 | 0.024365 |\u001b[0m\n", "| 51 | -0.829849 | 232.223377 | 36.959734 | 36.959498 | 0.003573 | 0.027057 |\u001b[0m\n", "| 52 | -0.829849 | -232.223377 | 36.959734 | 36.959498 | 0.003573 | 0.027057 |\u001b[0m\n", "| 53 | -0.833132 | 232.986723 | 37.081226 | 37.080989 | 0.003576 | 0.026968 |\u001b[0m\n", "| 54 | -0.833132 | -232.986723 | 37.081226 | 37.080989 | 0.003576 | 0.026968 |\u001b[0m\n", "| 55 | -0.837692 | 252.830750 | 40.239484 | 40.239264 | 0.003313 | 0.024851 |\u001b[0m\n", "| 56 | -0.837692 | -252.830750 | 40.239484 | 40.239264 | 0.003313 | 0.024851 |\u001b[0m\n", "| 57 | -0.843057 | -274.636584 | 43.709976 | 43.709770 | 0.003070 | 0.022878 |\u001b[0m\n", "| 58 | -0.843057 | 274.636584 | 43.709976 | 43.709770 | 0.003070 | 0.022878 |\u001b[0m\n", "| 59 | -0.855990 | -264.468496 | 42.091689 | 42.091468 | 0.003237 | 0.023758 |\u001b[0m\n", "| 60 | -0.855990 | 264.468496 | 42.091689 | 42.091468 | 0.003237 | 0.023758 |\u001b[0m\n", "| 61 | -0.864725 | 271.184095 | 43.160509 | 43.160289 | 0.003189 | 0.023169 |\u001b[0m\n", "| 62 | -0.864725 | -271.184095 | 43.160509 | 43.160289 | 0.003189 | 0.023169 |\u001b[0m\n", "| 63 | -0.871325 | -283.421756 | 45.108187 | 45.107973 | 0.003074 | 0.022169 |\u001b[0m\n", "| 64 | -0.871325 | 283.421756 | 45.108187 | 45.107973 | 0.003074 | 0.022169 |\u001b[0m\n", "| 65 | -0.878445 | 267.336890 | 42.548217 | 42.547987 | 0.003286 | 0.023503 |\u001b[0m\n", "| 66 | -0.878445 | -267.336890 | 42.548217 | 42.547987 | 0.003286 | 0.023503 |\u001b[0m\n", "| 67 | -0.882869 | -280.833495 | 44.696260 | 44.696039 | 0.003144 | 0.022373 |\u001b[0m\n", "| 68 | -0.882869 | 280.833495 | 44.696260 | 44.696039 | 0.003144 | 0.022373 |\u001b[0m\n", "| 69 | -0.884024 | -245.027542 | 38.997598 | 38.997344 | 0.003608 | 0.025643 |\u001b[0m\n", "| 70 | -0.884024 | 245.027542 | 38.997598 | 38.997344 | 0.003608 | 0.025643 |\u001b[0m\n", "| 71 | -0.886589 | -245.661879 | 39.098557 | 39.098302 | 0.003609 | 0.025577 |\u001b[0m\n", "| 72 | -0.886589 | 245.661879 | 39.098557 | 39.098302 | 0.003609 | 0.025577 |\u001b[0m\n", "| 73 | -0.891210 | -288.915188 | 45.982499 | 45.982280 | 0.003085 | 0.021748 |\u001b[0m\n", "| 74 | -0.891210 | 288.915188 | 45.982499 | 45.982280 | 0.003085 | 0.021748 |\u001b[0m\n", "| 75 | -0.908699 | 251.206723 | 39.981053 | 39.980792 | 0.003617 | 0.025012 |\u001b[0m\n", "| 76 | -0.908699 | -251.206723 | 39.981053 | 39.980792 | 0.003617 | 0.025012 |\u001b[0m\n", "| 77 | -0.910251 | -251.606127 | 40.044621 | 40.044359 | 0.003618 | 0.024972 |\u001b[0m\n", "| 78 | -0.910251 | 251.606127 | 40.044621 | 40.044359 | 0.003618 | 0.024972 |\u001b[0m\n", "| 79 | -0.914184 | -241.156682 | 38.381554 | 38.381278 | 0.003791 | 0.026054 |\u001b[0m\n", "| 80 | -0.914184 | 241.156682 | 38.381554 | 38.381278 | 0.003791 | 0.026054 |\u001b[0m\n", "| 81 | -0.915395 | 290.517030 | 46.237451 | 46.237221 | 0.003151 | 0.021628 |\u001b[0m\n", "| 82 | -0.915395 | -290.517030 | 46.237451 | 46.237221 | 0.003151 | 0.021628 |\u001b[0m\n", "| 83 | -0.933974 | -278.955360 | 44.397373 | 44.397124 | 0.003348 | 0.022524 |\u001b[0m\n", "| 84 | -0.933974 | 278.955360 | 44.397373 | 44.397124 | 0.003348 | 0.022524 |\u001b[0m\n", "| 85 | -0.943144 | -260.320871 | 41.431625 | 41.431353 | 0.003623 | 0.024136 |\u001b[0m\n", "| 86 | -0.943144 | 260.320871 | 41.431625 | 41.431353 | 0.003623 | 0.024136 |\u001b[0m\n", "| 87 | -0.944542 | -260.700481 | 41.492043 | 41.491770 | 0.003623 | 0.024101 |\u001b[0m\n", "| 88 | -0.944542 | 260.700481 | 41.492043 | 41.491770 | 0.003623 | 0.024101 |\u001b[0m\n", "| 89 | -0.953003 | -294.814043 | 46.921357 | 46.921112 | 0.003233 | 0.021312 |\u001b[0m\n", "| 90 | -0.953003 | 294.814043 | 46.921357 | 46.921112 | 0.003233 | 0.021312 |\u001b[0m\n", "| 91 | -0.960626 | -295.652742 | 47.054844 | 47.054595 | 0.003249 | 0.021252 |\u001b[0m\n", "| 92 | -0.960626 | 295.652742 | 47.054844 | 47.054595 | 0.003249 | 0.021252 |\u001b[0m\n", "| 93 | -0.960976 | -265.315963 | 42.226624 | 42.226347 | 0.003622 | 0.023682 |\u001b[0m\n", "| 94 | -0.960976 | 265.315963 | 42.226624 | 42.226347 | 0.003622 | 0.023682 |\u001b[0m\n", "| 95 | -0.961739 | 300.017779 | 47.749558 | 47.749313 | 0.003206 | 0.020943 |\u001b[0m\n", "| 96 | -0.961739 | -300.017779 | 47.749558 | 47.749313 | 0.003206 | 0.020943 |\u001b[0m\n", "| 97 | -0.961940 | -265.596058 | 42.271203 | 42.270925 | 0.003622 | 0.023657 |\u001b[0m\n", "| 98 | -0.961940 | 265.596058 | 42.271203 | 42.270925 | 0.003622 | 0.023657 |\u001b[0m\n", "| 99 | -0.965385 | -266.582863 | 42.428259 | 42.427980 | 0.003621 | 0.023569 |\u001b[0m\n", "\u001b[36mFINISHED - Elapsed time = 16.3462206 seconds\u001b[0m\n", "\u001b[36mFINISHED - CPU process time = 68.0131146 seconds\u001b[0m\n" ] } ], "source": [ "data = sharpy.sharpy_main.main(['', ws.case_route + '/' + ws.case_name + '.sharpy'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Post-processing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Nonlinear Equilibrium\n", "\n", "The files can be opened with Paraview to see the deformation and aerodynamic loading on the flying wing in trim conditions." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Asymptotic Stability" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "eigenvalues_trim = np.loadtxt('./output/horten_u_inf2800_M4N11Msf5/stability/eigenvalues.dat')\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Flight Dynamics modes\n", "\n", "The flight dynamics modes can be found close to the origin of the Argand diagram. In particular, the phugoid is the mode that is closest to the imaginary axis. An exercise is left to the user to compare this phugoid predicition with the nonlinear response!" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig = plt.figure()\n", "plt.scatter(eigenvalues_trim[:, 0], eigenvalues_trim[:, 1],\n", " marker='x',\n", " color='k')\n", "plt.xlim(-0.5, 0.5)\n", "plt.ylim(-0.5, 0.5)\n", "plt.grid()\n", "plt.xlabel('Real Part, $Re(\\lambda)$ [rad/s]')\n", "plt.ylabel('Imaginary Part, $Im(\\lambda)$ [rad/s]');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Structural Modes\n", "\n", "Looking further out on the plot, the structural modes appear. There is a curve given the Newmark-$\\beta$ integration scheme and on top of it several modes are damped by the presence of the aerodynamics.\n", "\n", "Try changing `newmark_damp` in the `LinearAssembler` settings to see how this plot changes!" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig = plt.figure()\n", "plt.scatter(eigenvalues_trim[:, 0], eigenvalues_trim[:, 1],\n", " marker='x',\n", " color='k')\n", "plt.xlim(-5, 0.5)\n", "plt.ylim(-200, 200)\n", "plt.grid()\n", "plt.xlabel('Real Part, $Re(\\lambda)$ [rad/s]')\n", "plt.ylabel('Imaginary Part, $Im(\\lambda)$ [rad/s]');" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: docs/source/content/example_notebooks/nonlinear_t-tail_HALE.ipynb ================================================ { "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", "%matplotlib inline\n", "%config InlineBackend.figure_format = 'svg'\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from IPython.display import Image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# T-Tail HALE Model tutorial" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The HALE T-Tail model intends to be a representative example of a typical HALE configuration, with high flexibility and aspect-ratio.\n", "\n", "A geometry outline and a summary of the beam properties are given next" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "url = 'https://raw.githubusercontent.com/ImperialCollegeLondon/sharpy/dev_doc/docs/source/content/example_notebooks/images/t-tail_geometry.png'\n", "Image(url=url, width=800)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "url = 'https://raw.githubusercontent.com/ImperialCollegeLondon/sharpy/dev_doc/docs/source/content/example_notebooks/images/t-tail_properties.png'\n", "Image(url=url, width=500)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This case is included in `tests/coupled/simple_HALE/`. The `generate_hale.py` file in that folder is the one that, if executed, generates all the required SHARPy files. This document is a step by step guide to how to process that case.\n", "\n", "The T-Tail HALE model is subject to a 20% 1-cos spanwise constant gust." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, let's start with importing SHARPy in our Python environment." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "import sharpy\n", "import sharpy.sharpy_main as sharpy_main" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now the `generate_HALE.py` file needs to be executed." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "route_to_case = '../../../../sharpy/cases/coupled/simple_HALE/'\n", "%run '../../../../sharpy/cases/coupled/simple_HALE/generate_hale.py'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There should be 3 new files, apart from the original `generate_hale.py`:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "__init__.py \u001b[34moutput\u001b[m\u001b[m simple_HALE.fem.h5\n", "generate_hale.py simple_HALE.aero.h5 simple_HALE.sharpy\n" ] } ], "source": [ "!ls ../../../../sharpy/cases/coupled/simple_HALE/" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "SHARPy can be run now. In the terminal, doing `cd` to the `simple_HALE` folder, the command would look like:\n", "```\n", "sharpy simple_HALE.sharpy\n", "```\n", "\n", "From a python console with `import sharpy` already run, the command is:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--------------------------------------------------------------------------------\u001b[0m\n", " ###### ## ## ### ######## ######## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ####\u001b[0m\n", " ###### ######### ## ## ######## ######## ##\u001b[0m\n", " ## ## ## ######### ## ## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ##\u001b[0m\n", " ###### ## ## ## ## ## ## ## ##\u001b[0m\n", "--------------------------------------------------------------------------------\u001b[0m\n", "Aeroelastics Lab, Aeronautics Department.\u001b[0m\n", " Copyright (c), Imperial College London.\u001b[0m\n", " All rights reserved.\u001b[0m\n", " License available at https://github.com/imperialcollegelondon/sharpy\u001b[0m\n", "\u001b[36mRunning SHARPy from /home/ng213/2TB/pazy_code/pazy-sharpy/lib/sharpy/docs/source/content/example_notebooks\u001b[0m\n", "\u001b[36mSHARPy being run is in /home/ng213/2TB/pazy_code/pazy-sharpy/lib/sharpy\u001b[0m\n", "\u001b[36mThe branch being run is dev_setting_error\u001b[0m\n", "\u001b[36mThe version and commit hash are: v1.2.1-348-g402e87e-402e87e\u001b[0m\n", "SHARPy output folder set\u001b[0m\n", "\u001b[34m\t/home/ng213/2TB/pazy_code/pazy-sharpy/lib/sharpy/cases/coupled/simple_HALE//output//simple_HALE/\u001b[0m\n", "\u001b[36mGenerating an instance of BeamLoader\u001b[0m\n", "Variable for_pos has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0, 0]\u001b[0m\n", "\u001b[36mGenerating an instance of AerogridLoader\u001b[0m\n", "Variable control_surface_deflection has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: []\u001b[0m\n", "Variable control_surface_deflection_generator_settings has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable dx1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: -1.0\u001b[0m\n", "Variable ndx1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable r has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1.0\u001b[0m\n", "Variable dxmax has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: -1.0\u001b[0m\n", "\u001b[34mThe aerodynamic grid contains 5 surfaces\u001b[0m\n", "\u001b[34m Surface 0, M=4, N=16\u001b[0m\n", " Wake 0, M=20, N=16\u001b[0m\n", "\u001b[34m Surface 1, M=4, N=16\u001b[0m\n", " Wake 1, M=20, N=16\u001b[0m\n", "\u001b[34m Surface 2, M=4, N=8\u001b[0m\n", " Wake 2, M=20, N=8\u001b[0m\n", "\u001b[34m Surface 3, M=4, N=8\u001b[0m\n", " Wake 3, M=20, N=8\u001b[0m\n", "\u001b[34m Surface 4, M=4, N=8\u001b[0m\n", " Wake 4, M=20, N=8\u001b[0m\n", " In total: 224 bound panels\u001b[0m\n", " In total: 1120 wake panels\u001b[0m\n", " Total number of panels = 1344\u001b[0m\n", "\u001b[36mGenerating an instance of StaticTrim\u001b[0m\n", "Variable print_info has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable max_iter has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 100\u001b[0m\n", "Variable fz_tolerance has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.01\u001b[0m\n", "Variable fx_tolerance has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.01\u001b[0m\n", "Variable m_tolerance has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.01\u001b[0m\n", "Variable tail_cs_index has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable thrust_nodes has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0]\u001b[0m\n", "Variable initial_angle_eps has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.05\u001b[0m\n", "Variable initial_thrust_eps has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 2.0\u001b[0m\n", "Variable relaxation_factor has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.2\u001b[0m\n", "Variable save_info has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "\u001b[36mGenerating an instance of StaticCoupled\u001b[0m\n", "Variable correct_forces_method has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable runtime_generators has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "\u001b[36mGenerating an instance of NonLinearStatic\u001b[0m\n", "Variable newmark_damp has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0001\u001b[0m\n", "Variable gravity_dir has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 1.0]\u001b[0m\n", "Variable relaxation_factor has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.3\u001b[0m\n", "Variable dt has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.01\u001b[0m\n", "Variable num_steps has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 500\u001b[0m\n", "Variable initial_position has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0. 0. 0.]\u001b[0m\n", "\u001b[36mGenerating an instance of StaticUvlm\u001b[0m\n", "Variable iterative_solver has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable iterative_tol has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0001\u001b[0m\n", "Variable iterative_precond has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable cfl1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable vortex_radius has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "Variable vortex_radius_wake_ind has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "Variable rbm_vel_g has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]\u001b[0m\n", "Variable centre_rot_g has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 0.0]\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "|==========|==========|==========|==========|==========|==========|==========|==========|==========|==========|\u001b[0m\n", "| iter |alpha[deg]|elev[deg] | thrust | Fx | Fy | Fz | Mx | My | Mz |\u001b[0m\n", "|==========|==========|==========|==========|==========|==========|==========|==========|==========|==========|\u001b[0m\n", "| 0 | 4.3100 | -2.0800 | 6.1600 | -0.3600 | -0.0000 | 0.8010 | 0.0000 | -0.6935 | 0.0000 |\u001b[0m\n", "| 0 | 7.1748 | -4.9448 | 6.1600 | -27.8570 | -0.0000 | 280.1530 | 0.0000 |-105.5351 | 0.0000 |\u001b[0m\n", "| 0 | 4.3100 | 0.7848 | 6.1600 | -2.4506 | -0.0000 | 38.7819 | 0.0000 |-377.3661 | -0.0000 |\u001b[0m\n", "| 0 | 4.3100 | -2.0800 | 8.1600 | -2.3600 | -0.0000 | 0.8010 | 0.0000 | -0.6935 | 0.0000 |\u001b[0m\n", "| 1 | 4.3018 | -2.0771 | 5.8000 | 0.0660 | 0.0000 | -0.2782 | -0.0000 | 0.2446 | 0.0000 |\u001b[0m\n", "| 2 | 4.3039 | -2.0778 | 5.8558 | -0.0068 | -0.0000 | 0.0003 | 0.0000 | 0.0008 | 0.0000 |\u001b[0m\n", "\u001b[36mGenerating an instance of BeamLoads\u001b[0m\n", "Variable output_file_name has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: beam_loads\u001b[0m\n", "\u001b[36mGenerating an instance of AerogridPlot\u001b[0m\n", "Variable include_unsteady_applied_forces has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable name_prefix has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable include_velocities has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable include_incidence_angle has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable num_cores has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable vortex_radius has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "\u001b[34m...Finished\u001b[0m\n", "\u001b[36mGenerating an instance of BeamPlot\u001b[0m\n", "Variable include_FoR has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable include_applied_moments has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable name_prefix has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable output_rbm has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "\u001b[34m...Finished\u001b[0m\n", "\u001b[36mGenerating an instance of DynamicCoupled\u001b[0m\n", "Variable print_info has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable structural_substeps has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable dynamic_relaxation has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable controller_id has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable controller_settings has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable cleanup_previous_solution has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable steps_without_unsteady_force has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable pseudosteps_ramp_unsteady_force has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable correct_forces_method has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable network_settings has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "Variable runtime_generators has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: {}\u001b[0m\n", "\u001b[36mGenerating an instance of NonLinearDynamicCoupledStep\u001b[0m\n", "Variable num_load_steps has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable gravity_dir has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 1.0]\u001b[0m\n", "Variable relaxation_factor has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.3\u001b[0m\n", "Variable balancing has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable initial_velocity_direction has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [-1.0, 0.0, 0.0]\u001b[0m\n", "\u001b[36mGenerating an instance of StepUvlm\u001b[0m\n", "Variable iterative_solver has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable iterative_tol has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0001\u001b[0m\n", "Variable iterative_precond has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable cfl1 has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable vortex_radius has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "Variable vortex_radius_wake_ind has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "Variable interp_coords has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable filter_method has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable interp_method has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0\u001b[0m\n", "Variable yaw_slerp has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0\u001b[0m\n", "Variable centre_rot has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [0.0, 0.0, 0.0]\u001b[0m\n", "Variable quasi_steady has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "\u001b[36mgamma_dot_filtering does not support even numbers.Changing 6 to 7\u001b[0m\n", "\u001b[36mGenerating an instance of BeamLoads\u001b[0m\n", "Variable output_file_name has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: beam_loads\u001b[0m\n", "\u001b[36mGenerating an instance of BeamPlot\u001b[0m\n", "Variable include_FoR has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable include_applied_moments has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "Variable name_prefix has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable output_rbm has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: True\u001b[0m\n", "\u001b[36mGenerating an instance of AerogridPlot\u001b[0m\n", "Variable include_forward_motion has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable include_unsteady_applied_forces has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable name_prefix has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: \u001b[0m\n", "Variable u_inf has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0\u001b[0m\n", "Variable dt has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0\u001b[0m\n", "Variable include_velocities has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable include_incidence_angle has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: False\u001b[0m\n", "Variable num_cores has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1\u001b[0m\n", "Variable vortex_radius has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1e-06\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "|=======|========|======|==============|==============|==============|==============|==============|\u001b[0m\n", "| ts | t | iter | struc ratio | iter time | residual vel | FoR_vel(x) | FoR_vel(z) |\u001b[0m\n", "|=======|========|======|==============|==============|==============|==============|==============|\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/home/ng213/2TB/pazy_code/pazy-sharpy/lib/sharpy/sharpy/aero/utils/uvlmlib.py:264: RuntimeWarning: invalid value encountered in true_divide\n", " flightconditions.uinf_direction = np.ctypeslib.as_ctypes(ts_info.u_ext[0][:, 0, 0]/flightconditions.uinf)\n", "/home/ng213/2TB/pazy_code/pazy-sharpy/lib/sharpy/sharpy/aero/utils/uvlmlib.py:325: RuntimeWarning: invalid value encountered in true_divide\n", " flightconditions.uinf_direction = np.ctypeslib.as_ctypes(ts_info.u_ext[0][:, 0, 0]/flightconditions.uinf)\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "| 1 | 0.0250 | 2 | 0.660158 | 0.788767 | -4.245993 |-9.971789e+00 |-7.504783e-01 |\u001b[0m\n", "| 2 | 0.0500 | 3 | 0.698975 | 0.965136 | -4.635143 |-9.971745e+00 |-7.505379e-01 |\u001b[0m\n", "| 3 | 0.0750 | 2 | 0.699509 | 0.744684 | -4.142638 |-9.971678e+00 |-7.506741e-01 |\u001b[0m\n", "| 4 | 0.1000 | 2 | 0.702553 | 0.742001 | -4.583042 |-9.971654e+00 |-7.508442e-01 |\u001b[0m\n", "| 5 | 0.1250 | 1 | 0.705584 | 0.512845 | -4.384776 |-9.971729e+00 |-7.509873e-01 |\u001b[0m\n", "| 6 | 0.1500 | 2 | 0.710967 | 0.733027 | -4.729199 |-9.971808e+00 |-7.510934e-01 |\u001b[0m\n", "| 7 | 0.1750 | 1 | 0.717295 | 0.511901 | -4.049012 |-9.971749e+00 |-7.511575e-01 |\u001b[0m\n", "| 8 | 0.2000 | 1 | 0.687752 | 0.519393 | -4.147149 |-9.971609e+00 |-7.511620e-01 |\u001b[0m\n", "| 9 | 0.2250 | 1 | 0.657027 | 0.533608 | -4.243231 |-9.971483e+00 |-7.511461e-01 |\u001b[0m\n", "| 10 | 0.2500 | 1 | 0.650443 | 0.540742 | -4.352169 |-9.971378e+00 |-7.511644e-01 |\u001b[0m\n", "| 11 | 0.2750 | 1 | 0.656218 | 0.534101 | -4.191190 |-9.971371e+00 |-7.511728e-01 |\u001b[0m\n", "| 12 | 0.3000 | 1 | 0.671768 | 0.554114 | -4.122823 |-9.971442e+00 |-7.511539e-01 |\u001b[0m\n", "| 13 | 0.3250 | 1 | 0.656158 | 0.537498 | -4.418002 |-9.971429e+00 |-7.511300e-01 |\u001b[0m\n", "| 14 | 0.3500 | 1 | 0.704366 | 0.508580 | -4.678378 |-9.971349e+00 |-7.510900e-01 |\u001b[0m\n", "| 15 | 0.3750 | 1 | 0.719952 | 0.514705 | -4.402185 |-9.971299e+00 |-7.510668e-01 |\u001b[0m\n", "| 16 | 0.4000 | 1 | 0.705019 | 0.512428 | -4.448460 |-9.971290e+00 |-7.510571e-01 |\u001b[0m\n", "| 17 | 0.4250 | 1 | 0.713898 | 0.511336 | -4.476767 |-9.971358e+00 |-7.510533e-01 |\u001b[0m\n", "| 18 | 0.4500 | 1 | 0.715516 | 0.511316 | -4.534268 |-9.971488e+00 |-7.510753e-01 |\u001b[0m\n", "| 19 | 0.4750 | 1 | 0.709680 | 0.508300 | -4.662816 |-9.971575e+00 |-7.510784e-01 |\u001b[0m\n", "| 20 | 0.5000 | 2 | 0.704182 | 0.744397 | -4.812376 |-9.971586e+00 |-7.510241e-01 |\u001b[0m\n", "| 21 | 0.5250 | 3 | 0.708522 | 0.965943 | -4.402757 |-9.971726e+00 |-7.507397e-01 |\u001b[0m\n", "| 22 | 0.5500 | 4 | 0.708365 | 1.200794 | -4.475885 |-9.972374e+00 |-7.497152e-01 |\u001b[0m\n", "| 23 | 0.5750 | 4 | 0.708962 | 1.199187 | -4.264700 |-9.973956e+00 |-7.471042e-01 |\u001b[0m\n", "| 24 | 0.6000 | 4 | 0.709056 | 1.201371 | -4.148547 |-9.977005e+00 |-7.417512e-01 |\u001b[0m\n", "| 25 | 0.6250 | 4 | 0.707360 | 1.214448 | -4.101431 |-9.982064e+00 |-7.323149e-01 |\u001b[0m\n", "| 26 | 0.6500 | 4 | 0.707825 | 1.211973 | -4.083226 |-9.989506e+00 |-7.173484e-01 |\u001b[0m\n", "| 27 | 0.6750 | 4 | 0.705636 | 1.209574 | -4.081045 |-9.999533e+00 |-6.954412e-01 |\u001b[0m\n", "| 28 | 0.7000 | 4 | 0.702970 | 1.199970 | -4.095091 |-1.001216e+01 |-6.653667e-01 |\u001b[0m\n", "| 29 | 0.7250 | 4 | 0.705531 | 1.206136 | -4.120431 |-1.002734e+01 |-6.261403e-01 |\u001b[0m\n", "| 30 | 0.7500 | 4 | 0.702935 | 1.213436 | -4.153507 |-1.004515e+01 |-5.771350e-01 |\u001b[0m\n", "| 31 | 0.7750 | 4 | 0.698230 | 1.203890 | -4.194909 |-1.006573e+01 |-5.180671e-01 |\u001b[0m\n", "| 32 | 0.8000 | 4 | 0.703664 | 1.208508 | -4.236529 |-1.008907e+01 |-4.489843e-01 |\u001b[0m\n", "| 33 | 0.8250 | 4 | 0.701559 | 1.203176 | -4.281618 |-1.011502e+01 |-3.702984e-01 |\u001b[0m\n", "| 34 | 0.8500 | 4 | 0.700114 | 1.215140 | -4.272027 |-1.014323e+01 |-2.827796e-01 |\u001b[0m\n", "| 35 | 0.8750 | 4 | 0.701394 | 1.206869 | -4.137638 |-1.017318e+01 |-1.875441e-01 |\u001b[0m\n", "| 36 | 0.9000 | 4 | 0.698019 | 1.213434 | -4.087979 |-1.020434e+01 |-8.606667e-02 |\u001b[0m\n", "| 37 | 0.9250 | 4 | 0.697820 | 1.213205 | -4.081173 |-1.023614e+01 | 1.986601e-02 |\u001b[0m\n", "| 38 | 0.9500 | 4 | 0.692925 | 1.210431 | -4.067952 |-1.026798e+01 | 1.282642e-01 |\u001b[0m\n", "| 39 | 0.9750 | 4 | 0.692719 | 1.203242 | -4.040392 |-1.029913e+01 | 2.371167e-01 |\u001b[0m\n", "| 40 | 1.0000 | 4 | 0.695089 | 1.205393 | -4.023531 |-1.032875e+01 | 3.445071e-01 |\u001b[0m\n", "| 41 | 1.0250 | 5 | 0.696443 | 1.438348 | -4.548408 |-1.035594e+01 | 4.482817e-01 |\u001b[0m\n", "| 42 | 1.0500 | 5 | 0.699286 | 1.456647 | -4.496733 |-1.037989e+01 | 5.458046e-01 |\u001b[0m\n", "| 43 | 1.0750 | 5 | 0.693181 | 1.451422 | -4.426046 |-1.040006e+01 | 6.342879e-01 |\u001b[0m\n", "| 44 | 1.1000 | 5 | 0.672004 | 1.475300 | -4.379701 |-1.041606e+01 | 7.112695e-01 |\u001b[0m\n", "| 45 | 1.1250 | 5 | 0.672251 | 1.468131 | -4.402469 |-1.042753e+01 | 7.747229e-01 |\u001b[0m\n", "| 46 | 1.1500 | 5 | 0.689079 | 1.445732 | -4.477108 |-1.043429e+01 | 8.230013e-01 |\u001b[0m\n", "| 47 | 1.1750 | 5 | 0.690835 | 1.446807 | -4.548994 |-1.043627e+01 | 8.549797e-01 |\u001b[0m\n", "| 48 | 1.2000 | 5 | 0.692859 | 1.458130 | -4.602614 |-1.043347e+01 | 8.701306e-01 |\u001b[0m\n", "| 49 | 1.2250 | 5 | 0.692201 | 1.450715 | -4.670662 |-1.042601e+01 | 8.683187e-01 |\u001b[0m\n", "| 50 | 1.2500 | 4 | 0.692043 | 1.211332 | -4.009458 |-1.041409e+01 | 8.498052e-01 |\u001b[0m\n", "| 51 | 1.2750 | 5 | 0.691406 | 1.450458 | -4.437774 |-1.039785e+01 | 8.153184e-01 |\u001b[0m\n", "| 52 | 1.3000 | 5 | 0.690078 | 1.456433 | -4.400817 |-1.037756e+01 | 7.658481e-01 |\u001b[0m\n", "| 53 | 1.3250 | 5 | 0.642589 | 1.516374 | -4.491993 |-1.035352e+01 | 7.027470e-01 |\u001b[0m\n", "| 54 | 1.3500 | 5 | 0.636213 | 1.529562 | -4.660919 |-1.032602e+01 | 6.277602e-01 |\u001b[0m\n", "| 55 | 1.3750 | 4 | 0.654913 | 1.257229 | -4.127066 |-1.029540e+01 | 5.429281e-01 |\u001b[0m\n", "| 56 | 1.4000 | 4 | 0.690183 | 1.213338 | -4.180862 |-1.026211e+01 | 4.504884e-01 |\u001b[0m\n", "| 57 | 1.4250 | 4 | 0.690784 | 1.220024 | -4.119756 |-1.022658e+01 | 3.526569e-01 |\u001b[0m\n", "| 58 | 1.4500 | 4 | 0.693190 | 1.230164 | -4.115756 |-1.018920e+01 | 2.516427e-01 |\u001b[0m\n", "| 59 | 1.4750 | 4 | 0.689614 | 1.227466 | -4.089544 |-1.015042e+01 | 1.494680e-01 |\u001b[0m\n", "| 60 | 1.5000 | 4 | 0.686766 | 1.221660 | -4.073221 |-1.011070e+01 | 4.789509e-02 |\u001b[0m\n", "| 61 | 1.5250 | 4 | 0.688439 | 1.225181 | -4.065044 |-1.007052e+01 |-5.142849e-02 |\u001b[0m\n", "| 62 | 1.5500 | 4 | 0.686541 | 1.221131 | -4.066648 |-1.003034e+01 |-1.468432e-01 |\u001b[0m\n", "| 63 | 1.5750 | 4 | 0.649074 | 1.269069 | -4.116776 |-9.990515e+00 |-2.367221e-01 |\u001b[0m\n", "| 64 | 1.6000 | 4 | 0.634231 | 1.298771 | -4.232749 |-9.951260e+00 |-3.197660e-01 |\u001b[0m\n", "| 65 | 1.6250 | 4 | 0.631620 | 1.295939 | -4.346372 |-9.912713e+00 |-3.949816e-01 |\u001b[0m\n", "| 66 | 1.6500 | 4 | 0.676708 | 1.241231 | -4.462283 |-9.875008e+00 |-4.616848e-01 |\u001b[0m\n", "| 67 | 1.6750 | 4 | 0.690826 | 1.236500 | -4.536211 |-9.838289e+00 |-5.194903e-01 |\u001b[0m\n", "| 68 | 1.7000 | 4 | 0.685831 | 1.222472 | -4.557395 |-9.802717e+00 |-5.683337e-01 |\u001b[0m\n", "| 69 | 1.7250 | 4 | 0.683209 | 1.225972 | -4.514445 |-9.768493e+00 |-6.084703e-01 |\u001b[0m\n", "| 70 | 1.7500 | 4 | 0.681402 | 1.219586 | -4.515767 |-9.735644e+00 |-6.403299e-01 |\u001b[0m\n", "| 71 | 1.7750 | 4 | 0.679701 | 1.241776 | -4.561782 |-9.704041e+00 |-6.644313e-01 |\u001b[0m\n", "| 72 | 1.8000 | 4 | 0.687585 | 1.233221 | -4.576978 |-9.673585e+00 |-6.813177e-01 |\u001b[0m\n", "| 73 | 1.8250 | 3 | 0.685128 | 0.992525 | -4.085489 |-9.644153e+00 |-6.916357e-01 |\u001b[0m\n", "| 74 | 1.8500 | 3 | 0.630406 | 1.081735 | -4.228827 |-9.615525e+00 |-6.960345e-01 |\u001b[0m\n", "| 75 | 1.8750 | 3 | 0.633953 | 1.068277 | -4.281040 |-9.587600e+00 |-6.952011e-01 |\u001b[0m\n", "| 76 | 1.9000 | 3 | 0.631463 | 1.060051 | -4.298619 |-9.560401e+00 |-6.899257e-01 |\u001b[0m\n", "| 77 | 1.9250 | 3 | 0.641269 | 1.076329 | -4.263283 |-9.533905e+00 |-6.810428e-01 |\u001b[0m\n", "| 78 | 1.9500 | 3 | 0.632168 | 1.056881 | -4.172553 |-9.508183e+00 |-6.694250e-01 |\u001b[0m\n", "| 79 | 1.9750 | 3 | 0.644723 | 1.078860 | -4.087294 |-9.483448e+00 |-6.559401e-01 |\u001b[0m\n", "| 80 | 2.0000 | 3 | 0.629024 | 1.058206 | -4.017983 |-9.459903e+00 |-6.414764e-01 |\u001b[0m\n", "| 81 | 2.0250 | 4 | 0.641541 | 1.337528 | -4.489993 |-9.437808e+00 |-6.269365e-01 |\u001b[0m\n", "| 82 | 2.0500 | 4 | 0.628707 | 1.310679 | -4.470211 |-9.417538e+00 |-6.131778e-01 |\u001b[0m\n", "| 83 | 2.0750 | 4 | 0.628663 | 1.307533 | -4.453642 |-9.399441e+00 |-6.009539e-01 |\u001b[0m\n", "| 84 | 2.1000 | 4 | 0.631778 | 1.301269 | -4.451153 |-9.383802e+00 |-5.909513e-01 |\u001b[0m\n", "| 85 | 2.1250 | 4 | 0.629938 | 1.308679 | -4.454054 |-9.370919e+00 |-5.837527e-01 |\u001b[0m\n", "| 86 | 2.1500 | 4 | 0.630758 | 1.309061 | -4.489244 |-9.361058e+00 |-5.798117e-01 |\u001b[0m\n", "| 87 | 2.1750 | 4 | 0.630606 | 1.308830 | -4.529495 |-9.354357e+00 |-5.794602e-01 |\u001b[0m\n", "| 88 | 2.2000 | 4 | 0.629895 | 1.306216 | -4.555062 |-9.350910e+00 |-5.828920e-01 |\u001b[0m\n", "| 89 | 2.2250 | 4 | 0.627658 | 1.299544 | -4.583501 |-9.350758e+00 |-5.901807e-01 |\u001b[0m\n", "| 90 | 2.2500 | 4 | 0.631380 | 1.304904 | -4.577435 |-9.353747e+00 |-6.012367e-01 |\u001b[0m\n", "| 91 | 2.2750 | 3 | 0.632640 | 1.049719 | -4.015225 |-9.359659e+00 |-6.158432e-01 |\u001b[0m\n", "| 92 | 2.3000 | 3 | 0.677727 | 0.998599 | -4.029324 |-9.368239e+00 |-6.336696e-01 |\u001b[0m\n", "| 93 | 2.3250 | 3 | 0.681171 | 0.994001 | -4.027658 |-9.379037e+00 |-6.542748e-01 |\u001b[0m\n", "| 94 | 2.3500 | 3 | 0.681259 | 0.989943 | -4.032139 |-9.391519e+00 |-6.771019e-01 |\u001b[0m\n", "| 95 | 2.3750 | 3 | 0.662018 | 1.012158 | -4.018542 |-9.405213e+00 |-7.015133e-01 |\u001b[0m\n", "| 96 | 2.4000 | 4 | 0.649296 | 1.280039 | -4.507534 |-9.419633e+00 |-7.269046e-01 |\u001b[0m\n", "| 97 | 2.4250 | 4 | 0.643727 | 1.278447 | -4.486197 |-9.434238e+00 |-7.525958e-01 |\u001b[0m\n", "| 98 | 2.4500 | 4 | 0.626945 | 1.300289 | -4.469752 |-9.448552e+00 |-7.778583e-01 |\u001b[0m\n", "| 99 | 2.4750 | 4 | 0.630502 | 1.316456 | -4.479896 |-9.462111e+00 |-8.020210e-01 |\u001b[0m\n", "| 100 | 2.5000 | 4 | 0.624389 | 1.310294 | -4.502345 |-9.474427e+00 |-8.243816e-01 |\u001b[0m\n", "| 101 | 2.5250 | 4 | 0.631037 | 1.315843 | -4.533404 |-9.485161e+00 |-8.442818e-01 |\u001b[0m\n", "| 102 | 2.5500 | 4 | 0.644611 | 1.283856 | -4.554463 |-9.494088e+00 |-8.611748e-01 |\u001b[0m\n", "| 103 | 2.5750 | 4 | 0.680174 | 1.237591 | -4.579594 |-9.501053e+00 |-8.746547e-01 |\u001b[0m\n", "| 104 | 2.6000 | 4 | 0.680077 | 1.236782 | -4.596771 |-9.506032e+00 |-8.844994e-01 |\u001b[0m\n", "| 105 | 2.6250 | 4 | 0.675584 | 1.232872 | -4.484950 |-9.509125e+00 |-8.907008e-01 |\u001b[0m\n", "| 106 | 2.6500 | 4 | 0.678916 | 1.235079 | -4.463714 |-9.510468e+00 |-8.934511e-01 |\u001b[0m\n", "| 107 | 2.6750 | 4 | 0.677563 | 1.234071 | -4.381520 |-9.510274e+00 |-8.931021e-01 |\u001b[0m\n", "| 108 | 2.7000 | 4 | 0.674451 | 1.246872 | -4.443680 |-9.508969e+00 |-8.901755e-01 |\u001b[0m\n", "| 109 | 2.7250 | 4 | 0.680008 | 1.243277 | -4.646030 |-9.507028e+00 |-8.852282e-01 |\u001b[0m\n", "| 110 | 2.7500 | 3 | 0.687824 | 1.013889 | -4.095041 |-9.504803e+00 |-8.787479e-01 |\u001b[0m\n", "| 111 | 2.7750 | 3 | 0.678555 | 1.000272 | -4.324971 |-9.502690e+00 |-8.711948e-01 |\u001b[0m\n", "| 112 | 2.8000 | 3 | 0.629410 | 1.056518 | -4.391682 |-9.501123e+00 |-8.630844e-01 |\u001b[0m\n", "| 113 | 2.8250 | 3 | 0.631289 | 1.061790 | -4.405523 |-9.500417e+00 |-8.548953e-01 |\u001b[0m\n", "| 114 | 2.8500 | 3 | 0.623451 | 1.061574 | -4.405133 |-9.500842e+00 |-8.469691e-01 |\u001b[0m\n", "| 115 | 2.8750 | 3 | 0.629521 | 1.063894 | -4.364937 |-9.502651e+00 |-8.395888e-01 |\u001b[0m\n", "| 116 | 2.9000 | 3 | 0.625789 | 1.052592 | -4.333192 |-9.505900e+00 |-8.329495e-01 |\u001b[0m\n", "| 117 | 2.9250 | 3 | 0.634558 | 1.059645 | -4.287009 |-9.510496e+00 |-8.271466e-01 |\u001b[0m\n", "| 118 | 2.9500 | 3 | 0.676550 | 1.015943 | -4.245942 |-9.516385e+00 |-8.222099e-01 |\u001b[0m\n", "| 119 | 2.9750 | 3 | 0.680614 | 1.002676 | -4.205825 |-9.523418e+00 |-8.181140e-01 |\u001b[0m\n", "| 120 | 3.0000 | 3 | 0.682271 | 1.006870 | -4.188187 |-9.531318e+00 |-8.148053e-01 |\u001b[0m\n", "| 121 | 3.0250 | 3 | 0.680266 | 0.996102 | -4.232299 |-9.539814e+00 |-8.121677e-01 |\u001b[0m\n", "| 122 | 3.0500 | 3 | 0.681732 | 0.999955 | -4.231690 |-9.548602e+00 |-8.100300e-01 |\u001b[0m\n", "| 123 | 3.0750 | 3 | 0.682858 | 1.010936 | -4.279188 |-9.557348e+00 |-8.082342e-01 |\u001b[0m\n", "| 124 | 3.1000 | 3 | 0.681190 | 1.009030 | -4.271080 |-9.565764e+00 |-8.066818e-01 |\u001b[0m\n", "| 125 | 3.1250 | 3 | 0.683038 | 1.007188 | -4.392752 |-9.573620e+00 |-8.052156e-01 |\u001b[0m\n", "| 126 | 3.1500 | 3 | 0.678701 | 1.000200 | -4.449904 |-9.580739e+00 |-8.036619e-01 |\u001b[0m\n", "| 127 | 3.1750 | 2 | 0.676607 | 0.762312 | -4.000854 |-9.587007e+00 |-8.019792e-01 |\u001b[0m\n", "| 128 | 3.2000 | 3 | 0.676056 | 1.006674 | -4.538007 |-9.592408e+00 |-8.001705e-01 |\u001b[0m\n", "| 129 | 3.2250 | 3 | 0.675263 | 1.005720 | -4.547103 |-9.596953e+00 |-7.981922e-01 |\u001b[0m\n", "| 130 | 3.2500 | 3 | 0.676868 | 1.006415 | -4.525018 |-9.600665e+00 |-7.960410e-01 |\u001b[0m\n", "| 131 | 3.2750 | 3 | 0.675184 | 1.010851 | -4.506300 |-9.603640e+00 |-7.937587e-01 |\u001b[0m\n", "| 132 | 3.3000 | 3 | 0.672328 | 1.002885 | -4.509537 |-9.606035e+00 |-7.913608e-01 |\u001b[0m\n", "| 133 | 3.3250 | 3 | 0.671273 | 1.006276 | -4.517757 |-9.608054e+00 |-7.889411e-01 |\u001b[0m\n", "| 134 | 3.3500 | 2 | 0.667902 | 0.767805 | -4.004169 |-9.609914e+00 |-7.866119e-01 |\u001b[0m\n", "| 135 | 3.3750 | 2 | 0.672012 | 0.762166 | -4.037206 |-9.611796e+00 |-7.844475e-01 |\u001b[0m\n", "| 136 | 3.4000 | 2 | 0.674040 | 0.766492 | -4.091199 |-9.613836e+00 |-7.825251e-01 |\u001b[0m\n", "| 137 | 3.4250 | 2 | 0.677833 | 0.764682 | -4.161763 |-9.616142e+00 |-7.808889e-01 |\u001b[0m\n", "| 138 | 3.4500 | 2 | 0.678520 | 0.772371 | -4.208416 |-9.618837e+00 |-7.795641e-01 |\u001b[0m\n", "| 139 | 3.4750 | 2 | 0.675557 | 0.769200 | -4.185191 |-9.622012e+00 |-7.786017e-01 |\u001b[0m\n", "| 140 | 3.5000 | 2 | 0.658891 | 0.777941 | -4.143336 |-9.625717e+00 |-7.780201e-01 |\u001b[0m\n", "| 141 | 3.5250 | 2 | 0.670239 | 0.764967 | -4.111505 |-9.629952e+00 |-7.777878e-01 |\u001b[0m\n", "| 142 | 3.5500 | 2 | 0.675294 | 0.780672 | -4.095589 |-9.634644e+00 |-7.778791e-01 |\u001b[0m\n", "| 143 | 3.5750 | 2 | 0.674070 | 0.768821 | -4.089987 |-9.639706e+00 |-7.782389e-01 |\u001b[0m\n", "| 144 | 3.6000 | 2 | 0.675235 | 0.770859 | -4.092880 |-9.645044e+00 |-7.787973e-01 |\u001b[0m\n", "| 145 | 3.6250 | 2 | 0.667235 | 0.766770 | -4.113065 |-9.650579e+00 |-7.794970e-01 |\u001b[0m\n", "| 146 | 3.6500 | 2 | 0.664948 | 0.775005 | -4.162262 |-9.656221e+00 |-7.802848e-01 |\u001b[0m\n", "| 147 | 3.6750 | 2 | 0.669905 | 0.778505 | -4.233073 |-9.661859e+00 |-7.810839e-01 |\u001b[0m\n", "| 148 | 3.7000 | 2 | 0.673135 | 0.772653 | -4.300425 |-9.667393e+00 |-7.818259e-01 |\u001b[0m\n", "| 149 | 3.7250 | 2 | 0.668814 | 0.767011 | -4.341062 |-9.672713e+00 |-7.824584e-01 |\u001b[0m\n", "| 150 | 3.7500 | 2 | 0.667366 | 0.768942 | -4.347827 |-9.677758e+00 |-7.829222e-01 |\u001b[0m\n", "| 151 | 3.7750 | 2 | 0.671508 | 0.768175 | -4.340552 |-9.682512e+00 |-7.832059e-01 |\u001b[0m\n", "| 152 | 3.8000 | 2 | 0.657508 | 0.783694 | -4.331623 |-9.686971e+00 |-7.832953e-01 |\u001b[0m\n", "| 153 | 3.8250 | 2 | 0.672415 | 0.768370 | -4.316722 |-9.691148e+00 |-7.831777e-01 |\u001b[0m\n", "| 154 | 3.8500 | 2 | 0.677129 | 0.779227 | -4.299262 |-9.695058e+00 |-7.828747e-01 |\u001b[0m\n", "| 155 | 3.8750 | 2 | 0.673584 | 0.770337 | -4.305853 |-9.698731e+00 |-7.823999e-01 |\u001b[0m\n", "| 156 | 3.9000 | 2 | 0.665485 | 0.772028 | -4.334291 |-9.702215e+00 |-7.817778e-01 |\u001b[0m\n", "| 157 | 3.9250 | 2 | 0.666994 | 0.776969 | -4.377407 |-9.705587e+00 |-7.810551e-01 |\u001b[0m\n", "| 158 | 3.9500 | 2 | 0.665925 | 0.780606 | -4.439104 |-9.708933e+00 |-7.802775e-01 |\u001b[0m\n", "| 159 | 3.9750 | 2 | 0.670904 | 0.771282 | -4.498475 |-9.712316e+00 |-7.794772e-01 |\u001b[0m\n", "| 160 | 4.0000 | 1 | 0.671639 | 0.530210 | -4.008078 |-9.715787e+00 |-7.786931e-01 |\u001b[0m\n", "| 161 | 4.0250 | 1 | 0.670327 | 0.533749 | -4.092355 |-9.719380e+00 |-7.779506e-01 |\u001b[0m\n", "| 162 | 4.0500 | 1 | 0.668751 | 0.537579 | -4.156978 |-9.723116e+00 |-7.772698e-01 |\u001b[0m\n", "| 163 | 4.0750 | 1 | 0.663677 | 0.531485 | -4.072783 |-9.727029e+00 |-7.766788e-01 |\u001b[0m\n", "| 164 | 4.1000 | 2 | 0.666350 | 0.773267 | -4.569537 |-9.731146e+00 |-7.761920e-01 |\u001b[0m\n", "| 165 | 4.1250 | 2 | 0.682839 | 0.788505 | -4.570174 |-9.735472e+00 |-7.758138e-01 |\u001b[0m\n", "| 166 | 4.1500 | 2 | 0.620765 | 0.812113 | -4.549853 |-9.739981e+00 |-7.755403e-01 |\u001b[0m\n", "| 167 | 4.1750 | 2 | 0.618753 | 0.825905 | -4.537868 |-9.744625e+00 |-7.753472e-01 |\u001b[0m\n", "| 168 | 4.2000 | 2 | 0.618510 | 0.819368 | -4.524976 |-9.749356e+00 |-7.752100e-01 |\u001b[0m\n", "| 169 | 4.2250 | 2 | 0.613543 | 0.817032 | -4.515910 |-9.754134e+00 |-7.751152e-01 |\u001b[0m\n", "| 170 | 4.2500 | 2 | 0.614536 | 0.814042 | -4.538699 |-9.758943e+00 |-7.750408e-01 |\u001b[0m\n", "| 171 | 4.2750 | 1 | 0.625472 | 0.574294 | -4.020787 |-9.763759e+00 |-7.749700e-01 |\u001b[0m\n", "| 172 | 4.3000 | 1 | 0.608604 | 0.560485 | -4.061045 |-9.768537e+00 |-7.748838e-01 |\u001b[0m\n", "| 173 | 4.3250 | 1 | 0.613070 | 0.563931 | -4.121220 |-9.773242e+00 |-7.747597e-01 |\u001b[0m\n", "| 174 | 4.3500 | 1 | 0.642887 | 0.551584 | -4.110854 |-9.777831e+00 |-7.745740e-01 |\u001b[0m\n", "| 175 | 4.3750 | 1 | 0.666433 | 0.534772 | -4.088306 |-9.782291e+00 |-7.743221e-01 |\u001b[0m\n", "| 176 | 4.4000 | 1 | 0.673361 | 0.543510 | -4.077439 |-9.786631e+00 |-7.740007e-01 |\u001b[0m\n", "| 177 | 4.4250 | 1 | 0.675407 | 0.545999 | -4.085008 |-9.790868e+00 |-7.736116e-01 |\u001b[0m\n", "| 178 | 4.4500 | 1 | 0.663487 | 0.537923 | -4.084174 |-9.795016e+00 |-7.731651e-01 |\u001b[0m\n", "| 179 | 4.4750 | 1 | 0.656179 | 0.541705 | -4.095747 |-9.799077e+00 |-7.726600e-01 |\u001b[0m\n", "| 180 | 4.5000 | 1 | 0.665239 | 0.538480 | -4.143039 |-9.803063e+00 |-7.721067e-01 |\u001b[0m\n", "| 181 | 4.5250 | 1 | 0.671591 | 0.542443 | -4.219653 |-9.806995e+00 |-7.715207e-01 |\u001b[0m\n", "| 182 | 4.5500 | 1 | 0.663922 | 0.536485 | -4.324315 |-9.810904e+00 |-7.709163e-01 |\u001b[0m\n", "| 183 | 4.5750 | 1 | 0.657884 | 0.540013 | -4.462694 |-9.814833e+00 |-7.703121e-01 |\u001b[0m\n", "| 184 | 4.6000 | 1 | 0.664151 | 0.536879 | -4.555594 |-9.818801e+00 |-7.697261e-01 |\u001b[0m\n", "| 185 | 4.6250 | 1 | 0.664279 | 0.534583 | -4.636689 |-9.822816e+00 |-7.691634e-01 |\u001b[0m\n", "| 186 | 4.6500 | 1 | 0.663570 | 0.535142 | -4.623202 |-9.826877e+00 |-7.686317e-01 |\u001b[0m\n", "| 187 | 4.6750 | 1 | 0.654862 | 0.535068 | -4.465982 |-9.830990e+00 |-7.681383e-01 |\u001b[0m\n", "| 188 | 4.7000 | 1 | 0.664028 | 0.540949 | -4.338112 |-9.835165e+00 |-7.676893e-01 |\u001b[0m\n", "| 189 | 4.7250 | 1 | 0.663004 | 0.538335 | -4.294846 |-9.839409e+00 |-7.672874e-01 |\u001b[0m\n", "| 190 | 4.7500 | 1 | 0.661509 | 0.535572 | -4.283904 |-9.843723e+00 |-7.669317e-01 |\u001b[0m\n", "| 191 | 4.7750 | 1 | 0.667410 | 0.544977 | -4.289664 |-9.848084e+00 |-7.666140e-01 |\u001b[0m\n", "| 192 | 4.8000 | 1 | 0.631005 | 0.557035 | -4.285048 |-9.852468e+00 |-7.663236e-01 |\u001b[0m\n", "| 193 | 4.8250 | 1 | 0.655634 | 0.543174 | -4.249586 |-9.856861e+00 |-7.660553e-01 |\u001b[0m\n", "| 194 | 4.8500 | 1 | 0.663471 | 0.540511 | -4.240242 |-9.861248e+00 |-7.657973e-01 |\u001b[0m\n", "| 195 | 4.8750 | 1 | 0.663061 | 0.537256 | -4.279527 |-9.865630e+00 |-7.655457e-01 |\u001b[0m\n", "| 196 | 4.9000 | 1 | 0.666065 | 0.559327 | -4.341423 |-9.869997e+00 |-7.652934e-01 |\u001b[0m\n", "| 197 | 4.9250 | 1 | 0.655532 | 0.540197 | -4.389727 |-9.874334e+00 |-7.650274e-01 |\u001b[0m\n", "| 198 | 4.9500 | 1 | 0.653665 | 0.536703 | -4.392163 |-9.878621e+00 |-7.647419e-01 |\u001b[0m\n", "| 199 | 4.9750 | 1 | 0.643891 | 0.551159 | -4.356375 |-9.882848e+00 |-7.644317e-01 |\u001b[0m\n", "| 200 | 5.0000 | 1 | 0.658182 | 0.536739 | -4.357632 |-9.887023e+00 |-7.640960e-01 |\u001b[0m\n", "| 201 | 5.0250 | 1 | 0.666568 | 0.547456 | -4.374468 |-9.891152e+00 |-7.637354e-01 |\u001b[0m\n", "| 202 | 5.0500 | 1 | 0.690858 | 0.576716 | -4.405701 |-9.895246e+00 |-7.633546e-01 |\u001b[0m\n", "| 203 | 5.0750 | 1 | 0.648976 | 0.543039 | -4.457054 |-9.899310e+00 |-7.629532e-01 |\u001b[0m\n", "| 204 | 5.1000 | 1 | 0.645745 | 0.541463 | -4.487556 |-9.903338e+00 |-7.625328e-01 |\u001b[0m\n", "| 205 | 5.1250 | 1 | 0.591463 | 0.597350 | -4.532516 |-9.907334e+00 |-7.620999e-01 |\u001b[0m\n", "| 206 | 5.1500 | 1 | 0.602864 | 0.567360 | -4.584225 |-9.911313e+00 |-7.616581e-01 |\u001b[0m\n", "| 207 | 5.1750 | 1 | 0.607332 | 0.564882 | -4.653390 |-9.915290e+00 |-7.612183e-01 |\u001b[0m\n", "| 208 | 5.2000 | 1 | 0.617578 | 0.575872 | -4.738979 |-9.919278e+00 |-7.607846e-01 |\u001b[0m\n", "| 209 | 5.2250 | 1 | 0.608336 | 0.568334 | -4.813565 |-9.923281e+00 |-7.603606e-01 |\u001b[0m\n", "| 210 | 5.2500 | 1 | 0.608519 | 0.571894 | -4.767231 |-9.927295e+00 |-7.599484e-01 |\u001b[0m\n", "| 211 | 5.2750 | 1 | 0.611513 | 0.573648 | -4.664724 |-9.931315e+00 |-7.595504e-01 |\u001b[0m\n", "| 212 | 5.3000 | 1 | 0.663295 | 0.546204 | -4.542805 |-9.935346e+00 |-7.591674e-01 |\u001b[0m\n", "| 213 | 5.3250 | 1 | 0.653387 | 0.540858 | -4.498708 |-9.939397e+00 |-7.588020e-01 |\u001b[0m\n", "| 214 | 5.3500 | 1 | 0.651487 | 0.537849 | -4.514320 |-9.943465e+00 |-7.584560e-01 |\u001b[0m\n", "| 215 | 5.3750 | 1 | 0.662014 | 0.545498 | -4.531746 |-9.947549e+00 |-7.581246e-01 |\u001b[0m\n", "| 216 | 5.4000 | 1 | 0.650294 | 0.540917 | -4.526842 |-9.951634e+00 |-7.578047e-01 |\u001b[0m\n", "| 217 | 5.4250 | 1 | 0.653085 | 0.536544 | -4.480060 |-9.955710e+00 |-7.574922e-01 |\u001b[0m\n", "| 218 | 5.4500 | 1 | 0.661230 | 0.548448 | -4.442806 |-9.959772e+00 |-7.571838e-01 |\u001b[0m\n", "| 219 | 5.4750 | 1 | 0.655229 | 0.543616 | -4.442100 |-9.963823e+00 |-7.568782e-01 |\u001b[0m\n", "| 220 | 5.5000 | 1 | 0.655222 | 0.546932 | -4.480309 |-9.967864e+00 |-7.565737e-01 |\u001b[0m\n", "| 221 | 5.5250 | 1 | 0.650105 | 0.542159 | -4.527770 |-9.971886e+00 |-7.562652e-01 |\u001b[0m\n", "| 222 | 5.5500 | 1 | 0.651475 | 0.537507 | -4.542362 |-9.975885e+00 |-7.559507e-01 |\u001b[0m\n", "| 223 | 5.5750 | 1 | 0.659401 | 0.547510 | -4.506293 |-9.979850e+00 |-7.556271e-01 |\u001b[0m\n", "| 224 | 5.6000 | 1 | 0.601784 | 0.569276 | -4.504088 |-9.983780e+00 |-7.552935e-01 |\u001b[0m\n", "| 225 | 5.6250 | 1 | 0.623897 | 0.557574 | -4.512552 |-9.987687e+00 |-7.549518e-01 |\u001b[0m\n", "| 226 | 5.6500 | 1 | 0.600244 | 0.569273 | -4.550006 |-9.991573e+00 |-7.546024e-01 |\u001b[0m\n", "| 227 | 5.6750 | 1 | 0.624355 | 0.556274 | -4.608499 |-9.995440e+00 |-7.542473e-01 |\u001b[0m\n", "| 228 | 5.7000 | 1 | 0.660840 | 0.555257 | -4.628774 |-9.999287e+00 |-7.538857e-01 |\u001b[0m\n", "| 229 | 5.7250 | 1 | 0.648993 | 0.540272 | -4.638629 |-1.000311e+01 |-7.535194e-01 |\u001b[0m\n", "| 230 | 5.7500 | 1 | 0.649691 | 0.542594 | -4.620373 |-1.000691e+01 |-7.531502e-01 |\u001b[0m\n", "| 231 | 5.7750 | 1 | 0.654206 | 0.542916 | -4.623969 |-1.001071e+01 |-7.527814e-01 |\u001b[0m\n", "| 232 | 5.8000 | 1 | 0.651407 | 0.541567 | -4.658962 |-1.001450e+01 |-7.524169e-01 |\u001b[0m\n", "| 233 | 5.8250 | 1 | 0.634053 | 0.546768 | -4.709952 |-1.001828e+01 |-7.520571e-01 |\u001b[0m\n", "| 234 | 5.8500 | 1 | 0.649142 | 0.540074 | -4.711982 |-1.002206e+01 |-7.517035e-01 |\u001b[0m\n", "| 235 | 5.8750 | 1 | 0.651331 | 0.541255 | -4.674097 |-1.002584e+01 |-7.513553e-01 |\u001b[0m\n", "| 236 | 5.9000 | 1 | 0.650602 | 0.540557 | -4.609681 |-1.002960e+01 |-7.510131e-01 |\u001b[0m\n", "| 237 | 5.9250 | 1 | 0.625181 | 0.556794 | -4.572926 |-1.003336e+01 |-7.506790e-01 |\u001b[0m\n", "| 238 | 5.9500 | 1 | 0.598372 | 0.577537 | -4.593342 |-1.003712e+01 |-7.503529e-01 |\u001b[0m\n", "| 239 | 5.9750 | 1 | 0.597025 | 0.572500 | -4.606399 |-1.004088e+01 |-7.500343e-01 |\u001b[0m\n", "| 240 | 6.0000 | 1 | 0.597859 | 0.576480 | -4.633256 |-1.004462e+01 |-7.497216e-01 |\u001b[0m\n", "| 241 | 6.0250 | 1 | 0.598875 | 0.573635 | -4.596024 |-1.004836e+01 |-7.494121e-01 |\u001b[0m\n", "| 242 | 6.0500 | 1 | 0.597534 | 0.573711 | -4.557101 |-1.005207e+01 |-7.491049e-01 |\u001b[0m\n", "| 243 | 6.0750 | 1 | 0.615834 | 0.560475 | -4.544709 |-1.005577e+01 |-7.487993e-01 |\u001b[0m\n", "| 244 | 6.1000 | 1 | 0.645833 | 0.545940 | -4.560556 |-1.005945e+01 |-7.484956e-01 |\u001b[0m\n", "| 245 | 6.1250 | 1 | 0.641262 | 0.550605 | -4.599376 |-1.006312e+01 |-7.481921e-01 |\u001b[0m\n", "| 246 | 6.1500 | 1 | 0.650546 | 0.543172 | -4.621850 |-1.006677e+01 |-7.478876e-01 |\u001b[0m\n", "| 247 | 6.1750 | 1 | 0.650686 | 0.547601 | -4.613310 |-1.007040e+01 |-7.475803e-01 |\u001b[0m\n", "| 248 | 6.2000 | 1 | 0.643572 | 0.551210 | -4.588594 |-1.007400e+01 |-7.472693e-01 |\u001b[0m\n", "| 249 | 6.2250 | 1 | 0.643333 | 0.539960 | -4.585553 |-1.007758e+01 |-7.469564e-01 |\u001b[0m\n", "| 250 | 6.2500 | 1 | 0.631976 | 0.548090 | -4.600869 |-1.008114e+01 |-7.466414e-01 |\u001b[0m\n", "| 251 | 6.2750 | 1 | 0.647855 | 0.541747 | -4.650767 |-1.008469e+01 |-7.463261e-01 |\u001b[0m\n", "| 252 | 6.3000 | 1 | 0.649241 | 0.544522 | -4.672520 |-1.008821e+01 |-7.460094e-01 |\u001b[0m\n", "| 253 | 6.3250 | 1 | 0.643516 | 0.545110 | -4.673014 |-1.009172e+01 |-7.456916e-01 |\u001b[0m\n", "| 254 | 6.3500 | 1 | 0.638767 | 0.540989 | -4.653989 |-1.009520e+01 |-7.453734e-01 |\u001b[0m\n", "| 255 | 6.3750 | 1 | 0.631475 | 0.546431 | -4.631053 |-1.009867e+01 |-7.450557e-01 |\u001b[0m\n", "| 256 | 6.4000 | 1 | 0.644038 | 0.541411 | -4.653231 |-1.010213e+01 |-7.447412e-01 |\u001b[0m\n", "| 257 | 6.4250 | 1 | 0.645015 | 0.541870 | -4.678113 |-1.010557e+01 |-7.444295e-01 |\u001b[0m\n", "| 258 | 6.4500 | 1 | 0.646439 | 0.544957 | -4.706046 |-1.010900e+01 |-7.441219e-01 |\u001b[0m\n", "| 259 | 6.4750 | 1 | 0.636618 | 0.544506 | -4.688480 |-1.011242e+01 |-7.438168e-01 |\u001b[0m\n", "| 260 | 6.5000 | 1 | 0.636472 | 0.544515 | -4.660864 |-1.011582e+01 |-7.435149e-01 |\u001b[0m\n", "| 261 | 6.5250 | 1 | 0.642801 | 0.547305 | -4.622978 |-1.011920e+01 |-7.432168e-01 |\u001b[0m\n", "| 262 | 6.5500 | 1 | 0.642521 | 0.549237 | -4.634656 |-1.012258e+01 |-7.429228e-01 |\u001b[0m\n", "| 263 | 6.5750 | 1 | 0.640326 | 0.546690 | -4.652540 |-1.012594e+01 |-7.426333e-01 |\u001b[0m\n", "| 264 | 6.6000 | 1 | 0.646080 | 0.569616 | -4.675082 |-1.012928e+01 |-7.423474e-01 |\u001b[0m\n", "| 265 | 6.6250 | 1 | 0.637068 | 0.545332 | -4.671475 |-1.013261e+01 |-7.420636e-01 |\u001b[0m\n", "| 266 | 6.6500 | 1 | 0.641454 | 0.542948 | -4.635937 |-1.013592e+01 |-7.417815e-01 |\u001b[0m\n", "| 267 | 6.6750 | 1 | 0.648429 | 0.563219 | -4.626090 |-1.013921e+01 |-7.415010e-01 |\u001b[0m\n", "| 268 | 6.7000 | 1 | 0.638889 | 0.546413 | -4.619397 |-1.014248e+01 |-7.412219e-01 |\u001b[0m\n", "| 269 | 6.7250 | 1 | 0.645476 | 0.551120 | -4.654354 |-1.014573e+01 |-7.409449e-01 |\u001b[0m\n", "| 270 | 6.7500 | 1 | 0.644807 | 0.553462 | -4.677256 |-1.014897e+01 |-7.406684e-01 |\u001b[0m\n", "| 271 | 6.7750 | 1 | 0.643342 | 0.545453 | -4.677941 |-1.015219e+01 |-7.403922e-01 |\u001b[0m\n", "| 272 | 6.8000 | 1 | 0.642737 | 0.554577 | -4.663328 |-1.015538e+01 |-7.401152e-01 |\u001b[0m\n", "| 273 | 6.8250 | 1 | 0.636924 | 0.547193 | -4.647117 |-1.015854e+01 |-7.398382e-01 |\u001b[0m\n", "| 274 | 6.8500 | 1 | 0.636881 | 0.549125 | -4.652275 |-1.016169e+01 |-7.395617e-01 |\u001b[0m\n", "| 275 | 6.8750 | 1 | 0.642097 | 0.546781 | -4.679255 |-1.016482e+01 |-7.392860e-01 |\u001b[0m\n", "| 276 | 6.9000 | 1 | 0.644362 | 0.546495 | -4.710093 |-1.016793e+01 |-7.390117e-01 |\u001b[0m\n", "| 277 | 6.9250 | 1 | 0.636971 | 0.552398 | -4.709777 |-1.017103e+01 |-7.387375e-01 |\u001b[0m\n", "| 278 | 6.9500 | 1 | 0.642068 | 0.550708 | -4.702989 |-1.017410e+01 |-7.384642e-01 |\u001b[0m\n", "| 279 | 6.9750 | 1 | 0.636245 | 0.548906 | -4.675771 |-1.017714e+01 |-7.381918e-01 |\u001b[0m\n", "| 280 | 7.0000 | 1 | 0.631892 | 0.553548 | -4.683037 |-1.018018e+01 |-7.379216e-01 |\u001b[0m\n", "| 281 | 7.0250 | 1 | 0.636876 | 0.548173 | -4.700304 |-1.018319e+01 |-7.376540e-01 |\u001b[0m\n", "| 282 | 7.0500 | 1 | 0.641117 | 0.548960 | -4.723800 |-1.018619e+01 |-7.373891e-01 |\u001b[0m\n", "| 283 | 7.0750 | 1 | 0.639323 | 0.547961 | -4.733571 |-1.018917e+01 |-7.371265e-01 |\u001b[0m\n", "| 284 | 7.1000 | 1 | 0.636166 | 0.555813 | -4.706688 |-1.019212e+01 |-7.368659e-01 |\u001b[0m\n", "| 285 | 7.1250 | 1 | 0.634238 | 0.555710 | -4.691827 |-1.019506e+01 |-7.366075e-01 |\u001b[0m\n", "| 286 | 7.1500 | 1 | 0.635902 | 0.551450 | -4.681559 |-1.019798e+01 |-7.363519e-01 |\u001b[0m\n", "| 287 | 7.1750 | 1 | 0.637927 | 0.565596 | -4.702253 |-1.020088e+01 |-7.360992e-01 |\u001b[0m\n", "| 288 | 7.2000 | 1 | 0.636154 | 0.548763 | -4.718294 |-1.020377e+01 |-7.358495e-01 |\u001b[0m\n", "| 289 | 7.2250 | 1 | 0.635841 | 0.549645 | -4.734186 |-1.020663e+01 |-7.356017e-01 |\u001b[0m\n", "| 290 | 7.2500 | 1 | 0.633560 | 0.553457 | -4.709277 |-1.020948e+01 |-7.353555e-01 |\u001b[0m\n", "| 291 | 7.2750 | 1 | 0.633063 | 0.551744 | -4.697645 |-1.021230e+01 |-7.351108e-01 |\u001b[0m\n", "| 292 | 7.3000 | 1 | 0.596012 | 0.573846 | -4.690652 |-1.021510e+01 |-7.348676e-01 |\u001b[0m\n", "| 293 | 7.3250 | 1 | 0.628225 | 0.550297 | -4.705748 |-1.021788e+01 |-7.346267e-01 |\u001b[0m\n", "| 294 | 7.3500 | 1 | 0.635229 | 0.558756 | -4.733700 |-1.022064e+01 |-7.343873e-01 |\u001b[0m\n", "| 295 | 7.3750 | 1 | 0.633064 | 0.553635 | -4.738990 |-1.022338e+01 |-7.341492e-01 |\u001b[0m\n", "| 296 | 7.4000 | 1 | 0.636859 | 0.550407 | -4.735953 |-1.022610e+01 |-7.339119e-01 |\u001b[0m\n", "| 297 | 7.4250 | 1 | 0.623950 | 0.565263 | -4.716011 |-1.022879e+01 |-7.336753e-01 |\u001b[0m\n", "| 298 | 7.4500 | 1 | 0.642621 | 0.555373 | -4.714487 |-1.023146e+01 |-7.334401e-01 |\u001b[0m\n", "| 299 | 7.4750 | 1 | 0.631974 | 0.548812 | -4.730516 |-1.023411e+01 |-7.332063e-01 |\u001b[0m\n", "| 300 | 7.5000 | 1 | 0.635308 | 0.557555 | -4.753499 |-1.023674e+01 |-7.329746e-01 |\u001b[0m\n", "| 301 | 7.5250 | 1 | 0.629950 | 0.550125 | -4.765495 |-1.023935e+01 |-7.327439e-01 |\u001b[0m\n", "| 302 | 7.5500 | 1 | 0.615770 | 0.560338 | -4.762381 |-1.024194e+01 |-7.325148e-01 |\u001b[0m\n", "| 303 | 7.5750 | 1 | 0.584256 | 0.590264 | -4.742845 |-1.024450e+01 |-7.322869e-01 |\u001b[0m\n", "| 304 | 7.6000 | 1 | 0.582597 | 0.587076 | -4.737265 |-1.024704e+01 |-7.320607e-01 |\u001b[0m\n", "| 305 | 7.6250 | 1 | 0.584283 | 0.579482 | -4.752925 |-1.024956e+01 |-7.318370e-01 |\u001b[0m\n", "| 306 | 7.6500 | 1 | 0.586216 | 0.584941 | -4.767222 |-1.025207e+01 |-7.316154e-01 |\u001b[0m\n", "| 307 | 7.6750 | 1 | 0.585430 | 0.579851 | -4.787773 |-1.025455e+01 |-7.313962e-01 |\u001b[0m\n", "| 308 | 7.7000 | 1 | 0.631306 | 0.551397 | -4.774572 |-1.025701e+01 |-7.311787e-01 |\u001b[0m\n", "| 309 | 7.7250 | 1 | 0.634317 | 0.553543 | -4.762915 |-1.025944e+01 |-7.309630e-01 |\u001b[0m\n", "| 310 | 7.7500 | 1 | 0.636891 | 0.556693 | -4.750823 |-1.026186e+01 |-7.307494e-01 |\u001b[0m\n", "| 311 | 7.7750 | 1 | 0.639769 | 0.563616 | -4.761181 |-1.026425e+01 |-7.305383e-01 |\u001b[0m\n", "| 312 | 7.8000 | 1 | 0.631767 | 0.554912 | -4.781589 |-1.026663e+01 |-7.303294e-01 |\u001b[0m\n", "| 313 | 7.8250 | 1 | 0.634194 | 0.558768 | -4.793059 |-1.026898e+01 |-7.301229e-01 |\u001b[0m\n", "| 314 | 7.8500 | 1 | 0.626796 | 0.556363 | -4.791256 |-1.027131e+01 |-7.299178e-01 |\u001b[0m\n", "| 315 | 7.8750 | 1 | 0.625787 | 0.550935 | -4.775101 |-1.027361e+01 |-7.297145e-01 |\u001b[0m\n", "| 316 | 7.9000 | 1 | 0.631936 | 0.553434 | -4.770127 |-1.027590e+01 |-7.295127e-01 |\u001b[0m\n", "| 317 | 7.9250 | 1 | 0.632807 | 0.554769 | -4.771512 |-1.027816e+01 |-7.293129e-01 |\u001b[0m\n", "| 318 | 7.9500 | 1 | 0.636354 | 0.555106 | -4.801316 |-1.028040e+01 |-7.291153e-01 |\u001b[0m\n", "| 319 | 7.9750 | 1 | 0.651982 | 0.601691 | -4.806386 |-1.028262e+01 |-7.289192e-01 |\u001b[0m\n", "| 320 | 8.0000 | 1 | 0.642667 | 0.630753 | -4.813430 |-1.028481e+01 |-7.287248e-01 |\u001b[0m\n", "| 321 | 8.0250 | 1 | 0.638641 | 0.626061 | -4.798908 |-1.028698e+01 |-7.285314e-01 |\u001b[0m\n", "| 322 | 8.0500 | 1 | 0.627977 | 0.630209 | -4.790992 |-1.028913e+01 |-7.283397e-01 |\u001b[0m\n", "| 323 | 8.0750 | 1 | 0.615535 | 0.632846 | -4.799474 |-1.029125e+01 |-7.281498e-01 |\u001b[0m\n", "| 324 | 8.1000 | 1 | 0.628741 | 0.623770 | -4.816641 |-1.029336e+01 |-7.279618e-01 |\u001b[0m\n", "| 325 | 8.1250 | 1 | 0.646119 | 0.631292 | -4.835626 |-1.029544e+01 |-7.277758e-01 |\u001b[0m\n", "| 326 | 8.1500 | 1 | 0.635751 | 0.604086 | -4.834189 |-1.029750e+01 |-7.275912e-01 |\u001b[0m\n", "| 327 | 8.1750 | 1 | 0.635591 | 0.596375 | -4.826611 |-1.029953e+01 |-7.274083e-01 |\u001b[0m\n", "| 328 | 8.2000 | 1 | 0.654686 | 0.625780 | -4.815149 |-1.030154e+01 |-7.272270e-01 |\u001b[0m\n", "| 329 | 8.2250 | 1 | 0.631972 | 0.593750 | -4.822991 |-1.030353e+01 |-7.270479e-01 |\u001b[0m\n", "| 330 | 8.2500 | 1 | 0.627881 | 0.594475 | -4.835602 |-1.030549e+01 |-7.268709e-01 |\u001b[0m\n", "| 331 | 8.2750 | 1 | 0.632779 | 0.588022 | -4.856780 |-1.030744e+01 |-7.266960e-01 |\u001b[0m\n", "| 332 | 8.3000 | 1 | 0.623576 | 0.572123 | -4.856935 |-1.030936e+01 |-7.265231e-01 |\u001b[0m\n", "| 333 | 8.3250 | 1 | 0.616926 | 0.572569 | -4.844436 |-1.031125e+01 |-7.263517e-01 |\u001b[0m\n", "| 334 | 8.3500 | 1 | 0.656718 | 0.615642 | -4.841857 |-1.031313e+01 |-7.261824e-01 |\u001b[0m\n", "| 335 | 8.3750 | 1 | 0.647848 | 0.593447 | -4.836181 |-1.031498e+01 |-7.260150e-01 |\u001b[0m\n", "| 336 | 8.4000 | 1 | 0.620897 | 0.556400 | -4.858940 |-1.031681e+01 |-7.258500e-01 |\u001b[0m\n", "| 337 | 8.4250 | 1 | 0.636878 | 0.568918 | -4.870038 |-1.031861e+01 |-7.256870e-01 |\u001b[0m\n", "| 338 | 8.4500 | 1 | 0.624409 | 0.560472 | -4.878751 |-1.032040e+01 |-7.255258e-01 |\u001b[0m\n", "| 339 | 8.4750 | 1 | 0.626291 | 0.560779 | -4.866752 |-1.032216e+01 |-7.253663e-01 |\u001b[0m\n", "| 340 | 8.5000 | 1 | 0.624523 | 0.557208 | -4.860873 |-1.032389e+01 |-7.252085e-01 |\u001b[0m\n", "| 341 | 8.5250 | 1 | 0.621932 | 0.557273 | -4.861571 |-1.032560e+01 |-7.250526e-01 |\u001b[0m\n", "| 342 | 8.5500 | 1 | 0.620140 | 0.558312 | -4.876236 |-1.032729e+01 |-7.248988e-01 |\u001b[0m\n", "| 343 | 8.5750 | 1 | 0.618488 | 0.558703 | -4.894443 |-1.032896e+01 |-7.247469e-01 |\u001b[0m\n", "| 344 | 8.6000 | 1 | 0.614675 | 0.555611 | -4.898885 |-1.033060e+01 |-7.245967e-01 |\u001b[0m\n", "| 345 | 8.6250 | 1 | 0.608933 | 0.564743 | -4.897533 |-1.033222e+01 |-7.244482e-01 |\u001b[0m\n", "| 346 | 8.6500 | 1 | 0.577061 | 0.585133 | -4.881841 |-1.033381e+01 |-7.243012e-01 |\u001b[0m\n", "| 347 | 8.6750 | 1 | 0.608953 | 0.587979 | -4.892088 |-1.033539e+01 |-7.241562e-01 |\u001b[0m\n", "| 348 | 8.7000 | 1 | 0.624541 | 0.558283 | -4.899079 |-1.033693e+01 |-7.240130e-01 |\u001b[0m\n", "| 349 | 8.7250 | 1 | 0.625952 | 0.557111 | -4.918737 |-1.033846e+01 |-7.238720e-01 |\u001b[0m\n", "| 350 | 8.7500 | 1 | 0.623287 | 0.557909 | -4.928123 |-1.033996e+01 |-7.237327e-01 |\u001b[0m\n", "| 351 | 8.7750 | 1 | 0.622417 | 0.555740 | -4.921486 |-1.034144e+01 |-7.235950e-01 |\u001b[0m\n", "| 352 | 8.8000 | 1 | 0.621352 | 0.554043 | -4.914257 |-1.034289e+01 |-7.234592e-01 |\u001b[0m\n", "| 353 | 8.8250 | 1 | 0.625442 | 0.559248 | -4.914487 |-1.034432e+01 |-7.233251e-01 |\u001b[0m\n", "| 354 | 8.8500 | 1 | 0.621001 | 0.554895 | -4.927093 |-1.034573e+01 |-7.231933e-01 |\u001b[0m\n", "| 355 | 8.8750 | 1 | 0.616331 | 0.556795 | -4.942194 |-1.034711e+01 |-7.230634e-01 |\u001b[0m\n", "| 356 | 8.9000 | 1 | 0.620400 | 0.561975 | -4.953444 |-1.034848e+01 |-7.229356e-01 |\u001b[0m\n", "| 357 | 8.9250 | 1 | 0.617789 | 0.557966 | -4.948106 |-1.034981e+01 |-7.228093e-01 |\u001b[0m\n", "| 358 | 8.9500 | 1 | 0.617529 | 0.559993 | -4.942308 |-1.035113e+01 |-7.226850e-01 |\u001b[0m\n", "| 359 | 8.9750 | 1 | 0.618381 | 0.557392 | -4.938375 |-1.035241e+01 |-7.225625e-01 |\u001b[0m\n", "| 360 | 9.0000 | 1 | 0.615879 | 0.559019 | -4.952078 |-1.035368e+01 |-7.224422e-01 |\u001b[0m\n", "| 361 | 9.0250 | 1 | 0.619261 | 0.558077 | -4.968153 |-1.035492e+01 |-7.223238e-01 |\u001b[0m\n", "| 362 | 9.0500 | 1 | 0.620542 | 0.558342 | -4.974721 |-1.035614e+01 |-7.222074e-01 |\u001b[0m\n", "| 363 | 9.0750 | 1 | 0.620811 | 0.562190 | -4.979177 |-1.035734e+01 |-7.220928e-01 |\u001b[0m\n", "| 364 | 9.1000 | 1 | 0.621215 | 0.561517 | -4.966813 |-1.035851e+01 |-7.219798e-01 |\u001b[0m\n", "| 365 | 9.1250 | 1 | 0.619293 | 0.556963 | -4.968124 |-1.035966e+01 |-7.218687e-01 |\u001b[0m\n", "| 366 | 9.1500 | 1 | 0.617414 | 0.560206 | -4.976628 |-1.036079e+01 |-7.217596e-01 |\u001b[0m\n", "| 367 | 9.1750 | 1 | 0.617224 | 0.560026 | -4.995733 |-1.036189e+01 |-7.216525e-01 |\u001b[0m\n", "| 368 | 9.2000 | 1 | 0.594361 | 0.572718 | -5.002079 |-1.036297e+01 |-7.215472e-01 |\u001b[0m\n", "| 369 | 9.2250 | 1 | 0.591752 | 0.620408 | -5.007823 |-1.036402e+01 |-7.214437e-01 |\u001b[0m\n", "| 370 | 9.2500 | 1 | 0.573933 | 0.595363 | -4.998087 |-1.036505e+01 |-7.213418e-01 |\u001b[0m\n", "| 371 | 9.2750 | 1 | 0.574780 | 0.599225 | -4.997593 |-1.036606e+01 |-7.212418e-01 |\u001b[0m\n", "| 372 | 9.3000 | 1 | 0.573589 | 0.590403 | -5.006758 |-1.036704e+01 |-7.211437e-01 |\u001b[0m\n", "| 373 | 9.3250 | 1 | 0.609827 | 0.562178 | -5.021476 |-1.036800e+01 |-7.210476e-01 |\u001b[0m\n", "| 374 | 9.3500 | 1 | 0.614184 | 0.559384 | -5.036294 |-1.036894e+01 |-7.209533e-01 |\u001b[0m\n", "| 375 | 9.3750 | 1 | 0.613003 | 0.559421 | -5.033179 |-1.036985e+01 |-7.208608e-01 |\u001b[0m\n", "| 376 | 9.4000 | 1 | 0.615698 | 0.562315 | -5.033218 |-1.037074e+01 |-7.207701e-01 |\u001b[0m\n", "| 377 | 9.4250 | 1 | 0.614765 | 0.561798 | -5.027569 |-1.037161e+01 |-7.206811e-01 |\u001b[0m\n", "| 378 | 9.4500 | 1 | 0.617829 | 0.560943 | -5.035541 |-1.037245e+01 |-7.205942e-01 |\u001b[0m\n", "| 379 | 9.4750 | 1 | 0.618720 | 0.564336 | -5.051179 |-1.037327e+01 |-7.205092e-01 |\u001b[0m\n", "| 380 | 9.5000 | 1 | 0.612249 | 0.559879 | -5.064730 |-1.037407e+01 |-7.204262e-01 |\u001b[0m\n", "| 381 | 9.5250 | 1 | 0.616751 | 0.558819 | -5.064004 |-1.037484e+01 |-7.203449e-01 |\u001b[0m\n", "| 382 | 9.5500 | 1 | 0.609852 | 0.562424 | -5.062977 |-1.037559e+01 |-7.202654e-01 |\u001b[0m\n", "| 383 | 9.5750 | 1 | 0.610932 | 0.561283 | -5.059836 |-1.037632e+01 |-7.201877e-01 |\u001b[0m\n", "| 384 | 9.6000 | 1 | 0.609683 | 0.559666 | -5.063394 |-1.037702e+01 |-7.201119e-01 |\u001b[0m\n", "| 385 | 9.6250 | 1 | 0.614492 | 0.563530 | -5.081452 |-1.037770e+01 |-7.200382e-01 |\u001b[0m\n", "| 386 | 9.6500 | 1 | 0.606921 | 0.560035 | -5.090793 |-1.037835e+01 |-7.199663e-01 |\u001b[0m\n", "| 387 | 9.6750 | 1 | 0.614644 | 0.567031 | -5.097605 |-1.037899e+01 |-7.198962e-01 |\u001b[0m\n", "| 388 | 9.7000 | 1 | 0.584276 | 0.590999 | -5.091026 |-1.037960e+01 |-7.198278e-01 |\u001b[0m\n", "| 389 | 9.7250 | 1 | 0.567643 | 0.593811 | -5.091591 |-1.038018e+01 |-7.197613e-01 |\u001b[0m\n", "| 390 | 9.7500 | 1 | 0.567374 | 0.596893 | -5.095862 |-1.038075e+01 |-7.196965e-01 |\u001b[0m\n", "| 391 | 9.7750 | 1 | 0.572616 | 0.596802 | -5.106980 |-1.038129e+01 |-7.196338e-01 |\u001b[0m\n", "| 392 | 9.8000 | 1 | 0.570078 | 0.591577 | -5.123029 |-1.038181e+01 |-7.195729e-01 |\u001b[0m\n", "| 393 | 9.8250 | 1 | 0.568206 | 0.592200 | -5.126210 |-1.038230e+01 |-7.195138e-01 |\u001b[0m\n", "| 394 | 9.8500 | 1 | 0.571314 | 0.611958 | -5.123923 |-1.038277e+01 |-7.194564e-01 |\u001b[0m\n", "| 395 | 9.8750 | 1 | 0.567253 | 0.597897 | -5.121337 |-1.038322e+01 |-7.194006e-01 |\u001b[0m\n", "| 396 | 9.9000 | 1 | 0.568210 | 0.593683 | -5.128361 |-1.038365e+01 |-7.193469e-01 |\u001b[0m\n", "| 397 | 9.9250 | 1 | 0.565010 | 0.597851 | -5.135472 |-1.038405e+01 |-7.192949e-01 |\u001b[0m\n", "| 398 | 9.9500 | 1 | 0.607473 | 0.563262 | -5.151271 |-1.038443e+01 |-7.192449e-01 |\u001b[0m\n", "| 399 | 9.9750 | 1 | 0.611926 | 0.565768 | -5.156484 |-1.038479e+01 |-7.191966e-01 |\u001b[0m\n", "| 400 |10.0000 | 1 | 0.609614 | 0.566774 | -5.154040 |-1.038512e+01 |-7.191501e-01 |\u001b[0m\n", "| 401 |10.0250 | 1 | 0.609190 | 0.563351 | -5.151962 |-1.038543e+01 |-7.191053e-01 |\u001b[0m\n", "| 402 |10.0500 | 1 | 0.609842 | 0.560413 | -5.156639 |-1.038572e+01 |-7.190623e-01 |\u001b[0m\n", "| 403 |10.0750 | 1 | 0.608545 | 0.562862 | -5.165364 |-1.038599e+01 |-7.190212e-01 |\u001b[0m\n", "| 404 |10.1000 | 1 | 0.609280 | 0.574591 | -5.175709 |-1.038623e+01 |-7.189820e-01 |\u001b[0m\n", "| 405 |10.1250 | 1 | 0.606715 | 0.563175 | -5.184143 |-1.038645e+01 |-7.189446e-01 |\u001b[0m\n", "| 406 |10.1500 | 1 | 0.604479 | 0.564748 | -5.181991 |-1.038665e+01 |-7.189088e-01 |\u001b[0m\n", "| 407 |10.1750 | 1 | 0.606050 | 0.565112 | -5.179552 |-1.038683e+01 |-7.188749e-01 |\u001b[0m\n", "| 408 |10.2000 | 1 | 0.607161 | 0.564970 | -5.182516 |-1.038698e+01 |-7.188427e-01 |\u001b[0m\n", "| 409 |10.2250 | 1 | 0.611589 | 0.565026 | -5.191318 |-1.038711e+01 |-7.188124e-01 |\u001b[0m\n", "| 410 |10.2500 | 1 | 0.609824 | 0.564363 | -5.199527 |-1.038722e+01 |-7.187839e-01 |\u001b[0m\n", "| 411 |10.2750 | 1 | 0.608612 | 0.565428 | -5.204957 |-1.038731e+01 |-7.187572e-01 |\u001b[0m\n", "| 412 |10.3000 | 1 | 0.607257 | 0.561791 | -5.208673 |-1.038738e+01 |-7.187322e-01 |\u001b[0m\n", "| 413 |10.3250 | 1 | 0.609691 | 0.568279 | -5.201979 |-1.038742e+01 |-7.187089e-01 |\u001b[0m\n", "| 414 |10.3500 | 1 | 0.601743 | 0.568179 | -5.206076 |-1.038744e+01 |-7.186873e-01 |\u001b[0m\n", "| 415 |10.3750 | 1 | 0.601717 | 0.567023 | -5.212946 |-1.038744e+01 |-7.186677e-01 |\u001b[0m\n", "| 416 |10.4000 | 1 | 0.559912 | 0.595790 | -5.218419 |-1.038742e+01 |-7.186497e-01 |\u001b[0m\n", "| 417 |10.4250 | 1 | 0.564230 | 0.598142 | -5.224424 |-1.038737e+01 |-7.186336e-01 |\u001b[0m\n", "| 418 |10.4500 | 1 | 0.583181 | 0.581345 | -5.225489 |-1.038730e+01 |-7.186191e-01 |\u001b[0m\n", "| 419 |10.4750 | 1 | 0.602301 | 0.565017 | -5.224159 |-1.038721e+01 |-7.186063e-01 |\u001b[0m\n", "| 420 |10.5000 | 1 | 0.603593 | 0.566494 | -5.222729 |-1.038710e+01 |-7.185953e-01 |\u001b[0m\n", "| 421 |10.5250 | 1 | 0.601339 | 0.567735 | -5.228588 |-1.038697e+01 |-7.185860e-01 |\u001b[0m\n", "| 422 |10.5500 | 1 | 0.600440 | 0.570387 | -5.234701 |-1.038682e+01 |-7.185785e-01 |\u001b[0m\n", "| 423 |10.5750 | 1 | 0.578888 | 0.583202 | -5.234590 |-1.038664e+01 |-7.185728e-01 |\u001b[0m\n", "| 424 |10.6000 | 1 | 0.604815 | 0.565890 | -5.239249 |-1.038645e+01 |-7.185687e-01 |\u001b[0m\n", "| 425 |10.6250 | 1 | 0.604126 | 0.569152 | -5.236159 |-1.038623e+01 |-7.185663e-01 |\u001b[0m\n", "| 426 |10.6500 | 1 | 0.603547 | 0.566174 | -5.235546 |-1.038599e+01 |-7.185655e-01 |\u001b[0m\n", "| 427 |10.6750 | 1 | 0.602865 | 0.569464 | -5.237490 |-1.038573e+01 |-7.185666e-01 |\u001b[0m\n", "| 428 |10.7000 | 1 | 0.600635 | 0.568652 | -5.241270 |-1.038545e+01 |-7.185694e-01 |\u001b[0m\n", "| 429 |10.7250 | 1 | 0.605226 | 0.574795 | -5.242437 |-1.038514e+01 |-7.185739e-01 |\u001b[0m\n", "| 430 |10.7500 | 1 | 0.600788 | 0.571143 | -5.241394 |-1.038482e+01 |-7.185801e-01 |\u001b[0m\n", "| 431 |10.7750 | 1 | 0.577222 | 0.591226 | -5.242569 |-1.038448e+01 |-7.185880e-01 |\u001b[0m\n", "| 432 |10.8000 | 1 | 0.560620 | 0.597055 | -5.240958 |-1.038411e+01 |-7.185975e-01 |\u001b[0m\n", "| 433 |10.8250 | 1 | 0.556709 | 0.596082 | -5.238278 |-1.038372e+01 |-7.186087e-01 |\u001b[0m\n", "| 434 |10.8500 | 1 | 0.556684 | 0.597562 | -5.242316 |-1.038332e+01 |-7.186217e-01 |\u001b[0m\n", "| 435 |10.8750 | 1 | 0.559204 | 0.599864 | -5.240932 |-1.038289e+01 |-7.186363e-01 |\u001b[0m\n", "| 436 |10.9000 | 1 | 0.554305 | 0.620321 | -5.238040 |-1.038244e+01 |-7.186526e-01 |\u001b[0m\n", "| 437 |10.9250 | 1 | 0.577513 | 0.588008 | -5.241438 |-1.038197e+01 |-7.186705e-01 |\u001b[0m\n", "| 438 |10.9500 | 1 | 0.599971 | 0.568971 | -5.235789 |-1.038148e+01 |-7.186901e-01 |\u001b[0m\n", "| 439 |10.9750 | 1 | 0.599382 | 0.571610 | -5.237054 |-1.038097e+01 |-7.187113e-01 |\u001b[0m\n", "| 440 |11.0000 | 1 | 0.597518 | 0.569614 | -5.232763 |-1.038044e+01 |-7.187342e-01 |\u001b[0m\n", "| 441 |11.0250 | 1 | 0.603696 | 0.575074 | -5.233276 |-1.037989e+01 |-7.187588e-01 |\u001b[0m\n", "| 442 |11.0500 | 1 | 0.583094 | 0.697022 | -5.231065 |-1.037932e+01 |-7.187850e-01 |\u001b[0m\n", "| 443 |11.0750 | 1 | 0.619875 | 0.676204 | -5.227894 |-1.037873e+01 |-7.188128e-01 |\u001b[0m\n", "| 444 |11.1000 | 1 | 0.598458 | 0.604789 | -5.230200 |-1.037812e+01 |-7.188422e-01 |\u001b[0m\n", "| 445 |11.1250 | 1 | 0.604274 | 0.596907 | -5.223329 |-1.037749e+01 |-7.188732e-01 |\u001b[0m\n", "| 446 |11.1500 | 1 | 0.573906 | 0.610394 | -5.222229 |-1.037684e+01 |-7.189059e-01 |\u001b[0m\n", "| 447 |11.1750 | 1 | 0.561556 | 0.624831 | -5.217935 |-1.037617e+01 |-7.189403e-01 |\u001b[0m\n", "| 448 |11.2000 | 1 | 0.569148 | 0.626453 | -5.216896 |-1.037548e+01 |-7.189761e-01 |\u001b[0m\n", "| 449 |11.2250 | 1 | 0.564965 | 0.673984 | -5.213489 |-1.037477e+01 |-7.190136e-01 |\u001b[0m\n", "| 450 |11.2500 | 1 | 0.561458 | 0.614712 | -5.212846 |-1.037404e+01 |-7.190527e-01 |\u001b[0m\n", "| 451 |11.2750 | 1 | 0.573335 | 0.635211 | -5.209958 |-1.037329e+01 |-7.190933e-01 |\u001b[0m\n", "| 452 |11.3000 | 1 | 0.601036 | 0.589727 | -5.203174 |-1.037253e+01 |-7.191356e-01 |\u001b[0m\n", "| 453 |11.3250 | 1 | 0.588650 | 0.649352 | -5.201050 |-1.037174e+01 |-7.191795e-01 |\u001b[0m\n", "| 454 |11.3500 | 1 | 0.581506 | 0.609257 | -5.196793 |-1.037094e+01 |-7.192250e-01 |\u001b[0m\n", "| 455 |11.3750 | 1 | 0.589146 | 0.602935 | -5.195584 |-1.037011e+01 |-7.192719e-01 |\u001b[0m\n", "| 456 |11.4000 | 1 | 0.604540 | 0.602902 | -5.192228 |-1.036927e+01 |-7.193204e-01 |\u001b[0m\n", "| 457 |11.4250 | 1 | 0.600579 | 0.594754 | -5.191012 |-1.036841e+01 |-7.193705e-01 |\u001b[0m\n", "| 458 |11.4500 | 1 | 0.594932 | 0.585988 | -5.182644 |-1.036753e+01 |-7.194222e-01 |\u001b[0m\n", "| 459 |11.4750 | 1 | 0.594136 | 0.587465 | -5.180474 |-1.036663e+01 |-7.194754e-01 |\u001b[0m\n", "| 460 |11.5000 | 1 | 0.615072 | 0.617353 | -5.174101 |-1.036571e+01 |-7.195302e-01 |\u001b[0m\n", "| 461 |11.5250 | 1 | 0.580867 | 0.632210 | -5.174853 |-1.036478e+01 |-7.195865e-01 |\u001b[0m\n", "| 462 |11.5500 | 1 | 0.594978 | 0.646045 | -5.170050 |-1.036382e+01 |-7.196442e-01 |\u001b[0m\n", "| 463 |11.5750 | 1 | 0.578537 | 0.617545 | -5.167418 |-1.036285e+01 |-7.197035e-01 |\u001b[0m\n", "| 464 |11.6000 | 1 | 0.591423 | 0.656918 | -5.163354 |-1.036186e+01 |-7.197643e-01 |\u001b[0m\n", "| 465 |11.6250 | 1 | 0.596206 | 0.593319 | -5.154243 |-1.036085e+01 |-7.198267e-01 |\u001b[0m\n", "| 466 |11.6500 | 1 | 0.594452 | 0.615504 | -5.154289 |-1.035983e+01 |-7.198905e-01 |\u001b[0m\n", "| 467 |11.6750 | 1 | 0.585962 | 0.589556 | -5.148727 |-1.035878e+01 |-7.199559e-01 |\u001b[0m\n", "| 468 |11.7000 | 1 | 0.590704 | 0.592317 | -5.148438 |-1.035772e+01 |-7.200226e-01 |\u001b[0m\n", "| 469 |11.7250 | 1 | 0.588268 | 0.610512 | -5.143636 |-1.035664e+01 |-7.200909e-01 |\u001b[0m\n", "| 470 |11.7500 | 1 | 0.613000 | 0.604874 | -5.138982 |-1.035554e+01 |-7.201607e-01 |\u001b[0m\n", "| 471 |11.7750 | 1 | 0.597844 | 0.624120 | -5.133073 |-1.035443e+01 |-7.202319e-01 |\u001b[0m\n", "| 472 |11.8000 | 1 | 0.612249 | 0.607180 | -5.128111 |-1.035330e+01 |-7.203046e-01 |\u001b[0m\n", "| 473 |11.8250 | 1 | 0.589453 | 0.613552 | -5.126736 |-1.035215e+01 |-7.203787e-01 |\u001b[0m\n", "| 474 |11.8500 | 1 | 0.593775 | 0.611720 | -5.123366 |-1.035098e+01 |-7.204543e-01 |\u001b[0m\n", "| 475 |11.8750 | 1 | 0.589657 | 0.587944 | -5.120797 |-1.034980e+01 |-7.205313e-01 |\u001b[0m\n", "| 476 |11.9000 | 1 | 0.589097 | 0.606891 | -5.114568 |-1.034860e+01 |-7.206098e-01 |\u001b[0m\n", "| 477 |11.9250 | 1 | 0.587495 | 0.629681 | -5.110620 |-1.034739e+01 |-7.206897e-01 |\u001b[0m\n", "| 478 |11.9500 | 1 | 0.584468 | 0.608920 | -5.103850 |-1.034615e+01 |-7.207710e-01 |\u001b[0m\n", "| 479 |11.9750 | 1 | 0.590653 | 0.613406 | -5.103272 |-1.034491e+01 |-7.208537e-01 |\u001b[0m\n", "| 480 |12.0000 | 1 | 0.622715 | 0.666483 | -5.099819 |-1.034364e+01 |-7.209378e-01 |\u001b[0m\n", "| 481 |12.0250 | 1 | 0.604598 | 0.600735 | -5.096876 |-1.034236e+01 |-7.210233e-01 |\u001b[0m\n", "| 482 |12.0500 | 1 | 0.607466 | 0.600419 | -5.092345 |-1.034106e+01 |-7.211102e-01 |\u001b[0m\n", "| 483 |12.0750 | 1 | 0.611803 | 0.651007 | -5.086132 |-1.033975e+01 |-7.211985e-01 |\u001b[0m\n", "| 484 |12.1000 | 1 | 0.600163 | 0.604296 | -5.082930 |-1.033842e+01 |-7.212882e-01 |\u001b[0m\n", "| 485 |12.1250 | 1 | 0.597081 | 0.608633 | -5.078037 |-1.033707e+01 |-7.213792e-01 |\u001b[0m\n", "| 486 |12.1500 | 1 | 0.599493 | 0.694460 | -5.078534 |-1.033571e+01 |-7.214716e-01 |\u001b[0m\n", "| 487 |12.1750 | 1 | 0.569671 | 0.611804 | -5.072894 |-1.033433e+01 |-7.215653e-01 |\u001b[0m\n", "| 488 |12.2000 | 1 | 0.590231 | 0.592073 | -5.070548 |-1.033294e+01 |-7.216604e-01 |\u001b[0m\n", "| 489 |12.2250 | 1 | 0.586426 | 0.587308 | -5.063791 |-1.033153e+01 |-7.217569e-01 |\u001b[0m\n", "| 490 |12.2500 | 1 | 0.605616 | 0.639888 | -5.060453 |-1.033011e+01 |-7.218546e-01 |\u001b[0m\n", "| 491 |12.2750 | 1 | 0.601391 | 0.589785 | -5.056911 |-1.032867e+01 |-7.219537e-01 |\u001b[0m\n", "| 492 |12.3000 | 1 | 0.583376 | 0.588843 | -5.054795 |-1.032722e+01 |-7.220541e-01 |\u001b[0m\n", "| 493 |12.3250 | 1 | 0.591293 | 0.608539 | -5.052808 |-1.032575e+01 |-7.221558e-01 |\u001b[0m\n", "| 494 |12.3500 | 1 | 0.568992 | 0.595768 | -5.047491 |-1.032427e+01 |-7.222588e-01 |\u001b[0m\n", "| 495 |12.3750 | 1 | 0.563630 | 0.599818 | -5.043728 |-1.032277e+01 |-7.223631e-01 |\u001b[0m\n", "| 496 |12.4000 | 1 | 0.583918 | 0.582994 | -5.038711 |-1.032126e+01 |-7.224687e-01 |\u001b[0m\n", "| 497 |12.4250 | 1 | 0.587738 | 0.584071 | -5.036205 |-1.031973e+01 |-7.225755e-01 |\u001b[0m\n", "| 498 |12.4500 | 1 | 0.575600 | 0.588839 | -5.033715 |-1.031819e+01 |-7.226837e-01 |\u001b[0m\n", "| 499 |12.4750 | 1 | 0.586772 | 0.589606 | -5.031815 |-1.031664e+01 |-7.227930e-01 |\u001b[0m\n", "| 500 |12.5000 | 1 | 0.577898 | 0.602334 | -5.027416 |-1.031507e+01 |-7.229037e-01 |\u001b[0m\n", "| 501 |12.5250 | 1 | 0.585324 | 0.589641 | -5.023384 |-1.031348e+01 |-7.230155e-01 |\u001b[0m\n", "| 502 |12.5500 | 1 | 0.585512 | 0.584594 | -5.019042 |-1.031189e+01 |-7.231287e-01 |\u001b[0m\n", "| 503 |12.5750 | 1 | 0.583865 | 0.582880 | -5.016063 |-1.031028e+01 |-7.232430e-01 |\u001b[0m\n", "| 504 |12.6000 | 1 | 0.588496 | 0.592948 | -5.014501 |-1.030865e+01 |-7.233586e-01 |\u001b[0m\n", "| 505 |12.6250 | 1 | 0.587081 | 0.593772 | -5.011221 |-1.030702e+01 |-7.234753e-01 |\u001b[0m\n", "| 506 |12.6500 | 1 | 0.585744 | 0.587407 | -5.009390 |-1.030537e+01 |-7.235933e-01 |\u001b[0m\n", "| 507 |12.6750 | 1 | 0.584881 | 0.596360 | -5.003415 |-1.030370e+01 |-7.237125e-01 |\u001b[0m\n", "| 508 |12.7000 | 1 | 0.596729 | 0.591525 | -5.001095 |-1.030203e+01 |-7.238329e-01 |\u001b[0m\n", "| 509 |12.7250 | 1 | 0.596917 | 0.597681 | -4.997208 |-1.030034e+01 |-7.239544e-01 |\u001b[0m\n", "| 510 |12.7500 | 1 | 0.591709 | 0.586425 | -4.995682 |-1.029863e+01 |-7.240771e-01 |\u001b[0m\n", "| 511 |12.7750 | 1 | 0.587641 | 0.591100 | -4.993524 |-1.029692e+01 |-7.242010e-01 |\u001b[0m\n", "| 512 |12.8000 | 1 | 0.587204 | 0.586659 | -4.990360 |-1.029519e+01 |-7.243260e-01 |\u001b[0m\n", "| 513 |12.8250 | 1 | 0.591092 | 0.587204 | -4.986833 |-1.029345e+01 |-7.244521e-01 |\u001b[0m\n", "| 514 |12.8500 | 1 | 0.585463 | 0.583301 | -4.982664 |-1.029170e+01 |-7.245795e-01 |\u001b[0m\n", "| 515 |12.8750 | 1 | 0.589045 | 0.586801 | -4.980566 |-1.028993e+01 |-7.247079e-01 |\u001b[0m\n", "| 516 |12.9000 | 1 | 0.596714 | 0.601805 | -4.977829 |-1.028816e+01 |-7.248374e-01 |\u001b[0m\n", "| 517 |12.9250 | 1 | 0.584486 | 0.594004 | -4.976834 |-1.028637e+01 |-7.249680e-01 |\u001b[0m\n", "| 518 |12.9500 | 1 | 0.577005 | 0.592874 | -4.973053 |-1.028457e+01 |-7.250998e-01 |\u001b[0m\n", "| 519 |12.9750 | 1 | 0.576873 | 0.590686 | -4.970798 |-1.028276e+01 |-7.252326e-01 |\u001b[0m\n", "| 520 |13.0000 | 1 | 0.588123 | 0.584250 | -4.966119 |-1.028094e+01 |-7.253666e-01 |\u001b[0m\n", "| 521 |13.0250 | 1 | 0.588080 | 0.592052 | -4.964473 |-1.027910e+01 |-7.255016e-01 |\u001b[0m\n", "| 522 |13.0500 | 1 | 0.586211 | 0.588701 | -4.961953 |-1.027726e+01 |-7.256376e-01 |\u001b[0m\n", "| 523 |13.0750 | 1 | 0.582357 | 0.595669 | -4.960468 |-1.027540e+01 |-7.257747e-01 |\u001b[0m\n", "| 524 |13.1000 | 1 | 0.569600 | 0.594107 | -4.958069 |-1.027353e+01 |-7.259128e-01 |\u001b[0m\n", "| 525 |13.1250 | 1 | 0.575376 | 0.594160 | -4.954661 |-1.027165e+01 |-7.260521e-01 |\u001b[0m\n", "| 526 |13.1500 | 1 | 0.578324 | 0.588696 | -4.952012 |-1.026976e+01 |-7.261923e-01 |\u001b[0m\n", "| 527 |13.1750 | 1 | 0.587095 | 0.592266 | -4.948571 |-1.026786e+01 |-7.263335e-01 |\u001b[0m\n", "| 528 |13.2000 | 1 | 0.569050 | 0.599524 | -4.947926 |-1.026595e+01 |-7.264758e-01 |\u001b[0m\n", "| 529 |13.2250 | 1 | 0.542086 | 0.614471 | -4.945114 |-1.026403e+01 |-7.266190e-01 |\u001b[0m\n", "| 530 |13.2500 | 1 | 0.540521 | 0.611883 | -4.943929 |-1.026210e+01 |-7.267632e-01 |\u001b[0m\n", "| 531 |13.2750 | 1 | 0.543265 | 0.621184 | -4.940566 |-1.026016e+01 |-7.269084e-01 |\u001b[0m\n", "| 532 |13.3000 | 1 | 0.541681 | 0.615142 | -4.937617 |-1.025821e+01 |-7.270546e-01 |\u001b[0m\n", "| 533 |13.3250 | 1 | 0.540352 | 0.616832 | -4.935709 |-1.025625e+01 |-7.272018e-01 |\u001b[0m\n", "| 534 |13.3500 | 1 | 0.544213 | 0.620769 | -4.933038 |-1.025428e+01 |-7.273499e-01 |\u001b[0m\n", "| 535 |13.3750 | 1 | 0.580695 | 0.590777 | -4.932776 |-1.025230e+01 |-7.274989e-01 |\u001b[0m\n", "| 536 |13.4000 | 1 | 0.575465 | 0.596223 | -4.929595 |-1.025031e+01 |-7.276489e-01 |\u001b[0m\n", "| 537 |13.4250 | 1 | 0.542536 | 0.618744 | -4.928176 |-1.024831e+01 |-7.277998e-01 |\u001b[0m\n", "| 538 |13.4500 | 1 | 0.580911 | 0.591514 | -4.924586 |-1.024631e+01 |-7.279516e-01 |\u001b[0m\n", "| 539 |13.4750 | 1 | 0.581108 | 0.591034 | -4.922988 |-1.024429e+01 |-7.281044e-01 |\u001b[0m\n", "| 540 |13.5000 | 1 | 0.545993 | 0.614974 | -4.920767 |-1.024227e+01 |-7.282580e-01 |\u001b[0m\n", "| 541 |13.5250 | 1 | 0.545921 | 0.615012 | -4.919915 |-1.024023e+01 |-7.284125e-01 |\u001b[0m\n", "| 542 |13.5500 | 1 | 0.542495 | 0.613665 | -4.917870 |-1.023819e+01 |-7.285679e-01 |\u001b[0m\n", "| 543 |13.5750 | 1 | 0.536964 | 0.622532 | -4.915675 |-1.023614e+01 |-7.287241e-01 |\u001b[0m\n", "| 544 |13.6000 | 1 | 0.554106 | 0.603419 | -4.913529 |-1.023408e+01 |-7.288812e-01 |\u001b[0m\n", "| 545 |13.6250 | 1 | 0.578840 | 0.604562 | -4.910680 |-1.023202e+01 |-7.290392e-01 |\u001b[0m\n", "| 546 |13.6500 | 1 | 0.585058 | 0.606280 | -4.910137 |-1.022994e+01 |-7.291979e-01 |\u001b[0m\n", "| 547 |13.6750 | 1 | 0.579318 | 0.591994 | -4.907749 |-1.022786e+01 |-7.293575e-01 |\u001b[0m\n", "| 548 |13.7000 | 1 | 0.580380 | 0.589279 | -4.907371 |-1.022577e+01 |-7.295179e-01 |\u001b[0m\n", "| 549 |13.7250 | 1 | 0.584794 | 0.591958 | -4.904445 |-1.022367e+01 |-7.296792e-01 |\u001b[0m\n", "| 550 |13.7500 | 1 | 0.588687 | 0.601052 | -4.902856 |-1.022157e+01 |-7.298412e-01 |\u001b[0m\n", "| 551 |13.7750 | 1 | 0.579102 | 0.595429 | -4.900526 |-1.021945e+01 |-7.300040e-01 |\u001b[0m\n", "| 552 |13.8000 | 1 | 0.579922 | 0.596135 | -4.899156 |-1.021733e+01 |-7.301676e-01 |\u001b[0m\n", "| 553 |13.8250 | 1 | 0.579598 | 0.597193 | -4.898152 |-1.021521e+01 |-7.303320e-01 |\u001b[0m\n", "| 554 |13.8500 | 1 | 0.589419 | 0.608047 | -4.896538 |-1.021307e+01 |-7.304971e-01 |\u001b[0m\n", "| 555 |13.8750 | 1 | 0.584321 | 0.601955 | -4.895262 |-1.021093e+01 |-7.306629e-01 |\u001b[0m\n", "| 556 |13.9000 | 1 | 0.579038 | 0.593430 | -4.892631 |-1.020879e+01 |-7.308295e-01 |\u001b[0m\n", "| 557 |13.9250 | 1 | 0.577858 | 0.591474 | -4.891491 |-1.020663e+01 |-7.309968e-01 |\u001b[0m\n", "| 558 |13.9500 | 1 | 0.578370 | 0.601104 | -4.889505 |-1.020447e+01 |-7.311649e-01 |\u001b[0m\n", "| 559 |13.9750 | 1 | 0.574518 | 0.594961 | -4.888943 |-1.020231e+01 |-7.313336e-01 |\u001b[0m\n", "| 560 |14.0000 | 1 | 0.578717 | 0.588370 | -4.887586 |-1.020014e+01 |-7.315030e-01 |\u001b[0m\n", "| 561 |14.0250 | 1 | 0.569221 | 0.592313 | -4.886037 |-1.019796e+01 |-7.316731e-01 |\u001b[0m\n", "| 562 |14.0500 | 1 | 0.569323 | 0.588773 | -4.884458 |-1.019577e+01 |-7.318439e-01 |\u001b[0m\n", "| 563 |14.0750 | 1 | 0.575350 | 0.590234 | -4.882463 |-1.019359e+01 |-7.320154e-01 |\u001b[0m\n", "| 564 |14.1000 | 1 | 0.565140 | 0.597502 | -4.881607 |-1.019139e+01 |-7.321875e-01 |\u001b[0m\n", "| 565 |14.1250 | 1 | 0.555995 | 0.601872 | -4.880250 |-1.018919e+01 |-7.323602e-01 |\u001b[0m\n", "| 566 |14.1500 | 1 | 0.582065 | 0.599824 | -4.879771 |-1.018699e+01 |-7.325336e-01 |\u001b[0m\n", "| 567 |14.1750 | 1 | 0.573287 | 0.591480 | -4.877873 |-1.018478e+01 |-7.327076e-01 |\u001b[0m\n", "| 568 |14.2000 | 1 | 0.574334 | 0.602253 | -4.876963 |-1.018256e+01 |-7.328822e-01 |\u001b[0m\n", "| 569 |14.2250 | 1 | 0.531243 | 0.628437 | -4.874752 |-1.018034e+01 |-7.330574e-01 |\u001b[0m\n", "| 570 |14.2500 | 1 | 0.530046 | 0.617958 | -4.874261 |-1.017812e+01 |-7.332332e-01 |\u001b[0m\n", "| 571 |14.2750 | 1 | 0.533493 | 0.621578 | -4.872975 |-1.017589e+01 |-7.334095e-01 |\u001b[0m\n", "| 572 |14.3000 | 1 | 0.531280 | 0.620959 | -4.872344 |-1.017365e+01 |-7.335865e-01 |\u001b[0m\n", "| 573 |14.3250 | 1 | 0.533888 | 0.617618 | -4.871330 |-1.017142e+01 |-7.337640e-01 |\u001b[0m\n", "| 574 |14.3500 | 1 | 0.538329 | 0.615710 | -4.869608 |-1.016917e+01 |-7.339420e-01 |\u001b[0m\n", "| 575 |14.3750 | 1 | 0.538880 | 0.617203 | -4.868750 |-1.016693e+01 |-7.341206e-01 |\u001b[0m\n", "| 576 |14.4000 | 1 | 0.538717 | 0.619451 | -4.867217 |-1.016468e+01 |-7.342997e-01 |\u001b[0m\n", "| 577 |14.4250 | 1 | 0.521259 | 0.628772 | -4.866965 |-1.016243e+01 |-7.344794e-01 |\u001b[0m\n", "| 578 |14.4500 | 1 | 0.532905 | 0.622927 | -4.865877 |-1.016017e+01 |-7.346595e-01 |\u001b[0m\n", "| 579 |14.4750 | 1 | 0.533617 | 0.622496 | -4.865300 |-1.015791e+01 |-7.348401e-01 |\u001b[0m\n", "| 580 |14.5000 | 1 | 0.534588 | 0.619178 | -4.863812 |-1.015565e+01 |-7.350212e-01 |\u001b[0m\n", "| 581 |14.5250 | 1 | 0.541063 | 0.619951 | -4.862879 |-1.015338e+01 |-7.352028e-01 |\u001b[0m\n", "| 582 |14.5500 | 1 | 0.552002 | 0.630617 | -4.861832 |-1.015111e+01 |-7.353848e-01 |\u001b[0m\n", "| 583 |14.5750 | 1 | 0.534411 | 0.618651 | -4.861210 |-1.014884e+01 |-7.355673e-01 |\u001b[0m\n", "| 584 |14.6000 | 1 | 0.519947 | 0.634783 | -4.860831 |-1.014656e+01 |-7.357502e-01 |\u001b[0m\n", "| 585 |14.6250 | 1 | 0.532511 | 0.623628 | -4.859751 |-1.014429e+01 |-7.359336e-01 |\u001b[0m\n", "| 586 |14.6500 | 1 | 0.533837 | 0.631545 | -4.859199 |-1.014201e+01 |-7.361174e-01 |\u001b[0m\n", "| 587 |14.6750 | 1 | 0.531337 | 0.621002 | -4.857699 |-1.013972e+01 |-7.363016e-01 |\u001b[0m\n", "| 588 |14.7000 | 1 | 0.533695 | 0.628152 | -4.857362 |-1.013744e+01 |-7.364862e-01 |\u001b[0m\n", "| 589 |14.7250 | 1 | 0.537053 | 0.620282 | -4.856500 |-1.013515e+01 |-7.366711e-01 |\u001b[0m\n", "| 590 |14.7500 | 1 | 0.530726 | 0.624015 | -4.856289 |-1.013286e+01 |-7.368564e-01 |\u001b[0m\n", "| 591 |14.7750 | 1 | 0.536236 | 0.627392 | -4.855632 |-1.013057e+01 |-7.370421e-01 |\u001b[0m\n", "| 592 |14.8000 | 1 | 0.543870 | 0.627993 | -4.854757 |-1.012828e+01 |-7.372282e-01 |\u001b[0m\n", "| 593 |14.8250 | 1 | 0.526978 | 0.622442 | -4.854112 |-1.012599e+01 |-7.374146e-01 |\u001b[0m\n", "| 594 |14.8500 | 1 | 0.533051 | 0.620095 | -4.853078 |-1.012369e+01 |-7.376013e-01 |\u001b[0m\n", "| 595 |14.8750 | 1 | 0.537832 | 0.629321 | -4.853194 |-1.012140e+01 |-7.377884e-01 |\u001b[0m\n", "| 596 |14.9000 | 1 | 0.534586 | 0.618118 | -4.852261 |-1.011910e+01 |-7.379757e-01 |\u001b[0m\n", "| 597 |14.9250 | 1 | 0.567604 | 0.593661 | -4.852487 |-1.011680e+01 |-7.381634e-01 |\u001b[0m\n", "| 598 |14.9500 | 1 | 0.572954 | 0.599532 | -4.851275 |-1.011451e+01 |-7.383513e-01 |\u001b[0m\n", "| 599 |14.9750 | 1 | 0.562174 | 0.595357 | -4.851012 |-1.011221e+01 |-7.385395e-01 |\u001b[0m\n", "| 600 |15.0000 | 1 | 0.565041 | 0.594815 | -4.850216 |-1.010991e+01 |-7.387280e-01 |\u001b[0m\n", "| 601 |15.0250 | 1 | 0.568619 | 0.602458 | -4.849993 |-1.010761e+01 |-7.389167e-01 |\u001b[0m\n", "| 602 |15.0500 | 1 | 0.565685 | 0.599703 | -4.849887 |-1.010531e+01 |-7.391057e-01 |\u001b[0m\n", "| 603 |15.0750 | 1 | 0.559376 | 0.612765 | -4.849425 |-1.010300e+01 |-7.392949e-01 |\u001b[0m\n", "| 604 |15.1000 | 1 | 0.558552 | 0.602107 | -4.849206 |-1.010070e+01 |-7.394843e-01 |\u001b[0m\n", "| 605 |15.1250 | 1 | 0.582718 | 0.633646 | -4.848406 |-1.009840e+01 |-7.396740e-01 |\u001b[0m\n", "| 606 |15.1500 | 1 | 0.581577 | 0.623610 | -4.848264 |-1.009610e+01 |-7.398638e-01 |\u001b[0m\n", "| 607 |15.1750 | 1 | 0.565744 | 0.594525 | -4.847774 |-1.009380e+01 |-7.400538e-01 |\u001b[0m\n", "| 608 |15.2000 | 1 | 0.564061 | 0.594557 | -4.848037 |-1.009150e+01 |-7.402440e-01 |\u001b[0m\n", "| 609 |15.2250 | 1 | 0.567888 | 0.599122 | -4.847578 |-1.008921e+01 |-7.404344e-01 |\u001b[0m\n", "| 610 |15.2500 | 1 | 0.570234 | 0.602823 | -4.847542 |-1.008691e+01 |-7.406249e-01 |\u001b[0m\n", "| 611 |15.2750 | 1 | 0.561263 | 0.598452 | -4.847030 |-1.008461e+01 |-7.408156e-01 |\u001b[0m\n", "| 612 |15.3000 | 1 | 0.568963 | 0.599018 | -4.846710 |-1.008231e+01 |-7.410064e-01 |\u001b[0m\n", "| 613 |15.3250 | 1 | 0.559163 | 0.605724 | -4.846813 |-1.008002e+01 |-7.411973e-01 |\u001b[0m\n", "| 614 |15.3500 | 1 | 0.564462 | 0.599463 | -4.846562 |-1.007773e+01 |-7.413883e-01 |\u001b[0m\n", "| 615 |15.3750 | 1 | 0.580952 | 0.610074 | -4.846943 |-1.007543e+01 |-7.415794e-01 |\u001b[0m\n", "| 616 |15.4000 | 1 | 0.568574 | 0.597096 | -4.846413 |-1.007314e+01 |-7.417706e-01 |\u001b[0m\n", "| 617 |15.4250 | 1 | 0.565757 | 0.596118 | -4.846541 |-1.007085e+01 |-7.419619e-01 |\u001b[0m\n", "| 618 |15.4500 | 1 | 0.537671 | 0.632581 | -4.846117 |-1.006857e+01 |-7.421533e-01 |\u001b[0m\n", "| 619 |15.4750 | 1 | 0.561591 | 0.596576 | -4.846302 |-1.006628e+01 |-7.423447e-01 |\u001b[0m\n", "| 620 |15.5000 | 1 | 0.559380 | 0.594044 | -4.846417 |-1.006400e+01 |-7.425361e-01 |\u001b[0m\n", "| 621 |15.5250 | 1 | 0.550661 | 0.639299 | -4.846521 |-1.006172e+01 |-7.427276e-01 |\u001b[0m\n", "| 622 |15.5500 | 1 | 0.564344 | 0.599497 | -4.846664 |-1.005944e+01 |-7.429191e-01 |\u001b[0m\n", "| 623 |15.5750 | 1 | 0.564470 | 0.598390 | -4.846375 |-1.005716e+01 |-7.431107e-01 |\u001b[0m\n", "| 624 |15.6000 | 1 | 0.582095 | 0.621527 | -4.846646 |-1.005489e+01 |-7.433022e-01 |\u001b[0m\n", "| 625 |15.6250 | 1 | 0.563067 | 0.594485 | -4.846450 |-1.005262e+01 |-7.434937e-01 |\u001b[0m\n", "| 626 |15.6500 | 1 | 0.556217 | 0.597713 | -4.847057 |-1.005035e+01 |-7.436852e-01 |\u001b[0m\n", "| 627 |15.6750 | 1 | 0.556730 | 0.599759 | -4.847059 |-1.004809e+01 |-7.438766e-01 |\u001b[0m\n", "| 628 |15.7000 | 1 | 0.559253 | 0.596950 | -4.847442 |-1.004582e+01 |-7.440680e-01 |\u001b[0m\n", "| 629 |15.7250 | 1 | 0.566564 | 0.599786 | -4.847356 |-1.004357e+01 |-7.442594e-01 |\u001b[0m\n", "| 630 |15.7500 | 1 | 0.541901 | 0.610011 | -4.847580 |-1.004131e+01 |-7.444507e-01 |\u001b[0m\n", "| 631 |15.7750 | 1 | 0.552107 | 0.603683 | -4.847782 |-1.003906e+01 |-7.446419e-01 |\u001b[0m\n", "| 632 |15.8000 | 1 | 0.559163 | 0.600536 | -4.848163 |-1.003681e+01 |-7.448331e-01 |\u001b[0m\n", "| 633 |15.8250 | 1 | 0.565520 | 0.607117 | -4.848649 |-1.003456e+01 |-7.450241e-01 |\u001b[0m\n", "| 634 |15.8500 | 1 | 0.563058 | 0.602004 | -4.848834 |-1.003232e+01 |-7.452150e-01 |\u001b[0m\n", "| 635 |15.8750 | 1 | 0.565077 | 0.604592 | -4.849242 |-1.003009e+01 |-7.454058e-01 |\u001b[0m\n", "| 636 |15.9000 | 1 | 0.561084 | 0.613315 | -4.849257 |-1.002785e+01 |-7.455965e-01 |\u001b[0m\n", "| 637 |15.9250 | 1 | 0.562131 | 0.608055 | -4.849893 |-1.002562e+01 |-7.457870e-01 |\u001b[0m\n", "| 638 |15.9500 | 1 | 0.567037 | 0.607231 | -4.850162 |-1.002340e+01 |-7.459774e-01 |\u001b[0m\n", "| 639 |15.9750 | 1 | 0.559362 | 0.605681 | -4.850853 |-1.002118e+01 |-7.461676e-01 |\u001b[0m\n", "| 640 |16.0000 | 1 | 0.559990 | 0.606771 | -4.851290 |-1.001896e+01 |-7.463577e-01 |\u001b[0m\n", "| 641 |16.0250 | 1 | 0.563540 | 0.604675 | -4.851567 |-1.001675e+01 |-7.465476e-01 |\u001b[0m\n", "| 642 |16.0500 | 1 | 0.569776 | 0.614118 | -4.852131 |-1.001455e+01 |-7.467372e-01 |\u001b[0m\n", "| 643 |16.0750 | 1 | 0.526022 | 0.638151 | -4.852404 |-1.001234e+01 |-7.469267e-01 |\u001b[0m\n", "| 644 |16.1000 | 1 | 0.561008 | 0.613815 | -4.853264 |-1.001015e+01 |-7.471159e-01 |\u001b[0m\n", "| 645 |16.1250 | 1 | 0.558149 | 0.610124 | -4.853699 |-1.000796e+01 |-7.473050e-01 |\u001b[0m\n", "| 646 |16.1500 | 1 | 0.558817 | 0.602424 | -4.854509 |-1.000577e+01 |-7.474938e-01 |\u001b[0m\n", "| 647 |16.1750 | 1 | 0.551800 | 0.649783 | -4.854874 |-1.000359e+01 |-7.476823e-01 |\u001b[0m\n", "| 648 |16.2000 | 1 | 0.563560 | 0.614133 | -4.830412 |-1.000141e+01 |-7.478702e-01 |\u001b[0m\n", "| 649 |16.2250 | 1 | 0.527692 | 0.637038 | -4.860230 |-9.999242e+00 |-7.480574e-01 |\u001b[0m\n", "| 650 |16.2500 | 1 | 0.520572 | 0.630335 | -4.856936 |-9.997077e+00 |-7.482449e-01 |\u001b[0m\n", "| 651 |16.2750 | 1 | 0.538065 | 0.623007 | -4.855762 |-9.994918e+00 |-7.484321e-01 |\u001b[0m\n", "| 652 |16.3000 | 1 | 0.563850 | 0.606399 | -4.859225 |-9.992765e+00 |-7.486189e-01 |\u001b[0m\n", "| 653 |16.3250 | 1 | 0.569525 | 0.613083 | -4.857579 |-9.990618e+00 |-7.488056e-01 |\u001b[0m\n", "| 654 |16.3500 | 1 | 0.572920 | 0.616659 | -4.856411 |-9.988477e+00 |-7.489920e-01 |\u001b[0m\n", "| 655 |16.3750 | 1 | 0.565054 | 0.622951 | -4.859712 |-9.986343e+00 |-7.491781e-01 |\u001b[0m\n", "| 656 |16.4000 | 1 | 0.538484 | 0.637184 | -4.858656 |-9.984214e+00 |-7.493640e-01 |\u001b[0m\n", "| 657 |16.4250 | 1 | 0.572575 | 0.628920 | -4.863059 |-9.982092e+00 |-7.495496e-01 |\u001b[0m\n", "| 658 |16.4500 | 1 | 0.553086 | 0.615147 | -4.865486 |-9.979977e+00 |-7.497349e-01 |\u001b[0m\n", "| 659 |16.4750 | 1 | 0.554584 | 0.608671 | -4.862494 |-9.977868e+00 |-7.499197e-01 |\u001b[0m\n", "| 660 |16.5000 | 1 | 0.566182 | 0.632013 | -4.865031 |-9.975766e+00 |-7.501042e-01 |\u001b[0m\n", "| 661 |16.5250 | 1 | 0.559237 | 0.607847 | -4.864222 |-9.973671e+00 |-7.502883e-01 |\u001b[0m\n", "| 662 |16.5500 | 1 | 0.547495 | 0.609668 | -4.865897 |-9.971582e+00 |-7.504721e-01 |\u001b[0m\n", "| 663 |16.5750 | 1 | 0.554912 | 0.604920 | -4.870242 |-9.969500e+00 |-7.506554e-01 |\u001b[0m\n", "| 664 |16.6000 | 1 | 0.563164 | 0.608630 | -4.868999 |-9.967426e+00 |-7.508382e-01 |\u001b[0m\n", "| 665 |16.6250 | 1 | 0.557828 | 0.607771 | -4.870617 |-9.965359e+00 |-7.510206e-01 |\u001b[0m\n", "| 666 |16.6500 | 1 | 0.564567 | 0.611154 | -4.869702 |-9.963299e+00 |-7.512027e-01 |\u001b[0m\n", "| 667 |16.6750 | 1 | 0.570958 | 0.634616 | -4.868988 |-9.961247e+00 |-7.513841e-01 |\u001b[0m\n", "| 668 |16.7000 | 1 | 0.555363 | 0.608414 | -4.873698 |-9.959202e+00 |-7.515652e-01 |\u001b[0m\n", "| 669 |16.7250 | 1 | 0.550943 | 0.608364 | -4.874542 |-9.957164e+00 |-7.517458e-01 |\u001b[0m\n", "| 670 |16.7500 | 1 | 0.555703 | 0.608518 | -4.875535 |-9.955134e+00 |-7.519257e-01 |\u001b[0m\n", "| 671 |16.7750 | 1 | 0.545052 | 0.613267 | -4.877426 |-9.953112e+00 |-7.521053e-01 |\u001b[0m\n", "| 672 |16.8000 | 1 | 0.553070 | 0.605358 | -4.874922 |-9.951098e+00 |-7.522844e-01 |\u001b[0m\n", "| 673 |16.8250 | 1 | 0.561627 | 0.612107 | -4.876898 |-9.949092e+00 |-7.524629e-01 |\u001b[0m\n", "| 674 |16.8500 | 1 | 0.555659 | 0.607956 | -4.879987 |-9.947094e+00 |-7.526409e-01 |\u001b[0m\n", "| 675 |16.8750 | 1 | 0.553403 | 0.616417 | -4.880702 |-9.945105e+00 |-7.528184e-01 |\u001b[0m\n", "| 676 |16.9000 | 1 | 0.553432 | 0.612899 | -4.884087 |-9.943123e+00 |-7.529953e-01 |\u001b[0m\n", "| 677 |16.9250 | 1 | 0.559188 | 0.618660 | -4.883577 |-9.941150e+00 |-7.531718e-01 |\u001b[0m\n", "| 678 |16.9500 | 1 | 0.555787 | 0.615341 | -4.883057 |-9.939185e+00 |-7.533476e-01 |\u001b[0m\n", "| 679 |16.9750 | 1 | 0.558311 | 0.612354 | -4.885587 |-9.937229e+00 |-7.535229e-01 |\u001b[0m\n", "| 680 |17.0000 | 1 | 0.561945 | 0.618028 | -4.886378 |-9.935282e+00 |-7.536977e-01 |\u001b[0m\n", "| 681 |17.0250 | 1 | 0.539489 | 0.628249 | -4.890029 |-9.933343e+00 |-7.538718e-01 |\u001b[0m\n", "| 682 |17.0500 | 1 | 0.514794 | 0.643622 | -4.892106 |-9.931413e+00 |-7.540454e-01 |\u001b[0m\n", "| 683 |17.0750 | 1 | 0.539393 | 0.626948 | -4.890958 |-9.929493e+00 |-7.542183e-01 |\u001b[0m\n", "| 684 |17.1000 | 1 | 0.560875 | 0.618428 | -4.893438 |-9.927581e+00 |-7.543907e-01 |\u001b[0m\n", "| 685 |17.1250 | 1 | 0.554366 | 0.611880 | -4.893277 |-9.925678e+00 |-7.545624e-01 |\u001b[0m\n", "| 686 |17.1500 | 1 | 0.553763 | 0.618234 | -4.894869 |-9.923784e+00 |-7.547335e-01 |\u001b[0m\n", "| 687 |17.1750 | 1 | 0.553390 | 0.611751 | -4.899919 |-9.921900e+00 |-7.549040e-01 |\u001b[0m\n", "| 688 |17.2000 | 1 | 0.556929 | 0.626983 | -4.899359 |-9.920025e+00 |-7.550737e-01 |\u001b[0m\n", "| 689 |17.2250 | 1 | 0.551784 | 0.617594 | -4.901290 |-9.918159e+00 |-7.552428e-01 |\u001b[0m\n", "| 690 |17.2500 | 1 | 0.534141 | 0.623701 | -4.902376 |-9.916303e+00 |-7.554113e-01 |\u001b[0m\n", "| 691 |17.2750 | 1 | 0.520007 | 0.638176 | -4.901707 |-9.914456e+00 |-7.555791e-01 |\u001b[0m\n", "| 692 |17.3000 | 1 | 0.514522 | 0.638778 | -4.905930 |-9.912619e+00 |-7.557462e-01 |\u001b[0m\n", "| 693 |17.3250 | 1 | 0.510692 | 0.643761 | -4.907376 |-9.910792e+00 |-7.559126e-01 |\u001b[0m\n", "| 694 |17.3500 | 1 | 0.517713 | 0.643936 | -4.909717 |-9.908974e+00 |-7.560782e-01 |\u001b[0m\n", "| 695 |17.3750 | 1 | 0.541182 | 0.655376 | -4.911696 |-9.907166e+00 |-7.562432e-01 |\u001b[0m\n", "| 696 |17.4000 | 1 | 0.551757 | 0.611377 | -4.910876 |-9.905369e+00 |-7.564075e-01 |\u001b[0m\n", "| 697 |17.4250 | 1 | 0.555262 | 0.614891 | -4.913403 |-9.903581e+00 |-7.565710e-01 |\u001b[0m\n", "| 698 |17.4500 | 1 | 0.564454 | 0.624082 | -4.915500 |-9.901804e+00 |-7.567338e-01 |\u001b[0m\n", "| 699 |17.4750 | 1 | 0.553199 | 0.610732 | -4.917215 |-9.900037e+00 |-7.568959e-01 |\u001b[0m\n", "| 700 |17.5000 | 1 | 0.548138 | 0.608044 | -4.921356 |-9.898280e+00 |-7.570572e-01 |\u001b[0m\n", "| 701 |17.5250 | 1 | 0.549388 | 0.607808 | -4.921642 |-9.896533e+00 |-7.572177e-01 |\u001b[0m\n", "| 702 |17.5500 | 1 | 0.553081 | 0.618402 | -4.922276 |-9.894797e+00 |-7.573775e-01 |\u001b[0m\n", "| 703 |17.5750 | 1 | 0.551544 | 0.613274 | -4.925124 |-9.893071e+00 |-7.575366e-01 |\u001b[0m\n", "| 704 |17.6000 | 1 | 0.537771 | 0.622332 | -4.925960 |-9.891356e+00 |-7.576948e-01 |\u001b[0m\n", "| 705 |17.6250 | 1 | 0.547941 | 0.612396 | -4.929428 |-9.889651e+00 |-7.578523e-01 |\u001b[0m\n", "| 706 |17.6500 | 1 | 0.558546 | 0.618044 | -4.932578 |-9.887957e+00 |-7.580090e-01 |\u001b[0m\n", "| 707 |17.6750 | 1 | 0.550670 | 0.613128 | -4.933198 |-9.886274e+00 |-7.581648e-01 |\u001b[0m\n", "| 708 |17.7000 | 1 | 0.549359 | 0.610619 | -4.935304 |-9.884602e+00 |-7.583199e-01 |\u001b[0m\n", "| 709 |17.7250 | 1 | 0.548109 | 0.608598 | -4.936363 |-9.882941e+00 |-7.584741e-01 |\u001b[0m\n", "| 710 |17.7500 | 1 | 0.550066 | 0.621723 | -4.938473 |-9.881290e+00 |-7.586276e-01 |\u001b[0m\n", "| 711 |17.7750 | 1 | 0.547227 | 0.613100 | -4.942387 |-9.879651e+00 |-7.587801e-01 |\u001b[0m\n", "| 712 |17.8000 | 1 | 0.549032 | 0.607084 | -4.943841 |-9.878023e+00 |-7.589318e-01 |\u001b[0m\n", "| 713 |17.8250 | 1 | 0.553408 | 0.626393 | -4.946716 |-9.876405e+00 |-7.590827e-01 |\u001b[0m\n", "| 714 |17.8500 | 1 | 0.552940 | 0.612425 | -4.948343 |-9.874799e+00 |-7.592327e-01 |\u001b[0m\n", "| 715 |17.8750 | 1 | 0.547835 | 0.608350 | -4.948430 |-9.873205e+00 |-7.593819e-01 |\u001b[0m\n", "| 716 |17.9000 | 1 | 0.540325 | 0.615331 | -4.952754 |-9.871621e+00 |-7.595302e-01 |\u001b[0m\n", "| 717 |17.9250 | 1 | 0.541983 | 0.615043 | -4.954732 |-9.870049e+00 |-7.596776e-01 |\u001b[0m\n", "| 718 |17.9500 | 1 | 0.545322 | 0.613781 | -4.957229 |-9.868489e+00 |-7.598241e-01 |\u001b[0m\n", "| 719 |17.9750 | 1 | 0.550283 | 0.619134 | -4.960988 |-9.866940e+00 |-7.599697e-01 |\u001b[0m\n", "| 720 |18.0000 | 1 | 0.545893 | 0.614056 | -4.961059 |-9.865402e+00 |-7.601144e-01 |\u001b[0m\n", "| 721 |18.0250 | 1 | 0.545672 | 0.612643 | -4.963571 |-9.863876e+00 |-7.602583e-01 |\u001b[0m\n", "| 722 |18.0500 | 1 | 0.547688 | 0.613754 | -4.966223 |-9.862362e+00 |-7.604012e-01 |\u001b[0m\n", "| 723 |18.0750 | 1 | 0.552767 | 0.618856 | -4.968695 |-9.860860e+00 |-7.605432e-01 |\u001b[0m\n", "| 724 |18.1000 | 1 | 0.536194 | 0.619256 | -4.972596 |-9.859369e+00 |-7.606842e-01 |\u001b[0m\n", "| 725 |18.1250 | 1 | 0.547934 | 0.614264 | -4.974514 |-9.857890e+00 |-7.608244e-01 |\u001b[0m\n", "| 726 |18.1500 | 1 | 0.546250 | 0.609647 | -4.976536 |-9.856423e+00 |-7.609636e-01 |\u001b[0m\n", "| 727 |18.1750 | 1 | 0.543682 | 0.612869 | -4.979007 |-9.854968e+00 |-7.611018e-01 |\u001b[0m\n", "| 728 |18.2000 | 1 | 0.540415 | 0.613370 | -4.980559 |-9.853525e+00 |-7.612392e-01 |\u001b[0m\n", "| 729 |18.2250 | 1 | 0.545937 | 0.614889 | -4.984680 |-9.852095e+00 |-7.613755e-01 |\u001b[0m\n", "| 730 |18.2500 | 1 | 0.545980 | 0.617822 | -4.988031 |-9.850676e+00 |-7.615109e-01 |\u001b[0m\n", "| 731 |18.2750 | 1 | 0.542477 | 0.615399 | -4.989711 |-9.849269e+00 |-7.616453e-01 |\u001b[0m\n", "| 732 |18.3000 | 1 | 0.547334 | 0.637717 | -4.993188 |-9.847874e+00 |-7.617788e-01 |\u001b[0m\n", "| 733 |18.3250 | 1 | 0.553981 | 0.623099 | -4.994720 |-9.846492e+00 |-7.619113e-01 |\u001b[0m\n", "| 734 |18.3500 | 1 | 0.548177 | 0.615461 | -4.996885 |-9.845122e+00 |-7.620428e-01 |\u001b[0m\n", "| 735 |18.3750 | 1 | 0.541814 | 0.619284 | -5.001297 |-9.843764e+00 |-7.621732e-01 |\u001b[0m\n", "| 736 |18.4000 | 1 | 0.545819 | 0.612507 | -5.004011 |-9.842419e+00 |-7.623028e-01 |\u001b[0m\n", "| 737 |18.4250 | 1 | 0.544200 | 0.612841 | -5.007008 |-9.841085e+00 |-7.624312e-01 |\u001b[0m\n", "| 738 |18.4500 | 1 | 0.542160 | 0.613612 | -5.009954 |-9.839765e+00 |-7.625587e-01 |\u001b[0m\n", "| 739 |18.4750 | 1 | 0.579838 | 0.652729 | -5.011577 |-9.838457e+00 |-7.626852e-01 |\u001b[0m\n", "| 740 |18.5000 | 1 | 0.502843 | 0.647341 | -5.014921 |-9.837161e+00 |-7.628107e-01 |\u001b[0m\n", "| 741 |18.5250 | 1 | 0.551553 | 0.615329 | -5.018108 |-9.835878e+00 |-7.629351e-01 |\u001b[0m\n", "| 742 |18.5500 | 1 | 0.527083 | 0.626063 | -5.021511 |-9.834607e+00 |-7.630585e-01 |\u001b[0m\n", "| 743 |18.5750 | 1 | 0.539414 | 0.623069 | -5.025618 |-9.833349e+00 |-7.631809e-01 |\u001b[0m\n", "| 744 |18.6000 | 1 | 0.525490 | 0.626166 | -5.027066 |-9.832104e+00 |-7.633022e-01 |\u001b[0m\n", "| 745 |18.6250 | 1 | 0.517001 | 0.654233 | -5.030386 |-9.830872e+00 |-7.634225e-01 |\u001b[0m\n", "| 746 |18.6500 | 1 | 0.534137 | 0.626574 | -5.033620 |-9.829652e+00 |-7.635418e-01 |\u001b[0m\n", "| 747 |18.6750 | 1 | 0.542476 | 0.619676 | -5.035894 |-9.828445e+00 |-7.636599e-01 |\u001b[0m\n", "| 748 |18.7000 | 1 | 0.553023 | 0.625866 | -5.041104 |-9.827250e+00 |-7.637771e-01 |\u001b[0m\n", "| 749 |18.7250 | 1 | 0.540949 | 0.619120 | -5.044064 |-9.826069e+00 |-7.638931e-01 |\u001b[0m\n", "| 750 |18.7500 | 1 | 0.537734 | 0.621421 | -5.046464 |-9.824900e+00 |-7.640081e-01 |\u001b[0m\n", "| 751 |18.7750 | 1 | 0.538831 | 0.623689 | -5.050195 |-9.823745e+00 |-7.641221e-01 |\u001b[0m\n", "| 752 |18.8000 | 1 | 0.541038 | 0.621485 | -5.052620 |-9.822602e+00 |-7.642350e-01 |\u001b[0m\n", "| 753 |18.8250 | 1 | 0.535511 | 0.620073 | -5.056418 |-9.821472e+00 |-7.643467e-01 |\u001b[0m\n", "| 754 |18.8500 | 1 | 0.530924 | 0.626769 | -5.060668 |-9.820355e+00 |-7.644574e-01 |\u001b[0m\n", "| 755 |18.8750 | 1 | 0.537222 | 0.615511 | -5.064038 |-9.819251e+00 |-7.645670e-01 |\u001b[0m\n", "| 756 |18.9000 | 1 | 0.534256 | 0.619716 | -5.067645 |-9.818160e+00 |-7.646756e-01 |\u001b[0m\n", "| 757 |18.9250 | 1 | 0.535977 | 0.618753 | -5.070290 |-9.817082e+00 |-7.647830e-01 |\u001b[0m\n", "| 758 |18.9500 | 1 | 0.540988 | 0.623975 | -5.073509 |-9.816018e+00 |-7.648893e-01 |\u001b[0m\n", "| 759 |18.9750 | 1 | 0.549132 | 0.621750 | -5.078004 |-9.814966e+00 |-7.649945e-01 |\u001b[0m\n", "| 760 |19.0000 | 1 | 0.539145 | 0.617640 | -5.081158 |-9.813927e+00 |-7.650987e-01 |\u001b[0m\n", "| 761 |19.0250 | 1 | 0.540759 | 0.626675 | -5.085734 |-9.812902e+00 |-7.652017e-01 |\u001b[0m\n", "| 762 |19.0500 | 1 | 0.538078 | 0.622588 | -5.089589 |-9.811890e+00 |-7.653036e-01 |\u001b[0m\n", "| 763 |19.0750 | 1 | 0.522208 | 0.636657 | -5.091690 |-9.810890e+00 |-7.654044e-01 |\u001b[0m\n", "| 764 |19.1000 | 1 | 0.533418 | 0.625254 | -5.096080 |-9.809904e+00 |-7.655040e-01 |\u001b[0m\n", "| 765 |19.1250 | 1 | 0.539772 | 0.619554 | -5.100005 |-9.808932e+00 |-7.656026e-01 |\u001b[0m\n", "| 766 |19.1500 | 1 | 0.539060 | 0.621184 | -5.103610 |-9.807972e+00 |-7.657000e-01 |\u001b[0m\n", "| 767 |19.1750 | 1 | 0.535861 | 0.620145 | -5.108621 |-9.807026e+00 |-7.657963e-01 |\u001b[0m\n", "| 768 |19.2000 | 1 | 0.536273 | 0.636314 | -5.112126 |-9.806093e+00 |-7.658914e-01 |\u001b[0m\n", "| 769 |19.2250 | 1 | 0.527759 | 0.633821 | -5.115285 |-9.805173e+00 |-7.659854e-01 |\u001b[0m\n", "| 770 |19.2500 | 1 | 0.544239 | 0.628317 | -5.119466 |-9.804266e+00 |-7.660783e-01 |\u001b[0m\n", "| 771 |19.2750 | 1 | 0.537259 | 0.622405 | -5.123024 |-9.803373e+00 |-7.661701e-01 |\u001b[0m\n", "| 772 |19.3000 | 1 | 0.534405 | 0.621513 | -5.128168 |-9.802493e+00 |-7.662607e-01 |\u001b[0m\n", "| 773 |19.3250 | 1 | 0.536213 | 0.619574 | -5.132116 |-9.801626e+00 |-7.663501e-01 |\u001b[0m\n", "| 774 |19.3500 | 1 | 0.547262 | 0.633010 | -5.136146 |-9.800773e+00 |-7.664384e-01 |\u001b[0m\n", "| 775 |19.3750 | 1 | 0.532413 | 0.624157 | -5.140640 |-9.799933e+00 |-7.665256e-01 |\u001b[0m\n", "| 776 |19.4000 | 1 | 0.528069 | 0.631161 | -5.143361 |-9.799106e+00 |-7.666116e-01 |\u001b[0m\n", "| 777 |19.4250 | 1 | 0.508258 | 0.655418 | -5.148293 |-9.798293e+00 |-7.666965e-01 |\u001b[0m\n", "| 778 |19.4500 | 1 | 0.531947 | 0.619965 | -5.153388 |-9.797493e+00 |-7.667802e-01 |\u001b[0m\n", "| 779 |19.4750 | 1 | 0.538257 | 0.624263 | -5.157054 |-9.796706e+00 |-7.668627e-01 |\u001b[0m\n", "| 780 |19.5000 | 1 | 0.513675 | 0.639047 | -5.162093 |-9.795933e+00 |-7.669441e-01 |\u001b[0m\n", "| 781 |19.5250 | 1 | 0.524592 | 0.629631 | -5.166237 |-9.795173e+00 |-7.670243e-01 |\u001b[0m\n", "| 782 |19.5500 | 1 | 0.501278 | 0.646392 | -5.169515 |-9.794426e+00 |-7.671034e-01 |\u001b[0m\n", "| 783 |19.5750 | 1 | 0.508310 | 0.653496 | -5.174678 |-9.793693e+00 |-7.671812e-01 |\u001b[0m\n", "| 784 |19.6000 | 1 | 0.501368 | 0.644803 | -5.179349 |-9.792973e+00 |-7.672580e-01 |\u001b[0m\n", "| 785 |19.6250 | 1 | 0.505631 | 0.653741 | -5.184141 |-9.792267e+00 |-7.673335e-01 |\u001b[0m\n", "| 786 |19.6500 | 1 | 0.501214 | 0.647573 | -5.188924 |-9.791574e+00 |-7.674079e-01 |\u001b[0m\n", "| 787 |19.6750 | 1 | 0.495648 | 0.655176 | -5.192845 |-9.790894e+00 |-7.674811e-01 |\u001b[0m\n", "| 788 |19.7000 | 1 | 0.499932 | 0.647758 | -5.197708 |-9.790228e+00 |-7.675532e-01 |\u001b[0m\n", "| 789 |19.7250 | 1 | 0.518200 | 0.630863 | -5.201669 |-9.789575e+00 |-7.676241e-01 |\u001b[0m\n", "| 790 |19.7500 | 1 | 0.536332 | 0.619421 | -5.206930 |-9.788935e+00 |-7.676937e-01 |\u001b[0m\n", "| 791 |19.7750 | 1 | 0.531387 | 0.621932 | -5.212611 |-9.788309e+00 |-7.677623e-01 |\u001b[0m\n", "| 792 |19.8000 | 1 | 0.527384 | 0.622412 | -5.216559 |-9.787696e+00 |-7.678296e-01 |\u001b[0m\n", "| 793 |19.8250 | 1 | 0.529678 | 0.620205 | -5.221430 |-9.787096e+00 |-7.678958e-01 |\u001b[0m\n", "| 794 |19.8500 | 1 | 0.508913 | 0.648031 | -5.226400 |-9.786510e+00 |-7.679608e-01 |\u001b[0m\n", "| 795 |19.8750 | 1 | 0.504351 | 0.648719 | -5.230347 |-9.785937e+00 |-7.680246e-01 |\u001b[0m\n", "| 796 |19.9000 | 1 | 0.500118 | 0.648664 | -5.235880 |-9.785377e+00 |-7.680872e-01 |\u001b[0m\n", "| 797 |19.9250 | 1 | 0.528246 | 0.630585 | -5.241694 |-9.784831e+00 |-7.681487e-01 |\u001b[0m\n", "| 798 |19.9500 | 1 | 0.542162 | 0.629759 | -5.245912 |-9.784298e+00 |-7.682090e-01 |\u001b[0m\n", "| 799 |19.9750 | 1 | 0.529981 | 0.619840 | -5.251163 |-9.783778e+00 |-7.682681e-01 |\u001b[0m\n", "| 800 |20.0000 | 1 | 0.529003 | 0.618461 | -5.255740 |-9.783271e+00 |-7.683260e-01 |\u001b[0m\n", "| 801 |20.0250 | 1 | 0.531965 | 0.615729 | -5.260634 |-9.782778e+00 |-7.683827e-01 |\u001b[0m\n", "| 802 |20.0500 | 1 | 0.535846 | 0.625167 | -5.265935 |-9.782298e+00 |-7.684383e-01 |\u001b[0m\n", "| 803 |20.0750 | 1 | 0.530973 | 0.621185 | -5.271230 |-9.781831e+00 |-7.684927e-01 |\u001b[0m\n", "| 804 |20.1000 | 1 | 0.534419 | 0.622091 | -5.277057 |-9.781378e+00 |-7.685459e-01 |\u001b[0m\n", "| 805 |20.1250 | 1 | 0.534982 | 0.622699 | -5.281286 |-9.780938e+00 |-7.685979e-01 |\u001b[0m\n", "| 806 |20.1500 | 1 | 0.530764 | 0.620805 | -5.286196 |-9.780510e+00 |-7.686487e-01 |\u001b[0m\n", "| 807 |20.1750 | 1 | 0.511985 | 0.636388 | -5.291943 |-9.780096e+00 |-7.686984e-01 |\u001b[0m\n", "| 808 |20.2000 | 1 | 0.496395 | 0.650078 | -5.296389 |-9.779696e+00 |-7.687469e-01 |\u001b[0m\n", "| 809 |20.2250 | 1 | 0.500372 | 0.658939 | -5.302208 |-9.779308e+00 |-7.687942e-01 |\u001b[0m\n", "| 810 |20.2500 | 1 | 0.499443 | 0.650036 | -5.308191 |-9.778933e+00 |-7.688403e-01 |\u001b[0m\n", "| 811 |20.2750 | 1 | 0.507094 | 0.660675 | -5.312377 |-9.778572e+00 |-7.688852e-01 |\u001b[0m\n", "| 812 |20.3000 | 1 | 0.501244 | 0.655777 | -5.317533 |-9.778224e+00 |-7.689290e-01 |\u001b[0m\n", "| 813 |20.3250 | 1 | 0.497886 | 0.659485 | -5.322998 |-9.777888e+00 |-7.689716e-01 |\u001b[0m\n", "| 814 |20.3500 | 1 | 0.531331 | 0.625960 | -5.327874 |-9.777566e+00 |-7.690130e-01 |\u001b[0m\n", "| 815 |20.3750 | 1 | 0.520818 | 0.633773 | -5.333552 |-9.777257e+00 |-7.690533e-01 |\u001b[0m\n", "| 816 |20.4000 | 1 | 0.528115 | 0.629487 | -5.338954 |-9.776961e+00 |-7.690924e-01 |\u001b[0m\n", "| 817 |20.4250 | 1 | 0.533945 | 0.639602 | -5.344271 |-9.776677e+00 |-7.691303e-01 |\u001b[0m\n", "| 818 |20.4500 | 1 | 0.518692 | 0.616616 | -5.348659 |-9.776407e+00 |-7.691670e-01 |\u001b[0m\n", "| 819 |20.4750 | 1 | 0.522226 | 0.616927 | -5.353802 |-9.776149e+00 |-7.692026e-01 |\u001b[0m\n", "| 820 |20.5000 | 1 | 0.488609 | 0.653811 | -5.359571 |-9.775905e+00 |-7.692370e-01 |\u001b[0m\n", "| 821 |20.5250 | 1 | 0.484709 | 0.644598 | -5.364204 |-9.775673e+00 |-7.692702e-01 |\u001b[0m\n", "| 822 |20.5500 | 1 | 0.486915 | 0.644077 | -5.369689 |-9.775454e+00 |-7.693023e-01 |\u001b[0m\n", "| 823 |20.5750 | 1 | 0.486652 | 0.644571 | -5.375147 |-9.775248e+00 |-7.693332e-01 |\u001b[0m\n", "| 824 |20.6000 | 1 | 0.504305 | 0.636577 | -5.379300 |-9.775055e+00 |-7.693629e-01 |\u001b[0m\n", "| 825 |20.6250 | 1 | 0.498041 | 0.634625 | -5.383927 |-9.774875e+00 |-7.693915e-01 |\u001b[0m\n", "| 826 |20.6500 | 1 | 0.491458 | 0.658197 | -5.389699 |-9.774707e+00 |-7.694190e-01 |\u001b[0m\n", "| 827 |20.6750 | 1 | 0.496825 | 0.658731 | -5.394111 |-9.774552e+00 |-7.694452e-01 |\u001b[0m\n", "| 828 |20.7000 | 1 | 0.502448 | 0.654295 | -5.398969 |-9.774409e+00 |-7.694703e-01 |\u001b[0m\n", "| 829 |20.7250 | 1 | 0.526343 | 0.629665 | -5.404191 |-9.774279e+00 |-7.694943e-01 |\u001b[0m\n", "| 830 |20.7500 | 1 | 0.527985 | 0.637049 | -5.408232 |-9.774162e+00 |-7.695171e-01 |\u001b[0m\n", "| 831 |20.7750 | 1 | 0.526428 | 0.631766 | -5.412435 |-9.774057e+00 |-7.695388e-01 |\u001b[0m\n", "| 832 |20.8000 | 1 | 0.549858 | 0.656701 | -5.417086 |-9.773965e+00 |-7.695593e-01 |\u001b[0m\n", "| 833 |20.8250 | 1 | 0.525367 | 0.640356 | -5.421889 |-9.773886e+00 |-7.695787e-01 |\u001b[0m\n", "| 834 |20.8500 | 1 | 0.526791 | 0.632546 | -5.425807 |-9.773818e+00 |-7.695970e-01 |\u001b[0m\n", "| 835 |20.8750 | 1 | 0.527689 | 0.634832 | -5.429904 |-9.773763e+00 |-7.696141e-01 |\u001b[0m\n", "| 836 |20.9000 | 1 | 0.527279 | 0.632626 | -5.434402 |-9.773721e+00 |-7.696301e-01 |\u001b[0m\n", "| 837 |20.9250 | 1 | 0.522657 | 0.645628 | -5.437396 |-9.773691e+00 |-7.696449e-01 |\u001b[0m\n", "| 838 |20.9500 | 1 | 0.525489 | 0.635935 | -5.441073 |-9.773673e+00 |-7.696586e-01 |\u001b[0m\n", "| 839 |20.9750 | 1 | 0.532311 | 0.650099 | -5.445642 |-9.773668e+00 |-7.696712e-01 |\u001b[0m\n", "| 840 |21.0000 | 1 | 0.529552 | 0.651858 | -5.448456 |-9.773674e+00 |-7.696827e-01 |\u001b[0m\n", "| 841 |21.0250 | 1 | 0.503458 | 0.671841 | -5.451750 |-9.773693e+00 |-7.696930e-01 |\u001b[0m\n", "| 842 |21.0500 | 1 | 0.501758 | 0.675092 | -5.455281 |-9.773724e+00 |-7.697022e-01 |\u001b[0m\n", "| 843 |21.0750 | 1 | 0.514197 | 0.677148 | -5.457769 |-9.773768e+00 |-7.697103e-01 |\u001b[0m\n", "| 844 |21.1000 | 1 | 0.501449 | 0.669868 | -5.460336 |-9.773823e+00 |-7.697173e-01 |\u001b[0m\n", "| 845 |21.1250 | 1 | 0.497707 | 0.677975 | -5.463272 |-9.773890e+00 |-7.697232e-01 |\u001b[0m\n", "| 846 |21.1500 | 1 | 0.500228 | 0.670709 | -5.466064 |-9.773969e+00 |-7.697280e-01 |\u001b[0m\n", "| 847 |21.1750 | 1 | 0.504896 | 0.676630 | -5.467633 |-9.774060e+00 |-7.697316e-01 |\u001b[0m\n", "| 848 |21.2000 | 1 | 0.500956 | 0.671256 | -5.469894 |-9.774164e+00 |-7.697342e-01 |\u001b[0m\n", "| 849 |21.2250 | 1 | 0.496505 | 0.674550 | -5.472033 |-9.774278e+00 |-7.697357e-01 |\u001b[0m\n", "| 850 |21.2500 | 1 | 0.529665 | 0.644782 | -5.472924 |-9.774405e+00 |-7.697361e-01 |\u001b[0m\n", "| 851 |21.2750 | 1 | 0.528408 | 0.645465 | -5.474619 |-9.774544e+00 |-7.697354e-01 |\u001b[0m\n", "| 852 |21.3000 | 1 | 0.519741 | 0.635947 | -5.476252 |-9.774694e+00 |-7.697336e-01 |\u001b[0m\n", "| 853 |21.3250 | 1 | 0.504346 | 0.651090 | -5.476890 |-9.774856e+00 |-7.697307e-01 |\u001b[0m\n", "| 854 |21.3500 | 1 | 0.532271 | 0.638573 | -5.477458 |-9.775029e+00 |-7.697268e-01 |\u001b[0m\n", "| 855 |21.3750 | 1 | 0.523634 | 0.638200 | -5.478494 |-9.775214e+00 |-7.697218e-01 |\u001b[0m\n", "| 856 |21.4000 | 1 | 0.519422 | 0.638265 | -5.478639 |-9.775411e+00 |-7.697157e-01 |\u001b[0m\n", "| 857 |21.4250 | 1 | 0.518510 | 0.639035 | -5.478368 |-9.775619e+00 |-7.697085e-01 |\u001b[0m\n", "| 858 |21.4500 | 1 | 0.510864 | 0.672835 | -5.478909 |-9.775839e+00 |-7.697003e-01 |\u001b[0m\n", "| 859 |21.4750 | 1 | 0.524935 | 0.637128 | -5.478554 |-9.776069e+00 |-7.696910e-01 |\u001b[0m\n", "| 860 |21.5000 | 1 | 0.507660 | 0.648373 | -5.477690 |-9.776312e+00 |-7.696807e-01 |\u001b[0m\n", "| 861 |21.5250 | 1 | 0.499457 | 0.665963 | -5.477335 |-9.776565e+00 |-7.696693e-01 |\u001b[0m\n", "| 862 |21.5500 | 1 | 0.499270 | 0.658582 | -5.476603 |-9.776830e+00 |-7.696569e-01 |\u001b[0m\n", "| 863 |21.5750 | 1 | 0.519785 | 0.638694 | -5.475176 |-9.777106e+00 |-7.696434e-01 |\u001b[0m\n", "| 864 |21.6000 | 1 | 0.534959 | 0.658340 | -5.473925 |-9.777393e+00 |-7.696289e-01 |\u001b[0m\n", "| 865 |21.6250 | 1 | 0.516683 | 0.652932 | -5.472998 |-9.777691e+00 |-7.696133e-01 |\u001b[0m\n", "| 866 |21.6500 | 1 | 0.524993 | 0.643355 | -5.470927 |-9.778000e+00 |-7.695968e-01 |\u001b[0m\n", "| 867 |21.6750 | 1 | 0.521338 | 0.641387 | -5.469193 |-9.778320e+00 |-7.695792e-01 |\u001b[0m\n", "| 868 |21.7000 | 1 | 0.511949 | 0.650276 | -5.467707 |-9.778651e+00 |-7.695606e-01 |\u001b[0m\n", "| 869 |21.7250 | 1 | 0.516787 | 0.641368 | -5.465221 |-9.778993e+00 |-7.695409e-01 |\u001b[0m\n", "| 870 |21.7500 | 1 | 0.526370 | 0.644767 | -5.462966 |-9.779345e+00 |-7.695203e-01 |\u001b[0m\n", "| 871 |21.7750 | 1 | 0.527064 | 0.650980 | -5.460746 |-9.779709e+00 |-7.694986e-01 |\u001b[0m\n", "| 872 |21.8000 | 1 | 0.516300 | 0.660074 | -5.458348 |-9.780083e+00 |-7.694760e-01 |\u001b[0m\n", "| 873 |21.8250 | 1 | 0.516653 | 0.640115 | -5.455473 |-9.780468e+00 |-7.694523e-01 |\u001b[0m\n", "| 874 |21.8500 | 1 | 0.519911 | 0.637954 | -5.452906 |-9.780863e+00 |-7.694277e-01 |\u001b[0m\n", "| 875 |21.8750 | 1 | 0.515326 | 0.653226 | -5.450254 |-9.781269e+00 |-7.694021e-01 |\u001b[0m\n", "| 876 |21.9000 | 1 | 0.519997 | 0.639056 | -5.446816 |-9.781685e+00 |-7.693755e-01 |\u001b[0m\n", "| 877 |21.9250 | 1 | 0.520825 | 0.640152 | -5.443891 |-9.782112e+00 |-7.693479e-01 |\u001b[0m\n", "| 878 |21.9500 | 1 | 0.517811 | 0.643432 | -5.441052 |-9.782549e+00 |-7.693193e-01 |\u001b[0m\n", "| 879 |21.9750 | 1 | 0.514777 | 0.645231 | -5.437508 |-9.782996e+00 |-7.692898e-01 |\u001b[0m\n", "| 880 |22.0000 | 1 | 0.509819 | 0.664910 | -5.434299 |-9.783453e+00 |-7.692593e-01 |\u001b[0m\n", "| 881 |22.0250 | 1 | 0.533964 | 0.644499 | -5.431166 |-9.783921e+00 |-7.692279e-01 |\u001b[0m\n", "| 882 |22.0500 | 1 | 0.515048 | 0.643263 | -5.427340 |-9.784399e+00 |-7.691955e-01 |\u001b[0m\n", "| 883 |22.0750 | 1 | 0.528883 | 0.638636 | -5.423886 |-9.784887e+00 |-7.691622e-01 |\u001b[0m\n", "| 884 |22.1000 | 1 | 0.520719 | 0.634711 | -5.420468 |-9.785385e+00 |-7.691279e-01 |\u001b[0m\n", "| 885 |22.1250 | 1 | 0.520284 | 0.649912 | -5.416873 |-9.785892e+00 |-7.690927e-01 |\u001b[0m\n", "| 886 |22.1500 | 1 | 0.528762 | 0.647822 | -5.413226 |-9.786410e+00 |-7.690565e-01 |\u001b[0m\n", "| 887 |22.1750 | 1 | 0.509786 | 0.659574 | -5.409500 |-9.786937e+00 |-7.690194e-01 |\u001b[0m\n", "| 888 |22.2000 | 1 | 0.520104 | 0.640645 | -5.405926 |-9.787475e+00 |-7.689815e-01 |\u001b[0m\n", "| 889 |22.2250 | 1 | 0.528201 | 0.646174 | -5.401876 |-9.788022e+00 |-7.689426e-01 |\u001b[0m\n", "| 890 |22.2500 | 1 | 0.517401 | 0.639639 | -5.398218 |-9.788578e+00 |-7.689027e-01 |\u001b[0m\n", "| 891 |22.2750 | 1 | 0.520639 | 0.632424 | -5.394694 |-9.789144e+00 |-7.688620e-01 |\u001b[0m\n", "| 892 |22.3000 | 1 | 0.522067 | 0.636225 | -5.390716 |-9.789720e+00 |-7.688204e-01 |\u001b[0m\n", "| 893 |22.3250 | 1 | 0.517790 | 0.632747 | -5.386888 |-9.790305e+00 |-7.687779e-01 |\u001b[0m\n", "| 894 |22.3500 | 1 | 0.507874 | 0.644954 | -5.383266 |-9.790899e+00 |-7.687345e-01 |\u001b[0m\n", "| 895 |22.3750 | 1 | 0.464989 | 0.691658 | -5.379134 |-9.791502e+00 |-7.686902e-01 |\u001b[0m\n", "| 896 |22.4000 | 1 | 0.480783 | 0.677711 | -5.375381 |-9.792115e+00 |-7.686451e-01 |\u001b[0m\n", "| 897 |22.4250 | 1 | 0.490632 | 0.674024 | -5.371811 |-9.792737e+00 |-7.685991e-01 |\u001b[0m\n", "| 898 |22.4500 | 1 | 0.481621 | 0.671897 | -5.367827 |-9.793368e+00 |-7.685522e-01 |\u001b[0m\n", "| 899 |22.4750 | 1 | 0.474796 | 0.677255 | -5.364149 |-9.794009e+00 |-7.685045e-01 |\u001b[0m\n", "| 900 |22.5000 | 1 | 0.491788 | 0.670256 | -5.360229 |-9.794658e+00 |-7.684559e-01 |\u001b[0m\n", "| 901 |22.5250 | 1 | 0.484136 | 0.663273 | -5.356469 |-9.795316e+00 |-7.684065e-01 |\u001b[0m\n", "| 902 |22.5500 | 1 | 0.494571 | 0.674221 | -5.352664 |-9.795983e+00 |-7.683562e-01 |\u001b[0m\n", "| 903 |22.5750 | 1 | 0.484502 | 0.666114 | -5.348923 |-9.796658e+00 |-7.683051e-01 |\u001b[0m\n", "| 904 |22.6000 | 1 | 0.500552 | 0.659357 | -5.345406 |-9.797343e+00 |-7.682532e-01 |\u001b[0m\n", "| 905 |22.6250 | 1 | 0.474210 | 0.675963 | -5.341552 |-9.798036e+00 |-7.682004e-01 |\u001b[0m\n", "| 906 |22.6500 | 1 | 0.512181 | 0.642814 | -5.337793 |-9.798737e+00 |-7.681468e-01 |\u001b[0m\n", "| 907 |22.6750 | 1 | 0.530902 | 0.671671 | -5.334230 |-9.799447e+00 |-7.680925e-01 |\u001b[0m\n", "| 908 |22.7000 | 1 | 0.514472 | 0.642086 | -5.330476 |-9.800166e+00 |-7.680373e-01 |\u001b[0m\n", "| 909 |22.7250 | 1 | 0.511914 | 0.644290 | -5.326850 |-9.800893e+00 |-7.679813e-01 |\u001b[0m\n", "| 910 |22.7500 | 1 | 0.510064 | 0.648282 | -5.323523 |-9.801628e+00 |-7.679245e-01 |\u001b[0m\n", "| 911 |22.7750 | 1 | 0.513836 | 0.638366 | -5.319777 |-9.802371e+00 |-7.678670e-01 |\u001b[0m\n", "| 912 |22.8000 | 1 | 0.522687 | 0.642639 | -5.316235 |-9.803123e+00 |-7.678087e-01 |\u001b[0m\n", "| 913 |22.8250 | 1 | 0.513428 | 0.637363 | -5.312791 |-9.803882e+00 |-7.677496e-01 |\u001b[0m\n", "| 914 |22.8500 | 1 | 0.512795 | 0.639266 | -5.309169 |-9.804650e+00 |-7.676897e-01 |\u001b[0m\n", "| 915 |22.8750 | 1 | 0.519954 | 0.638287 | -5.305885 |-9.805425e+00 |-7.676291e-01 |\u001b[0m\n", "| 916 |22.9000 | 1 | 0.481317 | 0.673174 | -5.302459 |-9.806209e+00 |-7.675677e-01 |\u001b[0m\n", "| 917 |22.9250 | 1 | 0.498218 | 0.656933 | -5.299122 |-9.807000e+00 |-7.675056e-01 |\u001b[0m\n", "| 918 |22.9500 | 1 | 0.515562 | 0.638124 | -5.295768 |-9.807799e+00 |-7.674427e-01 |\u001b[0m\n", "| 919 |22.9750 | 1 | 0.518832 | 0.657375 | -5.292315 |-9.808606e+00 |-7.673791e-01 |\u001b[0m\n", "| 920 |23.0000 | 1 | 0.509751 | 0.646163 | -5.289145 |-9.809420e+00 |-7.673148e-01 |\u001b[0m\n", "| 921 |23.0250 | 1 | 0.521466 | 0.646805 | -5.285912 |-9.810242e+00 |-7.672497e-01 |\u001b[0m\n", "| 922 |23.0500 | 1 | 0.512008 | 0.637779 | -5.282643 |-9.811071e+00 |-7.671840e-01 |\u001b[0m\n", "| 923 |23.0750 | 1 | 0.517762 | 0.647592 | -5.279669 |-9.811908e+00 |-7.671175e-01 |\u001b[0m\n", "| 924 |23.1000 | 1 | 0.521314 | 0.644842 | -5.276377 |-9.812752e+00 |-7.670503e-01 |\u001b[0m\n", "| 925 |23.1250 | 1 | 0.495290 | 0.652850 | -5.273205 |-9.813603e+00 |-7.669824e-01 |\u001b[0m\n", "| 926 |23.1500 | 1 | 0.516843 | 0.640946 | -5.270279 |-9.814461e+00 |-7.669139e-01 |\u001b[0m\n", "| 927 |23.1750 | 1 | 0.511137 | 0.637577 | -5.267108 |-9.815327e+00 |-7.668446e-01 |\u001b[0m\n", "| 928 |23.2000 | 1 | 0.512721 | 0.636781 | -5.264265 |-9.816199e+00 |-7.667747e-01 |\u001b[0m\n", "| 929 |23.2250 | 1 | 0.514486 | 0.641128 | -5.261287 |-9.817078e+00 |-7.667042e-01 |\u001b[0m\n", "| 930 |23.2500 | 1 | 0.502929 | 0.654364 | -5.258338 |-9.817965e+00 |-7.666329e-01 |\u001b[0m\n", "| 931 |23.2750 | 1 | 0.482228 | 0.677815 | -5.255424 |-9.818858e+00 |-7.665610e-01 |\u001b[0m\n", "| 932 |23.3000 | 1 | 0.476847 | 0.674906 | -5.252544 |-9.819758e+00 |-7.664885e-01 |\u001b[0m\n", "| 933 |23.3250 | 1 | 0.479599 | 0.666247 | -5.249762 |-9.820664e+00 |-7.664153e-01 |\u001b[0m\n", "| 934 |23.3500 | 1 | 0.481180 | 0.669578 | -5.247035 |-9.821577e+00 |-7.663414e-01 |\u001b[0m\n", "| 935 |23.3750 | 1 | 0.480612 | 0.667914 | -5.244279 |-9.822497e+00 |-7.662670e-01 |\u001b[0m\n", "| 936 |23.4000 | 1 | 0.494679 | 0.679912 | -5.241565 |-9.823423e+00 |-7.661919e-01 |\u001b[0m\n", "| 937 |23.4250 | 1 | 0.480638 | 0.670267 | -5.238915 |-9.824355e+00 |-7.661162e-01 |\u001b[0m\n", "| 938 |23.4500 | 1 | 0.476561 | 0.677960 | -5.236134 |-9.825294e+00 |-7.660399e-01 |\u001b[0m\n", "| 939 |23.4750 | 1 | 0.479632 | 0.672821 | -5.233690 |-9.826238e+00 |-7.659630e-01 |\u001b[0m\n", "| 940 |23.5000 | 1 | 0.473291 | 0.676737 | -5.231094 |-9.827189e+00 |-7.658855e-01 |\u001b[0m\n", "| 941 |23.5250 | 1 | 0.474442 | 0.677697 | -5.228548 |-9.828146e+00 |-7.658074e-01 |\u001b[0m\n", "| 942 |23.5500 | 1 | 0.485998 | 0.658387 | -5.226139 |-9.829110e+00 |-7.657288e-01 |\u001b[0m\n", "| 943 |23.5750 | 1 | 0.514283 | 0.634293 | -5.223562 |-9.830079e+00 |-7.656495e-01 |\u001b[0m\n", "| 944 |23.6000 | 1 | 0.516713 | 0.646159 | -5.221143 |-9.831053e+00 |-7.655697e-01 |\u001b[0m\n", "| 945 |23.6250 | 1 | 0.509222 | 0.641191 | -5.218806 |-9.832034e+00 |-7.654894e-01 |\u001b[0m\n", "| 946 |23.6500 | 1 | 0.511243 | 0.639904 | -5.216415 |-9.833020e+00 |-7.654084e-01 |\u001b[0m\n", "| 947 |23.6750 | 1 | 0.504852 | 0.645054 | -5.214173 |-9.834012e+00 |-7.653270e-01 |\u001b[0m\n", "| 948 |23.7000 | 1 | 0.481507 | 0.668694 | -5.211857 |-9.835010e+00 |-7.652450e-01 |\u001b[0m\n", "| 949 |23.7250 | 1 | 0.478430 | 0.667698 | -5.209544 |-9.836013e+00 |-7.651624e-01 |\u001b[0m\n", "| 950 |23.7500 | 1 | 0.473767 | 0.672205 | -5.207393 |-9.837021e+00 |-7.650793e-01 |\u001b[0m\n", "| 951 |23.7750 | 1 | 0.477168 | 0.673745 | -5.205109 |-9.838035e+00 |-7.649957e-01 |\u001b[0m\n", "| 952 |23.8000 | 1 | 0.473230 | 0.681051 | -5.203066 |-9.839054e+00 |-7.649116e-01 |\u001b[0m\n", "| 953 |23.8250 | 1 | 0.474712 | 0.669055 | -5.200959 |-9.840078e+00 |-7.648270e-01 |\u001b[0m\n", "| 954 |23.8500 | 1 | 0.478994 | 0.672140 | -5.198806 |-9.841107e+00 |-7.647419e-01 |\u001b[0m\n", "| 955 |23.8750 | 1 | 0.510193 | 0.643556 | -5.196823 |-9.842141e+00 |-7.646563e-01 |\u001b[0m\n", "| 956 |23.9000 | 1 | 0.510342 | 0.649685 | -5.194717 |-9.843180e+00 |-7.645702e-01 |\u001b[0m\n", "| 957 |23.9250 | 1 | 0.502699 | 0.645553 | -5.192758 |-9.844224e+00 |-7.644836e-01 |\u001b[0m\n", "| 958 |23.9500 | 1 | 0.509565 | 0.643627 | -5.190837 |-9.845273e+00 |-7.643966e-01 |\u001b[0m\n", "| 959 |23.9750 | 1 | 0.505842 | 0.650800 | -5.188929 |-9.846327e+00 |-7.643091e-01 |\u001b[0m\n", "| 960 |24.0000 | 1 | 0.509080 | 0.639700 | -5.187018 |-9.847385e+00 |-7.642211e-01 |\u001b[0m\n", "| 961 |24.0250 | 1 | 0.506969 | 0.643642 | -5.185180 |-9.848448e+00 |-7.641327e-01 |\u001b[0m\n", "| 962 |24.0500 | 1 | 0.502202 | 0.644327 | -5.183292 |-9.849515e+00 |-7.640438e-01 |\u001b[0m\n", "| 963 |24.0750 | 1 | 0.504608 | 0.646704 | -5.181523 |-9.850587e+00 |-7.639545e-01 |\u001b[0m\n", "| 964 |24.1000 | 1 | 0.506113 | 0.640549 | -5.179790 |-9.851663e+00 |-7.638648e-01 |\u001b[0m\n", "| 965 |24.1250 | 1 | 0.516273 | 0.650328 | -5.178029 |-9.852743e+00 |-7.637746e-01 |\u001b[0m\n", "| 966 |24.1500 | 1 | 0.514624 | 0.656699 | -5.176397 |-9.853828e+00 |-7.636840e-01 |\u001b[0m\n", "| 967 |24.1750 | 1 | 0.509777 | 0.652485 | -5.174642 |-9.854916e+00 |-7.635931e-01 |\u001b[0m\n", "| 968 |24.2000 | 1 | 0.506223 | 0.644373 | -5.173014 |-9.856009e+00 |-7.635017e-01 |\u001b[0m\n", "| 969 |24.2250 | 1 | 0.497977 | 0.651589 | -5.171420 |-9.857106e+00 |-7.634099e-01 |\u001b[0m\n", "| 970 |24.2500 | 1 | 0.542505 | 0.823516 | -5.169807 |-9.858206e+00 |-7.633177e-01 |\u001b[0m\n", "| 971 |24.2750 | 1 | 0.458664 | 0.726943 | -5.168319 |-9.859311e+00 |-7.632251e-01 |\u001b[0m\n", "| 972 |24.3000 | 1 | 0.501798 | 0.644348 | -5.166790 |-9.860419e+00 |-7.631322e-01 |\u001b[0m\n", "| 973 |24.3250 | 1 | 0.514156 | 0.663782 | -5.165249 |-9.861531e+00 |-7.630389e-01 |\u001b[0m\n", "| 974 |24.3500 | 1 | 0.509705 | 0.649251 | -5.163825 |-9.862646e+00 |-7.629452e-01 |\u001b[0m\n", "| 975 |24.3750 | 1 | 0.500570 | 0.646102 | -5.162345 |-9.863765e+00 |-7.628512e-01 |\u001b[0m\n", "| 976 |24.4000 | 1 | 0.501039 | 0.644028 | -5.160961 |-9.864888e+00 |-7.627568e-01 |\u001b[0m\n", "| 977 |24.4250 | 1 | 0.504128 | 0.646994 | -5.159634 |-9.866014e+00 |-7.626621e-01 |\u001b[0m\n", "| 978 |24.4500 | 1 | 0.509705 | 0.643946 | -5.158234 |-9.867143e+00 |-7.625670e-01 |\u001b[0m\n", "| 979 |24.4750 | 1 | 0.504889 | 0.642303 | -5.156955 |-9.868275e+00 |-7.624717e-01 |\u001b[0m\n", "| 980 |24.5000 | 1 | 0.511998 | 0.654649 | -5.155620 |-9.869411e+00 |-7.623759e-01 |\u001b[0m\n", "| 981 |24.5250 | 1 | 0.505531 | 0.643071 | -5.154346 |-9.870549e+00 |-7.622799e-01 |\u001b[0m\n", "| 982 |24.5500 | 1 | 0.505855 | 0.644088 | -5.153156 |-9.871691e+00 |-7.621836e-01 |\u001b[0m\n", "| 983 |24.5750 | 1 | 0.503710 | 0.643562 | -5.151921 |-9.872835e+00 |-7.620870e-01 |\u001b[0m\n", "| 984 |24.6000 | 1 | 0.503625 | 0.652749 | -5.150775 |-9.873983e+00 |-7.619900e-01 |\u001b[0m\n", "| 985 |24.6250 | 1 | 0.497902 | 0.668819 | -5.149604 |-9.875133e+00 |-7.618928e-01 |\u001b[0m\n", "| 986 |24.6500 | 1 | 0.508365 | 0.655839 | -5.148454 |-9.876286e+00 |-7.617953e-01 |\u001b[0m\n", "| 987 |24.6750 | 1 | 0.499875 | 0.649295 | -5.147360 |-9.877441e+00 |-7.616975e-01 |\u001b[0m\n", "| 988 |24.7000 | 1 | 0.507928 | 0.651539 | -5.146301 |-9.878599e+00 |-7.615995e-01 |\u001b[0m\n", "| 989 |24.7250 | 1 | 0.504901 | 0.643364 | -5.145247 |-9.879760e+00 |-7.615012e-01 |\u001b[0m\n", "| 990 |24.7500 | 1 | 0.496468 | 0.650975 | -5.144263 |-9.880923e+00 |-7.614026e-01 |\u001b[0m\n", "| 991 |24.7750 | 1 | 0.485625 | 0.660757 | -5.143244 |-9.882088e+00 |-7.613038e-01 |\u001b[0m\n", "| 992 |24.8000 | 1 | 0.511090 | 0.655112 | -5.142258 |-9.883256e+00 |-7.612048e-01 |\u001b[0m\n", "| 993 |24.8250 | 1 | 0.499859 | 0.652944 | -5.141341 |-9.884426e+00 |-7.611055e-01 |\u001b[0m\n", "| 994 |24.8500 | 1 | 0.503584 | 0.657031 | -5.140395 |-9.885598e+00 |-7.610060e-01 |\u001b[0m\n", "| 995 |24.8750 | 1 | 0.487252 | 0.669019 | -5.139545 |-9.886772e+00 |-7.609062e-01 |\u001b[0m\n", "| 996 |24.9000 | 1 | 0.486750 | 0.677291 | -5.138686 |-9.887948e+00 |-7.608063e-01 |\u001b[0m\n", "| 997 |24.9250 | 1 | 0.499252 | 0.655279 | -5.137821 |-9.889126e+00 |-7.607061e-01 |\u001b[0m\n", "| 998 |24.9500 | 1 | 0.490417 | 0.702562 | -5.137027 |-9.890305e+00 |-7.606058e-01 |\u001b[0m\n", "| 999 |24.9750 | 1 | 0.503546 | 0.651132 | -5.136210 |-9.891487e+00 |-7.605052e-01 |\u001b[0m\n", "| 1000 |25.0000 | 1 | 0.483413 | 0.668655 | -5.135456 |-9.892670e+00 |-7.604045e-01 |\u001b[0m\n", "| 1001 |25.0250 | 1 | 0.493134 | 0.660787 | -5.134750 |-9.893855e+00 |-7.603035e-01 |\u001b[0m\n", "| 1002 |25.0500 | 1 | 0.499426 | 0.657750 | -5.134009 |-9.895041e+00 |-7.602024e-01 |\u001b[0m\n", "| 1003 |25.0750 | 1 | 0.494080 | 0.661456 | -5.133351 |-9.896229e+00 |-7.601011e-01 |\u001b[0m\n", "| 1004 |25.1000 | 1 | 0.507680 | 0.664709 | -5.132659 |-9.897418e+00 |-7.599997e-01 |\u001b[0m\n", "| 1005 |25.1250 | 1 | 0.494671 | 0.669049 | -5.132007 |-9.898609e+00 |-7.598981e-01 |\u001b[0m\n", "| 1006 |25.1500 | 1 | 0.509767 | 0.652732 | -5.131421 |-9.899801e+00 |-7.597964e-01 |\u001b[0m\n", "| 1007 |25.1750 | 1 | 0.495492 | 0.653077 | -5.130814 |-9.900994e+00 |-7.596945e-01 |\u001b[0m\n", "| 1008 |25.2000 | 1 | 0.498837 | 0.670956 | -5.130269 |-9.902188e+00 |-7.595924e-01 |\u001b[0m\n", "| 1009 |25.2250 | 1 | 0.500010 | 0.651370 | -5.129726 |-9.903383e+00 |-7.594903e-01 |\u001b[0m\n", "| 1010 |25.2500 | 1 | 0.499026 | 0.649779 | -5.129186 |-9.904579e+00 |-7.593880e-01 |\u001b[0m\n", "| 1011 |25.2750 | 1 | 0.503929 | 0.657456 | -5.128693 |-9.905776e+00 |-7.592856e-01 |\u001b[0m\n", "| 1012 |25.3000 | 1 | 0.502894 | 0.652709 | -5.128228 |-9.906974e+00 |-7.591831e-01 |\u001b[0m\n", "| 1013 |25.3250 | 1 | 0.493295 | 0.659944 | -5.127776 |-9.908173e+00 |-7.590805e-01 |\u001b[0m\n", "| 1014 |25.3500 | 1 | 0.496672 | 0.655548 | -5.127374 |-9.909372e+00 |-7.589777e-01 |\u001b[0m\n", "| 1015 |25.3750 | 1 | 0.496958 | 0.668447 | -5.126963 |-9.910572e+00 |-7.588749e-01 |\u001b[0m\n", "| 1016 |25.4000 | 1 | 0.477043 | 0.671800 | -5.126575 |-9.911773e+00 |-7.587720e-01 |\u001b[0m\n", "| 1017 |25.4250 | 1 | 0.493859 | 0.660183 | -5.126228 |-9.912974e+00 |-7.586690e-01 |\u001b[0m\n", "| 1018 |25.4500 | 1 | 0.497632 | 0.654123 | -5.125876 |-9.914176e+00 |-7.585660e-01 |\u001b[0m\n", "| 1019 |25.4750 | 1 | 0.502519 | 0.653777 | -5.125597 |-9.915378e+00 |-7.584628e-01 |\u001b[0m\n", "| 1020 |25.5000 | 1 | 0.499899 | 0.652161 | -5.125308 |-9.916580e+00 |-7.583596e-01 |\u001b[0m\n", "| 1021 |25.5250 | 1 | 0.505590 | 0.660761 | -5.125039 |-9.917782e+00 |-7.582564e-01 |\u001b[0m\n", "| 1022 |25.5500 | 1 | 0.499622 | 0.658219 | -5.124808 |-9.918985e+00 |-7.581531e-01 |\u001b[0m\n", "| 1023 |25.5750 | 1 | 0.494296 | 0.658807 | -5.124571 |-9.920187e+00 |-7.580498e-01 |\u001b[0m\n", "| 1024 |25.6000 | 1 | 0.499221 | 0.654806 | -5.124375 |-9.921390e+00 |-7.579464e-01 |\u001b[0m\n", "| 1025 |25.6250 | 1 | 0.499540 | 0.652259 | -5.124226 |-9.922592e+00 |-7.578430e-01 |\u001b[0m\n", "| 1026 |25.6500 | 1 | 0.497204 | 0.653026 | -5.124062 |-9.923795e+00 |-7.577395e-01 |\u001b[0m\n", "| 1027 |25.6750 | 1 | 0.498324 | 0.660967 | -5.123947 |-9.924997e+00 |-7.576361e-01 |\u001b[0m\n", "| 1028 |25.7000 | 1 | 0.495586 | 0.657316 | -5.123838 |-9.926199e+00 |-7.575326e-01 |\u001b[0m\n", "| 1029 |25.7250 | 1 | 0.495336 | 0.659465 | -5.123735 |-9.927401e+00 |-7.574291e-01 |\u001b[0m\n", "| 1030 |25.7500 | 1 | 0.498829 | 0.654207 | -5.123691 |-9.928602e+00 |-7.573256e-01 |\u001b[0m\n", "| 1031 |25.7750 | 1 | 0.496229 | 0.656042 | -5.123643 |-9.929803e+00 |-7.572221e-01 |\u001b[0m\n", "| 1032 |25.8000 | 1 | 0.498100 | 0.658060 | -5.123639 |-9.931003e+00 |-7.571187e-01 |\u001b[0m\n", "| 1033 |25.8250 | 1 | 0.493844 | 0.656294 | -5.123650 |-9.932203e+00 |-7.570152e-01 |\u001b[0m\n", "| 1034 |25.8500 | 1 | 0.496206 | 0.652885 | -5.123656 |-9.933402e+00 |-7.569118e-01 |\u001b[0m\n", "| 1035 |25.8750 | 1 | 0.493092 | 0.655715 | -5.123718 |-9.934600e+00 |-7.568084e-01 |\u001b[0m\n", "| 1036 |25.9000 | 1 | 0.499616 | 0.658027 | -5.123774 |-9.935798e+00 |-7.567050e-01 |\u001b[0m\n", "| 1037 |25.9250 | 1 | 0.491226 | 0.658937 | -5.123870 |-9.936995e+00 |-7.566016e-01 |\u001b[0m\n", "| 1038 |25.9500 | 1 | 0.500340 | 0.653876 | -5.124001 |-9.938190e+00 |-7.564983e-01 |\u001b[0m\n", "| 1039 |25.9750 | 1 | 0.495677 | 0.650797 | -5.124126 |-9.939385e+00 |-7.563951e-01 |\u001b[0m\n", "| 1040 |26.0000 | 1 | 0.476759 | 0.680443 | -5.124280 |-9.940579e+00 |-7.562919e-01 |\u001b[0m\n", "| 1041 |26.0250 | 1 | 0.488234 | 0.659470 | -5.124460 |-9.941772e+00 |-7.561888e-01 |\u001b[0m\n", "| 1042 |26.0500 | 1 | 0.504142 | 0.662927 | -5.124649 |-9.942963e+00 |-7.560857e-01 |\u001b[0m\n", "| 1043 |26.0750 | 1 | 0.493124 | 0.657806 | -5.124875 |-9.944154e+00 |-7.559827e-01 |\u001b[0m\n", "| 1044 |26.1000 | 1 | 0.494713 | 0.663481 | -5.125130 |-9.945343e+00 |-7.558798e-01 |\u001b[0m\n", "| 1045 |26.1250 | 1 | 0.490307 | 0.656816 | -5.125384 |-9.946530e+00 |-7.557770e-01 |\u001b[0m\n", "| 1046 |26.1500 | 1 | 0.484677 | 0.662911 | -5.125675 |-9.947717e+00 |-7.556742e-01 |\u001b[0m\n", "| 1047 |26.1750 | 1 | 0.493045 | 0.654708 | -5.125970 |-9.948901e+00 |-7.555716e-01 |\u001b[0m\n", "| 1048 |26.2000 | 1 | 0.538497 | 0.726460 | -5.126294 |-9.950085e+00 |-7.554690e-01 |\u001b[0m\n", "| 1049 |26.2250 | 1 | 0.549513 | 0.737299 | -5.126653 |-9.951266e+00 |-7.553666e-01 |\u001b[0m\n", "| 1050 |26.2500 | 1 | 0.492540 | 0.664606 | -5.127014 |-9.952446e+00 |-7.552643e-01 |\u001b[0m\n", "| 1051 |26.2750 | 1 | 0.493577 | 0.659932 | -5.127419 |-9.953625e+00 |-7.551620e-01 |\u001b[0m\n", "| 1052 |26.3000 | 1 | 0.467239 | 0.712753 | -5.127825 |-9.954801e+00 |-7.550599e-01 |\u001b[0m\n", "| 1053 |26.3250 | 1 | 0.455075 | 0.725685 | -5.128240 |-9.955976e+00 |-7.549580e-01 |\u001b[0m\n", "| 1054 |26.3500 | 1 | 0.475621 | 0.676184 | -5.128711 |-9.957149e+00 |-7.548561e-01 |\u001b[0m\n", "| 1055 |26.3750 | 1 | 0.534025 | 0.705199 | -5.129171 |-9.958320e+00 |-7.547544e-01 |\u001b[0m\n", "| 1056 |26.4000 | 1 | 0.535417 | 0.733416 | -5.129674 |-9.959488e+00 |-7.546529e-01 |\u001b[0m\n", "| 1057 |26.4250 | 1 | 0.490559 | 0.667110 | -5.130201 |-9.960655e+00 |-7.545515e-01 |\u001b[0m\n", "| 1058 |26.4500 | 1 | 0.492721 | 0.667776 | -5.130724 |-9.961820e+00 |-7.544502e-01 |\u001b[0m\n", "| 1059 |26.4750 | 1 | 0.469686 | 0.716404 | -5.131284 |-9.962982e+00 |-7.543491e-01 |\u001b[0m\n", "| 1060 |26.5000 | 1 | 0.461801 | 0.727427 | -5.131858 |-9.964142e+00 |-7.542482e-01 |\u001b[0m\n", "| 1061 |26.5250 | 1 | 0.464589 | 0.694128 | -5.132457 |-9.965300e+00 |-7.541474e-01 |\u001b[0m\n", "| 1062 |26.5500 | 1 | 0.471781 | 0.706331 | -5.133085 |-9.966456e+00 |-7.540468e-01 |\u001b[0m\n", "| 1063 |26.5750 | 1 | 0.459214 | 0.698293 | -5.133724 |-9.967609e+00 |-7.539464e-01 |\u001b[0m\n", "| 1064 |26.6000 | 1 | 0.465039 | 0.701240 | -5.134387 |-9.968759e+00 |-7.538461e-01 |\u001b[0m\n", "| 1065 |26.6250 | 1 | 0.469236 | 0.698958 | -5.135067 |-9.969907e+00 |-7.537461e-01 |\u001b[0m\n", "| 1066 |26.6500 | 1 | 0.464358 | 0.702767 | -5.135759 |-9.971053e+00 |-7.536462e-01 |\u001b[0m\n", "| 1067 |26.6750 | 1 | 0.465766 | 0.699608 | -5.136493 |-9.972195e+00 |-7.535466e-01 |\u001b[0m\n", "| 1068 |26.7000 | 1 | 0.459619 | 0.698973 | -5.084913 |-9.973335e+00 |-7.534475e-01 |\u001b[0m\n", "| 1069 |26.7250 | 1 | 0.476216 | 0.700416 | -5.145987 |-9.974473e+00 |-7.533490e-01 |\u001b[0m\n", "| 1070 |26.7500 | 1 | 0.480763 | 0.738047 | -5.138887 |-9.975607e+00 |-7.532503e-01 |\u001b[0m\n", "| 1071 |26.7750 | 1 | 0.461171 | 0.701831 | -5.135784 |-9.976739e+00 |-7.531516e-01 |\u001b[0m\n", "| 1072 |26.8000 | 1 | 0.447415 | 0.732048 | -5.142042 |-9.977868e+00 |-7.530534e-01 |\u001b[0m\n", "| 1073 |26.8250 | 1 | 0.463453 | 0.697525 | -5.138247 |-9.978994e+00 |-7.529551e-01 |\u001b[0m\n", "| 1074 |26.8500 | 1 | 0.461543 | 0.697473 | -5.135863 |-9.980116e+00 |-7.528572e-01 |\u001b[0m\n", "| 1075 |26.8750 | 1 | 0.457196 | 0.716596 | -5.141322 |-9.981236e+00 |-7.527593e-01 |\u001b[0m\n", "| 1076 |26.9000 | 1 | 0.468679 | 0.696296 | -5.138910 |-9.982352e+00 |-7.526616e-01 |\u001b[0m\n", "| 1077 |26.9250 | 1 | 0.461713 | 0.706081 | -5.146215 |-9.983465e+00 |-7.525640e-01 |\u001b[0m\n", "| 1078 |26.9500 | 1 | 0.466057 | 0.697697 | -5.150453 |-9.984575e+00 |-7.524668e-01 |\u001b[0m\n", "| 1079 |26.9750 | 1 | 0.464665 | 0.702892 | -5.143797 |-9.985682e+00 |-7.523698e-01 |\u001b[0m\n", "| 1080 |27.0000 | 1 | 0.460304 | 0.700288 | -5.148337 |-9.986785e+00 |-7.522730e-01 |\u001b[0m\n", "| 1081 |27.0250 | 1 | 0.487071 | 0.704852 | -5.146060 |-9.987885e+00 |-7.521765e-01 |\u001b[0m\n", "| 1082 |27.0500 | 1 | 0.491921 | 0.701263 | -5.148301 |-9.988981e+00 |-7.520802e-01 |\u001b[0m\n", "| 1083 |27.0750 | 1 | 0.514891 | 0.691098 | -5.155984 |-9.990075e+00 |-7.519842e-01 |\u001b[0m\n", "| 1084 |27.1000 | 1 | 0.511289 | 0.700004 | -5.152389 |-9.991164e+00 |-7.518885e-01 |\u001b[0m\n", "| 1085 |27.1250 | 1 | 0.464907 | 0.689621 | -5.154942 |-9.992250e+00 |-7.517932e-01 |\u001b[0m\n", "| 1086 |27.1500 | 1 | 0.522192 | 0.703924 | -5.152321 |-9.993332e+00 |-7.516980e-01 |\u001b[0m\n", "| 1087 |27.1750 | 1 | 0.482099 | 0.686301 | -5.150296 |-9.994411e+00 |-7.516032e-01 |\u001b[0m\n", "| 1088 |27.2000 | 1 | 0.484473 | 0.666338 | -5.158286 |-9.995485e+00 |-7.515087e-01 |\u001b[0m\n", "| 1089 |27.2250 | 1 | 0.465606 | 0.714366 | -5.158936 |-9.996557e+00 |-7.514145e-01 |\u001b[0m\n", "| 1090 |27.2500 | 1 | 0.455517 | 0.696506 | -5.159762 |-9.997624e+00 |-7.513207e-01 |\u001b[0m\n", "| 1091 |27.2750 | 1 | 0.466815 | 0.703345 | -5.162399 |-9.998687e+00 |-7.512272e-01 |\u001b[0m\n", "| 1092 |27.3000 | 1 | 0.456211 | 0.701992 | -5.156860 |-9.999747e+00 |-7.511339e-01 |\u001b[0m\n", "| 1093 |27.3250 | 1 | 0.465226 | 0.696526 | -5.159533 |-1.000080e+01 |-7.510410e-01 |\u001b[0m\n", "| 1094 |27.3500 | 1 | 0.512126 | 0.700777 | -5.164709 |-1.000185e+01 |-7.509483e-01 |\u001b[0m\n", "| 1095 |27.3750 | 1 | 0.479834 | 0.671114 | -5.164529 |-1.000290e+01 |-7.508561e-01 |\u001b[0m\n", "| 1096 |27.4000 | 1 | 0.492537 | 0.673946 | -5.170199 |-1.000394e+01 |-7.507642e-01 |\u001b[0m\n", "| 1097 |27.4250 | 1 | 0.489561 | 0.672850 | -5.167845 |-1.000498e+01 |-7.506725e-01 |\u001b[0m\n", "| 1098 |27.4500 | 1 | 0.488160 | 0.668952 | -5.165967 |-1.000602e+01 |-7.505812e-01 |\u001b[0m\n", "| 1099 |27.4750 | 1 | 0.519246 | 0.695777 | -5.169713 |-1.000705e+01 |-7.504902e-01 |\u001b[0m\n", "| 1100 |27.5000 | 1 | 0.489192 | 0.667250 | -5.170115 |-1.000808e+01 |-7.503995e-01 |\u001b[0m\n", "| 1101 |27.5250 | 1 | 0.499167 | 0.682266 | -5.175767 |-1.000910e+01 |-7.503093e-01 |\u001b[0m\n", "| 1102 |27.5500 | 1 | 0.492088 | 0.672850 | -5.178382 |-1.001011e+01 |-7.502193e-01 |\u001b[0m\n", "| 1103 |27.5750 | 1 | 0.487768 | 0.663787 | -5.175107 |-1.001113e+01 |-7.501297e-01 |\u001b[0m\n", "| 1104 |27.6000 | 1 | 0.493979 | 0.674322 | -5.178327 |-1.001214e+01 |-7.500404e-01 |\u001b[0m\n", "| 1105 |27.6250 | 1 | 0.490692 | 0.663366 | -5.177274 |-1.001314e+01 |-7.499515e-01 |\u001b[0m\n", "| 1106 |27.6500 | 1 | 0.492805 | 0.674422 | -5.178603 |-1.001414e+01 |-7.498630e-01 |\u001b[0m\n", "| 1107 |27.6750 | 1 | 0.485858 | 0.664090 | -5.187219 |-1.001513e+01 |-7.497748e-01 |\u001b[0m\n", "| 1108 |27.7000 | 1 | 0.486335 | 0.663156 | -5.184418 |-1.001613e+01 |-7.496872e-01 |\u001b[0m\n", "| 1109 |27.7250 | 1 | 0.494404 | 0.702291 | -5.186787 |-1.001711e+01 |-7.495997e-01 |\u001b[0m\n", "| 1110 |27.7500 | 1 | 0.446076 | 0.705973 | -5.187599 |-1.001809e+01 |-7.495127e-01 |\u001b[0m\n", "| 1111 |27.7750 | 1 | 0.482750 | 0.662560 | -5.184811 |-1.001907e+01 |-7.494261e-01 |\u001b[0m\n", "| 1112 |27.8000 | 1 | 0.476889 | 0.669748 | -5.191664 |-1.002004e+01 |-7.493398e-01 |\u001b[0m\n", "| 1113 |27.8250 | 1 | 0.492741 | 0.670303 | -5.192749 |-1.002101e+01 |-7.492541e-01 |\u001b[0m\n", "| 1114 |27.8500 | 1 | 0.481042 | 0.663581 | -5.195669 |-1.002197e+01 |-7.491686e-01 |\u001b[0m\n", "| 1115 |27.8750 | 1 | 0.484773 | 0.664162 | -5.197804 |-1.002292e+01 |-7.490836e-01 |\u001b[0m\n", "| 1116 |27.9000 | 1 | 0.471439 | 0.679314 | -5.194899 |-1.002388e+01 |-7.489990e-01 |\u001b[0m\n", "| 1117 |27.9250 | 1 | 0.484387 | 0.664117 | -5.198012 |-1.002482e+01 |-7.489147e-01 |\u001b[0m\n", "| 1118 |27.9500 | 1 | 0.485070 | 0.664848 | -5.200681 |-1.002576e+01 |-7.488309e-01 |\u001b[0m\n", "| 1119 |27.9750 | 1 | 0.483672 | 0.663051 | -5.202168 |-1.002670e+01 |-7.487475e-01 |\u001b[0m\n", "| 1120 |28.0000 | 1 | 0.483831 | 0.665529 | -5.208302 |-1.002763e+01 |-7.486645e-01 |\u001b[0m\n", "| 1121 |28.0250 | 1 | 0.486482 | 0.662659 | -5.207243 |-1.002856e+01 |-7.485818e-01 |\u001b[0m\n", "| 1122 |28.0500 | 1 | 0.484792 | 0.665026 | -5.206583 |-1.002948e+01 |-7.484996e-01 |\u001b[0m\n", "| 1123 |28.0750 | 1 | 0.484451 | 0.669203 | -5.210633 |-1.003040e+01 |-7.484179e-01 |\u001b[0m\n", "| 1124 |28.1000 | 1 | 0.483054 | 0.665843 | -5.210285 |-1.003131e+01 |-7.483365e-01 |\u001b[0m\n", "| 1125 |28.1250 | 1 | 0.479900 | 0.666058 | -5.215282 |-1.003221e+01 |-7.482556e-01 |\u001b[0m\n", "| 1126 |28.1500 | 1 | 0.481851 | 0.666201 | -5.219299 |-1.003311e+01 |-7.481751e-01 |\u001b[0m\n", "| 1127 |28.1750 | 1 | 0.483100 | 0.663770 | -5.218610 |-1.003401e+01 |-7.480950e-01 |\u001b[0m\n", "| 1128 |28.2000 | 1 | 0.485134 | 0.663042 | -5.220768 |-1.003490e+01 |-7.480154e-01 |\u001b[0m\n", "| 1129 |28.2250 | 1 | 0.489655 | 0.667828 | -5.221060 |-1.003578e+01 |-7.479362e-01 |\u001b[0m\n", "| 1130 |28.2500 | 1 | 0.480716 | 0.672463 | -5.223165 |-1.003666e+01 |-7.478574e-01 |\u001b[0m\n", "| 1131 |28.2750 | 1 | 0.483455 | 0.666447 | -5.228675 |-1.003753e+01 |-7.477791e-01 |\u001b[0m\n", "| 1132 |28.3000 | 1 | 0.483978 | 0.671011 | -5.229529 |-1.003840e+01 |-7.477012e-01 |\u001b[0m\n", "| 1133 |28.3250 | 1 | 0.475710 | 0.669280 | -5.232726 |-1.003926e+01 |-7.476238e-01 |\u001b[0m\n", "| 1134 |28.3500 | 1 | 0.479361 | 0.669395 | -5.234044 |-1.004012e+01 |-7.475468e-01 |\u001b[0m\n", "| 1135 |28.3750 | 1 | 0.496386 | 0.682017 | -5.232092 |-1.004097e+01 |-7.474703e-01 |\u001b[0m\n", "| 1136 |28.4000 | 1 | 0.485283 | 0.674847 | -5.238405 |-1.004182e+01 |-7.473943e-01 |\u001b[0m\n", "| 1137 |28.4250 | 1 | 0.479666 | 0.672826 | -5.240062 |-1.004266e+01 |-7.473187e-01 |\u001b[0m\n", "| 1138 |28.4500 | 1 | 0.479845 | 0.676287 | -5.242591 |-1.004349e+01 |-7.472436e-01 |\u001b[0m\n", "| 1139 |28.4750 | 1 | 0.481158 | 0.666536 | -5.247594 |-1.004432e+01 |-7.471689e-01 |\u001b[0m\n", "| 1140 |28.5000 | 1 | 0.483141 | 0.667308 | -5.245477 |-1.004514e+01 |-7.470948e-01 |\u001b[0m\n", "| 1141 |28.5250 | 1 | 0.486443 | 0.666994 | -5.248138 |-1.004596e+01 |-7.470210e-01 |\u001b[0m\n", "| 1142 |28.5500 | 1 | 0.479723 | 0.665961 | -5.251076 |-1.004677e+01 |-7.469478e-01 |\u001b[0m\n", "| 1143 |28.5750 | 1 | 0.478889 | 0.668256 | -5.253471 |-1.004757e+01 |-7.468750e-01 |\u001b[0m\n", "| 1144 |28.6000 | 1 | 0.480882 | 0.666810 | -5.258529 |-1.004837e+01 |-7.468027e-01 |\u001b[0m\n", "| 1145 |28.6250 | 1 | 0.474088 | 0.675753 | -5.259920 |-1.004917e+01 |-7.467310e-01 |\u001b[0m\n", "| 1146 |28.6500 | 1 | 0.480283 | 0.668231 | -5.261212 |-1.004995e+01 |-7.466596e-01 |\u001b[0m\n", "| 1147 |28.6750 | 1 | 0.480788 | 0.674629 | -5.263745 |-1.005073e+01 |-7.465887e-01 |\u001b[0m\n", "| 1148 |28.7000 | 1 | 0.480191 | 0.669986 | -5.264326 |-1.005151e+01 |-7.465184e-01 |\u001b[0m\n", "| 1149 |28.7250 | 1 | 0.470319 | 0.675661 | -5.269713 |-1.005228e+01 |-7.464485e-01 |\u001b[0m\n", "| 1150 |28.7500 | 1 | 0.480411 | 0.673238 | -5.273651 |-1.005304e+01 |-7.463791e-01 |\u001b[0m\n", "| 1151 |28.7750 | 1 | 0.473258 | 0.678330 | -5.274198 |-1.005380e+01 |-7.463102e-01 |\u001b[0m\n", "| 1152 |28.8000 | 1 | 0.478426 | 0.670399 | -5.278360 |-1.005455e+01 |-7.462418e-01 |\u001b[0m\n", "| 1153 |28.8250 | 1 | 0.481835 | 0.677192 | -5.278659 |-1.005530e+01 |-7.461739e-01 |\u001b[0m\n", "| 1154 |28.8500 | 1 | 0.477153 | 0.671792 | -5.280378 |-1.005604e+01 |-7.461065e-01 |\u001b[0m\n", "| 1155 |28.8750 | 1 | 0.483710 | 0.675545 | -5.286094 |-1.005677e+01 |-7.460396e-01 |\u001b[0m\n", "| 1156 |28.9000 | 1 | 0.478907 | 0.667440 | -5.288677 |-1.005750e+01 |-7.459732e-01 |\u001b[0m\n", "| 1157 |28.9250 | 1 | 0.476032 | 0.683400 | -5.291475 |-1.005822e+01 |-7.459074e-01 |\u001b[0m\n", "| 1158 |28.9500 | 1 | 0.478212 | 0.670153 | -5.294549 |-1.005893e+01 |-7.458420e-01 |\u001b[0m\n", "| 1159 |28.9750 | 1 | 0.490696 | 0.674169 | -5.294755 |-1.005964e+01 |-7.457771e-01 |\u001b[0m\n", "| 1160 |29.0000 | 1 | 0.478562 | 0.668860 | -5.298482 |-1.006034e+01 |-7.457127e-01 |\u001b[0m\n", "| 1161 |29.0250 | 1 | 0.477472 | 0.670187 | -5.301871 |-1.006104e+01 |-7.456489e-01 |\u001b[0m\n", "| 1162 |29.0500 | 1 | 0.481258 | 0.684804 | -5.305298 |-1.006173e+01 |-7.455856e-01 |\u001b[0m\n", "| 1163 |29.0750 | 1 | 0.460059 | 0.686929 | -5.310364 |-1.006241e+01 |-7.455228e-01 |\u001b[0m\n", "| 1164 |29.1000 | 1 | 0.448954 | 0.699926 | -5.310048 |-1.006309e+01 |-7.454605e-01 |\u001b[0m\n", "| 1165 |29.1250 | 1 | 0.453478 | 0.709623 | -5.313561 |-1.006376e+01 |-7.453987e-01 |\u001b[0m\n", "| 1166 |29.1500 | 1 | 0.445714 | 0.707670 | -5.316738 |-1.006442e+01 |-7.453374e-01 |\u001b[0m\n", "| 1167 |29.1750 | 1 | 0.452987 | 0.698719 | -5.318177 |-1.006508e+01 |-7.452767e-01 |\u001b[0m\n", "| 1168 |29.2000 | 1 | 0.449597 | 0.705269 | -5.325022 |-1.006573e+01 |-7.452165e-01 |\u001b[0m\n", "| 1169 |29.2250 | 1 | 0.490915 | 0.676666 | -5.327513 |-1.006637e+01 |-7.451568e-01 |\u001b[0m\n", "| 1170 |29.2500 | 1 | 0.449594 | 0.712068 | -5.328948 |-1.006701e+01 |-7.450977e-01 |\u001b[0m\n", "| 1171 |29.2750 | 1 | 0.457821 | 0.707891 | -5.332960 |-1.006764e+01 |-7.450391e-01 |\u001b[0m\n", "| 1172 |29.3000 | 1 | 0.451920 | 0.700959 | -5.334478 |-1.006827e+01 |-7.449809e-01 |\u001b[0m\n", "| 1173 |29.3250 | 1 | 0.457635 | 0.705507 | -5.338479 |-1.006889e+01 |-7.449234e-01 |\u001b[0m\n", "| 1174 |29.3500 | 1 | 0.447267 | 0.700000 | -5.343494 |-1.006950e+01 |-7.448664e-01 |\u001b[0m\n", "| 1175 |29.3750 | 1 | 0.458476 | 0.705746 | -5.346398 |-1.007011e+01 |-7.448099e-01 |\u001b[0m\n", "| 1176 |29.4000 | 1 | 0.450082 | 0.701366 | -5.350024 |-1.007071e+01 |-7.447539e-01 |\u001b[0m\n", "| 1177 |29.4250 | 1 | 0.476268 | 0.681734 | -5.351738 |-1.007130e+01 |-7.446985e-01 |\u001b[0m\n", "| 1178 |29.4500 | 1 | 0.460729 | 0.691428 | -5.354504 |-1.007189e+01 |-7.446436e-01 |\u001b[0m\n", "| 1179 |29.4750 | 1 | 0.457731 | 0.717534 | -5.359754 |-1.007247e+01 |-7.445893e-01 |\u001b[0m\n", "| 1180 |29.5000 | 1 | 0.448818 | 0.702547 | -5.362285 |-1.007304e+01 |-7.445355e-01 |\u001b[0m\n", "| 1181 |29.5250 | 1 | 0.444427 | 0.710591 | -5.367519 |-1.007361e+01 |-7.444822e-01 |\u001b[0m\n", "| 1182 |29.5500 | 1 | 0.449657 | 0.703114 | -5.371200 |-1.007417e+01 |-7.444295e-01 |\u001b[0m\n", "| 1183 |29.5750 | 1 | 0.440618 | 0.711450 | -5.371774 |-1.007472e+01 |-7.443773e-01 |\u001b[0m\n", "| 1184 |29.6000 | 1 | 0.446356 | 0.706056 | -5.376509 |-1.007527e+01 |-7.443257e-01 |\u001b[0m\n", "| 1185 |29.6250 | 1 | 0.448475 | 0.708371 | -5.380511 |-1.007581e+01 |-7.442746e-01 |\u001b[0m\n", "| 1186 |29.6500 | 1 | 0.448725 | 0.706641 | -5.383604 |-1.007634e+01 |-7.442241e-01 |\u001b[0m\n", "| 1187 |29.6750 | 1 | 0.467714 | 0.685492 | -5.389558 |-1.007687e+01 |-7.441741e-01 |\u001b[0m\n", "| 1188 |29.7000 | 1 | 0.469720 | 0.679917 | -5.392343 |-1.007739e+01 |-7.441247e-01 |\u001b[0m\n", "| 1189 |29.7250 | 1 | 0.472234 | 0.676739 | -5.394581 |-1.007790e+01 |-7.440758e-01 |\u001b[0m\n", "| 1190 |29.7500 | 1 | 0.471790 | 0.681953 | -5.398936 |-1.007841e+01 |-7.440275e-01 |\u001b[0m\n", "| 1191 |29.7750 | 1 | 0.467655 | 0.680340 | -5.401760 |-1.007891e+01 |-7.439797e-01 |\u001b[0m\n", "| 1192 |29.8000 | 1 | 0.474229 | 0.672687 | -5.407802 |-1.007940e+01 |-7.439325e-01 |\u001b[0m\n", "| 1193 |29.8250 | 1 | 0.476449 | 0.675359 | -5.411367 |-1.007989e+01 |-7.438858e-01 |\u001b[0m\n", "| 1194 |29.8500 | 1 | 0.476582 | 0.678527 | -5.415002 |-1.008037e+01 |-7.438397e-01 |\u001b[0m\n", "| 1195 |29.8750 | 1 | 0.472185 | 0.686604 | -5.419606 |-1.008084e+01 |-7.437942e-01 |\u001b[0m\n", "| 1196 |29.9000 | 1 | 0.471914 | 0.678537 | -5.420797 |-1.008131e+01 |-7.437492e-01 |\u001b[0m\n", "| 1197 |29.9250 | 1 | 0.466368 | 0.687928 | -5.426230 |-1.008177e+01 |-7.437047e-01 |\u001b[0m\n", "| 1198 |29.9500 | 1 | 0.471255 | 0.677494 | -5.431831 |-1.008222e+01 |-7.436608e-01 |\u001b[0m\n", "| 1199 |29.9750 | 1 | 0.474277 | 0.679189 | -5.434645 |-1.008267e+01 |-7.436175e-01 |\u001b[0m\n", "| 1200 |30.0000 | 1 | 0.470302 | 0.680280 | -5.440101 |-1.008311e+01 |-7.435748e-01 |\u001b[0m\n", "\u001b[34m...Finished\u001b[0m\n", "\u001b[36mFINISHED - Elapsed time = 897.2004556 seconds\u001b[0m\n", "\u001b[36mFINISHED - CPU process time = 1055.3392616 seconds\u001b[0m\n" ] } ], "source": [ "case_data = sharpy_main.main(['', route_to_case + 'simple_HALE.sharpy'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The resulting data structure that is returned from the call to `main` contains all the time-dependant variables for both the structural and aerodynamic solvers.\n", "\n", "`timestep_info` can be found in `case_data.structure` and `case_data.aero`. It is an array with custom-made structure to contain the data of each solver.\n", "\n", "In the `.sharpy` file, we can see which solvers are run:\n", "```\n", "flow = ['BeamLoader',\n", " 'AerogridLoader',\n", " 'StaticTrim',\n", " 'BeamLoads',\n", " 'AerogridPlot',\n", " 'BeamPlot',\n", " 'DynamicCoupled',\n", " ]\n", "```\n", "\n", "In order:\n", "\n", "* BeamLoader: reads the `fem.h5` file and generates the structure for the beam solver.\n", "\n", "* AerogridLoader: reads the `aero.h5` file and generates the aerodynamic grid for the aerodynamic solver.\n", "\n", "* StaticTrim: this solver performs a longitudinal trim (Thrust, Angle of attack and Elevator deflection) using the StaticCoupled solver.\n", "\n", "* BeamLoads: calculates the internal beam loads for the static solution\n", "\n", "* AerogridPlot: outputs the aerodynamic grid for the static solution.\n", "\n", "* BeamPlot: outputs the structural discretisation for the static solution.\n", "\n", "* DynamicCoupled: is the main driver of the dynamic simulation: executes the structural and aerodynamic solvers and couples both. Every converged time step is followed by a BeamLoads, AerogridPlot and BeamPlot execution." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Structural data organisation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `timestep_info` structure contains several relevant variables:\n", "\n", "* `for_pos`: position of the body-attached frame of reference in inertial FoR.\n", "\n", "* `for_vel`: velocity (in body FoR) of the body FoR wrt inertial FoR.\n", "\n", "* `pos`: nodal position in A FoR.\n", "\n", "* `psi`: nodal rotations (from the material B FoR to A FoR) in a Cartesian Rotation Vector parametrisation.\n", "\n", "* `applied_steady_forces`: nodal forces from the aero solver and the applied forces.\n", "\n", "* `postproc_cell`: is a dictionary that contains the variables generated by a postprocessor, such as the internal beam loads.\n", "\n", "\n", "The structural `timestep_info` also contains some useful variables:\n", "\n", "* `cag` and `cga` return $C^{AG}$ and $C^{GA}$, the rotation matrices from the body-attached (A) FoR to the inertial (G).\n", "\n", "* `glob_pos` rotates the `pos` variable to give you the inertial nodal position. If `include_rbm = True` is passed, `for_pos` is added to it." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Aerodynamic data organisation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The aerodynamic datastructure can be found in `case_data.aero.timestep_info`.\n", "It contains useful variables, such as:\n", "\n", "* `dimensions` and `dimensions_star`: gives the dimensions of every surface and wake surface. Organised as: `dimensions[i_surf, 0] = chordwise panels`, `dimensions[i_surf, 1] = spanwise panels`.\n", "\n", "* `zeta` and `zeta_star`: they are the $G$ FoR coordinates of the surface vertices.\n", "\n", "* `gamma` and `gamma_star`: vortex ring circulations." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Structural dynamics" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now plot the rigid body dynamics:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*RBM trajectory*" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig, ax = plt.subplots(1, 1, figsize=(7, 4))\n", "\n", "# extract information\n", "n_tsteps = len(case_data.structure.timestep_info)\n", "xz = np.zeros((n_tsteps, 2))\n", "for it in range(n_tsteps):\n", " xz[it, 0] = -case_data.structure.timestep_info[it].for_pos[0] # the - is so that increasing time -> increasing x\n", " xz[it, 1] = case_data.structure.timestep_info[it].for_pos[2]\n", "ax.plot(xz[:, 0], xz[:, 1])\n", "fig.suptitle('Longitudinal trajectory of the T-Tail model in a 20% 1-cos gust encounter')\n", "ax.set_xlabel('X [m]')\n", "ax.set_ylabel('Z [m]');\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*RBM velocities*" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig, ax = plt.subplots(3, 1, figsize=(7, 6), sharex=True)\n", "ylabels = ['Vx [m/s]', 'Vy [m/s]', 'Vz [m/s]']\n", "\n", "# extract information\n", "n_tsteps = len(case_data.structure.timestep_info)\n", "dt = case_data.settings['DynamicCoupled']['dt']\n", "time_vec = np.linspace(0, n_tsteps*dt, n_tsteps)\n", "for_vel = np.zeros((n_tsteps, 3))\n", "for it in range(n_tsteps):\n", " for_vel[it, 0:3] = case_data.structure.timestep_info[it].for_vel[0:3]\n", " \n", "for idim in range(3):\n", " ax[idim].plot(time_vec, for_vel[:, idim])\n", " ax[idim].set_ylabel(ylabels[idim])\n", " \n", "ax[2].set_xlabel('time [s]')\n", "plt.subplots_adjust(hspace=0)\n", "fig.suptitle('Linear RBM velocities. T-Tail model in a 20% 1-cos gust encounter');\n", "# ax.set_xlabel('X [m]')\n", "# ax.set_ylabel('Z [m]');\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig, ax = plt.subplots(3, 1, figsize=(7, 6), sharex=True)\n", "ylabels = ['Roll rate [deg/s]', 'Pitch rate [deg/s]', 'Yaw rate [deg/s]']\n", "\n", "# extract information\n", "n_tsteps = len(case_data.structure.timestep_info)\n", "dt = case_data.settings['DynamicCoupled']['dt']\n", "time_vec = np.linspace(0, n_tsteps*dt, n_tsteps)\n", "for_vel = np.zeros((n_tsteps, 3))\n", "for it in range(n_tsteps):\n", " for_vel[it, 0:3] = case_data.structure.timestep_info[it].for_vel[3:6]*180/np.pi\n", " \n", "for idim in range(3):\n", " ax[idim].plot(time_vec, for_vel[:, idim])\n", " ax[idim].set_ylabel(ylabels[idim])\n", " \n", "ax[2].set_xlabel('time [s]')\n", "plt.subplots_adjust(hspace=0)\n", "fig.suptitle('Angular RBM velocities. T-Tail model in a 20% 1-cos gust encounter');\n", "# ax.set_xlabel('X [m]')\n", "# ax.set_ylabel('Z [m]');\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Wing tip deformation*\n", "\n", "It is stored in `timestep_info` as `pos`. We need to find the correct node." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Wing tip node is the maximum Y one: 16\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig, ax = plt.subplots(1, 1, figsize=(6, 6))\n", "ax.scatter(case_data.structure.ini_info.pos[:, 0], case_data.structure.ini_info.pos[:, 1])\n", "ax.axis('equal')\n", "tip_node = np.argmax(case_data.structure.ini_info.pos[:, 1])\n", "print('Wing tip node is the maximum Y one: ', tip_node)\n", "ax.scatter(case_data.structure.ini_info.pos[tip_node, 0], case_data.structure.ini_info.pos[tip_node, 1], color='red')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can plot now the `pos[tip_node,:]` variable:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig, ax = plt.subplots(1, 1, figsize=(7, 3))\n", "\n", "# extract information\n", "n_tsteps = len(case_data.structure.timestep_info)\n", "xz = np.zeros((n_tsteps, 2))\n", "for it in range(n_tsteps):\n", " xz[it, 0] = case_data.structure.timestep_info[it].pos[tip_node, 0]\n", " xz[it, 1] = case_data.structure.timestep_info[it].pos[tip_node, 2]\n", "ax.plot(time_vec, xz[:, 1])\n", "# fig.suptitle('Longitudinal trajectory of the T-Tail model in a 20% 1-cos gust encounter')\n", "ax.set_xlabel('time [s]')\n", "ax.set_ylabel('Vertical disp. [m]');\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig, ax = plt.subplots(1, 1, figsize=(7, 3))\n", "\n", "# extract information\n", "n_tsteps = len(case_data.structure.timestep_info)\n", "xz = np.zeros((n_tsteps, 2))\n", "for it in range(n_tsteps):\n", " xz[it, 0] = case_data.structure.timestep_info[it].pos[tip_node, 0]\n", " xz[it, 1] = case_data.structure.timestep_info[it].pos[tip_node, 2]\n", "ax.plot(time_vec, xz[:, 0])\n", "# fig.suptitle('Longitudinal trajectory of the T-Tail model in a 20% 1-cos gust encounter')\n", "ax.set_xlabel('time [s]')\n", "ax.set_ylabel('Horizontal disp. [m]\\nPositive is aft');\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Wing root loads*\n", "\n", "The wing root loads can be extracted from the `postproc_cell` structure in `timestep_info`." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig, ax = plt.subplots(3, 1, figsize=(7, 6), sharex=True)\n", "ylabels = ['Torsion [Nm2]', 'OOP [Nm2]', 'IP [Nm2]']\n", "\n", "# extract information\n", "n_tsteps = len(case_data.structure.timestep_info)\n", "dt = case_data.settings['DynamicCoupled']['dt']\n", "time_vec = np.linspace(0, n_tsteps*dt, n_tsteps)\n", "loads = np.zeros((n_tsteps, 3))\n", "for it in range(n_tsteps):\n", " loads[it, 0:3] = case_data.structure.timestep_info[it].postproc_cell['loads'][0, 3:6]\n", " \n", "for idim in range(3):\n", " ax[idim].plot(time_vec, loads[:, idim])\n", " ax[idim].set_ylabel(ylabels[idim])\n", " \n", "ax[2].set_xlabel('time [s]')\n", "plt.subplots_adjust(hspace=0)\n", "fig.suptitle('Wing root loads. T-Tail model in a 20% 1-cos gust encounter');\n", "# ax.set_xlabel('X [m]')\n", "# ax.set_ylabel('Z [m]');\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Aerodynamic analysis\n", "\n", "The aerodynamic analysis can be obviously conducted using python. However, the easiest way is to run the case by yourself and open the files in `output/simple_HALE/beam` and `output/simple_HALE/aero` with [Paraview](https://www.paraview.org/)." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "url = 'https://raw.githubusercontent.com/ImperialCollegeLondon/sharpy/dev_doc/docs/source/content/example_notebooks/images/t-tail_solution.png'\n", "Image(url=url, width=600)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.5" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: docs/source/content/example_notebooks/wind_turbine.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Simulation NREL 5MW wind turbine" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%config InlineBackend.figure_format = 'svg'\n", "from IPython.display import Image\n", "url = 'https://raw.githubusercontent.com/ImperialCollegeLondon/sharpy/dev_doc/docs/source/content/example_notebooks/images/turbulence_no_legend.png'\n", "Image(url=url, width=800)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this notebook:\n", "\n", "The blade loads on the NREL-5MW reference wind turbine computed with SHARPy and OpenFAST will be compared. However, zero-drag airfoils have been used." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "OpenFAST: _https://openfast.readthedocs.io_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "NREL-5MW: Jonkman, J.; Butterfield, S.; Musial, W. and Scott, G.. _Definition of a 5-MW Reference Wind Turbine for Offshore System Development_, Technical Report, NREL 2009" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Load the required packages:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# Required packages\n", "%matplotlib inline\n", "import numpy as np\n", "import os\n", "import matplotlib.pyplot as plt\n", "\n", "# Required SHARPy modules\n", "import sharpy.sharpy_main\n", "import sharpy.utils.algebra as algebra\n", "import sharpy.utils.generate_cases as gc\n", "import sharpy.cases.templates.template_wt as template_wt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These are the results from the OpenFAST simulation for comparison: out-of-plane `of_cNdrR` and in-plane `of_cTdrR` coefficients along the blade and thrust `of_ct` and power `of_cp` rotor coefficients" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "of_rR = np.array([0.20158356, 0.3127131, 0.40794048, 0.5984148, 0.6936519, 0.85238045, 0.899999, 0.95555407, 0.98729974, 1.0])\n", "of_cNdrR = np.array([0.08621394, 0.14687876, 0.19345148, 0.2942731, 0.36003628, 0.43748564, 0.44762507, 0.38839236, 0.29782477, 0.0])\n", "of_cTdrR = np.array([0.048268348, 0.051957503, 0.05304592, 0.052862607, 0.056001827, 0.0536646, 0.050112925, 0.038993906, 0.023664437, 0.0])\n", "\n", "of_ct = 0.69787693\n", "of_cp = 0.48813498" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create SHARPy case" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We define our parameters:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# Mathematical constants\n", "deg2rad = np.pi/180.\n", "\n", "# Case\n", "case = 'rotor'\n", "route = './'\n", "\n", "# Geometry discretization\n", "chord_panels = np.array([8], dtype=int)\n", "revs_in_wake = 5\n", "\n", "# Operation\n", "rotation_velocity = 12.1*2*np.pi/60\n", "pitch_deg = 0. #degrees\n", "\n", "# Wind\n", "WSP = 12.\n", "air_density = 1.225\n", "\n", "# Simulation\n", "dphi = 4.*deg2rad\n", "revs_to_simulate = 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Computation of associated parameters" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "dt = dphi/rotation_velocity\n", "time_steps = int(revs_to_simulate*2.*np.pi/dphi)\n", "mstar = int(revs_in_wake*2.*np.pi/dphi)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Generation of the rotor geometry based on information in the excel file:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[33mWARNING: The poisson cofficient is assumed equal to 0.3\u001b[0m\n", "\u001b[33mWARNING: Cross-section area is used as shear area\u001b[0m\n", "\u001b[33mWARNING: Using perpendicular axis theorem to compute the inertia around xB\u001b[0m\n", "\u001b[33mWARNING: Replacing node 29 by node 0\u001b[0m\n", "\u001b[33mWARNING: Replacing node 58 by node 0\u001b[0m\n" ] } ], "source": [ "op_params = {} \n", "op_params['rotation_velocity'] = rotation_velocity \n", "op_params['pitch_deg'] = pitch_deg \n", "op_params['wsp'] = WSP \n", "op_params['dt'] = dt \n", " \n", "geom_params = {} \n", "geom_params['chord_panels'] = chord_panels \n", "geom_params['tol_remove_points'] = 1e-8 \n", "geom_params['n_points_camber'] = 100 \n", "geom_params['h5_cross_sec_prop'] = None \n", "geom_params['m_distribution'] = 'uniform' \n", " \n", "options = {} \n", "options['camber_effect_on_twist'] = False \n", "options['user_defined_m_distribution_type'] = None \n", "options['include_polars'] = False \n", "options['separate_blades'] = False \n", " \n", "excel_description = {} \n", "excel_description['excel_file_name'] = 'source/type04_db_nrel5mw_oc3_v06.xlsx' \n", "excel_description['excel_sheet_parameters'] = 'parameters' \n", "excel_description['excel_sheet_structural_blade'] = 'structural_blade' \n", "excel_description['excel_sheet_discretization_blade'] = 'discretization_blade' \n", "excel_description['excel_sheet_aero_blade'] = 'aero_blade' \n", "excel_description['excel_sheet_airfoil_info'] = 'airfoil_info' \n", "excel_description['excel_sheet_airfoil_chord'] = 'airfoil_coord' \n", " \n", "rotor, hub_nodes = template_wt.rotor_from_excel_type03(op_params, \n", " geom_params, \n", " excel_description, \n", " options) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Define simulation details. The steady simulation is faster than the dynamic simulation. However, the dynamic simulation includes wake self-induction and provides more accurate results." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "steady_simulation = False" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "SimInfo = gc.SimulationInformation()\n", "SimInfo.set_default_values()\n", "\n", "if steady_simulation:\n", " SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader',\n", " 'AerogridLoader',\n", " 'StaticCoupled',\n", " 'BeamPlot',\n", " 'AerogridPlot', \n", " 'SaveData'] \n", "else:\n", " SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader',\n", " 'AerogridLoader',\n", " 'StaticCoupled',\n", " 'DynamicCoupled']\n", " \n", "SimInfo.solvers['SHARPy']['case'] = case\n", "SimInfo.solvers['SHARPy']['route'] = route\n", "SimInfo.solvers['SHARPy']['write_log'] = True\n", "SimInfo.set_variable_all_dicts('dt', dt)\n", "SimInfo.set_variable_all_dicts('rho', air_density)\n", "\n", "SimInfo.solvers['SteadyVelocityField']['u_inf'] = WSP\n", "SimInfo.solvers['SteadyVelocityField']['u_inf_direction'] = np.array([0., 0., 1.])\n", "\n", "SimInfo.solvers['BeamLoader']['unsteady'] = 'on'\n", "\n", "SimInfo.solvers['AerogridLoader']['unsteady'] = 'on'\n", "SimInfo.solvers['AerogridLoader']['mstar'] = mstar\n", "SimInfo.solvers['AerogridLoader']['freestream_dir'] = np.array([0.,0.,0.])\n", "SimInfo.solvers['AerogridLoader']['wake_shape_generator'] = 'HelicoidalWake'\n", "SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] = {'u_inf': WSP,\n", " 'u_inf_direction': SimInfo.solvers['SteadyVelocityField']['u_inf_direction'],\n", " 'rotation_velocity': rotation_velocity*np.array([0., 0., 1.]),\n", " 'dt': dt,\n", " 'dphi1': dphi,\n", " 'ndphi1': mstar,\n", " 'r': 1.,\n", " 'dphimax': 10*deg2rad}\n", " \n", "SimInfo.solvers['StaticCoupled']['structural_solver'] = 'RigidDynamicPrescribedStep'\n", "SimInfo.solvers['StaticCoupled']['structural_solver_settings'] = SimInfo.solvers['RigidDynamicPrescribedStep']\n", "SimInfo.solvers['StaticCoupled']['aero_solver'] = 'StaticUvlm'\n", "SimInfo.solvers['StaticCoupled']['aero_solver_settings'] = SimInfo.solvers['StaticUvlm']\n", "\n", "SimInfo.solvers['StaticCoupled']['tolerance'] = 1e-8\n", "SimInfo.solvers['StaticCoupled']['n_load_steps'] = 0\n", "SimInfo.solvers['StaticCoupled']['relaxation_factor'] = 0.\n", "\n", "SimInfo.solvers['StaticUvlm']['num_cores'] = 8\n", "SimInfo.solvers['StaticUvlm']['velocity_field_generator'] = 'SteadyVelocityField'\n", "SimInfo.solvers['StaticUvlm']['velocity_field_input'] = SimInfo.solvers['SteadyVelocityField']\n", "\n", "SimInfo.solvers['SaveData']['compress_float'] = True\n", " \n", "# Only used for steady_simulation = False\n", "SimInfo.solvers['StepUvlm']['convection_scheme'] = 3\n", "SimInfo.solvers['StepUvlm']['num_cores'] = 8\n", "SimInfo.solvers['StepUvlm']['velocity_field_generator'] = 'SteadyVelocityField'\n", "SimInfo.solvers['StepUvlm']['velocity_field_input'] = SimInfo.solvers['SteadyVelocityField']\n", "\n", "SimInfo.solvers['DynamicCoupled']['structural_solver'] = 'RigidDynamicPrescribedStep'\n", "SimInfo.solvers['DynamicCoupled']['structural_solver_settings'] = SimInfo.solvers['RigidDynamicPrescribedStep']\n", "SimInfo.solvers['DynamicCoupled']['aero_solver'] = 'StepUvlm'\n", "SimInfo.solvers['DynamicCoupled']['aero_solver_settings'] = SimInfo.solvers['StepUvlm']\n", "SimInfo.solvers['DynamicCoupled']['postprocessors'] = ['BeamPlot', 'AerogridPlot', 'Cleanup', 'SaveData']\n", "SimInfo.solvers['DynamicCoupled']['postprocessors_settings'] = {'BeamPlot': SimInfo.solvers['BeamPlot'],\n", " 'AerogridPlot': SimInfo.solvers['AerogridPlot'],\n", " 'Cleanup': SimInfo.solvers['Cleanup'],\n", " 'SaveData': SimInfo.solvers['SaveData']}\n", "SimInfo.solvers['DynamicCoupled']['minimum_steps'] = 0\n", "SimInfo.solvers['DynamicCoupled']['include_unsteady_force_contribution'] = True\n", "SimInfo.solvers['DynamicCoupled']['relaxation_factor'] = 0.\n", "SimInfo.solvers['DynamicCoupled']['final_relaxation_factor'] = 0.\n", "SimInfo.solvers['DynamicCoupled']['dynamic_relaxation'] = False\n", "SimInfo.solvers['DynamicCoupled']['relaxation_steps'] = 0\n", "\n", "# Define dynamic simulation (used regardless the value of \"steady_simulation\" variable)\n", "SimInfo.define_num_steps(time_steps)\n", "SimInfo.with_forced_vel = True\n", "SimInfo.for_vel = np.zeros((time_steps,6), dtype=float)\n", "SimInfo.for_vel[:,5] = rotation_velocity\n", "SimInfo.for_acc = np.zeros((time_steps,6), dtype=float)\n", "SimInfo.with_dynamic_forces = True\n", "SimInfo.dynamic_forces = np.zeros((time_steps,rotor.StructuralInformation.num_node,6), dtype=float)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Generate simulation files" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case'])\n", "rotor.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case'])\n", "SimInfo.generate_solver_file()\n", "SimInfo.generate_dyn_file(time_steps)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Run SHARPy case" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--------------------------------------------------------------------------------\u001b[0m\n", " ###### ## ## ### ######## ######## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ####\u001b[0m\n", " ###### ######### ## ## ######## ######## ##\u001b[0m\n", " ## ## ## ######### ## ## ## ##\u001b[0m\n", " ## ## ## ## ## ## ## ## ## ##\u001b[0m\n", " ###### ## ## ## ## ## ## ## ##\u001b[0m\n", "--------------------------------------------------------------------------------\u001b[0m\n", "Aeroelastics Lab, Aeronautics Department.\u001b[0m\n", " Copyright (c), Imperial College London.\u001b[0m\n", " All rights reserved.\u001b[0m\n", " License available at https://github.com/imperialcollegelondon/sharpy\u001b[0m\n", "\u001b[36mRunning SHARPy from /home/arturo/code/sharpy/docs/source/content/example_notebooks\u001b[0m\n", "\u001b[36mSHARPy being run is in /home/arturo/code/sharpy\u001b[0m\n", "\u001b[36mThe branch being run is dev_blade_pitch_v2\u001b[0m\n", "\u001b[36mThe version and commit hash are: v1.2.1-546-g05602d1f-05602d1f\u001b[0m\n", "SHARPy output folder set\u001b[0m\n", "\u001b[34m\t./output//rotor/\u001b[0m\n", "\u001b[36mGenerating an instance of BeamLoader\u001b[0m\n", "\u001b[36mGenerating an instance of AerogridLoader\u001b[0m\n", "Variable shear_direction has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: [1. 0. 0.]\u001b[0m\n", "Variable shear_exp has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 0.0\u001b[0m\n", "Variable h_ref has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1.0\u001b[0m\n", "Variable h_corr has no assigned value in the settings file.\u001b[0m\n", "\u001b[34m will default to the value: 1.0\u001b[0m\n", "\u001b[34mThe aerodynamic grid contains 3 surfaces\u001b[0m\n", "\u001b[34m Surface 0, M=8, N=26\u001b[0m\n", " Wake 0, M=450, N=26\u001b[0m\n", "\u001b[34m Surface 1, M=8, N=26\u001b[0m\n", " Wake 1, M=450, N=26\u001b[0m\n", "\u001b[34m Surface 2, M=8, N=26\u001b[0m\n", " Wake 2, M=450, N=26\u001b[0m\n", " In total: 624 bound panels\u001b[0m\n", " In total: 35100 wake panels\u001b[0m\n", " Total number of panels = 35724\u001b[0m\n", "\u001b[36mGenerating an instance of StaticCoupled\u001b[0m\n", "\u001b[36mGenerating an instance of RigidDynamicPrescribedStep\u001b[0m\n", "\u001b[36mGenerating an instance of StaticUvlm\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "|=====|=====|============|==========|==========|==========|==========|==========|==========|\u001b[0m\n", "|iter |step | log10(res) | Fx | Fy | Fz | Mx | My | Mz |\u001b[0m\n", "|=====|=====|============|==========|==========|==========|==========|==========|==========|\u001b[0m\n", "| 0 | 0 | 0.00000 | -0.0000 | 0.0000 |21927.8250| 0.0000 | -0.0000 |5962997.5361|\u001b[0m\n", "| 1 | 0 | -inf | -0.0000 | 0.0000 |21927.8250| 0.0000 | -0.0000 |5962997.5361|\u001b[0m\n", "\u001b[36mGenerating an instance of DynamicCoupled\u001b[0m\n", "\u001b[36mGenerating an instance of RigidDynamicPrescribedStep\u001b[0m\n", "\u001b[36mGenerating an instance of StepUvlm\u001b[0m\n", "\u001b[36mGenerating an instance of BeamPlot\u001b[0m\n", "\u001b[36mGenerating an instance of AerogridPlot\u001b[0m\n", "\u001b[36mGenerating an instance of Cleanup\u001b[0m\n", "\u001b[36mGenerating an instance of SaveData\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "\u001b[0m\n", "|=======|========|======|==============|==============|==============|==============|==============|\u001b[0m\n", "| ts | t | iter | struc ratio | iter time | residual vel | FoR_vel(x) | FoR_vel(z) |\u001b[0m\n", "|=======|========|======|==============|==============|==============|==============|==============|\u001b[0m\n", "| 1 | 0.0551 | 1 | 0.001471 | 11.072773 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 2 | 0.1102 | 1 | 0.001529 | 11.094774 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 3 | 0.1653 | 1 | 0.001487 | 11.017530 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 4 | 0.2204 | 1 | 0.001487 | 11.053551 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 5 | 0.2755 | 1 | 0.001495 | 10.972424 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 6 | 0.3306 | 1 | 0.001496 | 10.951425 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 7 | 0.3857 | 1 | 0.001492 | 11.018625 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 8 | 0.4408 | 1 | 0.001476 | 11.044770 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 9 | 0.4959 | 1 | 0.001478 | 11.122489 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 10 | 0.5510 | 1 | 0.001500 | 10.997606 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 11 | 0.6061 | 1 | 0.001481 | 11.076450 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 12 | 0.6612 | 1 | 0.001513 | 10.976762 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 13 | 0.7163 | 1 | 0.001489 | 11.056770 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 14 | 0.7713 | 1 | 0.001473 | 11.147662 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 15 | 0.8264 | 1 | 0.001485 | 11.047932 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 16 | 0.8815 | 1 | 0.001500 | 10.984375 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 17 | 0.9366 | 1 | 0.001492 | 10.999334 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 18 | 0.9917 | 1 | 0.001492 | 11.036468 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 19 | 1.0468 | 1 | 0.001490 | 10.989860 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 20 | 1.1019 | 1 | 0.001487 | 10.981977 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 21 | 1.1570 | 1 | 0.001480 | 11.026050 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 22 | 1.2121 | 1 | 0.001501 | 10.964416 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 23 | 1.2672 | 1 | 0.001483 | 11.061231 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 24 | 1.3223 | 1 | 0.001502 | 11.026285 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 25 | 1.3774 | 1 | 0.001497 | 10.937541 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 26 | 1.4325 | 1 | 0.001464 | 11.216671 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 27 | 1.4876 | 1 | 0.001489 | 11.077631 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 28 | 1.5427 | 1 | 0.001487 | 11.029765 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 29 | 1.5978 | 1 | 0.001484 | 11.057365 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 30 | 1.6529 | 1 | 0.001499 | 10.988523 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 31 | 1.7080 | 1 | 0.001480 | 11.082585 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 32 | 1.7631 | 1 | 0.001493 | 11.026814 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 33 | 1.8182 | 1 | 0.001500 | 10.964227 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 34 | 1.8733 | 1 | 0.001496 | 11.070894 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 35 | 1.9284 | 1 | 0.001499 | 10.991359 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 36 | 1.9835 | 1 | 0.001493 | 10.956692 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 37 | 2.0386 | 1 | 0.001489 | 11.022953 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 38 | 2.0937 | 1 | 0.001493 | 11.002933 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 39 | 2.1488 | 1 | 0.001494 | 10.971854 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 40 | 2.2039 | 1 | 0.001499 | 10.956611 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 41 | 2.2590 | 1 | 0.001484 | 11.020658 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 42 | 2.3140 | 1 | 0.001484 | 11.016274 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 43 | 2.3691 | 1 | 0.001490 | 11.171444 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 44 | 2.4242 | 1 | 0.001496 | 10.975332 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 45 | 2.4793 | 1 | 0.001479 | 11.094840 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 46 | 2.5344 | 1 | 0.001484 | 11.060059 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 47 | 2.5895 | 1 | 0.001329 | 12.309849 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 48 | 2.6446 | 1 | 0.001455 | 11.269876 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 49 | 2.6997 | 1 | 0.001489 | 11.076657 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 50 | 2.7548 | 1 | 0.001496 | 10.998456 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 51 | 2.8099 | 1 | 0.001471 | 11.215534 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 52 | 2.8650 | 1 | 0.001494 | 10.981076 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 53 | 2.9201 | 1 | 0.001476 | 11.089418 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 54 | 2.9752 | 1 | 0.001482 | 10.973016 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 55 | 3.0303 | 1 | 0.001469 | 11.115966 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 56 | 3.0854 | 1 | 0.001458 | 11.172810 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 57 | 3.1405 | 1 | 0.001496 | 10.984580 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 58 | 3.1956 | 1 | 0.001451 | 11.196615 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 59 | 3.2507 | 1 | 0.001498 | 10.961545 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 60 | 3.3058 | 1 | 0.001492 | 11.053819 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 61 | 3.3609 | 1 | 0.001492 | 11.066293 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 62 | 3.4160 | 1 | 0.001489 | 11.025009 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 63 | 3.4711 | 1 | 0.001485 | 10.999920 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 64 | 3.5262 | 1 | 0.001505 | 10.973439 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 65 | 3.5813 | 1 | 0.001495 | 10.981763 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 66 | 3.6364 | 1 | 0.001481 | 10.997240 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 67 | 3.6915 | 1 | 0.001489 | 11.030007 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 68 | 3.7466 | 1 | 0.001487 | 10.977346 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 69 | 3.8017 | 1 | 0.001490 | 11.086072 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 70 | 3.8567 | 1 | 0.001478 | 11.027897 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 71 | 3.9118 | 1 | 0.001492 | 10.997195 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 72 | 3.9669 | 1 | 0.001476 | 11.129629 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 73 | 4.0220 | 1 | 0.001491 | 10.996616 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 74 | 4.0771 | 1 | 0.001513 | 10.996785 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 75 | 4.1322 | 1 | 0.001487 | 11.010984 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 76 | 4.1873 | 1 | 0.001504 | 10.960333 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 77 | 4.2424 | 1 | 0.001483 | 11.062480 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 78 | 4.2975 | 1 | 0.001502 | 10.999189 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 79 | 4.3526 | 1 | 0.001504 | 10.989805 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 80 | 4.4077 | 1 | 0.001499 | 10.986292 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 81 | 4.4628 | 1 | 0.001471 | 11.064248 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 82 | 4.5179 | 1 | 0.001478 | 11.030763 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 83 | 4.5730 | 1 | 0.001423 | 11.418318 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 84 | 4.6281 | 1 | 0.001513 | 10.934981 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 85 | 4.6832 | 1 | 0.001477 | 10.947891 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 86 | 4.7383 | 1 | 0.001449 | 11.337449 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 87 | 4.7934 | 1 | 0.001446 | 11.356113 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 88 | 4.8485 | 1 | 0.001493 | 11.006986 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 89 | 4.9036 | 1 | 0.001508 | 10.995290 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 90 | 4.9587 | 1 | 0.001519 | 10.961908 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 91 | 5.0138 | 1 | 0.001511 | 11.004637 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 92 | 5.0689 | 1 | 0.001493 | 11.060139 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 93 | 5.1240 | 1 | 0.001475 | 11.108975 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 94 | 5.1791 | 1 | 0.001501 | 10.946505 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 95 | 5.2342 | 1 | 0.001504 | 10.936386 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 96 | 5.2893 | 1 | 0.001486 | 10.994701 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 97 | 5.3444 | 1 | 0.001483 | 11.023811 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 98 | 5.3994 | 1 | 0.001494 | 10.979126 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 99 | 5.4545 | 1 | 0.001481 | 11.163518 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 100 | 5.5096 | 1 | 0.001479 | 11.047313 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 101 | 5.5647 | 1 | 0.001473 | 11.060119 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 102 | 5.6198 | 1 | 0.001486 | 11.053105 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 103 | 5.6749 | 1 | 0.001486 | 11.092742 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 104 | 5.7300 | 1 | 0.001477 | 11.137425 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 105 | 5.7851 | 1 | 0.001491 | 11.010371 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 106 | 5.8402 | 1 | 0.001496 | 10.990369 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 107 | 5.8953 | 1 | 0.001473 | 11.243003 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 108 | 5.9504 | 1 | 0.001388 | 11.929604 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 109 | 6.0055 | 1 | 0.001458 | 11.222341 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 110 | 6.0606 | 1 | 0.001499 | 11.021925 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 111 | 6.1157 | 1 | 0.001489 | 10.988726 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 112 | 6.1708 | 1 | 0.001464 | 11.127002 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 113 | 6.2259 | 1 | 0.001490 | 10.982098 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 114 | 6.2810 | 1 | 0.001490 | 11.058744 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 115 | 6.3361 | 1 | 0.001501 | 11.036917 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 116 | 6.3912 | 1 | 0.001487 | 11.093127 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 117 | 6.4463 | 1 | 0.001511 | 10.986041 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 118 | 6.5014 | 1 | 0.001473 | 11.067499 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 119 | 6.5565 | 1 | 0.001495 | 10.978576 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 120 | 6.6116 | 1 | 0.001497 | 11.027401 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 121 | 6.6667 | 1 | 0.001487 | 11.056618 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 122 | 6.7218 | 1 | 0.001492 | 10.946566 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 123 | 6.7769 | 1 | 0.001492 | 10.977270 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 124 | 6.8320 | 1 | 0.001466 | 11.212596 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 125 | 6.8871 | 1 | 0.001493 | 10.996106 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 126 | 6.9421 | 1 | 0.001480 | 11.018607 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 127 | 6.9972 | 1 | 0.001509 | 10.976320 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 128 | 7.0523 | 1 | 0.001329 | 12.417034 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 129 | 7.1074 | 1 | 0.001510 | 10.938081 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 130 | 7.1625 | 1 | 0.001496 | 10.964072 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 131 | 7.2176 | 1 | 0.001497 | 11.006588 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 132 | 7.2727 | 1 | 0.001493 | 11.018465 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 133 | 7.3278 | 1 | 0.001493 | 11.030553 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 134 | 7.3829 | 1 | 0.001490 | 11.034491 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 135 | 7.4380 | 1 | 0.001493 | 10.942718 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 136 | 7.4931 | 1 | 0.001485 | 11.063141 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 137 | 7.5482 | 1 | 0.001490 | 10.940797 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 138 | 7.6033 | 1 | 0.001496 | 11.094267 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 139 | 7.6584 | 1 | 0.001476 | 11.092305 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 140 | 7.7135 | 1 | 0.001489 | 10.999871 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 141 | 7.7686 | 1 | 0.001486 | 10.969563 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 142 | 7.8237 | 1 | 0.001485 | 11.045270 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 143 | 7.8788 | 1 | 0.001493 | 10.999192 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 144 | 7.9339 | 1 | 0.001498 | 11.051950 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 145 | 7.9890 | 1 | 0.001489 | 11.134410 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 146 | 8.0441 | 1 | 0.001474 | 11.101941 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 147 | 8.0992 | 1 | 0.001480 | 11.063681 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 148 | 8.1543 | 1 | 0.001486 | 10.946801 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 149 | 8.2094 | 1 | 0.001484 | 11.076774 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 150 | 8.2645 | 1 | 0.001480 | 11.143664 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 151 | 8.3196 | 1 | 0.001509 | 10.947056 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 152 | 8.3747 | 1 | 0.001447 | 11.226169 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 153 | 8.4298 | 1 | 0.001479 | 11.063754 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 154 | 8.4848 | 1 | 0.001484 | 10.961506 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 155 | 8.5399 | 1 | 0.001497 | 11.050401 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 156 | 8.5950 | 1 | 0.001503 | 11.018204 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 157 | 8.6501 | 1 | 0.001472 | 11.145519 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 158 | 8.7052 | 1 | 0.001486 | 11.037257 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 159 | 8.7603 | 1 | 0.001492 | 11.036405 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 160 | 8.8154 | 1 | 0.001480 | 11.015381 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 161 | 8.8705 | 1 | 0.001498 | 10.979052 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 162 | 8.9256 | 1 | 0.001496 | 11.051611 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 163 | 8.9807 | 1 | 0.001486 | 11.052557 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 164 | 9.0358 | 1 | 0.001491 | 11.034617 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 165 | 9.0909 | 1 | 0.001488 | 11.049273 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 166 | 9.1460 | 1 | 0.001491 | 11.085268 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 167 | 9.2011 | 1 | 0.001503 | 10.958939 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 168 | 9.2562 | 1 | 0.001497 | 11.062020 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 169 | 9.3113 | 1 | 0.001518 | 10.996845 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 170 | 9.3664 | 1 | 0.001470 | 11.209611 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 171 | 9.4215 | 1 | 0.001477 | 11.023668 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 172 | 9.4766 | 1 | 0.001481 | 11.090508 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 173 | 9.5317 | 1 | 0.001512 | 10.958598 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 174 | 9.5868 | 1 | 0.001472 | 11.093911 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 175 | 9.6419 | 1 | 0.001495 | 11.022321 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 176 | 9.6970 | 1 | 0.001497 | 10.963686 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 177 | 9.7521 | 1 | 0.001495 | 11.014677 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 178 | 9.8072 | 1 | 0.001494 | 10.971079 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 179 | 9.8623 | 1 | 0.001478 | 11.075295 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 180 | 9.9174 | 1 | 0.001492 | 10.967837 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 181 | 9.9725 | 1 | 0.001502 | 11.002055 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 182 |10.0275 | 1 | 0.001478 | 11.256881 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 183 |10.0826 | 1 | 0.001503 | 10.965836 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 184 |10.1377 | 1 | 0.001492 | 11.003513 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 185 |10.1928 | 1 | 0.001499 | 11.033890 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 186 |10.2479 | 1 | 0.001477 | 11.086396 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 187 |10.3030 | 1 | 0.001494 | 11.022237 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 188 |10.3581 | 1 | 0.001488 | 11.065405 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 189 |10.4132 | 1 | 0.001479 | 11.172747 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 190 |10.4683 | 1 | 0.001488 | 11.135500 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 191 |10.5234 | 1 | 0.001485 | 10.995766 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 192 |10.5785 | 1 | 0.001494 | 10.953639 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 193 |10.6336 | 1 | 0.001492 | 11.006385 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 194 |10.6887 | 1 | 0.001384 | 11.898180 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 195 |10.7438 | 1 | 0.001505 | 10.971906 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 196 |10.7989 | 1 | 0.001506 | 10.982670 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 197 |10.8540 | 1 | 0.001324 | 12.325860 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 198 |10.9091 | 1 | 0.001502 | 11.026861 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 199 |10.9642 | 1 | 0.001505 | 10.987199 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 200 |11.0193 | 1 | 0.001488 | 11.061289 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 201 |11.0744 | 1 | 0.001489 | 11.049199 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 202 |11.1295 | 1 | 0.001501 | 10.973437 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 203 |11.1846 | 1 | 0.001500 | 10.937448 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 204 |11.2397 | 1 | 0.001499 | 10.940185 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 205 |11.2948 | 1 | 0.001506 | 10.956871 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 206 |11.3499 | 1 | 0.001496 | 10.959225 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 207 |11.4050 | 1 | 0.001380 | 11.911324 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 208 |11.4601 | 1 | 0.001505 | 10.983367 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 209 |11.5152 | 1 | 0.001483 | 11.116040 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 210 |11.5702 | 1 | 0.001499 | 10.946843 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 211 |11.6253 | 1 | 0.001495 | 11.007702 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 212 |11.6804 | 1 | 0.001484 | 11.036537 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 213 |11.7355 | 1 | 0.001501 | 10.962850 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 214 |11.7906 | 1 | 0.001458 | 11.231765 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 215 |11.8457 | 1 | 0.001500 | 10.974388 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 216 |11.9008 | 1 | 0.001502 | 10.971881 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 217 |11.9559 | 1 | 0.001484 | 11.094227 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 218 |12.0110 | 1 | 0.001465 | 11.285769 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 219 |12.0661 | 1 | 0.001472 | 11.138884 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 220 |12.1212 | 1 | 0.001462 | 11.085258 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 221 |12.1763 | 1 | 0.001506 | 10.962956 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 222 |12.2314 | 1 | 0.001481 | 11.063845 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 223 |12.2865 | 1 | 0.001490 | 10.940562 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 224 |12.3416 | 1 | 0.001487 | 11.074080 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 225 |12.3967 | 1 | 0.001500 | 10.966088 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 226 |12.4518 | 1 | 0.001503 | 10.940730 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 227 |12.5069 | 1 | 0.001500 | 10.983767 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 228 |12.5620 | 1 | 0.001495 | 11.087621 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 229 |12.6171 | 1 | 0.001491 | 11.030768 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 230 |12.6722 | 1 | 0.001501 | 10.986019 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 231 |12.7273 | 1 | 0.001471 | 11.210606 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 232 |12.7824 | 1 | 0.001496 | 10.989540 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 233 |12.8375 | 1 | 0.001479 | 10.991125 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 234 |12.8926 | 1 | 0.001484 | 10.997315 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 235 |12.9477 | 1 | 0.001495 | 10.981242 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 236 |13.0028 | 1 | 0.001494 | 11.034151 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 237 |13.0579 | 1 | 0.001521 | 11.393696 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 238 |13.1129 | 1 | 0.001483 | 11.187673 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 239 |13.1680 | 1 | 0.001476 | 11.165030 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 240 |13.2231 | 1 | 0.001461 | 11.121420 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 241 |13.2782 | 1 | 0.001497 | 11.077922 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 242 |13.3333 | 1 | 0.001472 | 11.274707 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 243 |13.3884 | 1 | 0.001477 | 11.087600 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 244 |13.4435 | 1 | 0.001469 | 11.061153 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 245 |13.4986 | 1 | 0.001449 | 11.292755 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 246 |13.5537 | 1 | 0.001495 | 11.001245 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 247 |13.6088 | 1 | 0.001493 | 11.024726 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 248 |13.6639 | 1 | 0.001480 | 10.968597 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 249 |13.7190 | 1 | 0.001478 | 11.008988 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 250 |13.7741 | 1 | 0.001464 | 11.120604 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 251 |13.8292 | 1 | 0.001494 | 10.976703 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 252 |13.8843 | 1 | 0.001464 | 11.173856 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 253 |13.9394 | 1 | 0.001478 | 11.182188 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 254 |13.9945 | 1 | 0.001474 | 11.138903 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 255 |14.0496 | 1 | 0.001498 | 10.998538 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 256 |14.1047 | 1 | 0.001490 | 10.959735 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 257 |14.1598 | 1 | 0.001497 | 11.015843 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 258 |14.2149 | 1 | 0.001442 | 11.296236 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 259 |14.2700 | 1 | 0.001470 | 11.035043 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 260 |14.3251 | 1 | 0.001499 | 11.025067 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 261 |14.3802 | 1 | 0.001483 | 11.032883 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 262 |14.4353 | 1 | 0.001450 | 11.225693 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 263 |14.4904 | 1 | 0.001490 | 11.049877 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 264 |14.5455 | 1 | 0.001500 | 10.961913 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 265 |14.6006 | 1 | 0.001468 | 11.227395 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 266 |14.6556 | 1 | 0.001500 | 10.950821 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 267 |14.7107 | 1 | 0.001496 | 11.034819 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 268 |14.7658 | 1 | 0.001478 | 11.039118 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 269 |14.8209 | 1 | 0.001480 | 11.054862 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 270 |14.8760 | 1 | 0.001499 | 10.966170 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 271 |14.9311 | 1 | 0.001492 | 11.013549 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 272 |14.9862 | 1 | 0.001487 | 11.063011 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 273 |15.0413 | 1 | 0.001494 | 11.025787 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 274 |15.0964 | 1 | 0.001492 | 11.056323 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 275 |15.1515 | 1 | 0.001502 | 11.017958 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 276 |15.2066 | 1 | 0.001498 | 10.993303 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 277 |15.2617 | 1 | 0.001492 | 11.045624 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 278 |15.3168 | 1 | 0.001488 | 11.001706 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 279 |15.3719 | 1 | 0.001501 | 11.019076 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 280 |15.4270 | 1 | 0.001482 | 10.980229 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 281 |15.4821 | 1 | 0.001500 | 10.990641 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 282 |15.5372 | 1 | 0.001482 | 11.039660 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 283 |15.5923 | 1 | 0.001506 | 10.960431 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 284 |15.6474 | 1 | 0.001483 | 11.035762 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 285 |15.7025 | 1 | 0.001483 | 10.972536 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 286 |15.7576 | 1 | 0.001497 | 11.078446 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 287 |15.8127 | 1 | 0.001494 | 11.003910 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 288 |15.8678 | 1 | 0.001524 | 10.954572 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 289 |15.9229 | 1 | 0.001468 | 11.170064 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 290 |15.9780 | 1 | 0.001466 | 11.078832 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 291 |16.0331 | 1 | 0.001500 | 10.965774 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 292 |16.0882 | 1 | 0.001492 | 10.988034 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 293 |16.1433 | 1 | 0.001498 | 11.034559 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 294 |16.1983 | 1 | 0.001485 | 10.967131 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 295 |16.2534 | 1 | 0.001480 | 11.102506 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 296 |16.3085 | 1 | 0.001504 | 10.998471 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 297 |16.3636 | 1 | 0.001499 | 10.959420 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 298 |16.4187 | 1 | 0.001483 | 11.099791 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 299 |16.4738 | 1 | 0.001493 | 10.974991 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 300 |16.5289 | 1 | 0.001487 | 10.989717 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 301 |16.5840 | 1 | 0.001484 | 11.069229 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 302 |16.6391 | 1 | 0.001503 | 10.985806 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 303 |16.6942 | 1 | 0.001496 | 11.041107 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 304 |16.7493 | 1 | 0.001487 | 10.991144 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 305 |16.8044 | 1 | 0.001492 | 11.005035 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 306 |16.8595 | 1 | 0.001488 | 11.087163 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 307 |16.9146 | 1 | 0.001491 | 11.002862 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 308 |16.9697 | 1 | 0.001519 | 11.018252 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 309 |17.0248 | 1 | 0.001489 | 11.030986 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 310 |17.0799 | 1 | 0.001509 | 11.001859 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 311 |17.1350 | 1 | 0.001492 | 11.019300 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 312 |17.1901 | 1 | 0.001488 | 11.036436 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 313 |17.2452 | 1 | 0.001493 | 11.046922 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 314 |17.3003 | 1 | 0.001485 | 10.976898 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 315 |17.3554 | 1 | 0.001497 | 10.941875 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 316 |17.4105 | 1 | 0.001497 | 11.056621 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 317 |17.4656 | 1 | 0.001471 | 11.117604 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 318 |17.5207 | 1 | 0.001469 | 11.168160 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 319 |17.5758 | 1 | 0.001476 | 11.020628 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 320 |17.6309 | 1 | 0.001490 | 11.020211 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 321 |17.6860 | 1 | 0.001488 | 11.010496 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 322 |17.7410 | 1 | 0.001475 | 11.125369 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 323 |17.7961 | 1 | 0.001494 | 10.955690 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 324 |17.8512 | 1 | 0.001493 | 11.078510 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 325 |17.9063 | 1 | 0.001362 | 12.022863 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 326 |17.9614 | 1 | 0.001489 | 11.021870 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 327 |18.0165 | 1 | 0.001463 | 11.139775 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 328 |18.0716 | 1 | 0.001475 | 11.015395 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 329 |18.1267 | 1 | 0.001475 | 10.995628 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 330 |18.1818 | 1 | 0.001490 | 11.037015 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 331 |18.2369 | 1 | 0.001489 | 10.994272 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 332 |18.2920 | 1 | 0.001485 | 11.023925 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 333 |18.3471 | 1 | 0.001465 | 11.131615 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 334 |18.4022 | 1 | 0.001481 | 10.996215 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 335 |18.4573 | 1 | 0.001493 | 11.042847 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 336 |18.5124 | 1 | 0.001493 | 11.153712 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 337 |18.5675 | 1 | 0.001497 | 10.981005 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 338 |18.6226 | 1 | 0.001490 | 10.993124 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 339 |18.6777 | 1 | 0.001498 | 10.970379 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 340 |18.7328 | 1 | 0.001492 | 10.992345 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 341 |18.7879 | 1 | 0.001492 | 10.998224 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 342 |18.8430 | 1 | 0.001523 | 10.982035 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 343 |18.8981 | 1 | 0.001485 | 11.054384 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 344 |18.9532 | 1 | 0.001486 | 11.027520 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 345 |19.0083 | 1 | 0.001482 | 11.016985 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 346 |19.0634 | 1 | 0.001484 | 11.169659 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 347 |19.1185 | 1 | 0.001476 | 11.164135 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 348 |19.1736 | 1 | 0.001499 | 10.963815 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 349 |19.2287 | 1 | 0.001481 | 11.041115 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 350 |19.2837 | 1 | 0.001478 | 11.025983 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 351 |19.3388 | 1 | 0.001504 | 10.984140 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 352 |19.3939 | 1 | 0.001478 | 11.033161 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 353 |19.4490 | 1 | 0.001490 | 11.040692 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 354 |19.5041 | 1 | 0.001479 | 11.018249 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 355 |19.5592 | 1 | 0.001471 | 10.995813 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 356 |19.6143 | 1 | 0.001478 | 11.009744 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 357 |19.6694 | 1 | 0.001489 | 11.012060 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 358 |19.7245 | 1 | 0.001487 | 11.043458 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 359 |19.7796 | 1 | 0.001492 | 10.996924 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 360 |19.8347 | 1 | 0.001486 | 10.997914 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 361 |19.8898 | 1 | 0.001462 | 11.193126 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 362 |19.9449 | 1 | 0.001476 | 11.131790 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 363 |20.0000 | 1 | 0.001456 | 11.255347 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 364 |20.0551 | 1 | 0.001443 | 11.262457 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 365 |20.1102 | 1 | 0.001442 | 11.282331 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 366 |20.1653 | 1 | 0.001460 | 11.133270 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 367 |20.2204 | 1 | 0.001461 | 11.137590 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 368 |20.2755 | 1 | 0.001458 | 11.279287 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 369 |20.3306 | 1 | 0.001473 | 11.155934 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 370 |20.3857 | 1 | 0.001480 | 11.140440 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 371 |20.4408 | 1 | 0.001464 | 11.196868 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 372 |20.4959 | 1 | 0.001473 | 11.196430 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 373 |20.5510 | 1 | 0.001467 | 11.166742 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 374 |20.6061 | 1 | 0.001488 | 11.095840 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 375 |20.6612 | 1 | 0.001456 | 11.119288 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 376 |20.7163 | 1 | 0.001514 | 10.997535 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 377 |20.7713 | 1 | 0.001500 | 10.973636 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 378 |20.8264 | 1 | 0.001485 | 11.041631 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 379 |20.8815 | 1 | 0.001474 | 11.054704 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 380 |20.9366 | 1 | 0.001505 | 10.956344 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 381 |20.9917 | 1 | 0.001485 | 11.037928 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 382 |21.0468 | 1 | 0.001494 | 11.004072 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 383 |21.1019 | 1 | 0.001460 | 11.191711 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 384 |21.1570 | 1 | 0.001464 | 11.213192 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 385 |21.2121 | 1 | 0.001297 | 12.715977 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 386 |21.2672 | 1 | 0.001473 | 11.214244 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 387 |21.3223 | 1 | 0.001488 | 11.175076 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 388 |21.3774 | 1 | 0.001477 | 11.140157 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 389 |21.4325 | 1 | 0.001476 | 11.217773 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 390 |21.4876 | 1 | 0.001487 | 11.070076 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 391 |21.5427 | 1 | 0.001476 | 11.079977 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 392 |21.5978 | 1 | 0.001497 | 10.963420 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 393 |21.6529 | 1 | 0.001492 | 10.965270 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 394 |21.7080 | 1 | 0.001499 | 11.031488 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 395 |21.7631 | 1 | 0.001379 | 11.928026 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 396 |21.8182 | 1 | 0.001474 | 11.124487 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 397 |21.8733 | 1 | 0.001482 | 11.037149 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 398 |21.9284 | 1 | 0.001491 | 11.062619 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 399 |21.9835 | 1 | 0.001483 | 11.227740 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 400 |22.0386 | 1 | 0.001460 | 11.253708 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 401 |22.0937 | 1 | 0.001474 | 11.210907 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 402 |22.1488 | 1 | 0.001483 | 11.119041 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 403 |22.2039 | 1 | 0.001490 | 11.014906 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 404 |22.2590 | 1 | 0.001499 | 10.956471 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 405 |22.3140 | 1 | 0.001468 | 11.109550 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 406 |22.3691 | 1 | 0.001490 | 10.982853 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 407 |22.4242 | 1 | 0.001478 | 11.193154 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 408 |22.4793 | 1 | 0.001498 | 11.031459 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 409 |22.5344 | 1 | 0.001508 | 10.978516 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 410 |22.5895 | 1 | 0.001481 | 11.028180 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 411 |22.6446 | 1 | 0.001492 | 11.037275 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 412 |22.6997 | 1 | 0.001483 | 10.967960 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 413 |22.7548 | 1 | 0.001493 | 10.990911 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 414 |22.8099 | 1 | 0.001484 | 11.067482 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 415 |22.8650 | 1 | 0.001486 | 10.987234 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 416 |22.9201 | 1 | 0.001453 | 11.105183 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 417 |22.9752 | 1 | 0.001484 | 11.019325 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 418 |23.0303 | 1 | 0.001489 | 11.033711 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 419 |23.0854 | 1 | 0.001486 | 10.961752 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 420 |23.1405 | 1 | 0.001488 | 11.020402 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 421 |23.1956 | 1 | 0.001485 | 10.985927 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 422 |23.2507 | 1 | 0.001484 | 11.122986 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 423 |23.3058 | 1 | 0.001488 | 11.010117 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 424 |23.3609 | 1 | 0.001494 | 11.027276 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 425 |23.4160 | 1 | 0.001498 | 11.002853 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 426 |23.4711 | 1 | 0.001504 | 10.948009 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 427 |23.5262 | 1 | 0.001537 | 10.965587 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 428 |23.5813 | 1 | 0.001480 | 11.023513 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 429 |23.6364 | 1 | 0.001495 | 11.016430 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 430 |23.6915 | 1 | 0.001498 | 10.990407 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 431 |23.7466 | 1 | 0.001482 | 11.029547 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 432 |23.8017 | 1 | 0.001489 | 11.028503 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 433 |23.8567 | 1 | 0.001494 | 10.994521 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 434 |23.9118 | 1 | 0.001497 | 11.033860 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 435 |23.9669 | 1 | 0.001472 | 11.085127 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 436 |24.0220 | 1 | 0.001519 | 10.940756 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 437 |24.0771 | 1 | 0.001477 | 11.058848 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 438 |24.1322 | 1 | 0.001479 | 11.067911 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 439 |24.1873 | 1 | 0.001488 | 11.029004 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 440 |24.2424 | 1 | 0.001497 | 10.977903 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 441 |24.2975 | 1 | 0.001486 | 11.038936 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 442 |24.3526 | 1 | 0.001492 | 11.042946 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 443 |24.4077 | 1 | 0.001492 | 10.970813 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 444 |24.4628 | 1 | 0.001486 | 11.023437 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 445 |24.5179 | 1 | 0.001504 | 10.981979 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 446 |24.5730 | 1 | 0.001487 | 10.943550 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 447 |24.6281 | 1 | 0.001504 | 10.990585 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 448 |24.6832 | 1 | 0.001488 | 11.020756 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 449 |24.7383 | 1 | 0.001502 | 11.020719 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "| 450 |24.7934 | 1 | 0.001490 | 10.944653 | 0.000000 | 0.000000e+00 | 0.000000e+00 |\u001b[0m\n", "\u001b[34m...Finished\u001b[0m\n", "\u001b[36mFINISHED - Elapsed time = 5192.1956986 seconds\u001b[0m\n", "\u001b[36mFINISHED - CPU process time = 38238.0321676 seconds\u001b[0m\n" ] } ], "source": [ "sharpy_output = sharpy.sharpy_main.main(['', SimInfo.solvers['SHARPy']['route'] + SimInfo.solvers['SHARPy']['case'] + '.sharpy'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Postprocessing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This reads the structural and aerodynamic information of the last time step." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "tstep = sharpy_output.structure.timestep_info[-1]\n", "astep = sharpy_output.aero.timestep_info[-1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we separate the structure into blades:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "# Define beams\n", "ielem = 0\n", "nblades = np.max(sharpy_output.structure.beam_number) + 1\n", "nodes_blade = []\n", "first_node = 0\n", "for iblade in range(nblades):\n", " nodes_blade.append(np.zeros((sharpy_output.structure.num_node,), dtype=bool))\n", " while sharpy_output.structure.beam_number[ielem] <= iblade:\n", " ielem += 1\n", " if ielem == sharpy_output.structure.num_elem:\n", " break\n", " nodes_blade[iblade][first_node:sharpy_output.structure.connectivities[ielem-1,1]+1] = True\n", " first_node = sharpy_output.structure.connectivities[ielem-1,1]+1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Compute the radial position of the nodes and initialise the rest of the variables." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "r = []\n", "c = []\n", "dr = []\n", "forces = []\n", "CN_drR = []\n", "CTan_drR = []\n", "CP_drR = []\n", "nodes_num = []\n", "for iblade in range(nblades):\n", " forces.append(tstep.steady_applied_forces[nodes_blade[iblade]].copy())\n", "\n", " nodes_num.append(np.arange(0, sharpy_output.structure.num_node, 1)[nodes_blade[iblade]])\n", "\n", " r.append(np.linalg.norm(tstep.pos[nodes_blade[iblade], :], axis=1))\n", " dr.append(np.zeros(np.sum(nodes_blade[iblade])))\n", " dr[iblade][0] = 0.5*(r[iblade][1]-r[iblade][0])\n", " dr[iblade][-1] = 0.5 * (r[iblade][-1] - r[iblade][-2])\n", " for inode in range(1,len(r[iblade]) - 1):\n", " dr[iblade][inode] = 0.5*(r[iblade][inode+1] - r[iblade][inode-1])\n", "\n", " CN_drR.append(np.zeros(len(r[iblade])))\n", " c.append(np.zeros(len(r[iblade])))\n", " CTan_drR.append(np.zeros(len(r[iblade])))\n", " CP_drR.append(np.zeros(len(r[iblade])))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Transform the loads computed by SHARPy into out-of-plane and in-plane components:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "rho = sharpy_output.settings['StaticCoupled']['aero_solver_settings']['rho']\n", "uinf = sharpy_output.settings['StaticCoupled']['aero_solver_settings']['velocity_field_input']['u_inf']\n", "R = np.max(r[0])\n", "Cp = 0\n", "Ct = 0\n", "\n", "global_force_factor = 0.5 * rho * uinf** 2 * np.pi * R**2\n", "global_power_factor = global_force_factor*uinf\n", "for iblade in range(nblades):\n", " for inode in range(len(r[iblade])):\n", " forces[iblade][inode, 0] *= 0. # Discard the spanwise component\n", "\n", " node_global_index = nodes_num[iblade][inode]\n", " ielem = sharpy_output.structure.node_master_elem[node_global_index, 0]\n", " inode_in_elem = sharpy_output.structure.node_master_elem[node_global_index, 1]\n", " CAB = algebra.crv2rotation(tstep.psi[ielem, inode_in_elem, :])\n", "\n", " c[iblade][inode] = sharpy_output.aero.data_dict['chord'][ielem,inode_in_elem]\n", "\n", " forces_AFoR = np.dot(CAB, forces[iblade][inode, 0:3])\n", "\n", " CN_drR[iblade][inode] = forces_AFoR[2]/dr[iblade][inode]*R / global_force_factor\n", " CTan_drR[iblade][inode] = np.linalg.norm(forces_AFoR[0:2])/dr[iblade][inode]*R / global_force_factor\n", " CP_drR[iblade][inode] = np.linalg.norm(forces_AFoR[0:2])/dr[iblade][inode]*R * r[iblade][inode]*rotation_velocity / global_power_factor\n", "\n", " Cp += np.sum(CP_drR[iblade]*dr[iblade]/R)\n", " Ct += np.sum(CN_drR[iblade]*dr[iblade]/R)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Results" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Plot of the loads along the blade:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig, list_plots = plt.subplots(1, 2, figsize=(12, 3))\n", "\n", "list_plots[0].grid()\n", "list_plots[0].set_xlabel(\"r/R [-]\")\n", "list_plots[0].set_ylabel(\"CN/d(r/R) [-]\")\n", "list_plots[0].plot(r[0]/R, CN_drR[0], '-', label='SHARPy')\n", "list_plots[0].plot(of_rR, of_cNdrR, '-', label='OpenFAST')\n", "list_plots[0].legend()\n", "\n", "list_plots[1].grid()\n", "list_plots[1].set_xlabel(\"r/R [-]\")\n", "list_plots[1].set_ylabel(\"CT/d(r/R) [-]\")\n", "list_plots[1].plot(r[0]/R, CTan_drR[0], '-', label='SHARPy')\n", "list_plots[1].plot(of_rR, of_cTdrR, '-', label='OpenFAST')\n", "list_plots[1].legend()\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Print the rotor thrust and power coefficients:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " OpenFAST SHARPy\n", "Cp[-] 0.49 0.52\n", "Ct[-] 0.70 0.70\n" ] } ], "source": [ "print(\" OpenFAST SHARPy\")\n", "print(\"Cp[-] %.2f %.2f\" % (of_cp, Cp))\n", "print(\"Ct[-] %.2f %.2f\" % (of_ct, Ct))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.9" } }, "nbformat": 4, "nbformat_minor": 4 } ================================================ FILE: docs/source/content/examples.rst ================================================ Examples ======== A set of SHARPy examples created with Jupyter Notebooks is provided for users to interact and modify cases running on SHARPy. .. toctree:: :maxdepth: 1 :caption: SHARPy Examples example_notebooks/linear_goland_flutter example_notebooks/nonlinear_t-tail_HALE example_notebooks/linear_horten example_notebooks/wind_turbine example_notebooks/cantilever/static_cantilever example_notebooks/cantilever_wing example_notebooks/UDP_control/tutorial_udp_control Downloadable files ------------------ * :download:`./example_notebooks/linear_goland_flutter.ipynb` * :download:`./example_notebooks/nonlinear_t-tail_HALE.ipynb` * :download:`./example_notebooks/linear_horten.ipynb` * :download:`./example_notebooks/wind_turbine.ipynb` * :download:`./example_notebooks/cantilever_wing.ipynb` * :download:`./example_notebooks/cantilever/static_cantilever.ipynb` * :download:`./example_notebooks/UDP_control/tutorial_udp_control.ipynb` Input data for wind turbine: * :download:`./example_notebooks/source/type02_db_NREL5MW_v02.xlsx` Input data for static cantilever: * :download:`./example_notebooks/cantilever/model_static_cantilever.py` External Scripts UDP control: * :download:`./example_notebooks/UDP_control/pazy_PID_controller_UDP.py` * :download:`./example_notebooks/UDP_control/control_design_script.m` Note: some of these examples may need additional files which would be located in the ``./example_notebooks/`` directory. It is recommended that you run these cases directly from there rather than downloading. If you download, make sure you replicate the folder structure such that the examples are capable of finding the required files. ================================================ FILE: docs/source/content/faqs.md ================================================ # Frequently Asked Questions [FAQs] Over the years, we have gathered a valuable experience running SHARPy so we would like to collect here a few of the most frequently asked questions we get here to hopefully help other users. In addition to the questions listed below, do check the [issues](https://github.com/ImperialCollegeLondon/sharpy/issues?q=is%3Aissue) page in our GitHub repo as you may find useful information there. Do also check our [Short Debugging Guide](./debug.html). * __[Q] I get a `ModuleNotFound Error` when trying to run SHARPy.__ _[A]_ Make sure you have loaded the SHARPy variables using the command `source /bin_sharpy_var.sh`. * __[Q] When plotting the aerodynamic forces in Paraview from the UVLM, the forces at the boundary between two surfaces (for instance at the wing root) appears halved. Is my simulation incorrect?__ _[A]_ This is most likely not an issue with your simulation. We have observed over time that Paraview actually only plots the result from one of the surfaces, hence why it appears half of what it should be. If you extract the forces without using Paraview using the [WriteVariablesTime](https://ic-sharpy.readthedocs.io/en/master/includes/postprocs/WriteVariablesTime.html) postprocessor you will get the correct result. * __[Q] My time-domain simulation does not converge. I get a `SolverNotConverged` error. What can I do?__ _[A]_ This is quite an open question and it could be for a wide variety of reasons. Things that should be in your First Aid kit for these situations: - Is your tolerance appropriate? If you raise the tolerance to something (maybe unreasonably) high does it converge? - Is your number of iterations sufficient? Increase the number of maximum allowed iterations. - Is there anything happening at all in your simulation? We have all fallen into the trap of trying to run a time domain simulation of something that is already in steady state. I.e. you calculate its static equilibrium and then try to advance in time. Since nothing is happening the convergence criteria in the solvers may not be triggered and reach the maximum number of iterations. Solution: make sure something happens in your time domain simulation: gusts, external forces, control surface deflections... - Are you giving your simulation too much of a "kick"? Sometimes we simulate things that dramatically change the state of the problem from one time step to another (like adding very large external forces at once) which may lead to trouble. You can choose to load the forces progressively by increasing the `num_load_steps` setting in our structural solvers. Hopefully this list will grow over time with some of the common questions previous users encounter. If you cannot solve your problem please open an [issue](https://github.com/ImperialCollegeLondon/sharpy/issues) on Github and assign it the label `label:question` so we can keep track of it and others can benefit of the discussion. ================================================ FILE: docs/source/content/installation.md ================================================ # SHARPy v2.3 Installation Guide __Last revision 10 June 2024__ The following step by step tutorial will guide you through the installation process of SHARPy. This is the updated process valid from v2.3. ## Requirements __Operating System Requirements__ SHARPy is being developed and tested on the following operating systems: * CentOS 7 and CentOS 8 * Ubuntu 22.04 LTS * Debian 12 * MacOS Mojave and Catalina (Intel) * MacOS Sonoma (Apple Silicon M2) Windows users can also run it by first installing the Windows Subsystem for Linux (https://learn.microsoft.com/en-us/windows/wsl/install) and a XServer such as GWSL, which can be installed through the Microsoft Store. SHARPy is also available to the vast majority of operating systems that are supported by Docker __Required Distributions__ * Python 3.10 or higher * CMake * GCC 6.0 or higher, G++, GFortran (all included in Anaconda) * Eigen3, BLAS, MKL/LAPACK (all included in Anaconda) If the prerequisite packages are not installed and you are not using Anaconda, they can be installed as following on Linux (with a Homebrew equivelent available for Mac installs): ```bash sudo apt install -y cmake g++ gfortran libblas-dev liblapack-dev libeigen3-dev ``` __Recommended Software__ You may find the applications below useful, we recommend you use them but cannot provide any direct support. * [HDFView](https://portal.hdfgroup.org/display/HDFVIEW/HDFView) to read and view `.h5` files. HDF5 is the SHARPy input file format. * [Paraview](https://www.paraview.org/) to visualise SHARPy's output. SHARPy can be installed from the source code available on GitHub or you can get it packed in a Docker container. If what you want is to give it a go and run some static or simple dynamic cases (and are familiar with Docker), we recommend the [Docker route](#using-sharpy-from-a-docker-container). If you want to check the code, modify it and compile the libraries with custom flags, build it from source (recommended). ## Building SHARPy from source (release or development builds) SHARPy can be built from source so that you can get the latest release or (stable) development build. SHARPy depends on two external libraries, [xbeam](http://github.com/imperialcollegelondon/xbeam) and [UVLM](http://github.com/imperialcollegelondon/UVLM). These are included as submodules to SHARPy and therefore once you initialise SHARPy you will also automatically clone the relevant versions of each library. ### Set up the folder structure Clone `sharpy` in your desired location, if you agree with the license in `license.txt`. ```bash git clone --recursive http://github.com/ImperialCollegeLondon/sharpy ``` The `--recursive` flag will also initialise and update the submodules SHARPy depends on, [xbeam](http://github.com/imperialcollegelondon/xbeam) and [UVLM](http://github.com/imperialcollegelondon/UVLM). ### Quick install (Standalone) SHARPy can be installed as a standalone package, without the use of a package manager. If you wish to install using the Anaconda package manager, please use the following tutorial [HERE](#setting-up-the-python-environment-anaconda), or make a custom installation with a develop build or modified compilation settings [HERE](#custom-installation). The quick install is geared towards getting the release build of SHARPy running as quickly and simply as possible. 1. Check that your Python version is 3.10 or higher. Other versions may be incompatible with the required modules. ```bash python --version ``` 2. Move into the cloned repository: ```bash cd sharpy ``` 3. Install SHARPy. This will install any required Python packages as well as building the xbeam and UVLM libraries, and may take a few minutes. ```bash pip install --user . ``` The ```--user``` flag is included for systems without root access (often Linux) as the install will fail otherwise. This flag can be removed when a global install is required, and your machine allows it (works on Mac). There are options for what to install if required. For instance, to install the basic package with documentation, just do ```bash pip install --user .[docs]```. For the whole lot, ```bash pip install --user .[all]```. 4. You can check the version of SHARPy you are running with: ```bash sharpy --version ``` __You are ready to run SHARPy__. Continue reading the [Running SHARPy](#running-sharpy) section. ### Setting up the Python Environment (Anaconda) SHARPy can use the Anaconda package manager to provide the necessary Python packages. These are specified in an Anaconda environment that shall be activated prior to compiling the xbeam and UVLM libraries or running any SHARPy cases. 1. If you still do not have it in your system, install the [Anaconda](https://conda.io/docs/) Python 3 distribution. 2. Check that your Python version is at least 3.10: ```bash python --version ``` 3. If a specific python version is required, for example 3.10, use: ```bash conda install python=3.10 ``` 4. Create the conda environment that SHARPy will use: ```bash conda env create -f environment.yml ``` This should take approximately 5 minutes to complete (Tested on Ubuntu 22.04.1). For installation on Apple Silicon, use ```environment_arm64.yml```; this requires GCC and GFortran to be installed prior. Installation using Conda can be memory intensive, and will give the message ```Collecting package metadata (repodata.json): - Killed``` if all the available RAM is filled. From testing, 16GB of total system RAM is reliable for Conda install, whereas 8GB may have issues. Three solutions are available: * Increase available RAM (if running on a compute cluster etc) * Use [Mamba](https://mamba.readthedocs.io/en/latest/), a more efficient drop-in replacement for Conda * Create a blank conda environment and install the required packages: ```bash conda create --name sharpy python=3.10 conda config –add channels conda-forge conda install eigen libopenblas libblas libcblas liblapack libgfortran libgcc libgfortran-ng ``` 6. Activate the `sharpy` conda environment: ```bash conda activate sharpy ``` You should now see ```(sharpy)``` on your command line. ### Quick install (Anaconda) The quick install is geared towards getting the release build of SHARPy running as quickly and simply as possible. 1. Move into the cloned repository: ```bash cd sharpy ``` 2. Ensure that the SHARPy environment is active in the session. Your terminal prompt line should begin with: ```bash (sharpy) [usr@host] $ ``` 3. Install SHARPy. This will install any required Python packages as well as building the xbeam and UVLM libraries, and may take a few minutes. ```bash pip install --user . ``` The ```--user``` flag is included for systems without root access (often Linux) as the install will fail otherwise. This flag can be removed when a global install is required, and your machine allows it (works on Mac). There are options for what to install if required. For instance, to install the basic package with documentation, just do ```bash pip install --user .[docs]```. For the whole lot, ```bash pip install --user .[all]```. 4. You can check the version of SHARPy you are running with: ```bash sharpy --version ``` If running SHARPy from Anaconda, you need to load the conda environment. Therefore, __before you run any SHARPy case or test__, activate the SHARPy conda environment: ```bash conda activate sharpy ``` __You are ready to run SHARPy__. Continue reading the [Running SHARPy](#running-sharpy) section. ### Custom installation These steps will show you how to compile the xbeam and UVLM libraries such that you can modify the compilation settings to your taste. This is compatible with both standalone and Anaconda installations. 1. If you want to use SHARPy's latest release, skip this step. If you would like to use the latest development work, you will need to checkout the `develop` branch. For more info on how we structure our development and what branches are used for what kind of features have a look at the [Contributing](contributing.html) page. ```bash git checkout develop git submodule update --recursive ``` This command will check out the `develop` branch and set it to track the remote origin. It will also set the submodules (xbeam and UVLM) to the right commit. 2. If using Anaconda, create the conda environment that SHARPy will use and activate the environment: ```bash conda env create -f environment.yml ``` ```bash conda activate sharpy ``` 4. Create a directory `build` that will be used during CMake's building process and `cd` into it: ```bash mkdir build cd build ``` 5. Run CMake with custom flags: 1. Choose your compilers for Fortran `FC` and C++ `CXX`, for instance: ```bash FC=gfortran CXX=g++ cmake .. ``` If you'd like to use the Intel compilers you can set them using: ```bash FC=ifort CXX=icpc cmake .. ``` 2. Alternatively, you can build the libraries in debug mode with the following flag for cmake: ```bash -DCMAKE_BUILD_TYPE=Debug ``` 6. Compile the libraries and parallelise as you prefer. ```bash make install -j 4 ``` 7. Finally, leave the build directory and install SHARPy. ```bash cd .. pip install . ``` If you want to install it in development mode (the source files will stay where the are so you can modify them), you can make an editable install: ``` pip install -e . ``` You can obtain further information on editable installs [here](https://pip.pypa.io/en/stable/cli/pip_install/#editable-installs) 8. This concludes the installation! Continue reading the [Running SHARPy](#running-sharpy) section. ## Using SHARPy from a Docker container > **Tip** To install the Python environment, miniconda needs approximatelly 16GB of > memory. It is recommended to have at least that amount available for the > container in which you are building SHARPy. Docker containers are similar to lightweight virtual machines. The SHARPy container distributed through [Docker Hub](https://hub.docker.com/) is a CentOS 8 machine with the libraries compiled with `gfortran` and `g++` and an Anaconda Python distribution. Make sure your machine has Docker working. The instructions are here: [link](https://docs.docker.com/v17.09/engine/installation/). You might want to run a test in your terminal: ``` docker pull hello-world docker run hello-world ``` If this works, you're good to go! First, obtain the SHARPy docker container: ``` docker pull ghcr.io/imperialcollegelondon/sharpy:main ``` You can obtain other versions as well, check those available in the [containers](https://github.com/ImperialCollegeLondon/sharpy/pkgs/container/sharpy) page. This will donwload a Docker image of SHARPy to your machine, from where you can create and run Docker containers. To create and run a container from the downloaded image use: ``` docker run --name sharpy -it -p 8080:8080 ghcr.io/imperialcollegelondon/sharpy:main ``` A few details about the above command, although if in doubt please check the Docker documentation. The `--name` argument gives a name to the container. Note you can create multiple containers from a single image. The `-it` is an important command as it runs the container in interactive mode with a terminal attached. Thus you can use it an navigate it. Otherwise the container will finish as soon as it is created. The `-p 8080:8080` argument connects the container to your machine through port `8080` (it could be any other) which may be useful for some applications. For instance, running SHARPy as hardware-in-the-loop through UDP. Once you run it, you should see a welcome dialog such as: ``` >>>> docker run --name sharpy -it -p 8080:8080 ghcr.io/imperialcollegelondon/sharpy:main SHARPy added to PATH from the directory: /sharpy_dir/bin ======================================================================= Welcome to the Docker image of SHARPy SHARPy is located in /sharpy_dir/ and the environment is already set up! Copyright Imperial College London. Released under BSD 3-Clause license. ======================================================================= SHARPy> ``` You are now good to go. You can check the version of SHARPy you are running with ``` sharpy --version ``` It is important to note that a docker container runs as an independent operating system with no access to your hard drive. If you want to copy your own files, run the container and from another terminal run: ``` docker cp my_file.txt sharpy:/my_file.txt # copy from host to container docker cp sharpy:/my_file.txt my_file.txt # copy from container to host ``` The `sharpy:` part is the `--name` argument you wrote in the `docker run` command. **Enjoy!** ## Testing with Docker You can run the test suite once inside the container as: ``` cd sharpy_dir python -m unittest ``` **Enjoy!** ## Obtain SHARPy from PyPI (experimental!) You can obtain a built version of SHARPy, ic-sharpy, from PyPI [here](https://pypi.org/project/ic-sharpy/). To install at default directory use ``` python3 -m pip install ic-sharpy ``` To install at current directory use ``` python3 -m pip install --prefix . ic-sharpy ``` The source code can be found at `/lib/python3.10/site-packages/sharpy` and the executable at `/bin/sharpy`. ## Running SHARPy ### Automated tests SHARPy uses unittests to verify the integrity of the code. These tests can be run from the `./sharpy` directory. ```bash python -m unittest ``` The tests will run and you should see a success message. If you don't... check the following options: * Check you are running the latest version. Running the following from the root directory should update to the latest release version: - `git pull` - `git submodule update --init --recursive` * If the tests don't run, make sure you have followed correctly the instructions and that you managed to compile xbeam and UVLM. * If some tests fail, i.e. you get a message after the tests run saying that certain tests did not pass, please open an [issue](http://www.github.com/imperialcollegelondon/sharpy/issues) with the following information: - Operating system - Whether you did a Custom/quick install - UVLM and xbeam compiler of choice - A log of the tests that failed ### The SHARPy Case Structure and input files __Setting up a SHARPy case__ SHARPy cases are usually structured in the following way: 1. A `generate_case.py` file: contains the setup of the problem like geometry, flight conditions etc. This script creates the output files that will then be used by SHARPy, namely: * The [structural](./casefiles.html#fem-file) `.fem.h5` file. * The [aerodynamic](./casefiles.html#aerodynamics-file) `.aero.h5` file. * [Simulation information](./casefiles.html#solver-configuration-file) and settings `.sharpy` file. * The dynamic forces file `.dyn.h5` (when required). * The linear input files `.lininput.h5` (when required). * The ROM settings file `.rom.h5` (when required). See the [chapter](./casefiles.html) on the case files for a detailed description on the contents of each one. Data is exchanged in binary format by means of `.h5` files that make the transmission efficient between the different languages of the required libraries. To view these `.h5` files, a viewer like [HDF5](https://portal.hdfgroup.org/display/support) is recommended. 2. The `h5` files contain data of the FEM, aerodynamics, dynamic conditions. They are later read by SHARPy. 3. The `.sharpy` file contains the settings for SHARPy and is the file that is parsed to SHARPy. __To run a SHARPy case__ SHARPy cases are therefore usually ran in the following way: 1. Create a `generate_case.py` file following the provided templates. 2. Run it to produce the `.h5` files and the `.sharpy` files. ```bash python generate_case.py ``` 3. Run SHARPy (ensure the environment is activated). ```bash sharpy case.sharpy ``` #### Output By default, the output is located in the `output` folder. The contents of the folder will typically be a `beam` and `aero` folders, which contain the output data that can then be loaded in Paraview. These are the `.vtu` format files that can be used with [Paraview](https://www.paraview.org/). ### Running (and modifiying) a test case 1. This command generates the required files for running a static, clamped beam case that is used as part of code verification: ```sh cd ../sharpy python ./tests/xbeam/geradin/generate_geradin.py ``` Now you should see a success message, and if you check the `./tests/xbeam/geradin/` folder, you should see two new files: + geradin_cardona.sharpy + geradin_cardona.fem.h5 Try to open the `sharpy` file with a plain text editor and have a quick look. The `sharpy` file is the main settings file. We'll get deeper into this later. If you try to open the `fem.h5` file, you'll get an error or something meaningless. This is because the structural data is stored in [HDF5](https://support.hdfgroup.org/HDF5/) format, which is compressed binary. 5. Run it (part 1) The `sharpy` call is: ```bash sharpy ``` 6. Results (part 1) Since this is a test case, there is no output directly to screen. We will therefore change this setting first. In the `generate_geradin.py` file, look for the `SHARPy` setting `write_screen` and set it to `on`. This will output the progress of the execution to the terminal. We would also like to create a Paraview file to view the beam deformation. Append the post-processor `BeamPlot` to the end of the `SHARPy` setting `flow`, which is a list. This will run the post-processor and plot the beam in Paraview format with the settings specified in the `generate_geradin.py` file under `config['BeamPlot]`. 7. Run (part 2) Now that we have made these modifications, run again the generation script: ```sh python ./tests/xbeam/geradin/generate_geradin.py ``` Check the solver file `geradin.sharpy` and look for the settings we just changed. Make sure they read what we wanted. You are now ready to run the case again: ```bash sharpy ``` 8. Post-processing After a successful execution, you should a long display of information in the terminal as the case is being executed. The deformed beam will have been written in a `.vtu` file and will be located in the `output/` folder (or where you specified in the settings) which you can open using Paraview. In the `output` directory you will also note a folder named `WriteVariablesTime` which outputs certain variables as a function of time to a `.dat` file. In this case, the beam tip position deflection and rotation is written. Check the values of those files and look for the following result: ``` Pos_def: 4.403530 0.000000 -2.159692 Psi_def: 0.000000 0.672006 0.000000 ``` FYI, the correct solution for this test case by Geradin and Cardona is `Delta R_3 = -2.159 m` and `Psi_2 = 0.6720 rad`. Congratulations, you've run your first case. You can now check the [Examples](examples.html) section for further cases. ================================================ FILE: docs/source/content/postproc.rst ================================================ Post-Processing --------------- .. toctree:: :glob: ../includes/postprocs/* ================================================ FILE: docs/source/content/publications.md ================================================ # Publications SHARPy has been used in many technical papers that have been both published in Journals and presented at conferences. Here we present a list of past papers which have used SHARPy for research purposes: ## 2022 * Goizueta, N. (2022). Parametric reduced-order aeroelastic modelling for analysis, dynamic system interpolation and control of flexible aircraft. PhD Thesis. [http://doi.org/10.25560/100137](http://doi.org/10.25560/100137) * Goizueta, N., Wynn, A., & Palacios, R. (2022). Adaptive sampling for interpolation of reduced-order aeroelastic dynamical systems. AIAA Journal. Article in Advance. [https://doi.org/10.2514/1.J062050](https://doi.org/10.2514/1.J062050) * Muñoz Simón, A. (2022). Vortex-lattice-based nonlinear aeroservoelastic modelling and analysis of large floating wind turbines. PhD Thesis. [https://doi.org/10.25560/96986](https://doi.org/10.25560/96986) * Goizueta, N., Wynn, A., Palacios, R., Drachinsky, A., & Raveh, D. E. (2022). Flutter Predictions for Very Flexible Wing Wind Tunnel Test. Journal of Aircraft: 59(4), pp. 1082-1097. [https://doi.org/10.2514/1.C036710](https://doi.org/10.2514/1.C036710) * Düssler, S., Goizueta, N., Muñoz-Simón, A., & Palacios, R. (2022). Modelling and Numerical Enhancements on a UVLM for Nonlinear Aeroelastic Simulation. AIAA SciTech Forum. [https://doi.org/10.2514/6.2022-2455](https://doi.org/10.2514/6.2022-2455) * Cea, A., Palacios, R. (2022). Parametric Reduced Order Models for Aeroelastic Design of Very Flexible Aircraft. AIAA SciTech Forum. [https://doi.org/10.2514/6.2022-0727](https://doi.org/10.2514/6.2022-0727) * Wynn, A., Artola, M., Palacios, R. (2022). Nonlinear optimal control for gust load alleviation with a physics-constrained data-driven internal model. AIAA SciTech Forum. [https://doi.org/10.2514/6.2022-0442](https://doi.org/10.2514/6.2022-0442) * Goizueta, N., Wynn, A., Palacios, R. (2022). Fast flutter evaluation of very flexible wing using interpolation on an optimal training dataset. In AIAA SciTech Forum, AIAA Paper 2022-1345. [https://doi.org/10.2514/6.2022-1345](https://doi.org/10.2514/6.2022-1345) ## 2021 * Artola, M., Goizueta, N., Wynn, A., & Palacios, R. (2021). Aeroelastic Control and Estimation with a Minimal Nonlinear Modal Description. AIAA Journal: 59(7), pp. 2697–2713. [https://doi.org/10.2514/1.j060018](https://doi.org/10.2514/1.j060018) * Artola, M., Goizueta, N., Wynn, A., & Palacios, R. (2021). Proof of Concept for a Hardware-in-the-Loop Nonlinear. In AIAA SciTech Forum (pp. 1–26). [https://doi.org/10.2514/6.2021-1392](https://doi.org/10.2514/6.2021-1392) * Goizueta, N., Drachinsky, A., Wynn, A., Raveh, D. E., & Palacios, R. (2021). Flutter prediction for a very flexible wing wind tunnel test. In AIAA SciTech Forum (pp. 1–17). [https://doi.org/10.2514/6.2021-1711](https://doi.org/10.2514/6.2021-1711) * Goizueta, N., Wynn, A., & Palacios, R. (2021). Parametric Krylov-based order reduction of aircraft aeroelastic models. In AIAA SciTech Forum (pp. 1–25). [https://doi.org/10.2514/6.2021-1798](https://doi.org/10.2514/6.2021-1798) ## 2020 * del Carre, A. (2020). Aeroelasticity of very flexible aircraft at low altitudes. PhD Thesis. [https://doi.org/10.25560/88269](https://doi.org/10.25560/88269) * Muñoz-Simón, A., Palacios, R., & Wynn, A. (2020). Benchmarking different fidelities in wind turbine aerodynamics under yaw. Journal of Physics: Conference Series, 1618, 42017. [https://doi.org/10.1088/1742-6596/1618/4/042017](https://doi.org/10.1088/1742-6596/1618/4/042017) * del Carre, A., & Palacios, R. (2020). Simulation and Optimization of Takeoff Maneuvers of Very Flexible Aircraft. Journal of Aircraft: 57(6) 1097-1110. [https://doi.org/10.2514/1.C035901](https://doi.org/10.2514/1.C035901) * Maraniello, S. & Palacios, R. (2020). Parametric Reduced-Order Modeling of the Unsteady Vortex-Lattice Method. AIAA Journal, 58(5), 2206-2220. [https://doi.org/10.2514/1.J058894](https://doi.org/10.2514/1.J058894) * Deskos, G., del Carre, A., & Palacios, R. (2020). Assessment of low-altitude atmospheric turbulence models for aircraft aeroelasticity. Journal of Fluids and Structures, 95, 102981. [https://doi.org/10.1016/j.jfluidstructs.2020.102981](https://doi.org/10.1016/j.jfluidstructs.2020.102981) * Goizueta, Norberto, del Carre, Alfonso, Muñoz-Simón, Arturo, & Palacios, Rafael. (2020, February). SHARPy: from a research code to an open-source software tool for the simulation of very flexible aircraft. RSLondonSouthEast 2020 Conference. Zenodo: [http://doi.org/10.5281/zenodo.3641216](http://doi.org/10.5281/zenodo.3641216) * Del Carre, A., Deskos, G., & Palacios, R. (2020). Realistic turbulence effects in low altitude dynamics of very flexible aircraft. In AIAA SciTech Forum (pp. 1–18). [https://doi.org/10.2514/6.2020-1187](https://doi.org/10.2514/6.2020-1187) * Artola, M., Goizueta, N., Wynn, A., & Palacios, R. (2020). Modal-Based Nonlinear Estimation and Control for Highly Flexible Aeroelastic Systems. In AIAA SciTech Forum (pp. 1–23). [https://doi.org/10.2514/6.2020-1192](https://doi.org/10.2514/6.2020-1192) * Muñoz-Simón, A., Wynn, A., & Palacios, R. (2020). Unsteady and three-dimensional aerodynamic effects on wind turbine rotor loads. In AIAA SciTech Forum. [https://doi.org/10.2514/6.2020-0991](https://doi.org/10.2514/6.2020-0991) ## 2019 * del Carre, A., Muñoz-Simón, A., Goizueta, N., & Palacios, R. (2019). SHARPy : A dynamic aeroelastic simulation toolbox for very flexible aircraft and wind turbines. Journal of Open Source Software, 4(44), 1885. [https://doi.org/10.21105/joss.01885](https://doi.org/10.21105/joss.01885) * del Carre, A., Teixeira, P. C., Palacios, R., & Cesnik, C. E. S. (2019). Nonlinear Response of a Very Flexible Aircraft Under Lateral Gust. In International Forum on Aeroelasticity and Structural Dynamics. * del Carre, A., & Palacios, R. (2019). Efficient Time-Domain Simulations in Nonlinear Aeroelasticity. In AIAA Scitech Forum (pp. 1–20). [https://doi.org/10.2514/6.2019-2038](https://doi.org/10.2514/6.2019-2038) * Maraniello, S., & Palacios, R. (2019). State-Space Realizations and Internal Balancing in Potential-Flow Aerodynamics with Arbitrary Kinematics. AIAA Journal, 57(6), 2308-2321. [https://doi.org/10.2514/1.J058153](https://doi.org/10.2514/1.J058153) ================================================ FILE: docs/source/content/solvers.rst ================================================ SHARPy Solvers ------------------ The available SHARPy solvers are listed below. Attending to SHARPy's modular structure, they can be run independently so the order in which you desire to run them is important. The starting point is the PreSharpy loader. It contains the simulation configuration and which solvers are to be run and in the order that should happen. .. toctree:: :glob: ../includes/solvers/* ================================================ FILE: docs/source/content/test_cases.rst ================================================ ``SHARPy`` Test Cases --------------------- The following test cases are provided as a tutorial and introduction to ``SHARPy`` as well as for code validation purposes. * Geradin and Cardona Beam - See Installation_ and see test case in ``./sharpy/tests/xbeam/`` .. _Installation: https://ic-sharpy.readthedocs.io/en/dev_doc/content/installation.html#running-and-modifiying-a-test-case ================================================ FILE: docs/source/includes/aero/index.rst ================================================ Aerodynamic Packages -------------------- .. toctree:: :maxdepth: 1 ./models/index ./utils/index ================================================ FILE: docs/source/includes/aero/models/aerogrid/AeroTimeStepInfo.rst ================================================ AeroTimeStepInfo ---------------- .. autoclass:: sharpy.aero.models.aerogrid.AeroTimeStepInfo :members: ================================================ FILE: docs/source/includes/aero/models/aerogrid/Aerogrid.rst ================================================ Aerogrid -------- .. autoclass:: sharpy.aero.models.aerogrid.Aerogrid :members: ================================================ FILE: docs/source/includes/aero/models/aerogrid/generate_strip.rst ================================================ generate_strip -------------- .. automodule:: sharpy.aero.models.aerogrid.generate_strip ================================================ FILE: docs/source/includes/aero/models/aerogrid/index.rst ================================================ Aerogrid ++++++++ Aerogrid contains all the necessary routines to generate an aerodynamic grid based on the input dictionaries. .. toctree:: :glob: ./AeroTimeStepInfo ./Aerogrid ./generate_strip ================================================ FILE: docs/source/includes/aero/models/index.rst ================================================ Models ------ .. toctree:: :maxdepth: 1 ./aerogrid/index ================================================ FILE: docs/source/includes/aero/utils/airfoilpolars/Polar.rst ================================================ Polar ----- .. autoclass:: sharpy.aero.utils.airfoilpolars.Polar :members: ================================================ FILE: docs/source/includes/aero/utils/airfoilpolars/index.rst ================================================ .. toctree:: :glob: ./Polar ================================================ FILE: docs/source/includes/aero/utils/index.rst ================================================ Aerodynamic Utilities --------------------- .. toctree:: :maxdepth: 1 ./airfoilpolars/index ./mapping/index ================================================ FILE: docs/source/includes/aero/utils/mapping/aero2struct_force_mapping.rst ================================================ aero2struct_force_mapping ------------------------- .. automodule:: sharpy.aero.utils.mapping.aero2struct_force_mapping ================================================ FILE: docs/source/includes/aero/utils/mapping/index.rst ================================================ Force Mapping Utilities +++++++++++++++++++++++ .. toctree:: :glob: ./aero2struct_force_mapping ./total_forces_moments ================================================ FILE: docs/source/includes/aero/utils/mapping/total_forces_moments.rst ================================================ total_forces_moments -------------------- .. automodule:: sharpy.aero.utils.mapping.total_forces_moments ================================================ FILE: docs/source/includes/cases/coupled/X-HALE/generate_xhale/generate_naca_camber.rst ================================================ generate_naca_camber -------------------- .. automodule:: sharpy.cases.coupled.X-HALE.generate_xhale.generate_naca_camber ================================================ FILE: docs/source/includes/cases/coupled/X-HALE/generate_xhale/generate_solver_file.rst ================================================ generate_solver_file -------------------- .. automodule:: sharpy.cases.coupled.X-HALE.generate_xhale.generate_solver_file ================================================ FILE: docs/source/includes/cases/coupled/X-HALE/generate_xhale/index.rst ================================================ .. toctree:: :glob: ./generate_naca_camber ./generate_solver_file ./read_beam_data ================================================ FILE: docs/source/includes/cases/coupled/X-HALE/generate_xhale/read_beam_data.rst ================================================ read_beam_data -------------- .. automodule:: sharpy.cases.coupled.X-HALE.generate_xhale.read_beam_data ================================================ FILE: docs/source/includes/cases/coupled/X-HALE/index.rst ================================================ X-hale ------ .. toctree:: :maxdepth: 1 ./generate_xhale/index ================================================ FILE: docs/source/includes/cases/coupled/index.rst ================================================ Coupled ------- .. toctree:: :maxdepth: 1 ./X-HALE/index ================================================ FILE: docs/source/includes/cases/hangar/horten_wing/HortenWing.rst ================================================ HortenWing ---------- .. autoclass:: sharpy.cases.hangar.horten_wing.HortenWing :members: ================================================ FILE: docs/source/includes/cases/hangar/horten_wing/index.rst ================================================ Horten Wing Class Generator +++++++++++++++++++++++++++ Horten Wing Class Generator N Goizueta Nov 18 .. toctree:: :glob: ./HortenWing ================================================ FILE: docs/source/includes/cases/hangar/index.rst ================================================ Hangar ------ .. toctree:: :maxdepth: 1 ./horten_wing/index ./richards_wing/index ./swept_flying_wing/index ================================================ FILE: docs/source/includes/cases/hangar/richards_wing/HortenWing.rst ================================================ HortenWing ---------- .. autoclass:: sharpy.cases.hangar.richards_wing.HortenWing :members: ================================================ FILE: docs/source/includes/cases/hangar/richards_wing/index.rst ================================================ Simple Horten Wing as used by Richards. Baseline and simplified models ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Simple Horten Wing as used by Richards. Baseline and simplified models Richards, P. W., Yao, Y., Herd, R. A., Hodges, D. H., & Mardanpour, P. (2016). Effect of Inertial and Constitutive Properties on Body-Freedom Flutter for Flying Wings. Journal of Aircraft. https://doi.org/10.2514/1.C033435 .. toctree:: :glob: ./HortenWing ================================================ FILE: docs/source/includes/cases/hangar/swept_flying_wing/SweptWing.rst ================================================ SweptWing --------- .. autoclass:: sharpy.cases.hangar.swept_flying_wing.SweptWing :members: ================================================ FILE: docs/source/includes/cases/hangar/swept_flying_wing/index.rst ================================================ Generic Swept Flying Wing Class Generator +++++++++++++++++++++++++++++++++++++++++ Generic Swept Flying Wing Class Generator N Goizueta Nov 18 .. toctree:: :glob: ./SweptWing ================================================ FILE: docs/source/includes/cases/index.rst ================================================ Cases ----- .. toctree:: :maxdepth: 1 ./coupled/index ./hangar/index ./templates/index ================================================ FILE: docs/source/includes/cases/templates/Ttail/Ttail_3beams.rst ================================================ Ttail_3beams ------------ .. autoclass:: sharpy.cases.templates.Ttail.Ttail_3beams :members: ================================================ FILE: docs/source/includes/cases/templates/Ttail/Ttail_canonical.rst ================================================ Ttail_canonical --------------- .. autoclass:: sharpy.cases.templates.Ttail.Ttail_canonical :members: ================================================ FILE: docs/source/includes/cases/templates/Ttail/index.rst ================================================ Templates to build T-tail models ++++++++++++++++++++++++++++++++ Templates to build T-tail models S. Maraniello, Oct 2018 classes: - Ttail_3beam allows to generate general T-tail models with 3-beam. - Ttail_canonical: builds the canonical test case as per Murua et al., Prog. Aerosp. Sci., 71 (2014) 54-84 .. toctree:: :glob: ./Ttail_3beams ./Ttail_canonical ================================================ FILE: docs/source/includes/cases/templates/flying_wings/FlyingWing.rst ================================================ FlyingWing ---------- .. autoclass:: sharpy.cases.templates.flying_wings.FlyingWing :members: ================================================ FILE: docs/source/includes/cases/templates/flying_wings/Goland.rst ================================================ Goland ------ .. autoclass:: sharpy.cases.templates.flying_wings.Goland :members: ================================================ FILE: docs/source/includes/cases/templates/flying_wings/Pazy.rst ================================================ Pazy ---- .. autoclass:: sharpy.cases.templates.flying_wings.Pazy :members: ================================================ FILE: docs/source/includes/cases/templates/flying_wings/QuasiInfinite.rst ================================================ QuasiInfinite ------------- .. autoclass:: sharpy.cases.templates.flying_wings.QuasiInfinite :members: ================================================ FILE: docs/source/includes/cases/templates/flying_wings/Smith.rst ================================================ Smith ----- .. autoclass:: sharpy.cases.templates.flying_wings.Smith :members: ================================================ FILE: docs/source/includes/cases/templates/flying_wings/index.rst ================================================ Templates to build flying wing models +++++++++++++++++++++++++++++++++++++ Templates to build flying wing models S. Maraniello, Jul 2018 classes: - FlyingWing: generate a flying wing model from a reduced set of input. The built in method 'update_mass_stiff' can be re-defined by the user to enter more complex inertial/stiffness properties - Smith(FlyingWing): generate HALE wing model - Goland(FlyingWing): generate Goland wing model .. toctree:: :glob: ./FlyingWing ./Goland ./Pazy ./QuasiInfinite ./Smith ================================================ FILE: docs/source/includes/cases/templates/index.rst ================================================ Templates --------- .. toctree:: :maxdepth: 1 ./Ttail/index ./flying_wings/index ./template_wt/index ================================================ FILE: docs/source/includes/cases/templates/template_wt/create_blade_coordinates.rst ================================================ create_blade_coordinates ------------------------ .. automodule:: sharpy.cases.templates.template_wt.create_blade_coordinates ================================================ FILE: docs/source/includes/cases/templates/template_wt/create_node_radial_pos_from_elem_centres.rst ================================================ create_node_radial_pos_from_elem_centres ---------------------------------------- .. automodule:: sharpy.cases.templates.template_wt.create_node_radial_pos_from_elem_centres ================================================ FILE: docs/source/includes/cases/templates/template_wt/generate_from_excel_type03.rst ================================================ generate_from_excel_type03 -------------------------- .. automodule:: sharpy.cases.templates.template_wt.generate_from_excel_type03 ================================================ FILE: docs/source/includes/cases/templates/template_wt/index.rst ================================================ template_wt +++++++++++ template_wt Functions needed to generate a wind turbines Notes: To load this library: import cases.templates.template_wt as template_wt .. toctree:: :glob: ./create_blade_coordinates ./create_node_radial_pos_from_elem_centres ./generate_from_excel_type03 ./rotor_from_excel_type03 ./spar_from_excel_type04 ================================================ FILE: docs/source/includes/cases/templates/template_wt/rotor_from_excel_type03.rst ================================================ rotor_from_excel_type03 ----------------------- .. automodule:: sharpy.cases.templates.template_wt.rotor_from_excel_type03 ================================================ FILE: docs/source/includes/cases/templates/template_wt/spar_from_excel_type04.rst ================================================ spar_from_excel_type04 ---------------------- .. automodule:: sharpy.cases.templates.template_wt.spar_from_excel_type04 ================================================ FILE: docs/source/includes/controllers/controlsurfacepidcontroller/ControlSurfacePidController.rst ================================================ ControlSurfacePidController --------------------------- .. autoclass:: sharpy.controllers.controlsurfacepidcontroller.ControlSurfacePidController :members: ================================================ FILE: docs/source/includes/controllers/controlsurfacepidcontroller/index.rst ================================================ .. toctree:: :glob: ./ControlSurfacePidController ================================================ FILE: docs/source/includes/controllers/index.rst ================================================ Controllers ----------- .. toctree:: :maxdepth: 1 ./controlsurfacepidcontroller/index ./takeofftrajectorycontroller/index ================================================ FILE: docs/source/includes/controllers/takeofftrajectorycontroller/TakeOffTrajectoryController.rst ================================================ TakeOffTrajectoryController --------------------------- .. autoclass:: sharpy.controllers.takeofftrajectorycontroller.TakeOffTrajectoryController :members: ================================================ FILE: docs/source/includes/controllers/takeofftrajectorycontroller/index.rst ================================================ .. toctree:: :glob: ./TakeOffTrajectoryController ================================================ FILE: docs/source/includes/generators/bumpvelocityfield/BumpVelocityField.rst ================================================ BumpVelocityField ----------------- .. autoclass:: sharpy.generators.bumpvelocityfield.BumpVelocityField :members: ================================================ FILE: docs/source/includes/generators/bumpvelocityfield/index.rst ================================================ .. toctree:: :glob: ./BumpVelocityField ================================================ FILE: docs/source/includes/generators/dynamiccontrolsurface/DynamicControlSurface.rst ================================================ DynamicControlSurface --------------------- .. autoclass:: sharpy.generators.dynamiccontrolsurface.DynamicControlSurface :members: ================================================ FILE: docs/source/includes/generators/dynamiccontrolsurface/index.rst ================================================ .. toctree:: :glob: ./DynamicControlSurface ================================================ FILE: docs/source/includes/generators/floatingforces/FloatingForces.rst ================================================ FloatingForces -------------- .. autoclass:: sharpy.generators.floatingforces.FloatingForces :members: ================================================ FILE: docs/source/includes/generators/floatingforces/change_of_to_sharpy.rst ================================================ change_of_to_sharpy ------------------- .. automodule:: sharpy.generators.floatingforces.change_of_to_sharpy ================================================ FILE: docs/source/includes/generators/floatingforces/compute_equiv_hd_added_mass.rst ================================================ compute_equiv_hd_added_mass --------------------------- .. automodule:: sharpy.generators.floatingforces.compute_equiv_hd_added_mass ================================================ FILE: docs/source/includes/generators/floatingforces/compute_jacobian.rst ================================================ compute_jacobian ---------------- .. automodule:: sharpy.generators.floatingforces.compute_jacobian ================================================ FILE: docs/source/includes/generators/floatingforces/compute_xf_zf.rst ================================================ compute_xf_zf ------------- .. automodule:: sharpy.generators.floatingforces.compute_xf_zf ================================================ FILE: docs/source/includes/generators/floatingforces/index.rst ================================================ .. toctree:: :glob: ./FloatingForces ./change_of_to_sharpy ./compute_equiv_hd_added_mass ./compute_jacobian ./compute_xf_zf ./jonswap_spectrum ./matrix_from_rf ./noise_freq_1s ./quasisteady_mooring ./rename_terms ./response_freq_dep_matrix ./rfval ./time_wave_forces ./wave_radiation_damping ================================================ FILE: docs/source/includes/generators/floatingforces/jonswap_spectrum.rst ================================================ jonswap_spectrum ---------------- .. automodule:: sharpy.generators.floatingforces.jonswap_spectrum ================================================ FILE: docs/source/includes/generators/floatingforces/matrix_from_rf.rst ================================================ matrix_from_rf -------------- .. automodule:: sharpy.generators.floatingforces.matrix_from_rf ================================================ FILE: docs/source/includes/generators/floatingforces/noise_freq_1s.rst ================================================ noise_freq_1s ------------- .. automodule:: sharpy.generators.floatingforces.noise_freq_1s ================================================ FILE: docs/source/includes/generators/floatingforces/quasisteady_mooring.rst ================================================ quasisteady_mooring ------------------- .. automodule:: sharpy.generators.floatingforces.quasisteady_mooring ================================================ FILE: docs/source/includes/generators/floatingforces/rename_terms.rst ================================================ rename_terms ------------ .. automodule:: sharpy.generators.floatingforces.rename_terms ================================================ FILE: docs/source/includes/generators/floatingforces/response_freq_dep_matrix.rst ================================================ response_freq_dep_matrix ------------------------ .. automodule:: sharpy.generators.floatingforces.response_freq_dep_matrix ================================================ FILE: docs/source/includes/generators/floatingforces/rfval.rst ================================================ rfval ----- .. automodule:: sharpy.generators.floatingforces.rfval ================================================ FILE: docs/source/includes/generators/floatingforces/time_wave_forces.rst ================================================ time_wave_forces ---------------- .. automodule:: sharpy.generators.floatingforces.time_wave_forces ================================================ FILE: docs/source/includes/generators/floatingforces/wave_radiation_damping.rst ================================================ wave_radiation_damping ---------------------- .. automodule:: sharpy.generators.floatingforces.wave_radiation_damping ================================================ FILE: docs/source/includes/generators/gridbox/GridBox.rst ================================================ GridBox ------- .. autoclass:: sharpy.generators.gridbox.GridBox :members: ================================================ FILE: docs/source/includes/generators/gridbox/index.rst ================================================ .. toctree:: :glob: ./GridBox ================================================ FILE: docs/source/includes/generators/gustvelocityfield/DARPA.rst ================================================ DARPA ----- .. autoclass:: sharpy.generators.gustvelocityfield.DARPA :members: ================================================ FILE: docs/source/includes/generators/gustvelocityfield/GustVelocityField.rst ================================================ GustVelocityField ----------------- .. autoclass:: sharpy.generators.gustvelocityfield.GustVelocityField :members: ================================================ FILE: docs/source/includes/generators/gustvelocityfield/continuous_sin.rst ================================================ continuous_sin -------------- .. autoclass:: sharpy.generators.gustvelocityfield.continuous_sin :members: ================================================ FILE: docs/source/includes/generators/gustvelocityfield/index.rst ================================================ Gust Velocity Field Generators ++++++++++++++++++++++++++++++ These generators are used to create a gust velocity field. :class:`.GustVelocityField` is the main class that should be parsed as the ``velocity_field_input`` to the desired aerodynamic solver. The remaining classes are the specific gust profiles and parsed as ``gust_shape``. Examples: The typical input to the aerodynamic solver settings would therefore read similar to: >>> aero_settings = {'': '', >>> 'velocity_field_generator': 'GustVelocityField', >>> 'velocity_field_input': {'u_inf': 1, >>> 'gust_shape': '', >>> 'gust_parameters': ''}} .. toctree:: :glob: ./DARPA ./GustVelocityField ./continuous_sin ./lateral_one_minus_cos ./one_minus_cos ./span_sine ./time_varying ./time_varying_global ================================================ FILE: docs/source/includes/generators/gustvelocityfield/lateral_one_minus_cos.rst ================================================ lateral_one_minus_cos --------------------- .. autoclass:: sharpy.generators.gustvelocityfield.lateral_one_minus_cos :members: ================================================ FILE: docs/source/includes/generators/gustvelocityfield/one_minus_cos.rst ================================================ one_minus_cos ------------- .. autoclass:: sharpy.generators.gustvelocityfield.one_minus_cos :members: ================================================ FILE: docs/source/includes/generators/gustvelocityfield/span_sine.rst ================================================ span_sine --------- .. autoclass:: sharpy.generators.gustvelocityfield.span_sine :members: ================================================ FILE: docs/source/includes/generators/gustvelocityfield/time_varying.rst ================================================ time_varying ------------ .. autoclass:: sharpy.generators.gustvelocityfield.time_varying :members: ================================================ FILE: docs/source/includes/generators/gustvelocityfield/time_varying_global.rst ================================================ time_varying_global ------------------- .. autoclass:: sharpy.generators.gustvelocityfield.time_varying_global :members: ================================================ FILE: docs/source/includes/generators/helicoidalwake/HelicoidalWake.rst ================================================ HelicoidalWake -------------- .. autoclass:: sharpy.generators.helicoidalwake.HelicoidalWake :members: ================================================ FILE: docs/source/includes/generators/helicoidalwake/index.rst ================================================ .. toctree:: :glob: ./HelicoidalWake ================================================ FILE: docs/source/includes/generators/index.rst ================================================ Generators ---------- Velocity field generators prescribe the flow conditions for your problem. For instance, you can have an aircraft at a prescribed fixed location in a velocity field towards the aircraft. Alternatively, you can have a free moving aircraft in a static velocity field. Dynamic Control Surface generators enable the user to prescribe a certain control surface deflection in time. .. toctree:: :maxdepth: 1 ./bumpvelocityfield/index ./dynamiccontrolsurface/index ./floatingforces/index ./gridbox/index ./gustvelocityfield/index ./helicoidalwake/index ./modifystructure/index ./polaraeroforces/index ./shearvelocityfield/index ./steadyvelocityfield/index ./straightwake/index ./trajectorygenerator/index ./turbvelocityfield/index ./turbvelocityfieldbts/index ================================================ FILE: docs/source/includes/generators/modifystructure/ChangeLumpedMass.rst ================================================ ChangeLumpedMass ---------------- .. autoclass:: sharpy.generators.modifystructure.ChangeLumpedMass :members: ================================================ FILE: docs/source/includes/generators/modifystructure/ChangedVariable.rst ================================================ ChangedVariable --------------- .. autoclass:: sharpy.generators.modifystructure.ChangedVariable :members: ================================================ FILE: docs/source/includes/generators/modifystructure/LumpedMassControl.rst ================================================ LumpedMassControl ----------------- .. autoclass:: sharpy.generators.modifystructure.LumpedMassControl :members: ================================================ FILE: docs/source/includes/generators/modifystructure/ModifyStructure.rst ================================================ ModifyStructure --------------- .. autoclass:: sharpy.generators.modifystructure.ModifyStructure :members: ================================================ FILE: docs/source/includes/generators/modifystructure/index.rst ================================================ .. toctree:: :glob: ./ChangeLumpedMass ./ChangedVariable ./LumpedMassControl ./ModifyStructure ================================================ FILE: docs/source/includes/generators/polaraeroforces/EfficiencyCorrection.rst ================================================ EfficiencyCorrection -------------------- .. autoclass:: sharpy.generators.polaraeroforces.EfficiencyCorrection :members: ================================================ FILE: docs/source/includes/generators/polaraeroforces/PolarCorrection.rst ================================================ PolarCorrection --------------- .. autoclass:: sharpy.generators.polaraeroforces.PolarCorrection :members: ================================================ FILE: docs/source/includes/generators/polaraeroforces/get_aoacl0_from_camber.rst ================================================ get_aoacl0_from_camber ---------------------- .. automodule:: sharpy.generators.polaraeroforces.get_aoacl0_from_camber ================================================ FILE: docs/source/includes/generators/polaraeroforces/index.rst ================================================ .. toctree:: :glob: ./EfficiencyCorrection ./PolarCorrection ./get_aoacl0_from_camber ./local_stability_axes ./magnitude_and_direction_of_relative_velocity ./span_chord ================================================ FILE: docs/source/includes/generators/polaraeroforces/local_stability_axes.rst ================================================ local_stability_axes -------------------- .. automodule:: sharpy.generators.polaraeroforces.local_stability_axes ================================================ FILE: docs/source/includes/generators/polaraeroforces/magnitude_and_direction_of_relative_velocity.rst ================================================ magnitude_and_direction_of_relative_velocity -------------------------------------------- .. automodule:: sharpy.generators.polaraeroforces.magnitude_and_direction_of_relative_velocity ================================================ FILE: docs/source/includes/generators/polaraeroforces/span_chord.rst ================================================ span_chord ---------- .. automodule:: sharpy.generators.polaraeroforces.span_chord ================================================ FILE: docs/source/includes/generators/shearvelocityfield/ShearVelocityField.rst ================================================ ShearVelocityField ------------------ .. autoclass:: sharpy.generators.shearvelocityfield.ShearVelocityField :members: ================================================ FILE: docs/source/includes/generators/shearvelocityfield/index.rst ================================================ .. toctree:: :glob: ./ShearVelocityField ================================================ FILE: docs/source/includes/generators/steadyvelocityfield/SteadyVelocityField.rst ================================================ SteadyVelocityField ------------------- .. autoclass:: sharpy.generators.steadyvelocityfield.SteadyVelocityField :members: ================================================ FILE: docs/source/includes/generators/steadyvelocityfield/index.rst ================================================ .. toctree:: :glob: ./SteadyVelocityField ================================================ FILE: docs/source/includes/generators/straightwake/StraightWake.rst ================================================ StraightWake ------------ .. autoclass:: sharpy.generators.straightwake.StraightWake :members: ================================================ FILE: docs/source/includes/generators/straightwake/index.rst ================================================ .. toctree:: :glob: ./StraightWake ================================================ FILE: docs/source/includes/generators/trajectorygenerator/TrajectoryGenerator.rst ================================================ TrajectoryGenerator ------------------- .. autoclass:: sharpy.generators.trajectorygenerator.TrajectoryGenerator :members: ================================================ FILE: docs/source/includes/generators/trajectorygenerator/index.rst ================================================ .. toctree:: :glob: ./TrajectoryGenerator ================================================ FILE: docs/source/includes/generators/turbvelocityfield/TurbVelocityField.rst ================================================ TurbVelocityField ----------------- .. autoclass:: sharpy.generators.turbvelocityfield.TurbVelocityField :members: ================================================ FILE: docs/source/includes/generators/turbvelocityfield/index.rst ================================================ .. toctree:: :glob: ./TurbVelocityField ================================================ FILE: docs/source/includes/generators/turbvelocityfieldbts/TurbVelocityFieldBts.rst ================================================ TurbVelocityFieldBts -------------------- .. autoclass:: sharpy.generators.turbvelocityfieldbts.TurbVelocityFieldBts :members: ================================================ FILE: docs/source/includes/generators/turbvelocityfieldbts/index.rst ================================================ .. toctree:: :glob: ./TurbVelocityFieldBts ================================================ FILE: docs/source/includes/index.rst ================================================ SHARPy Source Code ------------------ The core SHARPy documentation is found herein. .. note:: The docs are still a work in progress and therefore, most functions/classes with which there is not much user interaction are not fully documented. We would appreciate any help by means of you contributing to our growing documentation! If you feel that a function/class is not well documented and, hence, you cannot use it, feel free to raise an issue so that we can improve it. .. toctree:: :maxdepth: 1 ./aero/index ./cases/index ./controllers/index ./generators/index ./io/index ./linear/index ./rom/index ./structure/index ./utils/index ================================================ FILE: docs/source/includes/io/index.rst ================================================ UDP Input/Output ---------------- This package contains the routines for the SHARPy input and output via UDP. The main interface is performed through the :class:`~sharpy.io.network_interface.NetworkLoader` .. toctree:: :maxdepth: 1 ./inout_variables/index ./network_interface/index ================================================ FILE: docs/source/includes/io/inout_variables/SetOfVariables.rst ================================================ SetOfVariables -------------- .. autoclass:: sharpy.io.inout_variables.SetOfVariables :members: ================================================ FILE: docs/source/includes/io/inout_variables/index.rst ================================================ .. toctree:: :glob: ./SetOfVariables ================================================ FILE: docs/source/includes/io/network_interface/InNetwork.rst ================================================ InNetwork --------- .. autoclass:: sharpy.io.network_interface.InNetwork :members: ================================================ FILE: docs/source/includes/io/network_interface/Network.rst ================================================ Network ------- .. autoclass:: sharpy.io.network_interface.Network :members: ================================================ FILE: docs/source/includes/io/network_interface/NetworkLoader.rst ================================================ NetworkLoader ------------- .. autoclass:: sharpy.io.network_interface.NetworkLoader :members: ================================================ FILE: docs/source/includes/io/network_interface/OutNetwork.rst ================================================ OutNetwork ---------- .. autoclass:: sharpy.io.network_interface.OutNetwork :members: ================================================ FILE: docs/source/includes/io/network_interface/index.rst ================================================ .. toctree:: :glob: ./InNetwork ./Network ./NetworkLoader ./OutNetwork ================================================ FILE: docs/source/includes/linear/assembler/index.rst ================================================ Assembler --------- .. toctree:: :maxdepth: 1 ./lincontrolsurfacedeflector/index ./linearaeroelastic/index ./linearbeam/index ./lineargustassembler/index ./linearuvlm/index ================================================ FILE: docs/source/includes/linear/assembler/lincontrolsurfacedeflector/LinControlSurfaceDeflector.rst ================================================ LinControlSurfaceDeflector -------------------------- .. autoclass:: sharpy.linear.assembler.lincontrolsurfacedeflector.LinControlSurfaceDeflector :members: ================================================ FILE: docs/source/includes/linear/assembler/lincontrolsurfacedeflector/der_R_arbitrary_axis_times_v.rst ================================================ der_R_arbitrary_axis_times_v ---------------------------- .. automodule:: sharpy.linear.assembler.lincontrolsurfacedeflector.der_R_arbitrary_axis_times_v ================================================ FILE: docs/source/includes/linear/assembler/lincontrolsurfacedeflector/index.rst ================================================ Control surface deflector for linear systems ++++++++++++++++++++++++++++++++++++++++++++ Control surface deflector for linear systems .. toctree:: :glob: ./LinControlSurfaceDeflector ./der_R_arbitrary_axis_times_v ================================================ FILE: docs/source/includes/linear/assembler/linearaeroelastic/LinearAeroelastic.rst ================================================ LinearAeroelastic ----------------- .. autoclass:: sharpy.linear.assembler.linearaeroelastic.LinearAeroelastic :members: ================================================ FILE: docs/source/includes/linear/assembler/linearaeroelastic/index.rst ================================================ .. toctree:: :glob: ./LinearAeroelastic ================================================ FILE: docs/source/includes/linear/assembler/linearbeam/LinearBeam.rst ================================================ LinearBeam ---------- .. autoclass:: sharpy.linear.assembler.linearbeam.LinearBeam :members: ================================================ FILE: docs/source/includes/linear/assembler/linearbeam/index.rst ================================================ Linear State Beam Element Class +++++++++++++++++++++++++++++++ Linear State Beam Element Class .. toctree:: :glob: ./LinearBeam ================================================ FILE: docs/source/includes/linear/assembler/lineargustassembler/LeadingEdge.rst ================================================ LeadingEdge ----------- .. autoclass:: sharpy.linear.assembler.lineargustassembler.LeadingEdge :members: ================================================ FILE: docs/source/includes/linear/assembler/lineargustassembler/MultiLeadingEdge.rst ================================================ MultiLeadingEdge ---------------- .. autoclass:: sharpy.linear.assembler.lineargustassembler.MultiLeadingEdge :members: ================================================ FILE: docs/source/includes/linear/assembler/lineargustassembler/campbell.rst ================================================ campbell -------- .. automodule:: sharpy.linear.assembler.lineargustassembler.campbell ================================================ FILE: docs/source/includes/linear/assembler/lineargustassembler/gust_from_string.rst ================================================ gust_from_string ---------------- .. automodule:: sharpy.linear.assembler.lineargustassembler.gust_from_string ================================================ FILE: docs/source/includes/linear/assembler/lineargustassembler/index.rst ================================================ .. toctree:: :glob: ./LeadingEdge ./MultiLeadingEdge ./campbell ./gust_from_string ./spanwise_interpolation ================================================ FILE: docs/source/includes/linear/assembler/lineargustassembler/spanwise_interpolation.rst ================================================ spanwise_interpolation ---------------------- .. automodule:: sharpy.linear.assembler.lineargustassembler.spanwise_interpolation ================================================ FILE: docs/source/includes/linear/assembler/linearuvlm/LinearUVLM.rst ================================================ LinearUVLM ---------- .. autoclass:: sharpy.linear.assembler.linearuvlm.LinearUVLM :members: ================================================ FILE: docs/source/includes/linear/assembler/linearuvlm/index.rst ================================================ Linear UVLM State Space System ++++++++++++++++++++++++++++++ Linear UVLM State Space System .. toctree:: :glob: ./LinearUVLM ================================================ FILE: docs/source/includes/linear/index.rst ================================================ Linear SHARPy ------------- The code included herein enables the assembly of linearised state-space systems based on the previous solution of a nonlinear problem that will be used as linearisation reference. The code is structured in the following way: * Assembler: different state-spaces to assemble, from only structural/aerodynamic to fully coupled aeroelastic * Src: source code required for the linearisation and utilities for the manipulation of state-space elements References: Maraniello, S. , Palacios, R.. State-Space Realizations and Internal Balancing in Potential-Flow Aerodynamics with Arbitrary Kinematics. AIAA Journal, Vol. 57, No.6, June 2019 .. toctree:: :maxdepth: 1 ./assembler/index ./src/index ================================================ FILE: docs/source/includes/linear/src/assembly/AICs.rst ================================================ AICs ---- .. automodule:: sharpy.linear.src.assembly.AICs ================================================ FILE: docs/source/includes/linear/src/assembly/dfqsdgamma_vrel0.rst ================================================ dfqsdgamma_vrel0 ---------------- .. automodule:: sharpy.linear.src.assembly.dfqsdgamma_vrel0 ================================================ FILE: docs/source/includes/linear/src/assembly/dfqsduinput.rst ================================================ dfqsduinput ----------- .. automodule:: sharpy.linear.src.assembly.dfqsduinput ================================================ FILE: docs/source/includes/linear/src/assembly/dfqsdvind_gamma.rst ================================================ dfqsdvind_gamma --------------- .. automodule:: sharpy.linear.src.assembly.dfqsdvind_gamma ================================================ FILE: docs/source/includes/linear/src/assembly/dfqsdvind_zeta.rst ================================================ dfqsdvind_zeta -------------- .. automodule:: sharpy.linear.src.assembly.dfqsdvind_zeta ================================================ FILE: docs/source/includes/linear/src/assembly/dfqsdzeta_omega.rst ================================================ dfqsdzeta_omega --------------- .. automodule:: sharpy.linear.src.assembly.dfqsdzeta_omega ================================================ FILE: docs/source/includes/linear/src/assembly/dfqsdzeta_vrel0.rst ================================================ dfqsdzeta_vrel0 --------------- .. automodule:: sharpy.linear.src.assembly.dfqsdzeta_vrel0 ================================================ FILE: docs/source/includes/linear/src/assembly/dfunstdgamma_dot.rst ================================================ dfunstdgamma_dot ---------------- .. automodule:: sharpy.linear.src.assembly.dfunstdgamma_dot ================================================ FILE: docs/source/includes/linear/src/assembly/dvinddzeta.rst ================================================ dvinddzeta ---------- .. automodule:: sharpy.linear.src.assembly.dvinddzeta ================================================ FILE: docs/source/includes/linear/src/assembly/dvinddzeta_cpp.rst ================================================ dvinddzeta_cpp -------------- .. automodule:: sharpy.linear.src.assembly.dvinddzeta_cpp ================================================ FILE: docs/source/includes/linear/src/assembly/eval_panel_cpp.rst ================================================ eval_panel_cpp -------------- .. automodule:: sharpy.linear.src.assembly.eval_panel_cpp ================================================ FILE: docs/source/includes/linear/src/assembly/index.rst ================================================ Assembly of linearised UVLM system ++++++++++++++++++++++++++++++++++ S. Maraniello, 25 May 2018 Includes: - Boundary conditions methods: - AICs: allocate aero influence coefficient matrices of multi-surfaces configurations - ``nc_dqcdzeta_Sin_to_Sout``: derivative matrix of ``nc*dQ/dzeta`` where Q is the induced velocity at the bound collocation points of one surface to another. - ``nc_dqcdzeta_coll``: assembles ``nc_dqcdzeta_coll_Sin_to_Sout`` matrices in multi-surfaces configurations - ``uc_dncdzeta``: assemble derivative matrix dnc/dzeta*Uc at bound collocation points .. toctree:: :glob: ./AICs ./dfqsdgamma_vrel0 ./dfqsduinput ./dfqsdvind_gamma ./dfqsdvind_zeta ./dfqsdzeta_omega ./dfqsdzeta_vrel0 ./dfunstdgamma_dot ./dvinddzeta ./dvinddzeta_cpp ./eval_panel_cpp ./nc_domegazetadzeta ./nc_dqcdzeta ./nc_dqcdzeta_Sin_to_Sout ./test_wake_prop_term ./uc_dncdzeta ./wake_prop ./wake_prop_from_dimensions ================================================ FILE: docs/source/includes/linear/src/assembly/nc_domegazetadzeta.rst ================================================ nc_domegazetadzeta ------------------ .. automodule:: sharpy.linear.src.assembly.nc_domegazetadzeta ================================================ FILE: docs/source/includes/linear/src/assembly/nc_dqcdzeta.rst ================================================ nc_dqcdzeta ----------- .. automodule:: sharpy.linear.src.assembly.nc_dqcdzeta ================================================ FILE: docs/source/includes/linear/src/assembly/nc_dqcdzeta_Sin_to_Sout.rst ================================================ nc_dqcdzeta_Sin_to_Sout ----------------------- .. automodule:: sharpy.linear.src.assembly.nc_dqcdzeta_Sin_to_Sout ================================================ FILE: docs/source/includes/linear/src/assembly/test_wake_prop_term.rst ================================================ test_wake_prop_term ------------------- .. automodule:: sharpy.linear.src.assembly.test_wake_prop_term ================================================ FILE: docs/source/includes/linear/src/assembly/uc_dncdzeta.rst ================================================ uc_dncdzeta ----------- .. automodule:: sharpy.linear.src.assembly.uc_dncdzeta ================================================ FILE: docs/source/includes/linear/src/assembly/wake_prop.rst ================================================ wake_prop --------- .. automodule:: sharpy.linear.src.assembly.wake_prop ================================================ FILE: docs/source/includes/linear/src/assembly/wake_prop_from_dimensions.rst ================================================ wake_prop_from_dimensions ------------------------- .. automodule:: sharpy.linear.src.assembly.wake_prop_from_dimensions ================================================ FILE: docs/source/includes/linear/src/gridmapping/AeroGridMap.rst ================================================ AeroGridMap ----------- .. autoclass:: sharpy.linear.src.gridmapping.AeroGridMap :members: ================================================ FILE: docs/source/includes/linear/src/gridmapping/index.rst ================================================ Mapping methods for bound surface panels ++++++++++++++++++++++++++++++++++++++++ S. Maraniello, 19 May 2018 .. toctree:: :glob: ./AeroGridMap ================================================ FILE: docs/source/includes/linear/src/index.rst ================================================ Linearised System Source Code ----------------------------- .. toctree:: :maxdepth: 1 ./assembly/index ./gridmapping/index ./interp/index ./lib_dbiot/index ./lib_ucdncdzeta/index ./libfit/index ./libsparse/index ./libss/index ./lin_aeroelastic/index ./lin_utils/index ./lingebm/index ./linuvlm/index ./multisurfaces/index ./surface/index ================================================ FILE: docs/source/includes/linear/src/interp/get_Wnv_vector.rst ================================================ get_Wnv_vector -------------- .. automodule:: sharpy.linear.src.interp.get_Wnv_vector ================================================ FILE: docs/source/includes/linear/src/interp/get_Wvc_scalar.rst ================================================ get_Wvc_scalar -------------- .. automodule:: sharpy.linear.src.interp.get_Wvc_scalar ================================================ FILE: docs/source/includes/linear/src/interp/get_panel_wcv.rst ================================================ get_panel_wcv ------------- .. automodule:: sharpy.linear.src.interp.get_panel_wcv ================================================ FILE: docs/source/includes/linear/src/interp/index.rst ================================================ Defines interpolation methods (geometrically-exact) and matrices (linearisation) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Defines interpolation methods (geometrically-exact) and matrices (linearisation) S. Maraniello, 20 May 2018 .. toctree:: :glob: ./get_Wnv_vector ./get_Wvc_scalar ./get_panel_wcv ================================================ FILE: docs/source/includes/linear/src/lib_dbiot/Dvcross_by_skew3d.rst ================================================ Dvcross_by_skew3d ----------------- .. automodule:: sharpy.linear.src.lib_dbiot.Dvcross_by_skew3d ================================================ FILE: docs/source/includes/linear/src/lib_dbiot/eval_panel_comp.rst ================================================ eval_panel_comp --------------- .. automodule:: sharpy.linear.src.lib_dbiot.eval_panel_comp ================================================ FILE: docs/source/includes/linear/src/lib_dbiot/eval_panel_cpp.rst ================================================ eval_panel_cpp -------------- .. automodule:: sharpy.linear.src.lib_dbiot.eval_panel_cpp ================================================ FILE: docs/source/includes/linear/src/lib_dbiot/eval_panel_exp.rst ================================================ eval_panel_exp -------------- .. automodule:: sharpy.linear.src.lib_dbiot.eval_panel_exp ================================================ FILE: docs/source/includes/linear/src/lib_dbiot/eval_panel_fast.rst ================================================ eval_panel_fast --------------- .. automodule:: sharpy.linear.src.lib_dbiot.eval_panel_fast ================================================ FILE: docs/source/includes/linear/src/lib_dbiot/eval_panel_fast_coll.rst ================================================ eval_panel_fast_coll -------------------- .. automodule:: sharpy.linear.src.lib_dbiot.eval_panel_fast_coll ================================================ FILE: docs/source/includes/linear/src/lib_dbiot/eval_seg_comp_loop.rst ================================================ eval_seg_comp_loop ------------------ .. automodule:: sharpy.linear.src.lib_dbiot.eval_seg_comp_loop ================================================ FILE: docs/source/includes/linear/src/lib_dbiot/eval_seg_exp.rst ================================================ eval_seg_exp ------------ .. automodule:: sharpy.linear.src.lib_dbiot.eval_seg_exp ================================================ FILE: docs/source/includes/linear/src/lib_dbiot/eval_seg_exp_loop.rst ================================================ eval_seg_exp_loop ----------------- .. automodule:: sharpy.linear.src.lib_dbiot.eval_seg_exp_loop ================================================ FILE: docs/source/includes/linear/src/lib_dbiot/index.rst ================================================ Induced Velocity Derivatives ++++++++++++++++++++++++++++ Calculate derivatives of induced velocity. Methods: - eval_seg_exp and eval_seg_exp_loop: profide ders in format [Q_{x,y,z},ZetaPoint_{x,y,z}] and use fully-expanded analytical formula. - eval_panel_exp: iterates through whole panel - eval_seg_comp and eval_seg_comp_loop: profide ders in format [Q_{x,y,z},ZetaPoint_{x,y,z}] and use compact analytical formula. .. toctree:: :glob: ./Dvcross_by_skew3d ./eval_panel_comp ./eval_panel_cpp ./eval_panel_exp ./eval_panel_fast ./eval_panel_fast_coll ./eval_seg_comp_loop ./eval_seg_exp ./eval_seg_exp_loop ================================================ FILE: docs/source/includes/linear/src/lib_ucdncdzeta/eval.rst ================================================ eval ---- .. automodule:: sharpy.linear.src.lib_ucdncdzeta.eval ================================================ FILE: docs/source/includes/linear/src/lib_ucdncdzeta/index.rst ================================================ Induced Velocity Derivatives with respect to Panel Normal +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Calculate derivative of .. math:: \boldsymbol{u}_c\frac{\partial\boldsymbol{n}_c}{\partial\boldsymbol{zeta}} with respect to local panel coordinates. .. toctree:: :glob: ./eval ================================================ FILE: docs/source/includes/linear/src/libfit/fitfrd.rst ================================================ fitfrd ------ .. automodule:: sharpy.linear.src.libfit.fitfrd ================================================ FILE: docs/source/includes/linear/src/libfit/get_rfa_res.rst ================================================ get_rfa_res ----------- .. automodule:: sharpy.linear.src.libfit.get_rfa_res ================================================ FILE: docs/source/includes/linear/src/libfit/get_rfa_res_norm.rst ================================================ get_rfa_res_norm ---------------- .. automodule:: sharpy.linear.src.libfit.get_rfa_res_norm ================================================ FILE: docs/source/includes/linear/src/libfit/index.rst ================================================ Fitting Tools Library +++++++++++++++++++++ @author: Salvatore Maraniello @date: 15 Jan 2018 .. toctree:: :glob: ./fitfrd ./get_rfa_res ./get_rfa_res_norm ./poly_fit ./rfa ./rfa_fit_dev ./rfa_mimo ./rfader ================================================ FILE: docs/source/includes/linear/src/libfit/poly_fit.rst ================================================ poly_fit -------- .. automodule:: sharpy.linear.src.libfit.poly_fit ================================================ FILE: docs/source/includes/linear/src/libfit/rfa.rst ================================================ rfa --- .. automodule:: sharpy.linear.src.libfit.rfa ================================================ FILE: docs/source/includes/linear/src/libfit/rfa_fit_dev.rst ================================================ rfa_fit_dev ----------- .. automodule:: sharpy.linear.src.libfit.rfa_fit_dev ================================================ FILE: docs/source/includes/linear/src/libfit/rfa_mimo.rst ================================================ rfa_mimo -------- .. automodule:: sharpy.linear.src.libfit.rfa_mimo ================================================ FILE: docs/source/includes/linear/src/libfit/rfader.rst ================================================ rfader ------ .. automodule:: sharpy.linear.src.libfit.rfader ================================================ FILE: docs/source/includes/linear/src/libsparse/block_dot.rst ================================================ block_dot --------- .. automodule:: sharpy.linear.src.libsparse.block_dot ================================================ FILE: docs/source/includes/linear/src/libsparse/block_matrix_dot_vector.rst ================================================ block_matrix_dot_vector ----------------------- .. automodule:: sharpy.linear.src.libsparse.block_matrix_dot_vector ================================================ FILE: docs/source/includes/linear/src/libsparse/block_sum.rst ================================================ block_sum --------- .. automodule:: sharpy.linear.src.libsparse.block_sum ================================================ FILE: docs/source/includes/linear/src/libsparse/csc_matrix.rst ================================================ csc_matrix ---------- .. autoclass:: sharpy.linear.src.libsparse.csc_matrix :members: ================================================ FILE: docs/source/includes/linear/src/libsparse/dense.rst ================================================ dense ----- .. automodule:: sharpy.linear.src.libsparse.dense ================================================ FILE: docs/source/includes/linear/src/libsparse/dot.rst ================================================ dot --- .. automodule:: sharpy.linear.src.libsparse.dot ================================================ FILE: docs/source/includes/linear/src/libsparse/eye_as.rst ================================================ eye_as ------ .. automodule:: sharpy.linear.src.libsparse.eye_as ================================================ FILE: docs/source/includes/linear/src/libsparse/index.rst ================================================ Collect tools to manipulate sparse and/or mixed dense/sparse matrices. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Collect tools to manipulate sparse and/or mixed dense/sparse matrices. author: S. Maraniello date: Dec 2018 Comment: manipulating large linear system may require using both dense and sparse matrices. While numpy/scipy automatically handle most operations between mixed dense/sparse arrays, some (e.g. dot product) require more attention. This library collects methods to handle these situations. Classes: scipy.sparse matrices are wrapped so as to ensure compatibility with numpy arrays upon conversion to dense. - csc_matrix: this is a wrapper of scipy.csc_matrix. - SupportedTypes: types supported for operations - WarningTypes: due to some bugs in scipy (v.1.1.0), sum (+) operations between np.ndarray and scipy.sparse matrices can result in numpy.matrixlib.defmatrix.matrix types. This list contains such undesired types that can result from dense/sparse operations and raises a warning if required. (b) convert these types into numpy.ndarrays. Methods: - dot: handles matrix dot products across different types. - solve: solves linear systems Ax=b with A and b dense, sparse or mixed. - dense: convert matrix to numpy array Warning: - only sparse types into SupportedTypes are supported! To Do: - move these methods into an algebra module? .. toctree:: :glob: ./block_dot ./block_matrix_dot_vector ./block_sum ./csc_matrix ./dense ./dot ./eye_as ./solve ./zeros_as ================================================ FILE: docs/source/includes/linear/src/libsparse/solve.rst ================================================ solve ----- .. automodule:: sharpy.linear.src.libsparse.solve ================================================ FILE: docs/source/includes/linear/src/libsparse/zeros_as.rst ================================================ zeros_as -------- .. automodule:: sharpy.linear.src.libsparse.zeros_as ================================================ FILE: docs/source/includes/linear/src/libss/Hnorm_from_freq_resp.rst ================================================ Hnorm_from_freq_resp -------------------- .. automodule:: sharpy.linear.src.libss.Hnorm_from_freq_resp ================================================ FILE: docs/source/includes/linear/src/libss/SSconv.rst ================================================ SSconv ------ .. automodule:: sharpy.linear.src.libss.SSconv ================================================ FILE: docs/source/includes/linear/src/libss/SSderivative.rst ================================================ SSderivative ------------ .. automodule:: sharpy.linear.src.libss.SSderivative ================================================ FILE: docs/source/includes/linear/src/libss/SSintegr.rst ================================================ SSintegr -------- .. automodule:: sharpy.linear.src.libss.SSintegr ================================================ FILE: docs/source/includes/linear/src/libss/StateSpace.rst ================================================ StateSpace ---------- .. autoclass:: sharpy.linear.src.libss.StateSpace :members: ================================================ FILE: docs/source/includes/linear/src/libss/addGain.rst ================================================ addGain ------- .. automodule:: sharpy.linear.src.libss.addGain ================================================ FILE: docs/source/includes/linear/src/libss/adjust_phase.rst ================================================ adjust_phase ------------ .. automodule:: sharpy.linear.src.libss.adjust_phase ================================================ FILE: docs/source/includes/linear/src/libss/build_SS_poly.rst ================================================ build_SS_poly ------------- .. automodule:: sharpy.linear.src.libss.build_SS_poly ================================================ FILE: docs/source/includes/linear/src/libss/butter.rst ================================================ butter ------ .. automodule:: sharpy.linear.src.libss.butter ================================================ FILE: docs/source/includes/linear/src/libss/compare_ss.rst ================================================ compare_ss ---------- .. automodule:: sharpy.linear.src.libss.compare_ss ================================================ FILE: docs/source/includes/linear/src/libss/couple.rst ================================================ couple ------ .. automodule:: sharpy.linear.src.libss.couple ================================================ FILE: docs/source/includes/linear/src/libss/disc2cont.rst ================================================ disc2cont --------- .. automodule:: sharpy.linear.src.libss.disc2cont ================================================ FILE: docs/source/includes/linear/src/libss/eigvals.rst ================================================ eigvals ------- .. automodule:: sharpy.linear.src.libss.eigvals ================================================ FILE: docs/source/includes/linear/src/libss/freqresp.rst ================================================ freqresp -------- .. automodule:: sharpy.linear.src.libss.freqresp ================================================ FILE: docs/source/includes/linear/src/libss/get_freq_from_eigs.rst ================================================ get_freq_from_eigs ------------------ .. automodule:: sharpy.linear.src.libss.get_freq_from_eigs ================================================ FILE: docs/source/includes/linear/src/libss/index.rst ================================================ Linear Time Invariant systems +++++++++++++++++++++++++++++ Linear Time Invariant systems author: S. Maraniello date: 15 Sep 2017 (still basement...) Library of methods to build/manipulate state-space models. The module supports the sparse arrays types defined in libsparse. The module includes: Classes: - StateSpace: provides a class to build DLTI/LTI systems with full and/or sparse matrices and wraps many of the methods in these library. Methods include: - freqresp: wraps the freqresp function - addGain: adds gains in input/output. This is not a wrapper of addGain, as the system matrices are overwritten Methods for state-space manipulation: - couple: feedback coupling. Does not support sparsity - freqresp: calculate frequency response. Supports sparsity. - series: series connection between systems - parallel: parallel connection between systems - SSconv: convert state-space model with predictions and delays - addGain: add gains to state-space model. - join2: merge two state-space models into one. - join: merge a list of state-space models into one. - sum state-space models and/or gains - scale_SS: scale state-space model - simulate: simulates discrete time solution - Hnorm_from_freq_resp: compute H norm of a frequency response - adjust_phase: remove discontinuities from a frequency response Special Models: - SSderivative: produces DLTI of a numerical derivative scheme - SSintegr: produces DLTI of an integration scheme - build_SS_poly: build state-space model with polynomial terms. Filtering: - butter Utilities: - get_freq_from_eigs: clculate frequency corresponding to eigenvalues Comments: - the module supports sparse matrices hence relies on libsparse. to do: - remove unnecessary coupling routines - couple function can handle sparse matrices but only outputs dense matrices - verify if typical coupled systems are sparse - update routine - add method to automatically determine whether to use sparse or dense? .. toctree:: :glob: ./Hnorm_from_freq_resp ./SSconv ./SSderivative ./SSintegr ./StateSpace ./addGain ./adjust_phase ./build_SS_poly ./butter ./compare_ss ./couple ./disc2cont ./eigvals ./freqresp ./get_freq_from_eigs ./join ./join2 ./parallel ./project ./random_ss ./retain_inout_channels ./scale_SS ./series ./simulate ./ss_block ./ss_to_scipy ./sum_ss ================================================ FILE: docs/source/includes/linear/src/libss/join.rst ================================================ join ---- .. automodule:: sharpy.linear.src.libss.join ================================================ FILE: docs/source/includes/linear/src/libss/join2.rst ================================================ join2 ----- .. automodule:: sharpy.linear.src.libss.join2 ================================================ FILE: docs/source/includes/linear/src/libss/parallel.rst ================================================ parallel -------- .. automodule:: sharpy.linear.src.libss.parallel ================================================ FILE: docs/source/includes/linear/src/libss/project.rst ================================================ project ------- .. automodule:: sharpy.linear.src.libss.project ================================================ FILE: docs/source/includes/linear/src/libss/random_ss.rst ================================================ random_ss --------- .. automodule:: sharpy.linear.src.libss.random_ss ================================================ FILE: docs/source/includes/linear/src/libss/retain_inout_channels.rst ================================================ retain_inout_channels --------------------- .. automodule:: sharpy.linear.src.libss.retain_inout_channels ================================================ FILE: docs/source/includes/linear/src/libss/scale_SS.rst ================================================ scale_SS -------- .. automodule:: sharpy.linear.src.libss.scale_SS ================================================ FILE: docs/source/includes/linear/src/libss/series.rst ================================================ series ------ .. automodule:: sharpy.linear.src.libss.series ================================================ FILE: docs/source/includes/linear/src/libss/simulate.rst ================================================ simulate -------- .. automodule:: sharpy.linear.src.libss.simulate ================================================ FILE: docs/source/includes/linear/src/libss/ss_block.rst ================================================ ss_block -------- .. autoclass:: sharpy.linear.src.libss.ss_block :members: ================================================ FILE: docs/source/includes/linear/src/libss/ss_to_scipy.rst ================================================ ss_to_scipy ----------- .. automodule:: sharpy.linear.src.libss.ss_to_scipy ================================================ FILE: docs/source/includes/linear/src/libss/sum_ss.rst ================================================ sum_ss ------ .. automodule:: sharpy.linear.src.libss.sum_ss ================================================ FILE: docs/source/includes/linear/src/lin_aeroelastic/LinAeroEla.rst ================================================ LinAeroEla ---------- .. autoclass:: sharpy.linear.src.lin_aeroelastic.LinAeroEla :members: ================================================ FILE: docs/source/includes/linear/src/lin_aeroelastic/index.rst ================================================ Linear aeroelastic model based on coupled GEBM + UVLM +++++++++++++++++++++++++++++++++++++++++++++++++++++ Linear aeroelastic model based on coupled GEBM + UVLM S. Maraniello, Jul 2018 .. toctree:: :glob: ./LinAeroEla ================================================ FILE: docs/source/includes/linear/src/lin_utils/Info.rst ================================================ Info ---- .. autoclass:: sharpy.linear.src.lin_utils.Info :members: ================================================ FILE: docs/source/includes/linear/src/lin_utils/comp_tot_force.rst ================================================ comp_tot_force -------------- .. automodule:: sharpy.linear.src.lin_utils.comp_tot_force ================================================ FILE: docs/source/includes/linear/src/lin_utils/extract_from_data.rst ================================================ extract_from_data ----------------- .. automodule:: sharpy.linear.src.lin_utils.extract_from_data ================================================ FILE: docs/source/includes/linear/src/lin_utils/index.rst ================================================ Utilities functions for linear analysis +++++++++++++++++++++++++++++++++++++++ Utilities functions for linear analysis .. toctree:: :glob: ./Info ./comp_tot_force ./extract_from_data ./solve_linear ================================================ FILE: docs/source/includes/linear/src/lin_utils/solve_linear.rst ================================================ solve_linear ------------ .. automodule:: sharpy.linear.src.lin_utils.solve_linear ================================================ FILE: docs/source/includes/linear/src/lingebm/FlexDynamic.rst ================================================ FlexDynamic ----------- .. autoclass:: sharpy.linear.src.lingebm.FlexDynamic :members: ================================================ FILE: docs/source/includes/linear/src/lingebm/index.rst ================================================ Linear beam model class +++++++++++++++++++++++ Linear beam model class S. Maraniello, Aug 2018 N. Goizueta .. toctree:: :glob: ./FlexDynamic ./newmark_ss ./sort_eigvals ================================================ FILE: docs/source/includes/linear/src/lingebm/newmark_ss.rst ================================================ newmark_ss ---------- .. autofunction:: sharpy.linear.src.lingebm.newmark_ss ================================================ FILE: docs/source/includes/linear/src/lingebm/sort_eigvals.rst ================================================ sort_eigvals ------------ .. automodule:: sharpy.linear.src.lingebm.sort_eigvals ================================================ FILE: docs/source/includes/linear/src/linuvlm/Dynamic.rst ================================================ Dynamic ------- .. autoclass:: sharpy.linear.src.linuvlm.Dynamic :members: ================================================ FILE: docs/source/includes/linear/src/linuvlm/DynamicBlock.rst ================================================ DynamicBlock ------------ .. autoclass:: sharpy.linear.src.linuvlm.DynamicBlock :members: ================================================ FILE: docs/source/includes/linear/src/linuvlm/Frequency.rst ================================================ Frequency --------- .. autoclass:: sharpy.linear.src.linuvlm.Frequency :members: ================================================ FILE: docs/source/includes/linear/src/linuvlm/Static.rst ================================================ Static ------ .. autoclass:: sharpy.linear.src.linuvlm.Static :members: ================================================ FILE: docs/source/includes/linear/src/linuvlm/get_Cw_cpx.rst ================================================ get_Cw_cpx ---------- .. automodule:: sharpy.linear.src.linuvlm.get_Cw_cpx ================================================ FILE: docs/source/includes/linear/src/linuvlm/index.rst ================================================ Linear UVLM solver classes ++++++++++++++++++++++++++ Linear UVLM solver classes Contains classes to assemble a linear UVLM system. The three main classes are: * :class:`~sharpy.linear.src.linuvlm.Static`: : for static VLM solutions. * :class:`~sharpy.linear.src.linuvlm.Dynamic`: for dynamic UVLM solutions. * :class:`~sharpy.linear.src.linuvlm.DynamicBlock`: a more efficient representation of ``Dynamic`` using lists for the different blocks in the UVLM equations References: Maraniello, S., & Palacios, R.. State-Space Realizations and Internal Balancing in Potential-Flow Aerodynamics with Arbitrary Kinematics. AIAA Journal, 57(6), 1–14. 2019. https://doi.org/10.2514/1.J058153 .. toctree:: :glob: ./Dynamic ./DynamicBlock ./Frequency ./Static ./get_Cw_cpx ================================================ FILE: docs/source/includes/linear/src/multisurfaces/MultiAeroGridSurfaces.rst ================================================ MultiAeroGridSurfaces --------------------- .. autoclass:: sharpy.linear.src.multisurfaces.MultiAeroGridSurfaces :members: ================================================ FILE: docs/source/includes/linear/src/multisurfaces/index.rst ================================================ Generation of multiple aerodynamic surfaces +++++++++++++++++++++++++++++++++++++++++++ S. Maraniello, 25 May 2018 .. toctree:: :glob: ./MultiAeroGridSurfaces ================================================ FILE: docs/source/includes/linear/src/surface/AeroGridGeo.rst ================================================ AeroGridGeo ----------- .. autoclass:: sharpy.linear.src.surface.AeroGridGeo :members: ================================================ FILE: docs/source/includes/linear/src/surface/AeroGridSurface.rst ================================================ AeroGridSurface --------------- .. autoclass:: sharpy.linear.src.surface.AeroGridSurface :members: ================================================ FILE: docs/source/includes/linear/src/surface/get_aic3_cpp.rst ================================================ get_aic3_cpp ------------ .. automodule:: sharpy.linear.src.surface.get_aic3_cpp ================================================ FILE: docs/source/includes/linear/src/surface/index.rst ================================================ Geometrical methods for bound surfaces ++++++++++++++++++++++++++++++++++++++ Geometrical methods for bound surfaces S. Maraniello, 20 May 2018 .. toctree:: :glob: ./AeroGridGeo ./AeroGridSurface ./get_aic3_cpp ================================================ FILE: docs/source/includes/postprocs/AeroForcesCalculator.rst ================================================ AeroForcesCalculator -------------------- .. autoclass:: sharpy.postproc.aeroforcescalculator.AeroForcesCalculator :members: ================================================ FILE: docs/source/includes/postprocs/AerogridPlot.rst ================================================ AerogridPlot ------------ .. autoclass:: sharpy.postproc.aerogridplot.AerogridPlot :members: ================================================ FILE: docs/source/includes/postprocs/AsymptoticStability.rst ================================================ AsymptoticStability ------------------- .. autoclass:: sharpy.postproc.asymptoticstability.AsymptoticStability :members: ================================================ FILE: docs/source/includes/postprocs/BeamLoads.rst ================================================ BeamLoads --------- .. autoclass:: sharpy.postproc.beamloads.BeamLoads :members: ================================================ FILE: docs/source/includes/postprocs/BeamPlot.rst ================================================ BeamPlot -------- .. autoclass:: sharpy.postproc.beamplot.BeamPlot :members: ================================================ FILE: docs/source/includes/postprocs/Cleanup.rst ================================================ Cleanup ------- .. autoclass:: sharpy.postproc.cleanup.Cleanup :members: ================================================ FILE: docs/source/includes/postprocs/FrequencyResponse.rst ================================================ FrequencyResponse ----------------- .. autoclass:: sharpy.postproc.frequencyresponse.FrequencyResponse :members: ================================================ FILE: docs/source/includes/postprocs/LiftDistribution.rst ================================================ LiftDistribution ---------------- .. autoclass:: sharpy.postproc.liftdistribution.LiftDistribution :members: ================================================ FILE: docs/source/includes/postprocs/PickleData.rst ================================================ PickleData ---------- .. autoclass:: sharpy.postproc.pickledata.PickleData :members: ================================================ FILE: docs/source/includes/postprocs/PlotFlowField.rst ================================================ PlotFlowField ------------- .. autoclass:: sharpy.postproc.plotflowfield.PlotFlowField :members: ================================================ FILE: docs/source/includes/postprocs/SaveData.rst ================================================ SaveData -------- .. autoclass:: sharpy.postproc.savedata.SaveData :members: ================================================ FILE: docs/source/includes/postprocs/SaveParametricCase.rst ================================================ SaveParametricCase ------------------ .. autoclass:: sharpy.postproc.saveparametriccase.SaveParametricCase :members: ================================================ FILE: docs/source/includes/postprocs/StabilityDerivatives.rst ================================================ StabilityDerivatives -------------------- .. autoclass:: sharpy.postproc.stabilityderivatives.StabilityDerivatives :members: ================================================ FILE: docs/source/includes/postprocs/StallCheck.rst ================================================ StallCheck ---------- .. autoclass:: sharpy.postproc.stallcheck.StallCheck :members: ================================================ FILE: docs/source/includes/postprocs/UDPout.rst ================================================ UDPout ------ .. autoclass:: sharpy.postproc.udpout.UDPout :members: ================================================ FILE: docs/source/includes/postprocs/WriteVariablesTime.rst ================================================ WriteVariablesTime ------------------ .. autoclass:: sharpy.postproc.writevariablestime.WriteVariablesTime :members: ================================================ FILE: docs/source/includes/rom/balanced/Balanced.rst ================================================ Balanced -------- .. autoclass:: sharpy.rom.balanced.Balanced :members: ================================================ FILE: docs/source/includes/rom/balanced/Direct.rst ================================================ Direct ------ .. autoclass:: sharpy.rom.balanced.Direct :members: ================================================ FILE: docs/source/includes/rom/balanced/FrequencyLimited.rst ================================================ FrequencyLimited ---------------- .. autoclass:: sharpy.rom.balanced.FrequencyLimited :members: ================================================ FILE: docs/source/includes/rom/balanced/Iterative.rst ================================================ Iterative --------- .. autoclass:: sharpy.rom.balanced.Iterative :members: ================================================ FILE: docs/source/includes/rom/balanced/index.rst ================================================ Balancing Methods +++++++++++++++++ The following classes are available to reduce a linear system employing balancing methods. The main class is :class:`.Balanced` and the other available classes: * :class:`.Direct` * :class:`.Iterative` * :class:`.FrequencyLimited` correspond to the reduction algorithm. .. toctree:: :glob: ./Balanced ./Direct ./FrequencyLimited ./Iterative ================================================ FILE: docs/source/includes/rom/index.rst ================================================ Model Order Reduction --------------------- .. toctree:: :maxdepth: 1 ./balanced/index ./krylov/index ./utils/index ================================================ FILE: docs/source/includes/rom/krylov/Krylov.rst ================================================ Krylov ------ .. autoclass:: sharpy.rom.krylov.Krylov :members: ================================================ FILE: docs/source/includes/rom/krylov/index.rst ================================================ Krylov-subspaces model order reduction techniques +++++++++++++++++++++++++++++++++++++++++++++++++ .. toctree:: :glob: ./Krylov ================================================ FILE: docs/source/includes/rom/utils/index.rst ================================================ Utils ----- .. toctree:: :maxdepth: 1 ./krylovutils/index ./librom/index ./librom_interp/index ================================================ FILE: docs/source/includes/rom/utils/krylovutils/check_eye.rst ================================================ check_eye --------- .. automodule:: sharpy.rom.utils.krylovutils.check_eye ================================================ FILE: docs/source/includes/rom/utils/krylovutils/construct_krylov.rst ================================================ construct_krylov ---------------- .. automodule:: sharpy.rom.utils.krylovutils.construct_krylov ================================================ FILE: docs/source/includes/rom/utils/krylovutils/evec.rst ================================================ evec ---- .. automodule:: sharpy.rom.utils.krylovutils.evec ================================================ FILE: docs/source/includes/rom/utils/krylovutils/index.rst ================================================ Krylov Model Reduction Methods Utilities ++++++++++++++++++++++++++++++++++++++++ .. toctree:: :glob: ./check_eye ./construct_krylov ./evec ./lu_factor ./lu_solve ./mgs_ortho ./remove_a12 ./schur_ordered ================================================ FILE: docs/source/includes/rom/utils/krylovutils/lu_factor.rst ================================================ lu_factor --------- .. automodule:: sharpy.rom.utils.krylovutils.lu_factor ================================================ FILE: docs/source/includes/rom/utils/krylovutils/lu_solve.rst ================================================ lu_solve -------- .. automodule:: sharpy.rom.utils.krylovutils.lu_solve ================================================ FILE: docs/source/includes/rom/utils/krylovutils/mgs_ortho.rst ================================================ mgs_ortho --------- .. automodule:: sharpy.rom.utils.krylovutils.mgs_ortho ================================================ FILE: docs/source/includes/rom/utils/krylovutils/remove_a12.rst ================================================ remove_a12 ---------- .. automodule:: sharpy.rom.utils.krylovutils.remove_a12 ================================================ FILE: docs/source/includes/rom/utils/krylovutils/schur_ordered.rst ================================================ schur_ordered ------------- .. automodule:: sharpy.rom.utils.krylovutils.schur_ordered ================================================ FILE: docs/source/includes/rom/utils/librom/balfreq.rst ================================================ balfreq ------- .. automodule:: sharpy.rom.utils.librom.balfreq ================================================ FILE: docs/source/includes/rom/utils/librom/balreal_direct_py.rst ================================================ balreal_direct_py ----------------- .. automodule:: sharpy.rom.utils.librom.balreal_direct_py ================================================ FILE: docs/source/includes/rom/utils/librom/balreal_iter.rst ================================================ balreal_iter ------------ .. automodule:: sharpy.rom.utils.librom.balreal_iter ================================================ FILE: docs/source/includes/rom/utils/librom/balreal_iter_old.rst ================================================ balreal_iter_old ---------------- .. automodule:: sharpy.rom.utils.librom.balreal_iter_old ================================================ FILE: docs/source/includes/rom/utils/librom/check_stability.rst ================================================ check_stability --------------- .. automodule:: sharpy.rom.utils.librom.check_stability ================================================ FILE: docs/source/includes/rom/utils/librom/eigen_dec.rst ================================================ eigen_dec --------- .. automodule:: sharpy.rom.utils.librom.eigen_dec ================================================ FILE: docs/source/includes/rom/utils/librom/get_gauss_weights.rst ================================================ get_gauss_weights ----------------- .. automodule:: sharpy.rom.utils.librom.get_gauss_weights ================================================ FILE: docs/source/includes/rom/utils/librom/get_trapz_weights.rst ================================================ get_trapz_weights ----------------- .. automodule:: sharpy.rom.utils.librom.get_trapz_weights ================================================ FILE: docs/source/includes/rom/utils/librom/index.rst ================================================ General ROM utilities +++++++++++++++++++++ S. Maraniello, 14 Feb 2018 .. toctree:: :glob: ./balfreq ./balreal_direct_py ./balreal_iter ./balreal_iter_old ./check_stability ./eigen_dec ./get_gauss_weights ./get_trapz_weights ./low_rank_smith ./modred ./res_discrete_lyap ./smith_iter ./tune_rom ================================================ FILE: docs/source/includes/rom/utils/librom/low_rank_smith.rst ================================================ low_rank_smith -------------- .. automodule:: sharpy.rom.utils.librom.low_rank_smith ================================================ FILE: docs/source/includes/rom/utils/librom/modred.rst ================================================ modred ------ .. automodule:: sharpy.rom.utils.librom.modred ================================================ FILE: docs/source/includes/rom/utils/librom/res_discrete_lyap.rst ================================================ res_discrete_lyap ----------------- .. automodule:: sharpy.rom.utils.librom.res_discrete_lyap ================================================ FILE: docs/source/includes/rom/utils/librom/smith_iter.rst ================================================ smith_iter ---------- .. automodule:: sharpy.rom.utils.librom.smith_iter ================================================ FILE: docs/source/includes/rom/utils/librom/tune_rom.rst ================================================ tune_rom -------- .. automodule:: sharpy.rom.utils.librom.tune_rom ================================================ FILE: docs/source/includes/rom/utils/librom_interp/FLB_transfer_function.rst ================================================ FLB_transfer_function --------------------- .. automodule:: sharpy.rom.utils.librom_interp.FLB_transfer_function ================================================ FILE: docs/source/includes/rom/utils/librom_interp/InterpROM.rst ================================================ InterpROM --------- .. autoclass:: sharpy.rom.utils.librom_interp.InterpROM :members: ================================================ FILE: docs/source/includes/rom/utils/librom_interp/index.rst ================================================ Methods for the interpolation of DLTI ROMs ++++++++++++++++++++++++++++++++++++++++++ This is library for state-space models interpolation. These routines are intended for small size state-space models (ROMs), hence some methods may not be optimised to exploit sparsity structures. For generality purposes, all methods require in input interpolatory weights. The module includes the methods: - :func:`~sharpy.rom.utils.librom_interp.transfer_function`: returns an interpolatory state-space model based on the transfer function method [1]. This method is general and is, effectively, a wrapper of the :func:`sharpy.linear.src.libss.join` method. - :func:`~sharpy.rom.utils.librom_interp.BT_transfer_function`: evolution of transfer function methods. The growth of the interpolated system size is avoided through balancing. References: [1] Benner, P., Gugercin, S. & Willcox, K., 2015. A Survey of Projection-Based Model Reduction Methods for Parametric Dynamical Systems. SIAM Review, 57(4), pp.483–531. Author: S. Maraniello Date: Mar-Apr 2019 .. toctree:: :glob: ./FLB_transfer_function ./InterpROM ./transfer_function ================================================ FILE: docs/source/includes/rom/utils/librom_interp/transfer_function.rst ================================================ transfer_function ----------------- .. automodule:: sharpy.rom.utils.librom_interp.transfer_function ================================================ FILE: docs/source/includes/solvers/aero/DynamicUVLM.rst ================================================ DynamicUVLM ----------- .. autoclass:: sharpy.solvers.dynamicuvlm.DynamicUVLM :members: ================================================ FILE: docs/source/includes/solvers/aero/NoAero.rst ================================================ NoAero ------ .. autoclass:: sharpy.solvers.noaero.NoAero :members: ================================================ FILE: docs/source/includes/solvers/aero/PrescribedUvlm.rst ================================================ PrescribedUvlm -------------- .. autoclass:: sharpy.solvers.prescribeduvlm.PrescribedUvlm :members: ================================================ FILE: docs/source/includes/solvers/aero/StaticUvlm.rst ================================================ StaticUvlm ---------- .. autoclass:: sharpy.solvers.staticuvlm.StaticUvlm :members: ================================================ FILE: docs/source/includes/solvers/aero/StepLinearUVLM.rst ================================================ StepLinearUVLM -------------- .. autoclass:: sharpy.solvers.steplinearuvlm.StepLinearUVLM :members: ================================================ FILE: docs/source/includes/solvers/aero/StepUvlm.rst ================================================ StepUvlm -------- .. autoclass:: sharpy.solvers.stepuvlm.StepUvlm :members: ================================================ FILE: docs/source/includes/solvers/aero_solvers.rst ================================================ Aero Solvers ++++++++++++ .. toctree:: ./aero/DynamicUVLM ./aero/NoAero ./aero/PrescribedUvlm ./aero/StaticUvlm ./aero/StepLinearUVLM ./aero/StepUvlm ================================================ FILE: docs/source/includes/solvers/coupled/DynamicCoupled.rst ================================================ DynamicCoupled -------------- .. autoclass:: sharpy.solvers.dynamiccoupled.DynamicCoupled :members: ================================================ FILE: docs/source/includes/solvers/coupled/LinDynamicSim.rst ================================================ LinDynamicSim ------------- .. autoclass:: sharpy.solvers.lindynamicsim.LinDynamicSim :members: ================================================ FILE: docs/source/includes/solvers/coupled/StaticCoupled.rst ================================================ StaticCoupled ------------- .. autoclass:: sharpy.solvers.staticcoupled.StaticCoupled :members: ================================================ FILE: docs/source/includes/solvers/coupled/StaticCoupledRBM.rst ================================================ StaticCoupledRBM ---------------- .. autoclass:: sharpy.solvers.staticcoupledrbm.StaticCoupledRBM :members: ================================================ FILE: docs/source/includes/solvers/coupled_solvers.rst ================================================ Coupled Solvers +++++++++++++++ .. toctree:: ./coupled/DynamicCoupled ./coupled/LinDynamicSim ./coupled/StaticCoupled ./coupled/StaticCoupledRBM ================================================ FILE: docs/source/includes/solvers/flight dynamics/StaticTrim.rst ================================================ StaticTrim ---------- .. autoclass:: sharpy.solvers.statictrim.StaticTrim :members: ================================================ FILE: docs/source/includes/solvers/flight dynamics/Trim.rst ================================================ Trim ---- .. autoclass:: sharpy.solvers.trim.Trim :members: ================================================ FILE: docs/source/includes/solvers/flight dynamics_solvers.rst ================================================ Flight dynamics Solvers +++++++++++++++++++++++ .. toctree:: ./flight dynamics/StaticTrim ./flight dynamics/Trim ================================================ FILE: docs/source/includes/solvers/linear/LinearAssembler.rst ================================================ LinearAssembler --------------- .. autoclass:: sharpy.solvers.linearassembler.LinearAssembler :members: ================================================ FILE: docs/source/includes/solvers/linear/Modal.rst ================================================ Modal ----- .. autoclass:: sharpy.solvers.modal.Modal :members: ================================================ FILE: docs/source/includes/solvers/linear_solvers.rst ================================================ Linear Solvers ++++++++++++++ .. toctree:: ./linear/LinearAssembler ./linear/Modal ================================================ FILE: docs/source/includes/solvers/loader/AerogridLoader.rst ================================================ AerogridLoader -------------- .. autoclass:: sharpy.solvers.aerogridloader.AerogridLoader :members: ================================================ FILE: docs/source/includes/solvers/loader/BeamLoader.rst ================================================ BeamLoader ---------- .. autoclass:: sharpy.solvers.beamloader.BeamLoader :members: ================================================ FILE: docs/source/includes/solvers/loader/PreSharpy.rst ================================================ PreSharpy --------- .. autoclass:: sharpy.presharpy.presharpy.PreSharpy :members: ================================================ FILE: docs/source/includes/solvers/loader_solvers.rst ================================================ Loader Solvers ++++++++++++++ .. toctree:: ./loader/PreSharpy ./loader/AerogridLoader ./loader/BeamLoader ================================================ FILE: docs/source/includes/solvers/structural/NonLinearDynamic.rst ================================================ NonLinearDynamic ---------------- .. autoclass:: sharpy.solvers.nonlineardynamic.NonLinearDynamic :members: ================================================ FILE: docs/source/includes/solvers/structural/NonLinearDynamicCoupledStep.rst ================================================ NonLinearDynamicCoupledStep --------------------------- .. autoclass:: sharpy.solvers.nonlineardynamiccoupledstep.NonLinearDynamicCoupledStep :members: ================================================ FILE: docs/source/includes/solvers/structural/NonLinearDynamicMultibody.rst ================================================ NonLinearDynamicMultibody ------------------------- .. autoclass:: sharpy.solvers.nonlineardynamicmultibody.NonLinearDynamicMultibody :members: ================================================ FILE: docs/source/includes/solvers/structural/NonLinearDynamicPrescribedStep.rst ================================================ NonLinearDynamicPrescribedStep ------------------------------ .. autoclass:: sharpy.solvers.nonlineardynamicprescribedstep.NonLinearDynamicPrescribedStep :members: ================================================ FILE: docs/source/includes/solvers/structural/NonLinearStatic.rst ================================================ NonLinearStatic --------------- .. autoclass:: sharpy.solvers.nonlinearstatic.NonLinearStatic :members: ================================================ FILE: docs/source/includes/solvers/structural/RigidDynamicCoupledStep.rst ================================================ RigidDynamicCoupledStep ----------------------- .. autoclass:: sharpy.solvers.rigiddynamiccoupledstep.RigidDynamicCoupledStep :members: ================================================ FILE: docs/source/includes/solvers/structural/RigidDynamicPrescribedStep.rst ================================================ RigidDynamicPrescribedStep -------------------------- .. autoclass:: sharpy.solvers.rigiddynamicprescribedstep.RigidDynamicPrescribedStep :members: ================================================ FILE: docs/source/includes/solvers/structural_solvers.rst ================================================ Structural Solvers ++++++++++++++++++ .. toctree:: ./structural/NonLinearDynamic ./structural/NonLinearDynamicCoupledStep ./structural/NonLinearDynamicMultibody ./structural/NonLinearDynamicPrescribedStep ./structural/NonLinearStatic ./structural/RigidDynamicCoupledStep ./structural/RigidDynamicPrescribedStep ================================================ FILE: docs/source/includes/solvers/time_integrator/GeneralisedAlpha.rst ================================================ GeneralisedAlpha ---------------- .. autoclass:: sharpy.solvers.generalisedalpha.GeneralisedAlpha :members: ================================================ FILE: docs/source/includes/solvers/time_integrator/NewmarkBeta.rst ================================================ NewmarkBeta ----------- .. autoclass:: sharpy.solvers.newmarkbeta.NewmarkBeta :members: ================================================ FILE: docs/source/includes/solvers/time_integrator_solvers.rst ================================================ Time_integrator Solvers +++++++++++++++++++++++ .. toctree:: ./time_integrator/NewmarkBeta ./time_integrator/GeneralisedAlpha ================================================ FILE: docs/source/includes/structure/index.rst ================================================ Structural Packages ------------------- .. toctree:: :maxdepth: 1 ./models/index ./utils/index ================================================ FILE: docs/source/includes/structure/models/beam/StructTimeStepInfo.rst ================================================ StructTimeStepInfo ------------------ .. autoclass:: sharpy.structure.models.beam.StructTimeStepInfo :members: ================================================ FILE: docs/source/includes/structure/models/beam/index.rst ================================================ .. toctree:: :glob: ./StructTimeStepInfo ================================================ FILE: docs/source/includes/structure/models/beamstructures/Element.rst ================================================ Element ------- .. autoclass:: sharpy.structure.models.beamstructures.Element :members: ================================================ FILE: docs/source/includes/structure/models/beamstructures/index.rst ================================================ .. toctree:: :glob: ./Element ================================================ FILE: docs/source/includes/structure/models/index.rst ================================================ Models ------ .. toctree:: :maxdepth: 1 ./beam/index ./beamstructures/index ================================================ FILE: docs/source/includes/structure/utils/index.rst ================================================ Utils ----- .. toctree:: :maxdepth: 1 ./lagrangeconstraints/index ./modalutils/index ./xbeamlib/index ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/BaseLagrangeConstraint.rst ================================================ BaseLagrangeConstraint ---------------------- .. autoclass:: sharpy.structure.utils.lagrangeconstraints.BaseLagrangeConstraint :members: ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/constant_rot_vel_FoR.rst ================================================ constant_rot_vel_FoR -------------------- .. autoclass:: sharpy.structure.utils.lagrangeconstraints.constant_rot_vel_FoR :members: ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/constant_vel_FoR.rst ================================================ constant_vel_FoR ---------------- .. autoclass:: sharpy.structure.utils.lagrangeconstraints.constant_vel_FoR :members: ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/def_rot_axis_FoR_wrt_node_general.rst ================================================ def_rot_axis_FoR_wrt_node_general --------------------------------- .. automodule:: sharpy.structure.utils.lagrangeconstraints.def_rot_axis_FoR_wrt_node_general ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/def_rot_axis_FoR_wrt_node_xyz.rst ================================================ def_rot_axis_FoR_wrt_node_xyz ----------------------------- .. automodule:: sharpy.structure.utils.lagrangeconstraints.def_rot_axis_FoR_wrt_node_xyz ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/def_rot_vect_FoR_wrt_node.rst ================================================ def_rot_vect_FoR_wrt_node ------------------------- .. automodule:: sharpy.structure.utils.lagrangeconstraints.def_rot_vect_FoR_wrt_node ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/def_rot_vel_FoR_wrt_node.rst ================================================ def_rot_vel_FoR_wrt_node ------------------------ .. automodule:: sharpy.structure.utils.lagrangeconstraints.def_rot_vel_FoR_wrt_node ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/define_FoR_dof.rst ================================================ define_FoR_dof -------------- .. automodule:: sharpy.structure.utils.lagrangeconstraints.define_FoR_dof ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/define_node_dof.rst ================================================ define_node_dof --------------- .. automodule:: sharpy.structure.utils.lagrangeconstraints.define_node_dof ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/define_num_LM_eq.rst ================================================ define_num_LM_eq ---------------- .. automodule:: sharpy.structure.utils.lagrangeconstraints.define_num_LM_eq ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/equal_lin_vel_node_FoR.rst ================================================ equal_lin_vel_node_FoR ---------------------- .. automodule:: sharpy.structure.utils.lagrangeconstraints.equal_lin_vel_node_FoR ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/equal_pos_node_FoR.rst ================================================ equal_pos_node_FoR ------------------ .. automodule:: sharpy.structure.utils.lagrangeconstraints.equal_pos_node_FoR ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/free.rst ================================================ free ---- .. autoclass:: sharpy.structure.utils.lagrangeconstraints.free :members: ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/fully_constrained_node_FoR.rst ================================================ fully_constrained_node_FoR -------------------------- .. autoclass:: sharpy.structure.utils.lagrangeconstraints.fully_constrained_node_FoR :members: ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/generate_lagrange_matrix.rst ================================================ generate_lagrange_matrix ------------------------ .. automodule:: sharpy.structure.utils.lagrangeconstraints.generate_lagrange_matrix ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/hinge_FoR.rst ================================================ hinge_FoR --------- .. autoclass:: sharpy.structure.utils.lagrangeconstraints.hinge_FoR :members: ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/hinge_FoR_wrtG.rst ================================================ hinge_FoR_wrtG -------------- .. autoclass:: sharpy.structure.utils.lagrangeconstraints.hinge_FoR_wrtG :members: ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/hinge_node_FoR.rst ================================================ hinge_node_FoR -------------- .. autoclass:: sharpy.structure.utils.lagrangeconstraints.hinge_node_FoR :members: ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/hinge_node_FoR_constant_vel.rst ================================================ hinge_node_FoR_constant_vel --------------------------- .. autoclass:: sharpy.structure.utils.lagrangeconstraints.hinge_node_FoR_constant_vel :members: ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/index.rst ================================================ LagrangeConstraints library +++++++++++++++++++++++++++ LagrangeConstraints library Library used to create the matrices associated to boundary conditions through the method of Lagrange Multipliers. The source code includes four different sections. * Basic structures: basic functions and variables needed to organise the library with different Lagrange Constraints to enhance the interaction with this library. * Auxiliar functions: basic queries that are performed repeatedly. * Equations: functions that generate the equations associated to the constraint of basic degrees of freedom. * Lagrange Constraints: different available Lagrange Constraints. They tipically use the basic functions in "Equations" to assembly the required set of equations. Attributes: dict_of_lc (dict): Dictionary including the available Lagrange Contraint identifier (``_lc_id``) and the associated ``BaseLagrangeConstraint`` class Notes: To use this library: import sharpy.structure.utils.lagrangeconstraints as lagrangeconstraints Args: lc_list (list): list of all the defined contraints MBdict (dict): dictionary with the MultiBody and LagrangeMultipliers information MB_beam (list): list of :class:`~sharpy.structure.models.beam.Beam` of each of the bodies that form the system MB_tstep (list): list of :class:`~sharpy.utils.datastructures.StructTimeStepInfo` of each of the bodies that form the system num_LM_eq (int): number of new equations needed to define the boundary boundary conditions sys_size (int): total number of degrees of freedom of the multibody system dt (float): time step Lambda (np.ndarray): list of Lagrange multipliers values Lambda_dot (np.ndarray): list of the first derivative of the Lagrange multipliers values dynamic_or_static (str): string defining if the computation is dynamic or static LM_C (np.ndarray): Damping matrix associated to the Lagrange Multipliers equations LM_K (np.ndarray): Stiffness matrix associated to the Lagrange Multipliers equations LM_Q (np.ndarray): Vector of independent terms associated to the Lagrange Multipliers equations .. toctree:: :glob: ./BaseLagrangeConstraint ./constant_rot_vel_FoR ./constant_vel_FoR ./def_rot_axis_FoR_wrt_node_general ./def_rot_axis_FoR_wrt_node_xyz ./def_rot_vect_FoR_wrt_node ./def_rot_vel_FoR_wrt_node ./define_FoR_dof ./define_node_dof ./define_num_LM_eq ./equal_lin_vel_node_FoR ./equal_pos_node_FoR ./free ./fully_constrained_node_FoR ./generate_lagrange_matrix ./hinge_FoR ./hinge_FoR_wrtG ./hinge_node_FoR ./hinge_node_FoR_constant_vel ./initialise_lc ./lagrangeconstraint ./lc_from_string ./lin_vel_node_wrtA ./lin_vel_node_wrtG ./postprocess ./print_available_lc ./remove_constraint ./spherical_FoR ./spherical_node_FoR ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/initialise_lc.rst ================================================ initialise_lc ------------- .. automodule:: sharpy.structure.utils.lagrangeconstraints.initialise_lc ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/lagrangeconstraint.rst ================================================ lagrangeconstraint ------------------ .. automodule:: sharpy.structure.utils.lagrangeconstraints.lagrangeconstraint ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/lc_from_string.rst ================================================ lc_from_string -------------- .. automodule:: sharpy.structure.utils.lagrangeconstraints.lc_from_string ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/lin_vel_node_wrtA.rst ================================================ lin_vel_node_wrtA ----------------- .. autoclass:: sharpy.structure.utils.lagrangeconstraints.lin_vel_node_wrtA :members: ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/lin_vel_node_wrtG.rst ================================================ lin_vel_node_wrtG ----------------- .. autoclass:: sharpy.structure.utils.lagrangeconstraints.lin_vel_node_wrtG :members: ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/postprocess.rst ================================================ postprocess ----------- .. automodule:: sharpy.structure.utils.lagrangeconstraints.postprocess ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/print_available_lc.rst ================================================ print_available_lc ------------------ .. automodule:: sharpy.structure.utils.lagrangeconstraints.print_available_lc ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/remove_constraint.rst ================================================ remove_constraint ----------------- .. automodule:: sharpy.structure.utils.lagrangeconstraints.remove_constraint ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/spherical_FoR.rst ================================================ spherical_FoR ------------- .. autoclass:: sharpy.structure.utils.lagrangeconstraints.spherical_FoR :members: ================================================ FILE: docs/source/includes/structure/utils/lagrangeconstraints/spherical_node_FoR.rst ================================================ spherical_node_FoR ------------------ .. autoclass:: sharpy.structure.utils.lagrangeconstraints.spherical_node_FoR :members: ================================================ FILE: docs/source/includes/structure/utils/modalutils/assert_modes_mass_normalised.rst ================================================ assert_modes_mass_normalised ---------------------------- .. automodule:: sharpy.structure.utils.modalutils.assert_modes_mass_normalised ================================================ FILE: docs/source/includes/structure/utils/modalutils/assert_orthogonal_eigenvectors.rst ================================================ assert_orthogonal_eigenvectors ------------------------------ .. automodule:: sharpy.structure.utils.modalutils.assert_orthogonal_eigenvectors ================================================ FILE: docs/source/includes/structure/utils/modalutils/free_modes_principal_axes.rst ================================================ free_modes_principal_axes ------------------------- .. automodule:: sharpy.structure.utils.modalutils.free_modes_principal_axes ================================================ FILE: docs/source/includes/structure/utils/modalutils/get_mode_zeta.rst ================================================ get_mode_zeta ------------- .. automodule:: sharpy.structure.utils.modalutils.get_mode_zeta ================================================ FILE: docs/source/includes/structure/utils/modalutils/index.rst ================================================ .. toctree:: :glob: ./assert_modes_mass_normalised ./assert_orthogonal_eigenvectors ./free_modes_principal_axes ./get_mode_zeta ./mode_sign_convention ./modes_to_cg_ref ./principal_axes_inertia ./scale_mass_normalised_modes ./scale_mode ./write_modes_vtk ./write_zeta_vtk ================================================ FILE: docs/source/includes/structure/utils/modalutils/mode_sign_convention.rst ================================================ mode_sign_convention -------------------- .. automodule:: sharpy.structure.utils.modalutils.mode_sign_convention ================================================ FILE: docs/source/includes/structure/utils/modalutils/modes_to_cg_ref.rst ================================================ modes_to_cg_ref --------------- .. automodule:: sharpy.structure.utils.modalutils.modes_to_cg_ref ================================================ FILE: docs/source/includes/structure/utils/modalutils/principal_axes_inertia.rst ================================================ principal_axes_inertia ---------------------- .. automodule:: sharpy.structure.utils.modalutils.principal_axes_inertia ================================================ FILE: docs/source/includes/structure/utils/modalutils/scale_mass_normalised_modes.rst ================================================ scale_mass_normalised_modes --------------------------- .. automodule:: sharpy.structure.utils.modalutils.scale_mass_normalised_modes ================================================ FILE: docs/source/includes/structure/utils/modalutils/scale_mode.rst ================================================ scale_mode ---------- .. automodule:: sharpy.structure.utils.modalutils.scale_mode ================================================ FILE: docs/source/includes/structure/utils/modalutils/write_modes_vtk.rst ================================================ write_modes_vtk --------------- .. automodule:: sharpy.structure.utils.modalutils.write_modes_vtk ================================================ FILE: docs/source/includes/structure/utils/modalutils/write_zeta_vtk.rst ================================================ write_zeta_vtk -------------- .. automodule:: sharpy.structure.utils.modalutils.write_zeta_vtk ================================================ FILE: docs/source/includes/structure/utils/xbeamlib/Xbopts.rst ================================================ Xbopts ------ .. autoclass:: sharpy.structure.utils.xbeamlib.Xbopts :members: ================================================ FILE: docs/source/includes/structure/utils/xbeamlib/cbeam3_asbly_dynamic.rst ================================================ cbeam3_asbly_dynamic -------------------- .. automodule:: sharpy.structure.utils.xbeamlib.cbeam3_asbly_dynamic ================================================ FILE: docs/source/includes/structure/utils/xbeamlib/cbeam3_asbly_static.rst ================================================ cbeam3_asbly_static ------------------- .. automodule:: sharpy.structure.utils.xbeamlib.cbeam3_asbly_static ================================================ FILE: docs/source/includes/structure/utils/xbeamlib/cbeam3_correct_gravity_forces.rst ================================================ cbeam3_correct_gravity_forces ----------------------------- .. automodule:: sharpy.structure.utils.xbeamlib.cbeam3_correct_gravity_forces ================================================ FILE: docs/source/includes/structure/utils/xbeamlib/cbeam3_loads.rst ================================================ cbeam3_loads ------------ .. automodule:: sharpy.structure.utils.xbeamlib.cbeam3_loads ================================================ FILE: docs/source/includes/structure/utils/xbeamlib/cbeam3_solv_modal.rst ================================================ cbeam3_solv_modal ----------------- .. automodule:: sharpy.structure.utils.xbeamlib.cbeam3_solv_modal ================================================ FILE: docs/source/includes/structure/utils/xbeamlib/cbeam3_solv_nlnstatic.rst ================================================ cbeam3_solv_nlnstatic --------------------- .. automodule:: sharpy.structure.utils.xbeamlib.cbeam3_solv_nlnstatic ================================================ FILE: docs/source/includes/structure/utils/xbeamlib/index.rst ================================================ .. toctree:: :glob: ./Xbopts ./cbeam3_asbly_dynamic ./cbeam3_asbly_static ./cbeam3_correct_gravity_forces ./cbeam3_loads ./cbeam3_solv_modal ./cbeam3_solv_nlnstatic ./xbeam3_asbly_dynamic ================================================ FILE: docs/source/includes/structure/utils/xbeamlib/xbeam3_asbly_dynamic.rst ================================================ xbeam3_asbly_dynamic -------------------- .. automodule:: sharpy.structure.utils.xbeamlib.xbeam3_asbly_dynamic ================================================ FILE: docs/source/includes/utils/algebra/cross3.rst ================================================ cross3 ------ .. automodule:: sharpy.utils.algebra.cross3 ================================================ FILE: docs/source/includes/utils/algebra/crv2quat.rst ================================================ crv2quat -------- .. automodule:: sharpy.utils.algebra.crv2quat ================================================ FILE: docs/source/includes/utils/algebra/crv2rotation.rst ================================================ crv2rotation ------------ .. automodule:: sharpy.utils.algebra.crv2rotation ================================================ FILE: docs/source/includes/utils/algebra/crv2tan.rst ================================================ crv2tan ------- .. automodule:: sharpy.utils.algebra.crv2tan ================================================ FILE: docs/source/includes/utils/algebra/crv_bounds.rst ================================================ crv_bounds ---------- .. automodule:: sharpy.utils.algebra.crv_bounds ================================================ FILE: docs/source/includes/utils/algebra/der_CcrvT_by_v.rst ================================================ der_CcrvT_by_v -------------- .. automodule:: sharpy.utils.algebra.der_CcrvT_by_v ================================================ FILE: docs/source/includes/utils/algebra/der_Ccrv_by_v.rst ================================================ der_Ccrv_by_v ------------- .. automodule:: sharpy.utils.algebra.der_Ccrv_by_v ================================================ FILE: docs/source/includes/utils/algebra/der_Ceuler_by_v.rst ================================================ der_Ceuler_by_v --------------- .. automodule:: sharpy.utils.algebra.der_Ceuler_by_v ================================================ FILE: docs/source/includes/utils/algebra/der_Ceuler_by_v_NED.rst ================================================ der_Ceuler_by_v_NED ------------------- .. automodule:: sharpy.utils.algebra.der_Ceuler_by_v_NED ================================================ FILE: docs/source/includes/utils/algebra/der_CquatT_by_v.rst ================================================ der_CquatT_by_v --------------- .. automodule:: sharpy.utils.algebra.der_CquatT_by_v ================================================ FILE: docs/source/includes/utils/algebra/der_Cquat_by_v.rst ================================================ der_Cquat_by_v -------------- .. automodule:: sharpy.utils.algebra.der_Cquat_by_v ================================================ FILE: docs/source/includes/utils/algebra/der_Peuler_by_v.rst ================================================ der_Peuler_by_v --------------- .. automodule:: sharpy.utils.algebra.der_Peuler_by_v ================================================ FILE: docs/source/includes/utils/algebra/der_TanT_by_xv.rst ================================================ der_TanT_by_xv -------------- .. automodule:: sharpy.utils.algebra.der_TanT_by_xv ================================================ FILE: docs/source/includes/utils/algebra/der_Tan_by_xv.rst ================================================ der_Tan_by_xv ------------- .. automodule:: sharpy.utils.algebra.der_Tan_by_xv ================================================ FILE: docs/source/includes/utils/algebra/der_Teuler_by_w.rst ================================================ der_Teuler_by_w --------------- .. automodule:: sharpy.utils.algebra.der_Teuler_by_w ================================================ FILE: docs/source/includes/utils/algebra/der_Teuler_by_w_NED.rst ================================================ der_Teuler_by_w_NED ------------------- .. automodule:: sharpy.utils.algebra.der_Teuler_by_w_NED ================================================ FILE: docs/source/includes/utils/algebra/der_quat_wrt_crv.rst ================================================ der_quat_wrt_crv ---------------- .. automodule:: sharpy.utils.algebra.der_quat_wrt_crv ================================================ FILE: docs/source/includes/utils/algebra/der_skewp_skewp_v.rst ================================================ der_skewp_skewp_v ----------------- .. automodule:: sharpy.utils.algebra.der_skewp_skewp_v ================================================ FILE: docs/source/includes/utils/algebra/deuler_dt.rst ================================================ deuler_dt --------- .. automodule:: sharpy.utils.algebra.deuler_dt ================================================ FILE: docs/source/includes/utils/algebra/deuler_dt_NED.rst ================================================ deuler_dt_NED ------------- .. automodule:: sharpy.utils.algebra.deuler_dt_NED ================================================ FILE: docs/source/includes/utils/algebra/euler2quat.rst ================================================ euler2quat ---------- .. automodule:: sharpy.utils.algebra.euler2quat ================================================ FILE: docs/source/includes/utils/algebra/euler2rot.rst ================================================ euler2rot --------- .. automodule:: sharpy.utils.algebra.euler2rot ================================================ FILE: docs/source/includes/utils/algebra/get_transformation_matrix.rst ================================================ get_transformation_matrix ------------------------- .. automodule:: sharpy.utils.algebra.get_transformation_matrix ================================================ FILE: docs/source/includes/utils/algebra/get_triad.rst ================================================ get_triad --------- .. automodule:: sharpy.utils.algebra.get_triad ================================================ FILE: docs/source/includes/utils/algebra/index.rst ================================================ Algebra package +++++++++++++++ Algebra package Extensive library with geometrical and algebraic operations Note: Tests can be found in ``tests/utils/algebra_test`` .. toctree:: :glob: ./cross3 ./crv2quat ./crv2rotation ./crv2tan ./crv_bounds ./der_CcrvT_by_v ./der_Ccrv_by_v ./der_Ceuler_by_v ./der_Ceuler_by_v_NED ./der_CquatT_by_v ./der_Cquat_by_v ./der_Peuler_by_v ./der_TanT_by_xv ./der_Tan_by_xv ./der_Teuler_by_w ./der_Teuler_by_w_NED ./der_quat_wrt_crv ./der_skewp_skewp_v ./deuler_dt ./deuler_dt_NED ./euler2quat ./euler2rot ./get_transformation_matrix ./get_triad ./mat2quat ./multiply_matrices ./norm3d ./normsq3d ./panel_area ./quadskew ./quat2euler ./quat2rotation ./quat_bound ./rotation2crv ./rotation2quat ./rotation3d_x ./rotation3d_y ./rotation3d_z ./skew ./tangent_vector ./triad2rotation ./unit_vector ================================================ FILE: docs/source/includes/utils/algebra/mat2quat.rst ================================================ mat2quat -------- .. automodule:: sharpy.utils.algebra.mat2quat ================================================ FILE: docs/source/includes/utils/algebra/multiply_matrices.rst ================================================ multiply_matrices ----------------- .. automodule:: sharpy.utils.algebra.multiply_matrices ================================================ FILE: docs/source/includes/utils/algebra/norm3d.rst ================================================ norm3d ------ .. automodule:: sharpy.utils.algebra.norm3d ================================================ FILE: docs/source/includes/utils/algebra/normsq3d.rst ================================================ normsq3d -------- .. automodule:: sharpy.utils.algebra.normsq3d ================================================ FILE: docs/source/includes/utils/algebra/panel_area.rst ================================================ panel_area ---------- .. automodule:: sharpy.utils.algebra.panel_area ================================================ FILE: docs/source/includes/utils/algebra/quadskew.rst ================================================ quadskew -------- .. automodule:: sharpy.utils.algebra.quadskew ================================================ FILE: docs/source/includes/utils/algebra/quat2euler.rst ================================================ quat2euler ---------- .. automodule:: sharpy.utils.algebra.quat2euler ================================================ FILE: docs/source/includes/utils/algebra/quat2rotation.rst ================================================ quat2rotation ------------- .. automodule:: sharpy.utils.algebra.quat2rotation ================================================ FILE: docs/source/includes/utils/algebra/quat_bound.rst ================================================ quat_bound ---------- .. automodule:: sharpy.utils.algebra.quat_bound ================================================ FILE: docs/source/includes/utils/algebra/rotation2crv.rst ================================================ rotation2crv ------------ .. automodule:: sharpy.utils.algebra.rotation2crv ================================================ FILE: docs/source/includes/utils/algebra/rotation2quat.rst ================================================ rotation2quat ------------- .. automodule:: sharpy.utils.algebra.rotation2quat ================================================ FILE: docs/source/includes/utils/algebra/rotation3d_x.rst ================================================ rotation3d_x ------------ .. automodule:: sharpy.utils.algebra.rotation3d_x ================================================ FILE: docs/source/includes/utils/algebra/rotation3d_y.rst ================================================ rotation3d_y ------------ .. automodule:: sharpy.utils.algebra.rotation3d_y ================================================ FILE: docs/source/includes/utils/algebra/rotation3d_z.rst ================================================ rotation3d_z ------------ .. automodule:: sharpy.utils.algebra.rotation3d_z ================================================ FILE: docs/source/includes/utils/algebra/skew.rst ================================================ skew ---- .. automodule:: sharpy.utils.algebra.skew ================================================ FILE: docs/source/includes/utils/algebra/tangent_vector.rst ================================================ tangent_vector -------------- .. automodule:: sharpy.utils.algebra.tangent_vector ================================================ FILE: docs/source/includes/utils/algebra/triad2rotation.rst ================================================ triad2rotation -------------- .. automodule:: sharpy.utils.algebra.triad2rotation ================================================ FILE: docs/source/includes/utils/algebra/unit_vector.rst ================================================ unit_vector ----------- .. automodule:: sharpy.utils.algebra.unit_vector ================================================ FILE: docs/source/includes/utils/analytical/flat_plate_analytical.rst ================================================ flat_plate_analytical --------------------- .. automodule:: sharpy.utils.analytical.flat_plate_analytical ================================================ FILE: docs/source/includes/utils/analytical/garrick_drag_pitch.rst ================================================ garrick_drag_pitch ------------------ .. automodule:: sharpy.utils.analytical.garrick_drag_pitch ================================================ FILE: docs/source/includes/utils/analytical/garrick_drag_plunge.rst ================================================ garrick_drag_plunge ------------------- .. automodule:: sharpy.utils.analytical.garrick_drag_plunge ================================================ FILE: docs/source/includes/utils/analytical/index.rst ================================================ Analytical Functions ++++++++++++++++++++ Analytical solutions for 2D aerofoil based on thin plates theory Author: Salvatore Maraniello Date: 23 May 2017 References: 1. Simpson, R.J.S., Palacios, R. & Murua, J., 2013. Induced-Drag Calculations in the Unsteady Vortex Lattice Method. AIAA Journal, 51(7), pp.1775–1779. 2. Gulcat, U., 2009. Propulsive Force of a Flexible Flapping Thin Airfoil. Journal of Aircraft, 46(2), pp.465–473. .. toctree:: :glob: ./flat_plate_analytical ./garrick_drag_pitch ./garrick_drag_plunge ./nc_derivs ./qs_derivs ./sears_CL_freq_resp ./sears_fun ./sears_lift_sin_gust ./theo_CL_freq_resp ./theo_CM_freq_resp ./theo_fun ./theo_lift ./wagner_imp_start ================================================ FILE: docs/source/includes/utils/analytical/nc_derivs.rst ================================================ nc_derivs --------- .. automodule:: sharpy.utils.analytical.nc_derivs ================================================ FILE: docs/source/includes/utils/analytical/qs_derivs.rst ================================================ qs_derivs --------- .. automodule:: sharpy.utils.analytical.qs_derivs ================================================ FILE: docs/source/includes/utils/analytical/sears_CL_freq_resp.rst ================================================ sears_CL_freq_resp ------------------ .. automodule:: sharpy.utils.analytical.sears_CL_freq_resp ================================================ FILE: docs/source/includes/utils/analytical/sears_fun.rst ================================================ sears_fun --------- .. automodule:: sharpy.utils.analytical.sears_fun ================================================ FILE: docs/source/includes/utils/analytical/sears_lift_sin_gust.rst ================================================ sears_lift_sin_gust ------------------- .. automodule:: sharpy.utils.analytical.sears_lift_sin_gust ================================================ FILE: docs/source/includes/utils/analytical/theo_CL_freq_resp.rst ================================================ theo_CL_freq_resp ----------------- .. automodule:: sharpy.utils.analytical.theo_CL_freq_resp ================================================ FILE: docs/source/includes/utils/analytical/theo_CM_freq_resp.rst ================================================ theo_CM_freq_resp ----------------- .. automodule:: sharpy.utils.analytical.theo_CM_freq_resp ================================================ FILE: docs/source/includes/utils/analytical/theo_fun.rst ================================================ theo_fun -------- .. automodule:: sharpy.utils.analytical.theo_fun ================================================ FILE: docs/source/includes/utils/analytical/theo_lift.rst ================================================ theo_lift --------- .. automodule:: sharpy.utils.analytical.theo_lift ================================================ FILE: docs/source/includes/utils/analytical/wagner_imp_start.rst ================================================ wagner_imp_start ---------------- .. automodule:: sharpy.utils.analytical.wagner_imp_start ================================================ FILE: docs/source/includes/utils/control_utils/PID.rst ================================================ PID --- .. autoclass:: sharpy.utils.control_utils.PID :members: ================================================ FILE: docs/source/includes/utils/control_utils/index.rst ================================================ Controller Utilities ++++++++++++++++++++ .. toctree:: :glob: ./PID ================================================ FILE: docs/source/includes/utils/datastructures/AeroTimeStepInfo.rst ================================================ AeroTimeStepInfo ---------------- .. autoclass:: sharpy.utils.datastructures.AeroTimeStepInfo :members: ================================================ FILE: docs/source/includes/utils/datastructures/Linear.rst ================================================ Linear ------ .. autoclass:: sharpy.utils.datastructures.Linear :members: ================================================ FILE: docs/source/includes/utils/datastructures/LinearTimeStepInfo.rst ================================================ LinearTimeStepInfo ------------------ .. autoclass:: sharpy.utils.datastructures.LinearTimeStepInfo :members: ================================================ FILE: docs/source/includes/utils/datastructures/StructTimeStepInfo.rst ================================================ StructTimeStepInfo ------------------ .. autoclass:: sharpy.utils.datastructures.StructTimeStepInfo :members: ================================================ FILE: docs/source/includes/utils/datastructures/index.rst ================================================ Data Management Structures ++++++++++++++++++++++++++ These classes are responsible for storing the aerodynamic and structural time step information and relevant variables. .. toctree:: :glob: ./AeroTimeStepInfo ./Linear ./LinearTimeStepInfo ./StructTimeStepInfo ================================================ FILE: docs/source/includes/utils/docutils/check_folder_in_ignore.rst ================================================ check_folder_in_ignore ---------------------- .. automodule:: sharpy.utils.docutils.check_folder_in_ignore ================================================ FILE: docs/source/includes/utils/docutils/generate_documentation.rst ================================================ generate_documentation ---------------------- .. automodule:: sharpy.utils.docutils.generate_documentation ================================================ FILE: docs/source/includes/utils/docutils/index.rst ================================================ Documentation Generator +++++++++++++++++++++++ Functions to automatically document the code. Comments and complaints: N. Goizueta .. toctree:: :glob: ./check_folder_in_ignore ./generate_documentation ./output_documentation_module_page ./write_file ./write_folder ================================================ FILE: docs/source/includes/utils/docutils/output_documentation_module_page.rst ================================================ output_documentation_module_page -------------------------------- .. automodule:: sharpy.utils.docutils.output_documentation_module_page ================================================ FILE: docs/source/includes/utils/docutils/write_file.rst ================================================ write_file ---------- .. automodule:: sharpy.utils.docutils.write_file ================================================ FILE: docs/source/includes/utils/docutils/write_folder.rst ================================================ write_folder ------------ .. automodule:: sharpy.utils.docutils.write_folder ================================================ FILE: docs/source/includes/utils/exceptions/DocumentationError.rst ================================================ DocumentationError ------------------ .. autoclass:: sharpy.utils.exceptions.DocumentationError :members: ================================================ FILE: docs/source/includes/utils/exceptions/NotConvergedSolver.rst ================================================ NotConvergedSolver ------------------ .. autoclass:: sharpy.utils.exceptions.NotConvergedSolver :members: ================================================ FILE: docs/source/includes/utils/exceptions/NotRecognisedSetting.rst ================================================ NotRecognisedSetting -------------------- .. autoclass:: sharpy.utils.exceptions.NotRecognisedSetting :members: ================================================ FILE: docs/source/includes/utils/exceptions/NotValidSetting.rst ================================================ NotValidSetting --------------- .. autoclass:: sharpy.utils.exceptions.NotValidSetting :members: ================================================ FILE: docs/source/includes/utils/exceptions/index.rst ================================================ SHARPy Exception Classes ++++++++++++++++++++++++ .. toctree:: :glob: ./DocumentationError ./NotConvergedSolver ./NotRecognisedSetting ./NotValidSetting ================================================ FILE: docs/source/includes/utils/frequencyutils/find_limits.rst ================================================ find_limits ----------- .. automodule:: sharpy.utils.frequencyutils.find_limits ================================================ FILE: docs/source/includes/utils/frequencyutils/find_target_system.rst ================================================ find_target_system ------------------ .. automodule:: sharpy.utils.frequencyutils.find_target_system ================================================ FILE: docs/source/includes/utils/frequencyutils/freqresp_relative_error.rst ================================================ freqresp_relative_error ----------------------- .. automodule:: sharpy.utils.frequencyutils.freqresp_relative_error ================================================ FILE: docs/source/includes/utils/frequencyutils/frobenius_norm.rst ================================================ frobenius_norm -------------- .. automodule:: sharpy.utils.frequencyutils.frobenius_norm ================================================ FILE: docs/source/includes/utils/frequencyutils/h_infinity_norm.rst ================================================ h_infinity_norm --------------- .. automodule:: sharpy.utils.frequencyutils.h_infinity_norm ================================================ FILE: docs/source/includes/utils/frequencyutils/hamiltonian.rst ================================================ hamiltonian ----------- .. automodule:: sharpy.utils.frequencyutils.hamiltonian ================================================ FILE: docs/source/includes/utils/frequencyutils/index.rst ================================================ Frequency Space Tools +++++++++++++++++++++ .. toctree:: :glob: ./find_limits ./find_target_system ./freqresp_relative_error ./frobenius_norm ./h_infinity_norm ./hamiltonian ./l2norm ./max_eigs ================================================ FILE: docs/source/includes/utils/frequencyutils/l2norm.rst ================================================ l2norm ------ .. automodule:: sharpy.utils.frequencyutils.l2norm ================================================ FILE: docs/source/includes/utils/frequencyutils/max_eigs.rst ================================================ max_eigs -------- .. automodule:: sharpy.utils.frequencyutils.max_eigs ================================================ FILE: docs/source/includes/utils/generate_cases/AerodynamicInformation.rst ================================================ AerodynamicInformation ---------------------- .. autoclass:: sharpy.utils.generate_cases.AerodynamicInformation :members: ================================================ FILE: docs/source/includes/utils/generate_cases/AeroelasticInformation.rst ================================================ AeroelasticInformation ---------------------- .. autoclass:: sharpy.utils.generate_cases.AeroelasticInformation :members: ================================================ FILE: docs/source/includes/utils/generate_cases/SimulationInformation.rst ================================================ SimulationInformation --------------------- .. autoclass:: sharpy.utils.generate_cases.SimulationInformation :members: ================================================ FILE: docs/source/includes/utils/generate_cases/StructuralInformation.rst ================================================ StructuralInformation --------------------- .. autoclass:: sharpy.utils.generate_cases.StructuralInformation :members: ================================================ FILE: docs/source/includes/utils/generate_cases/clean_test_files.rst ================================================ clean_test_files ---------------- .. automodule:: sharpy.utils.generate_cases.clean_test_files ================================================ FILE: docs/source/includes/utils/generate_cases/from_node_array_to_elem_matrix.rst ================================================ from_node_array_to_elem_matrix ------------------------------ .. automodule:: sharpy.utils.generate_cases.from_node_array_to_elem_matrix ================================================ FILE: docs/source/includes/utils/generate_cases/from_node_list_to_elem_matrix.rst ================================================ from_node_list_to_elem_matrix ----------------------------- .. automodule:: sharpy.utils.generate_cases.from_node_list_to_elem_matrix ================================================ FILE: docs/source/includes/utils/generate_cases/get_airfoil_camber.rst ================================================ get_airfoil_camber ------------------ .. automodule:: sharpy.utils.generate_cases.get_airfoil_camber ================================================ FILE: docs/source/includes/utils/generate_cases/get_aoacl0_from_camber.rst ================================================ get_aoacl0_from_camber ---------------------- .. automodule:: sharpy.utils.generate_cases.get_aoacl0_from_camber ================================================ FILE: docs/source/includes/utils/generate_cases/get_factor_geometric_progression.rst ================================================ get_factor_geometric_progression -------------------------------- .. automodule:: sharpy.utils.generate_cases.get_factor_geometric_progression ================================================ FILE: docs/source/includes/utils/generate_cases/get_mu0_from_camber.rst ================================================ get_mu0_from_camber ------------------- .. automodule:: sharpy.utils.generate_cases.get_mu0_from_camber ================================================ FILE: docs/source/includes/utils/generate_cases/index.rst ================================================ Generate cases ++++++++++++++ Generate cases This library provides functions and classes to help in the definition of SHARPy cases Examples: tests in: tests/utils/generate_cases examples: test/coupled/multibody/fix_node_velocity_wrtG/test_fix_node_velocity_wrtG test/coupled/multibody/fix_node_velocity_wrtA/test_fix_node_velocity_wrtA test/coupled/multibody/double_pendulum/test_double_pendulum_geradin test/coupled/prescribed/WindTurbine/test_rotor Notes: To use this library: import sharpy.utils.generate_cases as generate_cases .. toctree:: :glob: ./AerodynamicInformation ./AeroelasticInformation ./SimulationInformation ./StructuralInformation ./clean_test_files ./from_node_array_to_elem_matrix ./from_node_list_to_elem_matrix ./get_airfoil_camber ./get_aoacl0_from_camber ./get_factor_geometric_progression ./get_mu0_from_camber ./read_column_sheet_type01 ================================================ FILE: docs/source/includes/utils/generate_cases/read_column_sheet_type01.rst ================================================ read_column_sheet_type01 ------------------------ .. automodule:: sharpy.utils.generate_cases.read_column_sheet_type01 ================================================ FILE: docs/source/includes/utils/generator_interface/index.rst ================================================ Generator Interface +++++++++++++++++++ .. toctree:: :glob: ./output_documentation ================================================ FILE: docs/source/includes/utils/generator_interface/output_documentation.rst ================================================ output_documentation -------------------- .. automodule:: sharpy.utils.generator_interface.output_documentation ================================================ FILE: docs/source/includes/utils/geo_utils/generate_naca_camber.rst ================================================ generate_naca_camber -------------------- .. automodule:: sharpy.utils.geo_utils.generate_naca_camber ================================================ FILE: docs/source/includes/utils/geo_utils/index.rst ================================================ Airfoil Geometry Utils ++++++++++++++++++++++ .. toctree:: :glob: ./generate_naca_camber ./interpolate_naca_camber ================================================ FILE: docs/source/includes/utils/geo_utils/interpolate_naca_camber.rst ================================================ interpolate_naca_camber ----------------------- .. automodule:: sharpy.utils.geo_utils.interpolate_naca_camber ================================================ FILE: docs/source/includes/utils/h5utils/add_array_to_grp.rst ================================================ add_array_to_grp ---------------- .. automodule:: sharpy.utils.h5utils.add_array_to_grp ================================================ FILE: docs/source/includes/utils/h5utils/add_as_grp.rst ================================================ add_as_grp ---------- .. automodule:: sharpy.utils.h5utils.add_as_grp ================================================ FILE: docs/source/includes/utils/h5utils/check_file_exists.rst ================================================ check_file_exists ----------------- .. automodule:: sharpy.utils.h5utils.check_file_exists ================================================ FILE: docs/source/includes/utils/h5utils/index.rst ================================================ H5 File Management Utilities ++++++++++++++++++++++++++++ Set of utilities for opening/reading files .. toctree:: :glob: ./add_array_to_grp ./add_as_grp ./check_file_exists ./read_group ./readh5 ./save_list_as_array ./saveh5 ================================================ FILE: docs/source/includes/utils/h5utils/read_group.rst ================================================ read_group ---------- .. automodule:: sharpy.utils.h5utils.read_group ================================================ FILE: docs/source/includes/utils/h5utils/readh5.rst ================================================ readh5 ------ .. automodule:: sharpy.utils.h5utils.readh5 ================================================ FILE: docs/source/includes/utils/h5utils/save_list_as_array.rst ================================================ save_list_as_array ------------------ .. automodule:: sharpy.utils.h5utils.save_list_as_array ================================================ FILE: docs/source/includes/utils/h5utils/saveh5.rst ================================================ saveh5 ------ .. automodule:: sharpy.utils.h5utils.saveh5 ================================================ FILE: docs/source/includes/utils/index.rst ================================================ Utilities --------- .. toctree:: :maxdepth: 1 ./algebra/index ./analytical/index ./control_utils/index ./datastructures/index ./docutils/index ./exceptions/index ./frequencyutils/index ./generate_cases/index ./generator_interface/index ./geo_utils/index ./h5utils/index ./model_utils/index ./multibody/index ./plotutils/index ./settings/index ================================================ FILE: docs/source/includes/utils/model_utils/index.rst ================================================ Modelling Utilities +++++++++++++++++++ Modelling Utilities .. toctree:: :glob: ./mass_matrix_generator ================================================ FILE: docs/source/includes/utils/model_utils/mass_matrix_generator.rst ================================================ mass_matrix_generator --------------------- .. automodule:: sharpy.utils.model_utils.mass_matrix_generator ================================================ FILE: docs/source/includes/utils/multibody/disp_and_accel2state.rst ================================================ disp_and_accel2state -------------------- .. automodule:: sharpy.utils.multibody.disp_and_accel2state ================================================ FILE: docs/source/includes/utils/multibody/get_elems_nodes_list.rst ================================================ get_elems_nodes_list -------------------- .. automodule:: sharpy.utils.multibody.get_elems_nodes_list ================================================ FILE: docs/source/includes/utils/multibody/index.rst ================================================ Multibody library +++++++++++++++++ Multibody library Library used to manipulate multibody systems To use this library: import sharpy.utils.multibody as mb .. toctree:: :glob: ./disp_and_accel2state ./get_elems_nodes_list ./merge_multibody ./split_multibody ./state2disp_and_accel ./update_mb_dB_before_merge ================================================ FILE: docs/source/includes/utils/multibody/merge_multibody.rst ================================================ merge_multibody --------------- .. automodule:: sharpy.utils.multibody.merge_multibody ================================================ FILE: docs/source/includes/utils/multibody/split_multibody.rst ================================================ split_multibody --------------- .. automodule:: sharpy.utils.multibody.split_multibody ================================================ FILE: docs/source/includes/utils/multibody/state2disp_and_accel.rst ================================================ state2disp_and_accel -------------------- .. automodule:: sharpy.utils.multibody.state2disp_and_accel ================================================ FILE: docs/source/includes/utils/multibody/update_mb_dB_before_merge.rst ================================================ update_mb_dB_before_merge ------------------------- .. automodule:: sharpy.utils.multibody.update_mb_dB_before_merge ================================================ FILE: docs/source/includes/utils/plotutils/index.rst ================================================ Plotting utilities ++++++++++++++++++ .. toctree:: :glob: ./plot_timestep ./set_axes_equal ================================================ FILE: docs/source/includes/utils/plotutils/plot_timestep.rst ================================================ plot_timestep ------------- .. automodule:: sharpy.utils.plotutils.plot_timestep ================================================ FILE: docs/source/includes/utils/plotutils/set_axes_equal.rst ================================================ set_axes_equal -------------- .. automodule:: sharpy.utils.plotutils.set_axes_equal ================================================ FILE: docs/source/includes/utils/settings/SettingsTable.rst ================================================ SettingsTable ------------- .. autoclass:: sharpy.utils.settings.SettingsTable :members: ================================================ FILE: docs/source/includes/utils/settings/check_settings_in_options.rst ================================================ check_settings_in_options ------------------------- .. automodule:: sharpy.utils.settings.check_settings_in_options ================================================ FILE: docs/source/includes/utils/settings/index.rst ================================================ Settings Generator Utilities ++++++++++++++++++++++++++++ Settings Generator Utilities .. toctree:: :glob: ./SettingsTable ./check_settings_in_options ./load_config_file ================================================ FILE: docs/source/includes/utils/settings/load_config_file.rst ================================================ load_config_file ---------------- .. automodule:: sharpy.utils.settings.load_config_file ================================================ FILE: docs/source/index.rst ================================================ .. SHARPy documentation master file, created by sphinx-quickstart on Wed Oct 19 16:40:48 2016. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Simulation of High Aspect Ratio planes in Python [SHARPy] ========================================================= .. image:: https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fraw.githubusercontent.com%2FImperialCollegeLondon%2Fsharpy%2Fmaster%2F.version.json .. image:: https://codecov.io/gh/ImperialCollegeLondon/sharpy/branch/main/graph/badge.svg :target: https://codecov.io/gh/ImperialCollegeLondon/sharpy .. image:: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg :target: https://opensource.org/licenses/BSD-3-Clause .. image:: https://readthedocs.org/projects/ic-sharpy/badge/?version=main .. image:: https://joss.theoj.org/papers/f7ccd562160f1a54f64a81e90f5d9af9/status.svg :target: https://joss.theoj.org/papers/f7ccd562160f1a54f64a81e90f5d9af9 .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.3531965.svg :target: https://doi.org/10.5281/zenodo.3531965 Welcome to SHARPy (Simulation of High Aspect Ratio aeroplanes in Python)! SHARPy is an aeroelastic analysis package currently under development at the Department of Aeronautics, Imperial College London. It can be used for the structural, aerodynamic, aeroelastic and flight dynamics analysis of flexible aircraft, flying wings and wind turbines. Amongst other capabilities_, it offers the following solutions to the user: * Static aerodynamic, structural and aeroelastic solutions including fuselage effects * Finding trim conditions for aeroelastic configurations * Nonlinear, dynamic time domain simulations under a large number of conditions such as: + Prescribed trajectories. + Free flight. + Dynamic follower forces. + Control inputs in thrust, control surface deflection... + Arbitrary time-domain gusts, including non span-constant ones. + Full 3D turbulent fields. * Multibody dynamics with hinges, articulations and prescribed nodal motions. + Applicable to wind turbines. + Hinged aircraft. + Catapult assisted takeoffs. * Linear analysis + Linearisation around a nonlinear equilibrium. + Frequency response analysis. + Asymptotic stability analysis. * Model order reduction + Krylov-subspace reduction methods. + Balancing reduction methods. The modular design of SHARPy allows to simulate complex aeroelastic cases involving very flexible aircraft. The structural solver supports very complex beam arrangements, while retaining geometrical nonlinearity. The UVLM solver features different wake modelling fidelities while supporting large lifting surface deformations in a native way. Detailed information on each of the solvers is presented in their respective documentation packages. Contents -------- .. toctree:: :maxdepth: 1 content/installation content/capabilities content/publications content/examples content/contributing content/casefiles content/solvers content/postproc includes/index content/test_cases content/debug content/faqs Citing SHARPy ------------- SHARPy has been published in the Journal of Open Source Software (JOSS) and the relevant paper can be found here_. If you are using SHARPy for your work, please remember to cite it using the paper in JOSS as: del Carre et al., (2019). SHARPy: A dynamic aeroelastic simulation toolbox for very flexible aircraft and wind turbines. Journal of Open Source Software, 4(44), 1885, https://doi.org/10.21105/joss.01885 The bibtex entry for this citation is: .. code-block:: none @Article{delCarre2019, doi = {10.21105/joss.01885}, url = {https://doi.org/10.21105/joss.01885}, year = {2019}, month = dec, publisher = {The Open Journal}, volume = {4}, number = {44}, pages = {1885}, author = {Alfonso del Carre and Arturo Mu{\~{n}}oz-Sim\'on and Norberto Goizueta and Rafael Palacios}, title = {{SHARPy}: A dynamic aeroelastic simulation toolbox for very flexible aircraft and wind turbines}, journal = {Journal of Open Source Software} } .. _here: https://joss.theoj.org/papers/10.21105/joss.01885 Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` Contact ------- SHARPy is developed at the Department of Aeronautics, Imperial College London. To get in touch, visit the `Loads Control and Aeroelastics Lab `_ website. .. _capabilities: ./content/capabilities.html ================================================ FILE: environment.yml ================================================ name: sharpy channels: - conda-forge - defaults dependencies: - eigen - libopenblas - libblas - libcblas - liblapack - libgfortran - libgcc - libgfortran-ng - python=3.10 ================================================ FILE: environment_arm64.yml ================================================ name: sharpy channels: - conda-forge - defaults dependencies: - eigen - libopenblas - libblas - libcblas - liblapack - libgfortran - python=3.10 ================================================ FILE: lib/.placeholder ================================================ ================================================ FILE: lib/CMakeLists.txt ================================================ add_subdirectory(xbeam) add_subdirectory(UVLM) ================================================ FILE: pyproject.toml ================================================ [build-system] requires = [ "setuptools", #"scikit-build>=0.13", "cmake>=3.14.3" ] ================================================ FILE: scripts/__init__.py ================================================ ================================================ FILE: scripts/optimiser/__init__.py ================================================ ================================================ FILE: scripts/optimiser/base_case/generate.py ================================================ #! /usr/bin/env python3 import os import math import h5py as h5 import numpy as np import sharpy.utils.algebra as algebra import sharpy.utils.generate_cases as gc def generate(x_dict={}, case_name=None): """ """ if case_name is None: case_name = 'base' route = os.path.dirname(os.path.realpath(__file__)) + '/' case_notes = str(x_dict) # EXECUTION flow = ['BeamLoader', 'AerogridLoader', # 'StaticTrim', 'StaticCoupled', 'BeamLoads', 'DynamicCoupled', 'PickleData' ] # FLIGHT CONDITIONS # the simulation is set such that the aircraft flies at a u_inf velocity while # the air is calm. try: u_inf = x_dict['release_velocity'] except KeyError: print('Using default value of 10 for u_inf') u_inf = 10 u_inf_cruise = 10 original_u_inf = u_inf u_background = 0.5 rho = 1.225 # trim sigma = 1.5 try: alpha_cato_delta = x_dict['dAoA']*np.pi/180 except KeyError: print('Using default value of 0 for dAoA') alpha_cato_delta = 0*np.pi/180 try: ramp_angle = x_dict['ramp_angle']*np.pi/180 except KeyError: print('Using default value of 0 for ramp_angle') ramp_angle = 0.0 alpha = 4.0782*np.pi/180 + alpha_cato_delta beta = 0 roll = 0 gravity = 'on' cs_deflection = -1.2703*np.pi/180 rudder_static_deflection = 0.0 # rudder_step = 0.0*np.pi/180 thrust = 3.8682 sigma = 1.5 lambda_dihedral = 20*np.pi/180 # trajectory t_start = 1.5 try: acceleration = x_dict['acceleration'] except KeyError: print('Using default value of 3 for acceleration') acceleration = 3. t_ramp = u_inf/acceleration t_finish = t_start + t_ramp t_free = 8 controller_ramp = -1 # numerics n_step = 1 relaxation_factor = 0.4 tolerance = 1e-6 fsi_tolerance = 1e-5 structural_substeps = 1 num_cores = 4 # MODEL GEOMETRY # beam span_main = 16.0 lambda_main = 0.25 ea_main = 0.3 ea = 1e7 ga = 1e5 gj = 1e4 eiy = 2e4 eiz = 4e5 m_bar_main = 0.75 j_bar_main = 0.075 length_fuselage = 10 offset_fuselage = 0 sigma_fuselage = 10 m_bar_fuselage = 0.2 j_bar_fuselage = 0.08 span_tail = 2.5 ea_tail = 0.5 fin_height = 2.5 ea_fin = 0.5 sigma_tail = 10 m_bar_tail = 0.3 j_bar_tail = 0.08 # lumped masses n_lumped_mass = 1 lumped_mass_nodes = np.zeros((n_lumped_mass, ), dtype=int) lumped_mass = np.zeros((n_lumped_mass, )) lumped_mass[0] = 50 lumped_mass_inertia = np.zeros((n_lumped_mass, 3, 3)) lumped_mass_position = np.zeros((n_lumped_mass, 3)) # aero chord_main = 1.0 chord_tail = 0.5 chord_fin = 0.5 # DISCRETISATION # spatial discretisation # chordiwse panels m = 4 # spanwise elements n_elem_multiplier = 2 n_elem_main = int(4*n_elem_multiplier) n_elem_tail = int(2*n_elem_multiplier) n_elem_fin = int(2*n_elem_multiplier) n_elem_fuselage = int(2*n_elem_multiplier) n_surfaces = 5 # temporal discretisation physical_time = t_finish + t_free tstep_factor = 1. dt = 1.0/m/u_inf_cruise*tstep_factor n_tstep = int(round(physical_time/dt)) # END OF INPUT----------------------------------------------------------------- end_of_fuselage_node = 0 flat_end_node = np.zeros((2, ), dtype=int) - 1 # beam processing n_node_elem = 3 span_main1 = (1.0 - lambda_main)*span_main span_main2 = lambda_main*span_main n_elem_main1 = round(n_elem_main*(1 - lambda_main)) n_elem_main2 = n_elem_main - n_elem_main1 # total number of elements n_elem = 0 n_elem += n_elem_main1 + n_elem_main1 n_elem += n_elem_main2 + n_elem_main2 n_elem += n_elem_fuselage n_elem += n_elem_fin n_elem += n_elem_tail + n_elem_tail # number of nodes per part n_node_main1 = n_elem_main1*(n_node_elem - 1) + 1 n_node_main2 = n_elem_main2*(n_node_elem - 1) + 1 n_node_main = n_node_main1 + n_node_main2 - 1 n_node_fuselage = n_elem_fuselage*(n_node_elem - 1) + 1 n_node_fin = n_elem_fin*(n_node_elem - 1) + 1 n_node_tail = n_elem_tail*(n_node_elem - 1) + 1 # total number of nodes n_node = 0 n_node += n_node_main1 + n_node_main1 - 1 n_node += n_node_main2 - 1 + n_node_main2 - 1 n_node += n_node_fuselage - 1 n_node += n_node_fin - 1 n_node += n_node_tail - 1 n_node += n_node_tail - 1 # stiffness and mass matrices n_stiffness = 3 base_stiffness_main = sigma*np.diag([ea, ga, ga, gj, eiy, eiz]) base_stiffness_fuselage = base_stiffness_main.copy()*sigma_fuselage base_stiffness_fuselage[4, 4] = base_stiffness_fuselage[5, 5] base_stiffness_tail = base_stiffness_main.copy()*sigma_tail base_stiffness_tail[4, 4] = base_stiffness_tail[5, 5] n_mass = 3 base_mass_main = np.diag([m_bar_main, m_bar_main, m_bar_main, j_bar_main, 0.5*j_bar_main, 0.5*j_bar_main]) base_mass_fuselage = np.diag([m_bar_fuselage, m_bar_fuselage, m_bar_fuselage, j_bar_fuselage, j_bar_fuselage*0.5, j_bar_fuselage*0.5]) base_mass_tail = np.diag([m_bar_tail, m_bar_tail, m_bar_tail, j_bar_tail, j_bar_tail*0.5, j_bar_tail*0.5]) # PLACEHOLDERS # beam x = np.zeros((n_node, )) y = np.zeros((n_node, )) z = np.zeros((n_node, )) beam_number = np.zeros((n_elem, ), dtype=int) frame_of_reference_delta = np.zeros((n_elem, n_node_elem, 3)) structural_twist = np.zeros((n_elem, n_node_elem)) conn = np.zeros((n_elem, n_node_elem), dtype=int) stiffness = np.zeros((n_stiffness, 6, 6)) elem_stiffness = np.zeros((n_elem, ), dtype=int) mass = np.zeros((n_mass, 6, 6)) elem_mass = np.zeros((n_elem, ), dtype=int) boundary_conditions = np.zeros((n_node, ), dtype=int) app_forces = np.zeros((n_node, 6)) trajectory_file = route + '/' + case_name + '.traj.csv' LC = None first_node_centre = np.zeros((2, ), dtype=int) # aero airfoil_distribution = np.zeros((n_elem, n_node_elem), dtype=int) surface_distribution = np.zeros((n_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces, ), dtype=int) m_distribution = 'uniform' aero_node = np.zeros((n_node,), dtype=bool) twist = np.zeros((n_elem, n_node_elem)) sweep = np.zeros((n_elem, n_node_elem)) chord = np.zeros((n_elem, n_node_elem,)) elastic_axis = np.zeros((n_elem, n_node_elem,)) # FUNCTIONS------------------------------------------------------------- def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) dyn_file_name = route + '/' + case_name + '.dyn.h5' if os.path.isfile(dyn_file_name): os.remove(dyn_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) traj_file_name = route + '/' + case_name + '.traj.csv' if os.path.isfile(traj_file_name): os.remove(traj_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def generate_trajectory_file(): it_start = math.ceil(t_start/dt) it_ramp = math.ceil(t_ramp/dt) it_total = it_start + it_ramp out_t = np.linspace(0, it_total*dt, it_total) out_x = np.zeros((it_total, )) out_y = np.zeros((it_total, )) out_z = np.zeros((it_total, )) t_hist_ramp = np.linspace(0, dt*it_ramp, it_ramp) x_ramp = -0.5*np.cos(ramp_angle)*acceleration*t_hist_ramp**2 z_ramp = 0.5*np.sin(ramp_angle)*acceleration*t_hist_ramp**2 out_x[:it_start] = 0.0 out_x[it_start:] = x_ramp out_z[it_start:] = z_ramp out = np.zeros((it_total, 4)) out[:, 0] = out_t out[:, 1] = out_x out[:, 2] = out_y out[:, 3] = out_z np.savetxt(trajectory_file, out, delimiter=',') def generate_dyn_file(): global dt global n_tstep global route global case_name global num_elem global num_node_elem global num_node global amplitude global period dynamic_forces_time = None with_dynamic_forces = False with_forced_vel = False if with_dynamic_forces: f1 = 100 dynamic_forces = np.zeros((num_node, 6)) app_node = [int(num_node_main - 1), int(num_node_main)] dynamic_forces[app_node, 2] = f1 force_time = np.zeros((n_tstep, )) limit = round(0.05/dt) force_time[50:61] = 1 dynamic_forces_time = np.zeros((n_tstep, num_node, 6)) for it in range(n_tstep): dynamic_forces_time[it, :, :] = force_time[it]*dynamic_forces forced_for_vel = None if with_forced_vel: forced_for_vel = np.zeros((n_tstep, 6)) forced_for_acc = np.zeros((n_tstep, 6)) for it in range(n_tstep): # if dt*it < period: # forced_for_vel[it, 2] = 2*np.pi/period*amplitude*np.sin(2*np.pi*dt*it/period) # forced_for_acc[it, 2] = (2*np.pi/period)**2*amplitude*np.cos(2*np.pi*dt*it/period) forced_for_vel[it, 3] = 2*np.pi/period*amplitude*np.sin(2*np.pi*dt*it/period) forced_for_acc[it, 3] = (2*np.pi/period)**2*amplitude*np.cos(2*np.pi*dt*it/period) if with_dynamic_forces or with_forced_vel: with h5.File(route + '/' + case_name + '.dyn.h5', 'a') as h5file: if with_dynamic_forces: h5file.create_dataset( 'dynamic_forces', data=dynamic_forces_time) if with_forced_vel: h5file.create_dataset( 'for_vel', data=forced_for_vel) h5file.create_dataset( 'for_acc', data=forced_for_acc) h5file.create_dataset( 'num_steps', data=n_tstep) def generate_fem(): stiffness[0, ...] = base_stiffness_main stiffness[1, ...] = base_stiffness_fuselage stiffness[2, ...] = base_stiffness_tail mass[0, ...] = base_mass_main mass[1, ...] = base_mass_fuselage mass[2, ...] = base_mass_tail we = 0 wn = 0 # inner right wing beam_number[we:we + n_elem_main1] = 0 y[wn:wn + n_node_main1] = np.linspace(0.0, span_main1, n_node_main1) for ielem in range(n_elem_main1): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main1] = 0 elem_mass[we:we + n_elem_main1] = 0 flat_end_node[0] = n_node_main1 - 1 first_node_centre[0] = wn + 1 + 1 boundary_conditions[0] = 1 # remember this is in B FoR app_forces[0] = [0, thrust, 0, 0, 0, 0] we += n_elem_main1 wn += n_node_main1 # outer right wing beam_number[we:we + n_elem_main1] = 0 y[wn:wn + n_node_main2 - 1] = y[wn - 1] + np.linspace(0.0, np.cos(lambda_dihedral)*span_main2, n_node_main2)[1:] z[wn:wn + n_node_main2 - 1] = z[wn - 1] + np.linspace(0.0, np.sin(lambda_dihedral)*span_main2, n_node_main2)[1:] for ielem in range(n_elem_main2): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main2] = 0 elem_mass[we:we + n_elem_main2] = 0 boundary_conditions[wn + n_node_main2 - 2] = -1 we += n_elem_main2 wn += n_node_main2 - 1 # inner left wing beam_number[we:we + n_elem_main1 - 1] = 1 y[wn:wn + n_node_main1 - 1] = np.linspace(0.0, -span_main1, n_node_main1)[1:] for ielem in range(n_elem_main1): conn[we + ielem, :] = ((np.ones((3, ))*(we+ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = 0 elem_stiffness[we:we + n_elem_main1] = 0 elem_mass[we:we + n_elem_main1] = 0 flat_end_node[1] = wn + n_node_main1 - 1 - 1 first_node_centre[1] = wn + 1 we += n_elem_main1 wn += n_node_main1 - 1 # outer left wing beam_number[we:we + n_elem_main2] = 1 y[wn:wn + n_node_main2 - 1] = y[wn - 1] + np.linspace(0.0, -np.cos(lambda_dihedral)*span_main2, n_node_main2)[1:] z[wn:wn + n_node_main2 - 1] = z[wn - 1] + np.linspace(0.0, np.sin(lambda_dihedral)*span_main2, n_node_main2)[1:] for ielem in range(n_elem_main2): conn[we + ielem, :] = ((np.ones((3, ))*(we+ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main2] = 0 elem_mass[we:we + n_elem_main2] = 0 boundary_conditions[wn + n_node_main2 - 2] = -1 we += n_elem_main2 wn += n_node_main2 - 1 # fuselage beam_number[we:we + n_elem_fuselage] = 2 x[wn:wn + n_node_fuselage - 1] = np.linspace(0.0, length_fuselage, n_node_fuselage)[1:] z[wn:wn + n_node_fuselage - 1] = np.linspace(0.0, offset_fuselage, n_node_fuselage)[1:] for ielem in range(n_elem_fuselage): conn[we + ielem, :] = ((np.ones((3,))*(we + ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [0.0, 1.0, 0.0] conn[we, 0] = 0 elem_stiffness[we:we + n_elem_fuselage] = 1 elem_mass[we:we + n_elem_fuselage] = 1 we += n_elem_fuselage wn += n_node_fuselage - 1 global end_of_fuselage_node end_of_fuselage_node = wn - 1 # fin beam_number[we:we + n_elem_fin] = 3 x[wn:wn + n_node_fin - 1] = x[end_of_fuselage_node] z[wn:wn + n_node_fin - 1] = z[end_of_fuselage_node] + np.linspace(0.0, fin_height, n_node_fin)[1:] for ielem in range(n_elem_fin): conn[we + ielem, :] = ((np.ones((3,))*(we + ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_of_fuselage_node elem_stiffness[we:we + n_elem_fin] = 2 elem_mass[we:we + n_elem_fin] = 2 we += n_elem_fin wn += n_node_fin - 1 end_of_fin_node = wn - 1 # right tail beam_number[we:we + n_elem_tail] = 4 x[wn:wn + n_node_tail - 1] = x[end_of_fin_node] y[wn:wn + n_node_tail - 1] = np.linspace(0.0, span_tail, n_node_tail)[1:] z[wn:wn + n_node_tail - 1] = z[end_of_fin_node] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_of_fin_node elem_stiffness[we:we + n_elem_tail] = 2 elem_mass[we:we + n_elem_tail] = 2 boundary_conditions[wn + n_node_tail - 2] = -1 we += n_elem_tail wn += n_node_tail - 1 # left tail beam_number[we:we + n_elem_tail] = 5 x[wn:wn + n_node_tail - 1] = x[end_of_fin_node] y[wn:wn + n_node_tail - 1] = np.linspace(0.0, -span_tail, n_node_tail)[1:] z[wn:wn + n_node_tail - 1] = z[end_of_fin_node] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = end_of_fin_node elem_stiffness[we:we + n_elem_tail] = 2 elem_mass[we:we + n_elem_tail] = 2 boundary_conditions[wn + n_node_tail - 2] = -1 we += n_elem_tail wn += n_node_tail - 1 with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=n_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=n_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=n_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) lumped_mass_nodes_handle = h5file.create_dataset( 'lumped_mass_nodes', data=lumped_mass_nodes) lumped_mass_handle = h5file.create_dataset( 'lumped_mass', data=lumped_mass) lumped_mass_inertia_handle = h5file.create_dataset( 'lumped_mass_inertia', data=lumped_mass_inertia) lumped_mass_position_handle = h5file.create_dataset( 'lumped_mass_position', data=lumped_mass_position) def generate_aero_file(): global x, y, z # control surfaces n_control_surfaces = 2 control_surface = np.zeros((n_elem, n_node_elem), dtype=int) - 1 control_surface_type = np.zeros((n_control_surfaces, ), dtype=int) control_surface_deflection = np.zeros((n_control_surfaces, )) control_surface_chord = np.zeros((n_control_surfaces, ), dtype=int) control_surface_hinge_coord = np.zeros((n_control_surfaces, ), dtype=float) # control surface type 0 = static # control surface type 1 = dynamic control_surface_type[0] = 0 control_surface_deflection[0] = cs_deflection control_surface_chord[0] = m control_surface_hinge_coord[0] = -0.25 # nondimensional wrt elastic axis (+ towards the trailing edge) control_surface_type[1] = 0 control_surface_deflection[1] = rudder_static_deflection control_surface_chord[1] = 1 control_surface_hinge_coord[1] = -0. # nondimensional wrt elastic axis (+ towards the trailing edge) we = 0 wn = 0 # right wing (surface 0, beam 0) i_surf = 0 airfoil_distribution[we:we + n_elem_main, :] = 0 surface_distribution[we:we + n_elem_main] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_main] = True temp_chord = np.linspace(chord_main, chord_main, n_node_main) temp_sweep = np.linspace(0.0, 0*np.pi/180, n_node_main) node_counter = 0 for i_elem in range(we, we + n_elem_main): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = temp_chord[node_counter] elastic_axis[i_elem, i_local_node] = ea_main sweep[i_elem, i_local_node] = temp_sweep[node_counter] we += n_elem_main wn += n_node_main # left wing (surface 1, beam 1) i_surf = 1 airfoil_distribution[we:we + n_elem_main, :] = 0 # airfoil_distribution[wn:wn + n_node_main - 1] = 0 surface_distribution[we:we + n_elem_main] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_main - 1] = True # chord[wn:wn + num_node_main - 1] = np.linspace(main_chord, main_tip_chord, num_node_main)[1:] # chord[wn:wn + num_node_main - 1] = main_chord # elastic_axis[wn:wn + num_node_main - 1] = main_ea temp_chord = np.linspace(chord_main, chord_main, n_node_main) node_counter = 0 for i_elem in range(we, we + n_elem_main): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = temp_chord[node_counter] elastic_axis[i_elem, i_local_node] = ea_main sweep[i_elem, i_local_node] = -temp_sweep[node_counter] we += n_elem_main wn += n_node_main - 1 we += n_elem_fuselage wn += n_node_fuselage - 1 - 1 # fin (surface 2, beam 3) i_surf = 2 airfoil_distribution[we:we + n_elem_fin, :] = 1 # airfoil_distribution[wn:wn + n_node_fin] = 0 surface_distribution[we:we + n_elem_fin] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_fin] = True # chord[wn:wn + num_node_fin] = fin_chord for i_elem in range(we, we + n_elem_fin): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_fin elastic_axis[i_elem, i_local_node] = ea_fin control_surface[i_elem, i_local_node] = 1 # twist[end_of_fuselage_node] = 0 # twist[wn:] = 0 # elastic_axis[wn:wn + num_node_main] = fin_ea we += n_elem_fin wn += n_node_fin - 1 # # # # right tail (surface 3, beam 4) i_surf = 3 airfoil_distribution[we:we + n_elem_tail, :] = 2 # airfoil_distribution[wn:wn + n_node_tail] = 0 surface_distribution[we:we + n_elem_tail] = i_surf surface_m[i_surf] = m # XXX not very elegant aero_node[wn:] = True # chord[wn:wn + num_node_tail] = tail_chord # elastic_axis[wn:wn + num_node_main] = tail_ea for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): twist[i_elem, i_local_node] = -0 for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_tail elastic_axis[i_elem, i_local_node] = ea_tail control_surface[i_elem, i_local_node] = 0 we += n_elem_tail wn += n_node_tail # # # left tail (surface 4, beam 5) i_surf = 4 airfoil_distribution[we:we + n_elem_tail, :] = 2 # airfoil_distribution[wn:wn + n_node_tail - 1] = 0 surface_distribution[we:we + n_elem_tail] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_tail - 1] = True # chord[wn:wn + num_node_tail] = tail_chord # elastic_axis[wn:wn + num_node_main] = tail_ea # twist[we:we + num_elem_tail] = -tail_twist for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): twist[i_elem, i_local_node] = -0 for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_tail elastic_axis[i_elem, i_local_node] = ea_tail control_surface[i_elem, i_local_node] = 0 we += n_elem_tail wn += n_node_tail with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( generate_naca_camber(P=0, M=0))) naca_airfoil_tail = airfoils_group.create_dataset('1', data=np.column_stack( generate_naca_camber(P=0, M=0))) naca_airfoil_fin = airfoils_group.create_dataset('2', data=np.column_stack( generate_naca_camber(P=0, M=0))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input .attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # sweep sweep_input = h5file.create_dataset('sweep', data=sweep) dim_attr = sweep_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) control_surface_input = h5file.create_dataset('control_surface', data=control_surface) control_surface_deflection_input = h5file.create_dataset('control_surface_deflection', data=control_surface_deflection) control_surface_chord_input = h5file.create_dataset('control_surface_chord', data=control_surface_chord) control_surface_hinge_coord_input = h5file.create_dataset('control_surface_hinge_coord', data=control_surface_hinge_coord) control_surface_types_input = h5file.create_dataset('control_surface_type', data=control_surface_type) def generate_naca_camber(M=0, P=0): mm = M*1e-2 p = P*1e-1 def naca(x, mm, p): if x < 1e-6: return 0.0 elif x < p: return mm/(p*p)*(2*p*x - x*x) elif x > p and x < 1+1e-6: return mm/((1-p)*(1-p))*(1 - 2*p + 2*p*x - x*x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, mm, p) for x in x_vec]) return x_vec, y_vec def generate_multibody_file(): global end_of_fuselage_node global LC # LCR = gc.LagrangeConstraint() # LCR.behaviour = 'lin_vel_node_wrtG' # LCR.velocity = np.zeros((3,)) # LCR.body_number = 0 # LCR.node_number = flat_end_node[0] # LCL = gc.LagrangeConstraint() # LCL.behaviour = 'lin_vel_node_wrtG' # LCL.velocity = np.zeros((3,)) # LCL.body_number = 0 # LCL.node_number = flat_end_node[1] LCF = gc.LagrangeConstraint() LCF.behaviour = 'lin_vel_node_wrtG' LCF.velocity = np.zeros((3,)) LCF.body_number = 0 LCF.node_number = end_of_fuselage_node LCCR = gc.LagrangeConstraint() LCCR.behaviour = 'lin_vel_node_wrtG' LCCR.velocity = np.zeros((3,)) LCCR.body_number = 0 LCCR.node_number = first_node_centre[0] LCCL = gc.LagrangeConstraint() LCCL.behaviour = 'lin_vel_node_wrtG' LCCL.velocity = np.zeros((3,)) LCCL.body_number = 0 LCCL.node_number = first_node_centre[1] # LC = [LCR, LCL, LCF, LCCR, LCCL] LC = [LCF, LCCR, LCCL] MB1 = gc.BodyInformation() MB1.body_number = 0 MB1.FoR_position = np.zeros((6,)) MB1.FoR_velocity = np.zeros((6,)) MB1.FoR_acceleration = np.zeros((6,)) MB1.FoR_movement = 'free' MB1.quat = np.array([1.0, 0.0, 0.0, 0.0]) MB = [MB1] gc.generate_multibody_file(LC, MB, route, case_name) def generate_solver_file(): file_name = route + '/' + case_name + '.sharpy' settings = dict() settings['SHARPy'] = {'case': case_name, 'route': route, 'flow': flow, 'write_screen': 'off', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} settings['BeamLoader'] = {'unsteady': 'on', 'orientation': algebra.euler2quat(np.array([roll, alpha, beta]))} settings['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', # 'mstar': int(160/tstep_factor), 'mstar': int(100/tstep_factor), 'freestream_dir': ['1', '0', '0'], 'control_surface_deflection': ['', ''], 'control_surface_deflection_generator': {'0': {}, '1': {}}} settings['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-1, 'min_delta': tolerance, 'gravity_on': gravity, 'gravity': 0*9.81} settings['StaticUvlm'] = {'print_info': 'off', 'horseshoe': 'off', 'num_cores': num_cores, 'n_rollup': 0, 'rollup_dt': dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_background, 'u_inf_direction': [1., 0, 0]}, 'rho': 0*rho} settings['StaticCoupled'] = {'print_info': 'off', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': settings['NonLinearStatic'], 'aero_solver': 'StaticUvlm', 'aero_solver_settings': settings['StaticUvlm'], 'max_iter': 100, 'n_load_steps': n_step, 'tolerance': fsi_tolerance, 'relaxation_factor': relaxation_factor} settings['StaticTrim'] = {'solver': 'StaticCoupled', 'solver_settings': settings['StaticCoupled'], 'initial_alpha': alpha, 'initial_deflection': cs_deflection, 'initial_thrust': thrust} settings['NonLinearDynamicCoupledStep'] = {'print_info': 'off', 'max_iterations': 950, 'delta_curved': 1e-1, 'min_delta': tolerance, 'newmark_damp': 1e-2, 'gravity_on': gravity, 'gravity': 9.81, 'num_steps': n_tstep, 'dt': dt, 'initial_velocity': 0} settings['NonLinearDynamicMultibody'] = {'print_info': 'off', 'max_iterations': 950, 'delta_curved': 1e-1, 'min_delta': tolerance, 'newmark_damp': 1e-2, 'gravity_on': gravity, 'gravity': 9.81, 'num_steps': n_tstep, 'dt': dt} relative_motion = 'off' settings['StepUvlm'] = {'print_info': 'off', 'horseshoe': 'off', 'num_cores': num_cores, 'n_rollup': 0, 'convection_scheme': 2, 'rollup_dt': dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'gamma_dot_filtering': 6, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_background, 'u_inf_direction': [1., 0, 0]}, 'rho': rho, 'n_time_steps': n_tstep, 'dt': dt} solver = 'NonLinearDynamicMultibody' settings['PickleData'] = {'folder': route + '/'} settings['DynamicCoupled'] = {'structural_solver': solver, 'structural_solver_settings': settings[solver], 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'fsi_substeps': 200, 'fsi_tolerance': fsi_tolerance, 'relaxation_factor': relaxation_factor, 'minimum_steps': 2, 'relaxation_steps': 150, 'dynamic_relaxation': 'off', 'final_relaxation_factor': 0.5, 'n_time_steps': n_tstep, 'dt': dt, 'structural_substeps': structural_substeps, 'include_unsteady_force_contribution': 'on', 'steps_without_unsteady_force': 9, 'controller_id': {#'controller_right': 'TakeOffTrajectoryController', # 'controller_left': 'TakeOffTrajectoryController', 'controller_tail': 'TakeOffTrajectoryController', 'controller_cright': 'TakeOffTrajectoryController', 'controller_cleft': 'TakeOffTrajectoryController', }, 'controller_settings': { # 'controller_right': # { # 'trajectory_input_file': trajectory_file, # 'dt': dt, # 'trajectory_method': 'lagrange', # 'controlled_constraint': 'constraint_00', # 'initial_ramp_length_structural_substeps': controller_ramp, # 'write_controller_log': 'off', # }, # 'controller_left': # { # 'trajectory_input_file': trajectory_file, # 'dt': dt, # 'trajectory_method': 'lagrange', # 'controlled_constraint': 'constraint_01', # 'initial_ramp_length_structural_substeps': controller_ramp, # 'write_controller_log': 'off', # }, 'controller_tail': { 'trajectory_input_file': trajectory_file, 'dt': dt, 'trajectory_method': 'lagrange', 'controlled_constraint': 'constraint_00', 'initial_ramp_length_structural_substeps': controller_ramp, 'write_controller_log': 'off', }, 'controller_cright': { 'trajectory_input_file': trajectory_file, 'dt': dt, 'trajectory_method': 'lagrange', 'controlled_constraint': 'constraint_01', 'initial_ramp_length_structural_substeps': controller_ramp, 'write_controller_log': 'off', }, 'controller_cleft': { 'trajectory_input_file': trajectory_file, 'dt': dt, 'trajectory_method': 'lagrange', 'controlled_constraint': 'constraint_02', 'initial_ramp_length_structural_substeps': controller_ramp, 'write_controller_log': 'off', }}, 'postprocessors': ['BeamLoads'], 'postprocessors_settings': {'BeamLoads': {'folder': route + '/output/', 'csv_output': 'off'}, 'BeamPlot': {'folder': route + '/output/', 'include_rbm': 'on', 'include_applied_forces': 'on'}, 'AerogridPlot': { 'folder': route + '/output/', 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, } } settings['BeamLoads'] = {'folder': route + '/output/', 'csv_output': 'off'} settings['BeamPlot'] = {'folder': route + '/output/', 'include_rbm': 'on', 'include_applied_forces': 'on', 'include_forward_motion': 'on'} settings['AerogridPlot'] = {'folder': route + '/output/', 'include_rbm': 'on', 'include_forward_motion': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0, 'u_inf': 0, 'dt': dt} settings['Notes'] = {'note': case_notes} import configobj config = configobj.ConfigObj() config.filename = file_name for k, v in settings.items(): config[k] = v config.write() gc.clean_test_files(route, case_name) generate_fem() generate_multibody_file() generate_aero_file() generate_solver_file() generate_dyn_file() if 'StaticTrim' not in flow: generate_trajectory_file() return {'sharpy': route + '/' + case_name + '.sharpy'} if __name__ == "__main__": generate() ================================================ FILE: scripts/optimiser/optimiser.py ================================================ #! /usr/bin/env python3 """ This script is an example of the use of sharpy as a "black box" for optimisation. It works like this: *DRIVER -> *PARSE_INPUTS -> parser.input_file *READ_YAML -> yaml_dict OPTIMISER ( -> yaml_dict, x *WRAPPER ( -> yaml_dict, x *UNFOLD_X -> yaml_dict, x_dict *EVALUATE ( *CASE_ID -> yaml_dict, case_name, x_dict SET_CASE -> file_names RUN_CASE -> data, x_dict, cost_dict *COST_FUNCTION ( # GROUND CLEARANCE CONTRIBUTION *GET_GROUND_CLEARANCE *COST_SIGMOID ) -> cost CLEAN_CASE -> ) ) ) """ import os import sys import glob import shutil import argparse import warnings import random import pprint import numpy as np import scipy import scipy.optimize as optimize from scipy.interpolate import Rbf import yaml import dill as pickle import GPyOpt import sharpy.sharpy_main import sharpy.utils.exceptions as exc cases = list() loads_cost_array = None prev_result = None def driver(): # print information # parse args parser = parse_inputs() # read yaml yaml_dict = read_yaml(parser.input_file) pprint.pprint(yaml_dict) # get previous cases previous_x, previous_y = process_previous_cases(yaml_dict) # call optimiser optimiser(yaml_dict, previous_x, previous_y) # postprocess output return 0 def parse_inputs(): parser = argparse.ArgumentParser(description= """The optimiser.py script is an example of the use of SHARPy as a black box for an optimiser. """) parser.add_argument('input_file', help='input file in YAML format') parser.add_argument("-v", "--verbosity", action="count", default=0, help="increase output verbosity") parser = parser.parse_args() return parser def read_yaml(file_name): """read_yaml """ with open(file_name, 'r') as ifile: try: yaml_dict = yaml.safe_load(ifile) except yaml.YAMLError as exc: print(exc) return yaml_dict def optimiser(in_dict, previous_x, previous_y): settings_dict = in_dict['settings'] case_dict = in_dict['case'] base_dict = in_dict['base'] # create folder for cases if it doesn't exist try: os.mkdir(settings_dict['cases_folder']) except FileExistsError: pass # create folder for cases if it doesn't exist try: os.mkdir((settings_dict['cases_folder'] + '/' + case_dict['name'] + '/').replace('//', '/')) print('Folder made') except FileExistsError: print('cases_folder already exists') # clean folder for the new case to be run case_route = (settings_dict['cases_folder'] + '/' + case_dict['name'] + '/').replace('//', '/') # copy case try: print(base_dict['route'] + '/generate.py', case_route + 'generate.py') shutil.copyfile(base_dict['route'] + '/generate.py', case_route + 'generate.py') except IOError as error: print('Problem copying the case') print('Original error was: {}'.format(error)) # add the case folder to the python path to run generate with it sys.path.append(case_route) # create folder for output if doesnt exist try: os.mkdir(in_dict['case']['output_folder']) except FileExistsError: pass output_route = in_dict['case']['output_folder'] + '/' + in_dict['case']['name'] + '/' if os.path.exists(output_route): warnings.warn('The folder ' + output_route + ' exists, cleaning it.') # cleanup folder try: shutil.rmtree(output_route) except: pass os.mkdir(output_route) n_params = len(in_dict['optimiser']['parameters']) bounds = [] for k, v in in_dict['optimiser']['parameters_initial'].items(): bounds.append({'name': in_dict['optimiser']['parameters'][k], 'type': 'continuous', 'domain': in_dict['optimiser']['parameters_bounds'][k]}) pprint.pprint(bounds) constraints = list() try: length = in_dict['optimiser']['constraints']['ramp_length'] acc_var_i = None release_vel_var_i = None for k, v in in_dict['optimiser']['parameters'].items(): if v == 'acceleration': acc_var_i = k if v == 'release_velocity': release_vel_var_i = k # ramp_length = release_vel**2 / acceleration constraints.append({'name': 'length', 'constraint': '0.5*(x[:, ' + str(release_vel_var_i) + ']**2' + '/x[:, ' + str(acc_var_i) + '])' + ' - ' + str(length)}) except KeyError: pass try: limit = in_dict['optimiser']['constraints']['incidence_angle']['limit'] base_aoa = in_dict['optimiser']['constraints']['incidence_angle']['base_aoa'] dAoA_var_i = None ramp_angle_var_i = None for k, v in in_dict['optimiser']['parameters'].items(): if v == 'dAoA': dAoA_var_i = k if v == 'ramp_angle': ramp_angle_var_i = k # base_aoa + dAoA - ramp_angle < limit constraint_string = '' constraint_string += str(base_aoa) + ' + ' constraint_string += 'x[:, ' + str(dAoA_var_i) + '] - ' constraint_string += 'x[:, ' + str(ramp_angle_var_i) + '] - ' constraint_string += str(limit) constraints.append({'name': 'angle', 'constraint': constraint_string}) except KeyError: pass print(constraints) gpyopt_wrapper = lambda x: wrapper(x, in_dict) batch_size = in_dict['optimiser']['numerics']['batch_size'] num_cores = in_dict['optimiser']['numerics']['n_cores'] opt = GPyOpt.methods.BayesianOptimization( f=gpyopt_wrapper, domain=bounds, exact_feval=True, model_type='GP', acquisition_type='EI', normalize_y=False, initial_design_numdata=in_dict['optimiser']['numerics']['initial_design_numdata'], evaluator_type='local_penalization', batch_size=batch_size, num_cores=num_cores, acquisition_jitter=0, de_duplication=True, constraints=constraints, X=previous_x, Y=previous_y) opt.run_optimization(in_dict['optimiser']['numerics']['n_iter'], report_file=output_route + 'report.log', evaluations_file=output_route + 'evaluations.log', models_file=output_route + 'models.log', verbosity=True ) print('*'*60) print('Best one cost: ', opt.fx_opt) print('\tParameters: ', opt.x_opt) print('*'*60) with open(output_route + 'optimiser.pkl', 'wb') as f: pickle.dump(opt, f, protocol=pickle.HIGHEST_PROTOCOL) print('Running local optimisation step') local_x, local_cost = local_optimisation(opt, in_dict) if np.linalg.norm(opt.x_opt - local_x) < 1e-1: print('Results are very close, no need to dig deeper') else: new_cost = gpyopt_wrapper(local_x) print('New cost: ', new_cost) print('Improvement over the previous solution with the local min.: ', -(local_cost - opt.fx_opt)/opt.fx_opt*100, '%') print('The RBF estimation of the cost was off by: ', (local_cost - new_cost)/new_cost) print('FINISHED') def local_optimisation(opt, yaml_dict=None, min_method='Powell'): x_in = opt.X y_in = opt.Y # rbf = create_rbf_surrogate(x_in, y_in) points = x_in values = y_in method = 'linear' options = {'eps': 0.1, 'gtol': 1e-3} # scipy.optimize local_opt = optimize.minimize( lambda x: gp_constrained(x, opt, yaml_dict), x0=opt.x_opt, method=min_method, options=options, jac='2-point') print('Local optimisation result: ') print('X = ', local_opt.x) print('f(X) = ', local_opt.fun) print('sucess = ', local_opt.success) print('n_inter = ', local_opt.nit) print('message = ', local_opt.message) return local_opt.x, local_opt.fun # def create_gp_surrogate(opt, yaml_dict): # breakpoint() # opt.mode def gp_constrained(x_in, opt, yaml_dict): values, _ = opt.model.predict(np.atleast_2d(x_in)) parameters = yaml_dict['optimiser']['parameters'] bounds = np.zeros((len(parameters), 2)) for k, v in parameters.items(): bounds[k, :] = yaml_dict['optimiser']['parameters_bounds'][k] constraints_list = opt.constraints constraints = list() for v in constraints_list: constraints.append(v['constraint']) constraints[-1] = constraints[-1].replace(':,', '') + ' <= 0' multidim = True if len(x_in.shape) == 1: multidim = False if multidim: for i in range(x_in.shape[0]): for i_cons in range(len(constraints)): x = x_in[i, :] if not eval(constraints[i_cons]): values[i] += 15 else: for i_cons in range(len(constraints)): x = x_in if not eval(constraints[i_cons]): values += 15 return values def rbf_constrained(x_in, rbf, yaml_dict, opt): parameters = yaml_dict['optimiser']['parameters'] bounds = np.zeros((len(parameters), 2)) for k, v in parameters.items(): bounds[k, :] = yaml_dict['optimiser']['parameters_bounds'][k] constraints_list = opt.constraints constraints = list() for v in constraints_list: constraints.append(v['constraint']) constraints[-1] = constraints[-1].replace(':,', '') + ' <= 0' values = rbf(*x_in) multidim = True if len(x_in.shape) == 1: multidim = False if multidim: for i in range(x_in.shape[0]): for i_cons in range(len(constraints)): x = x_in[i, :] if not eval(constraints[i_cons]): values[i] += 15 else: for i_cons in range(len(constraints)): x = x_in if not eval(constraints[i_cons]): values += 15 return values def create_rbf_surrogate(X, Y): rbf = Rbf(*(X.T), Y, function='multiquadric') return rbf def case_id(case, x_dict): case_name = case for k, v in x_dict.items(): case_name += f'_{k}_{v:7.5f}' case_name = case_name.replace('.', 'p') return case_name def evaluate(x_dict, yaml_dict): case_name = case_id(yaml_dict['case']['name'], x_dict) print('Running ' + case_name) files, case_name = set_case(case_name, yaml_dict['base'], x_dict, yaml_dict['settings'], yaml_dict['case']) data = run_case(files) cost = cost_function(data, x_dict, yaml_dict['optimiser']['cost']) print(' Case: ' + str(case_name) + '; cost = ', cost) if data is not None: data.cost = cost if yaml_dict['settings']['delete_case_folders']: raise NotImplementedError('delete_case_folders not supported yet') if yaml_dict['settings']['save_data']: try: os.mkdir(yaml_dict['settings']['cases_folder'] + '/' + yaml_dict['case']['name'] + '/') except FileExistsError: pass with open(yaml_dict['settings']['cases_folder'] + '/' + yaml_dict['case']['name'] + '/' + 'data.pkl', 'wb') as data_file: pickle.dump(data, data_file, -1) return cost def wrapper(x, yaml_dict): x_dict = unfold_x(x, yaml_dict['optimiser']['parameters']) cost = evaluate(x_dict, yaml_dict) return cost def set_case(case_name, base_dict, x_dict, settings_dict, case_dict): """set_case: takes care of the setup of the case This function copies the original case, given by route_base, then adds the folder to the python path, runs the generate.py file in there and removes the folder from the path. Args: case_name (str): name of the new case. base_dict(dict): dictionary with the base case info x_dict(dict): dictionary of state variables """ # runs the generate.py import generate file_names = generate.generate(x_dict, case_name) return file_names, case_name def run_case(files): try: warnings.filterwarnings('ignore') data = sharpy.sharpy_main.main(args=['', files['sharpy']]) except exc.NotConvergedSolver: print('The solver is not converged in this simulation with inputs') print('Returning None as data') data = None return data def cost_function(data, x_dict, cost_dict, insight=False): """ x_dict is here to potentially impose constraints on the optimised parameters """ cost = 0.0 clearance_cost = 0.0 loading_cost = 0.0 output_dict = dict() # check for data == None: if data is None: cost = 15. # need a better way return cost # ground clearance cost contribution try: cost_dict['ground_clearance'] clearance, ts_clearance = get_ground_clearance(data) output_dict['ground_clearance'] = dict() clearance_cost = cost_sigmoid(clearance, **cost_dict['ground_clearance']) output_dict['ground_clearance']['clearance'] = clearance output_dict['ground_clearance']['cost'] = clearance_cost cost += clearance_cost except KeyError: pass # loads cost contribution try: cost_dict['loads'] loading_cost = loads_cost(data, cost_dict['loads']) cost += loading_cost output_dict['loads'] = {'cost': loading_cost} except KeyError: pass if insight: return cost, output_dict else: return cost # def loads_cost(data, cost_loads_dict): # index2load = {0: 'Torsion', # 1: 'OOP', # 2: 'IP'} # try: # loads_array = np.loadtxt( # cost_loads_dict['reference_loads'], # skiprows=1, # delimiter=',') # except OSError: # try: # warnings.warn( # 'Not found reference_loads file, trying parent folder') # loads_array = np.loadtxt( # '../' + cost_loads_dict['reference_loads'], # skiprows=1, # delimiter=',') # except OSError: # warnings.warn('Not found reference_loads file, anywhere. Filling up with ones instead') # loads_array = np.ones((data.structure.ini_info.psi.shape[0], 4)) # separate_cost = np.zeros((3,)) # loads_array = np.abs(loads_array) # loads_array_norm = np.linalg.norm(loads_array, axis=0) # for row in range(loads_array.shape[0]): # for col in range(loads_array.shape[1]): # if loads_array[row, col] < loads_array_norm[col]: # loads_array[row, col] = loads_array_norm[col] # max_cost = np.zeros((3,)) # for it, tstep in enumerate(data.structure.timestep_info): # temp = np.abs(tstep.postproc_cell['loads'][:, 3:]) # max_vals = np.max(temp/loads_array[:, 1:] - 1.0, axis=0) # for i_dim in range(3): # max_cost[i_dim] = max(max_cost[i_dim], max_vals[i_dim]) # separate_cost = max_cost # for k, v in index2load.items(): # separate_cost[k] *= cost_loads_dict[v]['scale'] # return np.sum(separate_cost) def loads_cost(data, cost_loads_dict): index2load = {0: 'Torsion', 1: 'OOP', 2: 'IP'} try: loads_array = np.loadtxt( cost_loads_dict['reference_loads'], skiprows=1, delimiter=',') except OSError: try: warnings.warn( 'Not found reference_loads file, trying parent folder') loads_array = np.loadtxt( '../' + cost_loads_dict['reference_loads'], skiprows=1, delimiter=',') except OSError: warnings.warn('Not found reference_loads file, anywhere. Filling up with ones instead') loads_array = np.ones((data.structure.ini_info.psi.shape[0], 4)) separate_cost = np.zeros((3,)) loads_array_root = np.abs(loads_array[0, 1:]) # max_cost = np.zeros((3,)) # for it, tstep in enumerate(data.structure.timestep_info): # temp = np.abs(tstep.postproc_cell['loads'][:, 3:]) # max_vals = np.max(temp/loads_array[:, 1:] - 1.0, axis=0) # for i_dim in range(3): # max_cost[i_dim] = max(max_cost[i_dim], max_vals[i_dim]) # separate_cost = max_cost loads_history = np.zeros((len(data.structure.timestep_info), 3)) for it, tstep in enumerate(data.structure.timestep_info): loads_history[it, :] = tstep.postproc_cell['loads'][0, 3:]/loads_array_root separate_cost = np.max(loads_history, axis=0) - 1. separate_cost = separate_cost*(separate_cost > 0) for k, v in index2load.items(): separate_cost[k] *= cost_loads_dict[v]['scale'] return np.sum(separate_cost) def get_ground_clearance(data): """ Extracts the minimum value of for_pos[2] and returns that value and the timestep it happened at. """ structure = data.structure min_clear = np.PINF ts_min_clear = None for ts, tstep in enumerate(structure.timestep_info): try: tstep.mb_dict['constraint_00'] continue except KeyError: pass if tstep.for_pos[2] < min_clear: min_clear = tstep.for_pos[2] ts_min_clear = ts return min_clear, ts_min_clear def cost_sigmoid(z, z_min, z_0, x_offset=0.5, offset=0.0, scale=1.): # I need the input to f to be between 0 and 1 for relevant values def sigmoid_mod(x, c=4, x_offset=0.0, offset=0.0, scale=1.): return scale/(1. + np.exp(c*(x - x_offset))) + offset val = sigmoid_mod((z - z_min)/(z_0 - z_min), x_offset=x_offset, offset=offset, c=5, scale=scale) return val def unfold_x(x, parameters_dict): x_dict = dict() for k, v in parameters_dict.items(): x_dict[v] = x.flatten()[k] return x_dict def process_previous_cases(yaml_dict): try: previous_cases_string = yaml_dict['previous_data']['cases'] except KeyError: return None, None n_cases = len(glob.glob(previous_cases_string)) x_out = np.zeros((n_cases, len(yaml_dict['optimiser']['parameters']))) y_out = np.zeros((n_cases, 1)) for i, f in enumerate(glob.glob(previous_cases_string)): print('Loading ', f) with open(f, 'rb') as fhandle: data = pickle.load(fhandle) x_vec, x_dict = x_vec_from_data(data, yaml_dict['optimiser']['parameters']) x_out[i, :] = x_vec cost = cost_function(data, x_dict, yaml_dict['optimiser']['cost']) y_out[i, 0] = cost return x_out, y_out def x_vec_from_data(data, param_dict): input_dict = eval(data.settings['Notes']['note']) x_vec = np.zeros((len(param_dict),)) for k, v in param_dict.items(): x_vec[k] = input_dict[v] return x_vec, input_dict if __name__ == '__main__': driver() ================================================ FILE: scripts/optimiser/optimiser_input.yaml ================================================ case: name: r05_sc0p2_newcost output_folder: ./output/ base: name: base route: ./base_case/ generate_file: generate.py settings: delete_case_folders: false cases_folder: ./cases/ save_data: true previous_data: cases: ../../../../optimisers_postproc/cases/r10_sc0p1_newcost/*.pkl optimiser: numerics: tolerance: 0.01 n_iter: 30 batch_size: 4 n_cores: 16 initial_design_numdata: 4 parameters: 0: acceleration 1: dAoA 2: ramp_angle 3: release_velocity parameters_initial: 0: 2.0 1: 0.1 2: 0.0 3: 9.5 parameters_bounds: 0: [1.0, 7.0] 1: [-1.0, 8.0] 2: [-1.0, 8.0] 3: [3.0, 15.0] cost: ground_clearance: z_min: -4 z_0: 2 scale: 10 loads: reference_loads: 'input/cruise_loads.csv' Torsion: scale: 0.2 OOP: scale: 0.2 IP: scale: 0.2 constraints: ramp_length: 5.0 incidence_angle: limit: 9.0 base_aoa: 4.09 ================================================ FILE: scripts/xplaneUDPout/HALE_varDIe.acf ================================================ I 1004 version ACF PROPERTIES_BEGIN P _cgpt/0/_name empty craft P _cgpt/0/_w_max 142.199996948 P _cgpt/0/_w_now 142.199996948 P _cgpt/0/_w_test 142.198120117 P _cgpt/0/_z_ref 0.0 P _cgpt/1/_name fuel tank #1 P _cgpt/1/_w_max 0.0 P _cgpt/1/_w_now 1.0 P _cgpt/1/_w_test 0.0 P _cgpt/1/_z_ref 0.0 P _cgpt/10/_w_max 0.0 P _cgpt/10/_w_now 1.0 P _cgpt/10/_w_test 0.0 P _cgpt/10/_z_ref 0.0 P _cgpt/11/_w_max 0.0 P _cgpt/11/_w_now 1.0 P _cgpt/11/_w_test 0.0 P _cgpt/11/_z_ref 0.0 P _cgpt/12/_w_max 0.0 P _cgpt/12/_w_now 1.0 P _cgpt/12/_w_test 0.0 P _cgpt/12/_z_ref 0.0 P _cgpt/13/_w_max 0.0 P _cgpt/13/_w_now 1.0 P _cgpt/13/_w_test 0.0 P _cgpt/13/_z_ref 0.0 P _cgpt/14/_w_max 0.0 P _cgpt/14/_w_now 1.0 P _cgpt/14/_w_test 0.0 P _cgpt/14/_z_ref 0.0 P _cgpt/15/_w_max 0.0 P _cgpt/15/_w_now 1.0 P _cgpt/15/_w_test 0.0 P _cgpt/15/_z_ref 0.0 P _cgpt/16/_w_max 0.0 P _cgpt/16/_w_now 1.0 P _cgpt/16/_w_test 0.0 P _cgpt/16/_z_ref 0.0 P _cgpt/17/_w_max 0.0 P _cgpt/17/_w_now 1.0 P _cgpt/17/_w_test 0.0 P _cgpt/17/_z_ref 0.0 P _cgpt/18/_w_max 0.0 P _cgpt/18/_w_now 1.0 P _cgpt/18/_w_test 0.0 P _cgpt/18/_z_ref 0.0 P _cgpt/19/_w_max 0.0 P _cgpt/19/_w_now 1.0 P _cgpt/19/_w_test 0.0 P _cgpt/19/_z_ref 0.0 P _cgpt/2/_name fuel tank #2 P _cgpt/2/_w_max 0.0 P _cgpt/2/_w_now 1.0 P _cgpt/2/_w_test 0.0 P _cgpt/2/_z_ref 0.0 P _cgpt/3/_name fuel tank #3 P _cgpt/3/_w_max 0.0 P _cgpt/3/_w_now 1.0 P _cgpt/3/_w_test 0.0 P _cgpt/3/_z_ref 0.0 P _cgpt/4/_name fuel tank #4 P _cgpt/4/_w_max 0.0 P _cgpt/4/_w_now 1.0 P _cgpt/4/_w_test 0.0 P _cgpt/4/_z_ref 0.0 P _cgpt/5/_name fuel tank #5 P _cgpt/5/_w_max 0.0 P _cgpt/5/_w_now 1.0 P _cgpt/5/_w_test 0.0 P _cgpt/5/_z_ref 0.0 P _cgpt/6/_name fuel tank #6 P _cgpt/6/_w_max 0.0 P _cgpt/6/_w_now 1.0 P _cgpt/6/_w_test 0.0 P _cgpt/6/_z_ref 0.0 P _cgpt/7/_name fuel tank #7 P _cgpt/7/_w_max 0.0 P _cgpt/7/_w_now 1.0 P _cgpt/7/_w_test 0.0 P _cgpt/7/_z_ref 0.0 P _cgpt/8/_name fuel tank #8 P _cgpt/8/_w_max 0.0 P _cgpt/8/_w_now 1.0 P _cgpt/8/_w_test 0.0 P _cgpt/8/_z_ref 0.0 P _cgpt/9/_name fuel tank #9 P _cgpt/9/_w_max 0.0 P _cgpt/9/_w_now 1.0 P _cgpt/9/_w_test 0.0 P _cgpt/9/_z_ref 0.0 P _cgpt/count 20 P _door/0/_type 0 P _door/1/_type 0 P _door/10/_type 0 P _door/11/_type 0 P _door/12/_type 0 P _door/13/_type 0 P _door/14/_type 0 P _door/15/_type 0 P _door/16/_type 0 P _door/17/_type 0 P _door/18/_type 0 P _door/19/_type 0 P _door/2/_type 0 P _door/3/_type 0 P _door/4/_type 0 P _door/5/_type 0 P _door/6/_type 0 P _door/7/_type 0 P _door/8/_type 0 P _door/9/_type 0 P _door/count 20 P _engn/count 8 P _gear/0/_axiE 0.0 P _gear/0/_axiN 0.0 P _gear/0/_axiR 0.0 P _gear/0/_cyc_time 5.0 P _gear/0/_damp -nan P _gear/0/_dep_rat 1.0 P _gear/0/_eagle_claw_deg 0.0 P _gear/0/_gear_brakes 0 P _gear/0/_gear_can_retract 0 P _gear/0/_gear_castors 0 P _gear/0/_gear_def_empty 0.0 P _gear/0/_gear_def_gross 0.0 P _gear/0/_gear_load_fraction -nan P _gear/0/_gear_type 2 P _gear/0/_gear_x 0.0 P _gear/0/_gear_y 0.0 P _gear/0/_gear_z 0.0 P _gear/0/_latE 0.0 P _gear/0/_latN 0.0 P _gear/0/_latR 0.0 P _gear/0/_leg_len 0.0 P _gear/0/_lonE 0.0 P _gear/0/_lonN 0.0 P _gear/0/_lonR 0.0 P _gear/0/_scon -nan P _gear/0/_stat_def 0.0 P _gear/0/_steerdeg_hispeed 0.0 P _gear/0/_steerdeg_lospeed 0.0 P _gear/0/_strut_comp 0.0 P _gear/0/_strut_s1 0.0 P _gear/0/_strut_s2 0.015625000 P _gear/0/_strut_t1 0.128906250 P _gear/0/_strut_t2 0.252929688 P _gear/0/_tire_mi 0.0 P _gear/0/_tire_radius 0.0 P _gear/0/_tire_swidth 0.0 P _gear/1/_axiE 0.0 P _gear/1/_axiN 0.0 P _gear/1/_axiR 0.0 P _gear/1/_cyc_time 5.0 P _gear/1/_damp -nan P _gear/1/_dep_rat 1.0 P _gear/1/_eagle_claw_deg 0.0 P _gear/1/_gear_brakes 0 P _gear/1/_gear_can_retract 0 P _gear/1/_gear_castors 0 P _gear/1/_gear_def_empty 0.0 P _gear/1/_gear_def_gross 0.0 P _gear/1/_gear_load_fraction -nan P _gear/1/_gear_type 2 P _gear/1/_gear_x 0.0 P _gear/1/_gear_y 0.0 P _gear/1/_gear_z 0.0 P _gear/1/_latE 0.0 P _gear/1/_latN 0.0 P _gear/1/_latR 0.0 P _gear/1/_leg_len 0.0 P _gear/1/_lonE 0.0 P _gear/1/_lonN 0.0 P _gear/1/_lonR 0.0 P _gear/1/_scon -nan P _gear/1/_stat_def 0.0 P _gear/1/_steerdeg_hispeed 0.0 P _gear/1/_steerdeg_lospeed 0.0 P _gear/1/_strut_comp 0.0 P _gear/1/_strut_s1 0.0 P _gear/1/_strut_s2 0.015625000 P _gear/1/_strut_t1 0.128906250 P _gear/1/_strut_t2 0.252929688 P _gear/1/_tire_mi 0.0 P _gear/1/_tire_radius 0.0 P _gear/1/_tire_swidth 0.0 P _gear/2/_axiE 0.0 P _gear/2/_axiN 0.0 P _gear/2/_axiR 0.0 P _gear/2/_cyc_time 5.0 P _gear/2/_damp -nan P _gear/2/_dep_rat 1.0 P _gear/2/_eagle_claw_deg 0.0 P _gear/2/_gear_brakes 0 P _gear/2/_gear_can_retract 0 P _gear/2/_gear_castors 0 P _gear/2/_gear_def_empty 0.0 P _gear/2/_gear_def_gross 0.0 P _gear/2/_gear_load_fraction -nan P _gear/2/_gear_type 2 P _gear/2/_gear_x 0.0 P _gear/2/_gear_y 0.0 P _gear/2/_gear_z 0.0 P _gear/2/_latE 0.0 P _gear/2/_latN 0.0 P _gear/2/_latR 0.0 P _gear/2/_leg_len 0.0 P _gear/2/_lonE 0.0 P _gear/2/_lonN 0.0 P _gear/2/_lonR 0.0 P _gear/2/_scon -nan P _gear/2/_stat_def 0.0 P _gear/2/_steerdeg_hispeed 0.0 P _gear/2/_steerdeg_lospeed 0.0 P _gear/2/_strut_comp 0.0 P _gear/2/_strut_s1 0.0 P _gear/2/_strut_s2 0.015625000 P _gear/2/_strut_t1 0.128906250 P _gear/2/_strut_t2 0.252929688 P _gear/2/_tire_mi 0.0 P _gear/2/_tire_radius 0.0 P _gear/2/_tire_swidth 0.0 P _gear/3/_gear_type 0 P _gear/4/_gear_type 0 P _gear/5/_gear_type 0 P _gear/6/_gear_type 0 P _gear/7/_gear_type 0 P _gear/8/_gear_type 0 P _gear/9/_gear_type 0 P _gear/count 10 P _lite/count 0 P _lite_equip/_land_lite_off_on_retract_EQ 0 P _lite_equip/_taxi_lite_off_on_retract_EQ 0 P _obja/count 0 P _panel_inst_3d 1 P _part/16/_aero_x_os 0.0 P _part/16/_aero_y_os 0.0 P _part/16/_aero_z_os 0.0 P _part/16/_area_frnt 0.0 P _part/16/_area_side 0.0 P _part/16/_area_vert 0.0 P _part/16/_bot_s1 0.755999982 P _part/16/_bot_s2 1.0 P _part/16/_bot_t1 0.509999990 P _part/16/_bot_t2 0.754000008 P _part/16/_damp 1.883831739 P _part/16/_geo_xyz/0,0,0 0.0 P _part/16/_geo_xyz/0,0,1 12.303149223 P _part/16/_geo_xyz/0,0,2 32.398292542 P _part/16/_geo_xyz/0,1,0 0.0 P _part/16/_geo_xyz/0,1,1 12.332676888 P _part/16/_geo_xyz/0,1,2 32.447505951 P _part/16/_geo_xyz/0,10,0 0.0 P _part/16/_geo_xyz/0,10,1 12.229330063 P _part/16/_geo_xyz/0,10,2 32.808399200 P _part/16/_geo_xyz/0,11,0 0.0 P _part/16/_geo_xyz/0,11,1 12.244093895 P _part/16/_geo_xyz/0,11,2 32.562335968 P _part/16/_geo_xyz/0,12,0 0.0 P _part/16/_geo_xyz/0,12,1 12.273621559 P _part/16/_geo_xyz/0,12,2 32.447505951 P _part/16/_geo_xyz/0,13,0 0.0 P _part/16/_geo_xyz/0,13,1 12.303149223 P _part/16/_geo_xyz/0,13,2 32.398292542 P _part/16/_geo_xyz/0,14,0 0.0 P _part/16/_geo_xyz/0,14,1 0.0 P _part/16/_geo_xyz/0,14,2 0.0 P _part/16/_geo_xyz/0,15,0 0.0 P _part/16/_geo_xyz/0,15,1 0.0 P _part/16/_geo_xyz/0,15,2 0.0 P _part/16/_geo_xyz/0,16,0 0.0 P _part/16/_geo_xyz/0,16,1 0.0 P _part/16/_geo_xyz/0,16,2 0.0 P _part/16/_geo_xyz/0,17,0 0.0 P _part/16/_geo_xyz/0,17,1 0.0 P _part/16/_geo_xyz/0,17,2 0.0 P _part/16/_geo_xyz/0,2,0 0.0 P _part/16/_geo_xyz/0,2,1 12.362204552 P _part/16/_geo_xyz/0,2,2 32.562335968 P _part/16/_geo_xyz/0,3,0 0.0 P _part/16/_geo_xyz/0,3,1 12.376968384 P _part/16/_geo_xyz/0,3,2 32.808399200 P _part/16/_geo_xyz/0,4,0 0.0 P _part/16/_geo_xyz/0,4,1 12.376968384 P _part/16/_geo_xyz/0,4,2 33.136482239 P _part/16/_geo_xyz/0,5,0 0.0 P _part/16/_geo_xyz/0,5,1 12.336703300 P _part/16/_geo_xyz/0,5,2 33.628608704 P _part/16/_geo_xyz/0,6,0 0.0 P _part/16/_geo_xyz/0,6,1 12.303149223 P _part/16/_geo_xyz/0,6,2 34.038715363 P _part/16/_geo_xyz/0,7,0 0.0 P _part/16/_geo_xyz/0,7,1 12.303149223 P _part/16/_geo_xyz/0,7,2 34.038715363 P _part/16/_geo_xyz/0,8,0 0.0 P _part/16/_geo_xyz/0,8,1 12.269595146 P _part/16/_geo_xyz/0,8,2 33.628608704 P _part/16/_geo_xyz/0,9,0 0.0 P _part/16/_geo_xyz/0,9,1 12.229330063 P _part/16/_geo_xyz/0,9,2 33.136482239 P _part/16/_geo_xyz/1,0,0 -4.101049900 P _part/16/_geo_xyz/1,0,1 12.303149223 P _part/16/_geo_xyz/1,0,2 32.398292542 P _part/16/_geo_xyz/1,1,0 -4.101049900 P _part/16/_geo_xyz/1,1,1 12.332676888 P _part/16/_geo_xyz/1,1,2 32.447505951 P _part/16/_geo_xyz/1,10,0 -4.101049900 P _part/16/_geo_xyz/1,10,1 12.229330063 P _part/16/_geo_xyz/1,10,2 32.808399200 P _part/16/_geo_xyz/1,11,0 -4.101049900 P _part/16/_geo_xyz/1,11,1 12.244093895 P _part/16/_geo_xyz/1,11,2 32.562335968 P _part/16/_geo_xyz/1,12,0 -4.101049900 P _part/16/_geo_xyz/1,12,1 12.273621559 P _part/16/_geo_xyz/1,12,2 32.447505951 P _part/16/_geo_xyz/1,13,0 -4.101049900 P _part/16/_geo_xyz/1,13,1 12.303149223 P _part/16/_geo_xyz/1,13,2 32.398292542 P _part/16/_geo_xyz/1,14,0 0.0 P _part/16/_geo_xyz/1,14,1 0.0 P _part/16/_geo_xyz/1,14,2 0.0 P _part/16/_geo_xyz/1,15,0 0.0 P _part/16/_geo_xyz/1,15,1 0.0 P _part/16/_geo_xyz/1,15,2 0.0 P _part/16/_geo_xyz/1,16,0 0.0 P _part/16/_geo_xyz/1,16,1 0.0 P _part/16/_geo_xyz/1,16,2 0.0 P _part/16/_geo_xyz/1,17,0 0.0 P _part/16/_geo_xyz/1,17,1 0.0 P _part/16/_geo_xyz/1,17,2 0.0 P _part/16/_geo_xyz/1,2,0 -4.101049900 P _part/16/_geo_xyz/1,2,1 12.362204552 P _part/16/_geo_xyz/1,2,2 32.562335968 P _part/16/_geo_xyz/1,3,0 -4.101049900 P _part/16/_geo_xyz/1,3,1 12.376968384 P _part/16/_geo_xyz/1,3,2 32.808399200 P _part/16/_geo_xyz/1,4,0 -4.101049900 P _part/16/_geo_xyz/1,4,1 12.376968384 P _part/16/_geo_xyz/1,4,2 33.136482239 P _part/16/_geo_xyz/1,5,0 -4.101049900 P _part/16/_geo_xyz/1,5,1 12.336703300 P _part/16/_geo_xyz/1,5,2 33.628608704 P _part/16/_geo_xyz/1,6,0 -4.101049900 P _part/16/_geo_xyz/1,6,1 12.303149223 P _part/16/_geo_xyz/1,6,2 34.038715363 P _part/16/_geo_xyz/1,7,0 -4.101049900 P _part/16/_geo_xyz/1,7,1 12.303149223 P _part/16/_geo_xyz/1,7,2 34.038715363 P _part/16/_geo_xyz/1,8,0 -4.101049900 P _part/16/_geo_xyz/1,8,1 12.269595146 P _part/16/_geo_xyz/1,8,2 33.628608704 P _part/16/_geo_xyz/1,9,0 -4.101049900 P _part/16/_geo_xyz/1,9,1 12.229330063 P _part/16/_geo_xyz/1,9,2 33.136482239 P _part/16/_geo_xyz/10,0,0 0.0 P _part/16/_geo_xyz/10,0,1 0.0 P _part/16/_geo_xyz/10,0,2 0.0 P _part/16/_geo_xyz/10,1,0 0.0 P _part/16/_geo_xyz/10,1,1 0.0 P _part/16/_geo_xyz/10,1,2 0.0 P _part/16/_geo_xyz/10,10,0 0.0 P _part/16/_geo_xyz/10,10,1 0.0 P _part/16/_geo_xyz/10,10,2 0.0 P _part/16/_geo_xyz/10,11,0 0.0 P _part/16/_geo_xyz/10,11,1 0.0 P _part/16/_geo_xyz/10,11,2 0.0 P _part/16/_geo_xyz/10,12,0 0.0 P _part/16/_geo_xyz/10,12,1 0.0 P _part/16/_geo_xyz/10,12,2 0.0 P _part/16/_geo_xyz/10,13,0 0.0 P _part/16/_geo_xyz/10,13,1 0.0 P _part/16/_geo_xyz/10,13,2 0.0 P _part/16/_geo_xyz/10,14,0 0.0 P _part/16/_geo_xyz/10,14,1 0.0 P _part/16/_geo_xyz/10,14,2 0.0 P _part/16/_geo_xyz/10,15,0 0.0 P _part/16/_geo_xyz/10,15,1 0.0 P _part/16/_geo_xyz/10,15,2 0.0 P _part/16/_geo_xyz/10,16,0 0.0 P _part/16/_geo_xyz/10,16,1 0.0 P _part/16/_geo_xyz/10,16,2 0.0 P _part/16/_geo_xyz/10,17,0 0.0 P _part/16/_geo_xyz/10,17,1 0.0 P _part/16/_geo_xyz/10,17,2 0.0 P _part/16/_geo_xyz/10,2,0 0.0 P _part/16/_geo_xyz/10,2,1 0.0 P _part/16/_geo_xyz/10,2,2 0.0 P _part/16/_geo_xyz/10,3,0 0.0 P _part/16/_geo_xyz/10,3,1 0.0 P _part/16/_geo_xyz/10,3,2 0.0 P _part/16/_geo_xyz/10,4,0 0.0 P _part/16/_geo_xyz/10,4,1 0.0 P _part/16/_geo_xyz/10,4,2 0.0 P _part/16/_geo_xyz/10,5,0 0.0 P _part/16/_geo_xyz/10,5,1 0.0 P _part/16/_geo_xyz/10,5,2 0.0 P _part/16/_geo_xyz/10,6,0 0.0 P _part/16/_geo_xyz/10,6,1 0.0 P _part/16/_geo_xyz/10,6,2 0.0 P _part/16/_geo_xyz/10,7,0 0.0 P _part/16/_geo_xyz/10,7,1 0.0 P _part/16/_geo_xyz/10,7,2 0.0 P _part/16/_geo_xyz/10,8,0 0.0 P _part/16/_geo_xyz/10,8,1 0.0 P _part/16/_geo_xyz/10,8,2 0.0 P _part/16/_geo_xyz/10,9,0 0.0 P _part/16/_geo_xyz/10,9,1 0.0 P _part/16/_geo_xyz/10,9,2 0.0 P _part/16/_geo_xyz/11,0,0 0.0 P _part/16/_geo_xyz/11,0,1 0.0 P _part/16/_geo_xyz/11,0,2 0.0 P _part/16/_geo_xyz/11,1,0 0.0 P _part/16/_geo_xyz/11,1,1 0.0 P _part/16/_geo_xyz/11,1,2 0.0 P _part/16/_geo_xyz/11,10,0 0.0 P _part/16/_geo_xyz/11,10,1 0.0 P _part/16/_geo_xyz/11,10,2 0.0 P _part/16/_geo_xyz/11,11,0 0.0 P _part/16/_geo_xyz/11,11,1 0.0 P _part/16/_geo_xyz/11,11,2 0.0 P _part/16/_geo_xyz/11,12,0 0.0 P _part/16/_geo_xyz/11,12,1 0.0 P _part/16/_geo_xyz/11,12,2 0.0 P _part/16/_geo_xyz/11,13,0 0.0 P _part/16/_geo_xyz/11,13,1 0.0 P _part/16/_geo_xyz/11,13,2 0.0 P _part/16/_geo_xyz/11,14,0 0.0 P _part/16/_geo_xyz/11,14,1 0.0 P _part/16/_geo_xyz/11,14,2 0.0 P _part/16/_geo_xyz/11,15,0 0.0 P _part/16/_geo_xyz/11,15,1 0.0 P _part/16/_geo_xyz/11,15,2 0.0 P _part/16/_geo_xyz/11,16,0 0.0 P _part/16/_geo_xyz/11,16,1 0.0 P _part/16/_geo_xyz/11,16,2 0.0 P _part/16/_geo_xyz/11,17,0 0.0 P _part/16/_geo_xyz/11,17,1 0.0 P _part/16/_geo_xyz/11,17,2 0.0 P _part/16/_geo_xyz/11,2,0 0.0 P _part/16/_geo_xyz/11,2,1 0.0 P _part/16/_geo_xyz/11,2,2 0.0 P _part/16/_geo_xyz/11,3,0 0.0 P _part/16/_geo_xyz/11,3,1 0.0 P _part/16/_geo_xyz/11,3,2 0.0 P _part/16/_geo_xyz/11,4,0 0.0 P _part/16/_geo_xyz/11,4,1 0.0 P _part/16/_geo_xyz/11,4,2 0.0 P _part/16/_geo_xyz/11,5,0 0.0 P _part/16/_geo_xyz/11,5,1 0.0 P _part/16/_geo_xyz/11,5,2 0.0 P _part/16/_geo_xyz/11,6,0 0.0 P _part/16/_geo_xyz/11,6,1 0.0 P _part/16/_geo_xyz/11,6,2 0.0 P _part/16/_geo_xyz/11,7,0 0.0 P _part/16/_geo_xyz/11,7,1 0.0 P _part/16/_geo_xyz/11,7,2 0.0 P _part/16/_geo_xyz/11,8,0 0.0 P _part/16/_geo_xyz/11,8,1 0.0 P _part/16/_geo_xyz/11,8,2 0.0 P _part/16/_geo_xyz/11,9,0 0.0 P _part/16/_geo_xyz/11,9,1 0.0 P _part/16/_geo_xyz/11,9,2 0.0 P _part/16/_geo_xyz/12,0,0 0.0 P _part/16/_geo_xyz/12,0,1 0.0 P _part/16/_geo_xyz/12,0,2 0.0 P _part/16/_geo_xyz/12,1,0 0.0 P _part/16/_geo_xyz/12,1,1 0.0 P _part/16/_geo_xyz/12,1,2 0.0 P _part/16/_geo_xyz/12,10,0 0.0 P _part/16/_geo_xyz/12,10,1 0.0 P _part/16/_geo_xyz/12,10,2 0.0 P _part/16/_geo_xyz/12,11,0 0.0 P _part/16/_geo_xyz/12,11,1 0.0 P _part/16/_geo_xyz/12,11,2 0.0 P _part/16/_geo_xyz/12,12,0 0.0 P _part/16/_geo_xyz/12,12,1 0.0 P _part/16/_geo_xyz/12,12,2 0.0 P _part/16/_geo_xyz/12,13,0 0.0 P _part/16/_geo_xyz/12,13,1 0.0 P _part/16/_geo_xyz/12,13,2 0.0 P _part/16/_geo_xyz/12,14,0 0.0 P _part/16/_geo_xyz/12,14,1 0.0 P _part/16/_geo_xyz/12,14,2 0.0 P _part/16/_geo_xyz/12,15,0 0.0 P _part/16/_geo_xyz/12,15,1 0.0 P _part/16/_geo_xyz/12,15,2 0.0 P _part/16/_geo_xyz/12,16,0 0.0 P _part/16/_geo_xyz/12,16,1 0.0 P _part/16/_geo_xyz/12,16,2 0.0 P _part/16/_geo_xyz/12,17,0 0.0 P _part/16/_geo_xyz/12,17,1 0.0 P _part/16/_geo_xyz/12,17,2 0.0 P _part/16/_geo_xyz/12,2,0 0.0 P _part/16/_geo_xyz/12,2,1 0.0 P _part/16/_geo_xyz/12,2,2 0.0 P _part/16/_geo_xyz/12,3,0 0.0 P _part/16/_geo_xyz/12,3,1 0.0 P _part/16/_geo_xyz/12,3,2 0.0 P _part/16/_geo_xyz/12,4,0 0.0 P _part/16/_geo_xyz/12,4,1 0.0 P _part/16/_geo_xyz/12,4,2 0.0 P _part/16/_geo_xyz/12,5,0 0.0 P _part/16/_geo_xyz/12,5,1 0.0 P _part/16/_geo_xyz/12,5,2 0.0 P _part/16/_geo_xyz/12,6,0 0.0 P _part/16/_geo_xyz/12,6,1 0.0 P _part/16/_geo_xyz/12,6,2 0.0 P _part/16/_geo_xyz/12,7,0 0.0 P _part/16/_geo_xyz/12,7,1 0.0 P _part/16/_geo_xyz/12,7,2 0.0 P _part/16/_geo_xyz/12,8,0 0.0 P _part/16/_geo_xyz/12,8,1 0.0 P _part/16/_geo_xyz/12,8,2 0.0 P _part/16/_geo_xyz/12,9,0 0.0 P _part/16/_geo_xyz/12,9,1 0.0 P _part/16/_geo_xyz/12,9,2 0.0 P _part/16/_geo_xyz/13,0,0 0.0 P _part/16/_geo_xyz/13,0,1 0.0 P _part/16/_geo_xyz/13,0,2 0.0 P _part/16/_geo_xyz/13,1,0 0.0 P _part/16/_geo_xyz/13,1,1 0.0 P _part/16/_geo_xyz/13,1,2 0.0 P _part/16/_geo_xyz/13,10,0 0.0 P _part/16/_geo_xyz/13,10,1 0.0 P _part/16/_geo_xyz/13,10,2 0.0 P _part/16/_geo_xyz/13,11,0 0.0 P _part/16/_geo_xyz/13,11,1 0.0 P _part/16/_geo_xyz/13,11,2 0.0 P _part/16/_geo_xyz/13,12,0 0.0 P _part/16/_geo_xyz/13,12,1 0.0 P _part/16/_geo_xyz/13,12,2 0.0 P _part/16/_geo_xyz/13,13,0 0.0 P _part/16/_geo_xyz/13,13,1 0.0 P _part/16/_geo_xyz/13,13,2 0.0 P _part/16/_geo_xyz/13,14,0 0.0 P _part/16/_geo_xyz/13,14,1 0.0 P _part/16/_geo_xyz/13,14,2 0.0 P _part/16/_geo_xyz/13,15,0 0.0 P _part/16/_geo_xyz/13,15,1 0.0 P _part/16/_geo_xyz/13,15,2 0.0 P _part/16/_geo_xyz/13,16,0 0.0 P _part/16/_geo_xyz/13,16,1 0.0 P _part/16/_geo_xyz/13,16,2 0.0 P _part/16/_geo_xyz/13,17,0 0.0 P _part/16/_geo_xyz/13,17,1 0.0 P _part/16/_geo_xyz/13,17,2 0.0 P _part/16/_geo_xyz/13,2,0 0.0 P _part/16/_geo_xyz/13,2,1 0.0 P _part/16/_geo_xyz/13,2,2 0.0 P _part/16/_geo_xyz/13,3,0 0.0 P _part/16/_geo_xyz/13,3,1 0.0 P _part/16/_geo_xyz/13,3,2 0.0 P _part/16/_geo_xyz/13,4,0 0.0 P _part/16/_geo_xyz/13,4,1 0.0 P _part/16/_geo_xyz/13,4,2 0.0 P _part/16/_geo_xyz/13,5,0 0.0 P _part/16/_geo_xyz/13,5,1 0.0 P _part/16/_geo_xyz/13,5,2 0.0 P _part/16/_geo_xyz/13,6,0 0.0 P _part/16/_geo_xyz/13,6,1 0.0 P _part/16/_geo_xyz/13,6,2 0.0 P _part/16/_geo_xyz/13,7,0 0.0 P _part/16/_geo_xyz/13,7,1 0.0 P _part/16/_geo_xyz/13,7,2 0.0 P _part/16/_geo_xyz/13,8,0 0.0 P _part/16/_geo_xyz/13,8,1 0.0 P _part/16/_geo_xyz/13,8,2 0.0 P _part/16/_geo_xyz/13,9,0 0.0 P _part/16/_geo_xyz/13,9,1 0.0 P _part/16/_geo_xyz/13,9,2 0.0 P _part/16/_geo_xyz/14,0,0 0.0 P _part/16/_geo_xyz/14,0,1 0.0 P _part/16/_geo_xyz/14,0,2 0.0 P _part/16/_geo_xyz/14,1,0 0.0 P _part/16/_geo_xyz/14,1,1 0.0 P _part/16/_geo_xyz/14,1,2 0.0 P _part/16/_geo_xyz/14,10,0 0.0 P _part/16/_geo_xyz/14,10,1 0.0 P _part/16/_geo_xyz/14,10,2 0.0 P _part/16/_geo_xyz/14,11,0 0.0 P _part/16/_geo_xyz/14,11,1 0.0 P _part/16/_geo_xyz/14,11,2 0.0 P _part/16/_geo_xyz/14,12,0 0.0 P _part/16/_geo_xyz/14,12,1 0.0 P _part/16/_geo_xyz/14,12,2 0.0 P _part/16/_geo_xyz/14,13,0 0.0 P _part/16/_geo_xyz/14,13,1 0.0 P _part/16/_geo_xyz/14,13,2 0.0 P _part/16/_geo_xyz/14,14,0 0.0 P _part/16/_geo_xyz/14,14,1 0.0 P _part/16/_geo_xyz/14,14,2 0.0 P _part/16/_geo_xyz/14,15,0 0.0 P _part/16/_geo_xyz/14,15,1 0.0 P _part/16/_geo_xyz/14,15,2 0.0 P _part/16/_geo_xyz/14,16,0 0.0 P _part/16/_geo_xyz/14,16,1 0.0 P _part/16/_geo_xyz/14,16,2 0.0 P _part/16/_geo_xyz/14,17,0 0.0 P _part/16/_geo_xyz/14,17,1 0.0 P _part/16/_geo_xyz/14,17,2 0.0 P _part/16/_geo_xyz/14,2,0 0.0 P _part/16/_geo_xyz/14,2,1 0.0 P _part/16/_geo_xyz/14,2,2 0.0 P _part/16/_geo_xyz/14,3,0 0.0 P _part/16/_geo_xyz/14,3,1 0.0 P _part/16/_geo_xyz/14,3,2 0.0 P _part/16/_geo_xyz/14,4,0 0.0 P _part/16/_geo_xyz/14,4,1 0.0 P _part/16/_geo_xyz/14,4,2 0.0 P _part/16/_geo_xyz/14,5,0 0.0 P _part/16/_geo_xyz/14,5,1 0.0 P _part/16/_geo_xyz/14,5,2 0.0 P _part/16/_geo_xyz/14,6,0 0.0 P _part/16/_geo_xyz/14,6,1 0.0 P _part/16/_geo_xyz/14,6,2 0.0 P _part/16/_geo_xyz/14,7,0 0.0 P _part/16/_geo_xyz/14,7,1 0.0 P _part/16/_geo_xyz/14,7,2 0.0 P _part/16/_geo_xyz/14,8,0 0.0 P _part/16/_geo_xyz/14,8,1 0.0 P _part/16/_geo_xyz/14,8,2 0.0 P _part/16/_geo_xyz/14,9,0 0.0 P _part/16/_geo_xyz/14,9,1 0.0 P _part/16/_geo_xyz/14,9,2 0.0 P _part/16/_geo_xyz/15,0,0 0.0 P _part/16/_geo_xyz/15,0,1 0.0 P _part/16/_geo_xyz/15,0,2 0.0 P _part/16/_geo_xyz/15,1,0 0.0 P _part/16/_geo_xyz/15,1,1 0.0 P _part/16/_geo_xyz/15,1,2 0.0 P _part/16/_geo_xyz/15,10,0 0.0 P _part/16/_geo_xyz/15,10,1 0.0 P _part/16/_geo_xyz/15,10,2 0.0 P _part/16/_geo_xyz/15,11,0 0.0 P _part/16/_geo_xyz/15,11,1 0.0 P _part/16/_geo_xyz/15,11,2 0.0 P _part/16/_geo_xyz/15,12,0 0.0 P _part/16/_geo_xyz/15,12,1 0.0 P _part/16/_geo_xyz/15,12,2 0.0 P _part/16/_geo_xyz/15,13,0 0.0 P _part/16/_geo_xyz/15,13,1 0.0 P _part/16/_geo_xyz/15,13,2 0.0 P _part/16/_geo_xyz/15,14,0 0.0 P _part/16/_geo_xyz/15,14,1 0.0 P _part/16/_geo_xyz/15,14,2 0.0 P _part/16/_geo_xyz/15,15,0 0.0 P _part/16/_geo_xyz/15,15,1 0.0 P _part/16/_geo_xyz/15,15,2 0.0 P _part/16/_geo_xyz/15,16,0 0.0 P _part/16/_geo_xyz/15,16,1 0.0 P _part/16/_geo_xyz/15,16,2 0.0 P _part/16/_geo_xyz/15,17,0 0.0 P _part/16/_geo_xyz/15,17,1 0.0 P _part/16/_geo_xyz/15,17,2 0.0 P _part/16/_geo_xyz/15,2,0 0.0 P _part/16/_geo_xyz/15,2,1 0.0 P _part/16/_geo_xyz/15,2,2 0.0 P _part/16/_geo_xyz/15,3,0 0.0 P _part/16/_geo_xyz/15,3,1 0.0 P _part/16/_geo_xyz/15,3,2 0.0 P _part/16/_geo_xyz/15,4,0 0.0 P _part/16/_geo_xyz/15,4,1 0.0 P _part/16/_geo_xyz/15,4,2 0.0 P _part/16/_geo_xyz/15,5,0 0.0 P _part/16/_geo_xyz/15,5,1 0.0 P _part/16/_geo_xyz/15,5,2 0.0 P _part/16/_geo_xyz/15,6,0 0.0 P _part/16/_geo_xyz/15,6,1 0.0 P _part/16/_geo_xyz/15,6,2 0.0 P _part/16/_geo_xyz/15,7,0 0.0 P _part/16/_geo_xyz/15,7,1 0.0 P _part/16/_geo_xyz/15,7,2 0.0 P _part/16/_geo_xyz/15,8,0 0.0 P _part/16/_geo_xyz/15,8,1 0.0 P _part/16/_geo_xyz/15,8,2 0.0 P _part/16/_geo_xyz/15,9,0 0.0 P _part/16/_geo_xyz/15,9,1 0.0 P _part/16/_geo_xyz/15,9,2 0.0 P _part/16/_geo_xyz/16,0,0 0.0 P _part/16/_geo_xyz/16,0,1 0.0 P _part/16/_geo_xyz/16,0,2 0.0 P _part/16/_geo_xyz/16,1,0 0.0 P _part/16/_geo_xyz/16,1,1 0.0 P _part/16/_geo_xyz/16,1,2 0.0 P _part/16/_geo_xyz/16,10,0 0.0 P _part/16/_geo_xyz/16,10,1 0.0 P _part/16/_geo_xyz/16,10,2 0.0 P _part/16/_geo_xyz/16,11,0 0.0 P _part/16/_geo_xyz/16,11,1 0.0 P _part/16/_geo_xyz/16,11,2 0.0 P _part/16/_geo_xyz/16,12,0 0.0 P _part/16/_geo_xyz/16,12,1 0.0 P _part/16/_geo_xyz/16,12,2 0.0 P _part/16/_geo_xyz/16,13,0 0.0 P _part/16/_geo_xyz/16,13,1 0.0 P _part/16/_geo_xyz/16,13,2 0.0 P _part/16/_geo_xyz/16,14,0 0.0 P _part/16/_geo_xyz/16,14,1 0.0 P _part/16/_geo_xyz/16,14,2 0.0 P _part/16/_geo_xyz/16,15,0 0.0 P _part/16/_geo_xyz/16,15,1 0.0 P _part/16/_geo_xyz/16,15,2 0.0 P _part/16/_geo_xyz/16,16,0 0.0 P _part/16/_geo_xyz/16,16,1 0.0 P _part/16/_geo_xyz/16,16,2 0.0 P _part/16/_geo_xyz/16,17,0 0.0 P _part/16/_geo_xyz/16,17,1 0.0 P _part/16/_geo_xyz/16,17,2 0.0 P _part/16/_geo_xyz/16,2,0 0.0 P _part/16/_geo_xyz/16,2,1 0.0 P _part/16/_geo_xyz/16,2,2 0.0 P _part/16/_geo_xyz/16,3,0 0.0 P _part/16/_geo_xyz/16,3,1 0.0 P _part/16/_geo_xyz/16,3,2 0.0 P _part/16/_geo_xyz/16,4,0 0.0 P _part/16/_geo_xyz/16,4,1 0.0 P _part/16/_geo_xyz/16,4,2 0.0 P _part/16/_geo_xyz/16,5,0 0.0 P _part/16/_geo_xyz/16,5,1 0.0 P _part/16/_geo_xyz/16,5,2 0.0 P _part/16/_geo_xyz/16,6,0 0.0 P _part/16/_geo_xyz/16,6,1 0.0 P _part/16/_geo_xyz/16,6,2 0.0 P _part/16/_geo_xyz/16,7,0 0.0 P _part/16/_geo_xyz/16,7,1 0.0 P _part/16/_geo_xyz/16,7,2 0.0 P _part/16/_geo_xyz/16,8,0 0.0 P _part/16/_geo_xyz/16,8,1 0.0 P _part/16/_geo_xyz/16,8,2 0.0 P _part/16/_geo_xyz/16,9,0 0.0 P _part/16/_geo_xyz/16,9,1 0.0 P _part/16/_geo_xyz/16,9,2 0.0 P _part/16/_geo_xyz/17,0,0 0.0 P _part/16/_geo_xyz/17,0,1 0.0 P _part/16/_geo_xyz/17,0,2 0.0 P _part/16/_geo_xyz/17,1,0 0.0 P _part/16/_geo_xyz/17,1,1 0.0 P _part/16/_geo_xyz/17,1,2 0.0 P _part/16/_geo_xyz/17,10,0 0.0 P _part/16/_geo_xyz/17,10,1 0.0 P _part/16/_geo_xyz/17,10,2 0.0 P _part/16/_geo_xyz/17,11,0 0.0 P _part/16/_geo_xyz/17,11,1 0.0 P _part/16/_geo_xyz/17,11,2 0.0 P _part/16/_geo_xyz/17,12,0 0.0 P _part/16/_geo_xyz/17,12,1 0.0 P _part/16/_geo_xyz/17,12,2 0.0 P _part/16/_geo_xyz/17,13,0 0.0 P _part/16/_geo_xyz/17,13,1 0.0 P _part/16/_geo_xyz/17,13,2 0.0 P _part/16/_geo_xyz/17,14,0 0.0 P _part/16/_geo_xyz/17,14,1 0.0 P _part/16/_geo_xyz/17,14,2 0.0 P _part/16/_geo_xyz/17,15,0 0.0 P _part/16/_geo_xyz/17,15,1 0.0 P _part/16/_geo_xyz/17,15,2 0.0 P _part/16/_geo_xyz/17,16,0 0.0 P _part/16/_geo_xyz/17,16,1 0.0 P _part/16/_geo_xyz/17,16,2 0.0 P _part/16/_geo_xyz/17,17,0 0.0 P _part/16/_geo_xyz/17,17,1 0.0 P _part/16/_geo_xyz/17,17,2 0.0 P _part/16/_geo_xyz/17,2,0 0.0 P _part/16/_geo_xyz/17,2,1 0.0 P _part/16/_geo_xyz/17,2,2 0.0 P _part/16/_geo_xyz/17,3,0 0.0 P _part/16/_geo_xyz/17,3,1 0.0 P _part/16/_geo_xyz/17,3,2 0.0 P _part/16/_geo_xyz/17,4,0 0.0 P _part/16/_geo_xyz/17,4,1 0.0 P _part/16/_geo_xyz/17,4,2 0.0 P _part/16/_geo_xyz/17,5,0 0.0 P _part/16/_geo_xyz/17,5,1 0.0 P _part/16/_geo_xyz/17,5,2 0.0 P _part/16/_geo_xyz/17,6,0 0.0 P _part/16/_geo_xyz/17,6,1 0.0 P _part/16/_geo_xyz/17,6,2 0.0 P _part/16/_geo_xyz/17,7,0 0.0 P _part/16/_geo_xyz/17,7,1 0.0 P _part/16/_geo_xyz/17,7,2 0.0 P _part/16/_geo_xyz/17,8,0 0.0 P _part/16/_geo_xyz/17,8,1 0.0 P _part/16/_geo_xyz/17,8,2 0.0 P _part/16/_geo_xyz/17,9,0 0.0 P _part/16/_geo_xyz/17,9,1 0.0 P _part/16/_geo_xyz/17,9,2 0.0 P _part/16/_geo_xyz/18,0,0 0.0 P _part/16/_geo_xyz/18,0,1 0.0 P _part/16/_geo_xyz/18,0,2 0.0 P _part/16/_geo_xyz/18,1,0 0.0 P _part/16/_geo_xyz/18,1,1 0.0 P _part/16/_geo_xyz/18,1,2 0.0 P _part/16/_geo_xyz/18,10,0 0.0 P _part/16/_geo_xyz/18,10,1 0.0 P _part/16/_geo_xyz/18,10,2 0.0 P _part/16/_geo_xyz/18,11,0 0.0 P _part/16/_geo_xyz/18,11,1 0.0 P _part/16/_geo_xyz/18,11,2 0.0 P _part/16/_geo_xyz/18,12,0 0.0 P _part/16/_geo_xyz/18,12,1 0.0 P _part/16/_geo_xyz/18,12,2 0.0 P _part/16/_geo_xyz/18,13,0 0.0 P _part/16/_geo_xyz/18,13,1 0.0 P _part/16/_geo_xyz/18,13,2 0.0 P _part/16/_geo_xyz/18,14,0 0.0 P _part/16/_geo_xyz/18,14,1 0.0 P _part/16/_geo_xyz/18,14,2 0.0 P _part/16/_geo_xyz/18,15,0 0.0 P _part/16/_geo_xyz/18,15,1 0.0 P _part/16/_geo_xyz/18,15,2 0.0 P _part/16/_geo_xyz/18,16,0 0.0 P _part/16/_geo_xyz/18,16,1 0.0 P _part/16/_geo_xyz/18,16,2 0.0 P _part/16/_geo_xyz/18,17,0 0.0 P _part/16/_geo_xyz/18,17,1 0.0 P _part/16/_geo_xyz/18,17,2 0.0 P _part/16/_geo_xyz/18,2,0 0.0 P _part/16/_geo_xyz/18,2,1 0.0 P _part/16/_geo_xyz/18,2,2 0.0 P _part/16/_geo_xyz/18,3,0 0.0 P _part/16/_geo_xyz/18,3,1 0.0 P _part/16/_geo_xyz/18,3,2 0.0 P _part/16/_geo_xyz/18,4,0 0.0 P _part/16/_geo_xyz/18,4,1 0.0 P _part/16/_geo_xyz/18,4,2 0.0 P _part/16/_geo_xyz/18,5,0 0.0 P _part/16/_geo_xyz/18,5,1 0.0 P _part/16/_geo_xyz/18,5,2 0.0 P _part/16/_geo_xyz/18,6,0 0.0 P _part/16/_geo_xyz/18,6,1 0.0 P _part/16/_geo_xyz/18,6,2 0.0 P _part/16/_geo_xyz/18,7,0 0.0 P _part/16/_geo_xyz/18,7,1 0.0 P _part/16/_geo_xyz/18,7,2 0.0 P _part/16/_geo_xyz/18,8,0 0.0 P _part/16/_geo_xyz/18,8,1 0.0 P _part/16/_geo_xyz/18,8,2 0.0 P _part/16/_geo_xyz/18,9,0 0.0 P _part/16/_geo_xyz/18,9,1 0.0 P _part/16/_geo_xyz/18,9,2 0.0 P _part/16/_geo_xyz/19,0,0 0.0 P _part/16/_geo_xyz/19,0,1 0.0 P _part/16/_geo_xyz/19,0,2 0.0 P _part/16/_geo_xyz/19,1,0 0.0 P _part/16/_geo_xyz/19,1,1 0.0 P _part/16/_geo_xyz/19,1,2 0.0 P _part/16/_geo_xyz/19,10,0 0.0 P _part/16/_geo_xyz/19,10,1 0.0 P _part/16/_geo_xyz/19,10,2 0.0 P _part/16/_geo_xyz/19,11,0 0.0 P _part/16/_geo_xyz/19,11,1 0.0 P _part/16/_geo_xyz/19,11,2 0.0 P _part/16/_geo_xyz/19,12,0 0.0 P _part/16/_geo_xyz/19,12,1 0.0 P _part/16/_geo_xyz/19,12,2 0.0 P _part/16/_geo_xyz/19,13,0 0.0 P _part/16/_geo_xyz/19,13,1 0.0 P _part/16/_geo_xyz/19,13,2 0.0 P _part/16/_geo_xyz/19,14,0 0.0 P _part/16/_geo_xyz/19,14,1 0.0 P _part/16/_geo_xyz/19,14,2 0.0 P _part/16/_geo_xyz/19,15,0 0.0 P _part/16/_geo_xyz/19,15,1 0.0 P _part/16/_geo_xyz/19,15,2 0.0 P _part/16/_geo_xyz/19,16,0 0.0 P _part/16/_geo_xyz/19,16,1 0.0 P _part/16/_geo_xyz/19,16,2 0.0 P _part/16/_geo_xyz/19,17,0 0.0 P _part/16/_geo_xyz/19,17,1 0.0 P _part/16/_geo_xyz/19,17,2 0.0 P _part/16/_geo_xyz/19,2,0 0.0 P _part/16/_geo_xyz/19,2,1 0.0 P _part/16/_geo_xyz/19,2,2 0.0 P _part/16/_geo_xyz/19,3,0 0.0 P _part/16/_geo_xyz/19,3,1 0.0 P _part/16/_geo_xyz/19,3,2 0.0 P _part/16/_geo_xyz/19,4,0 0.0 P _part/16/_geo_xyz/19,4,1 0.0 P _part/16/_geo_xyz/19,4,2 0.0 P _part/16/_geo_xyz/19,5,0 0.0 P _part/16/_geo_xyz/19,5,1 0.0 P _part/16/_geo_xyz/19,5,2 0.0 P _part/16/_geo_xyz/19,6,0 0.0 P _part/16/_geo_xyz/19,6,1 0.0 P _part/16/_geo_xyz/19,6,2 0.0 P _part/16/_geo_xyz/19,7,0 0.0 P _part/16/_geo_xyz/19,7,1 0.0 P _part/16/_geo_xyz/19,7,2 0.0 P _part/16/_geo_xyz/19,8,0 0.0 P _part/16/_geo_xyz/19,8,1 0.0 P _part/16/_geo_xyz/19,8,2 0.0 P _part/16/_geo_xyz/19,9,0 0.0 P _part/16/_geo_xyz/19,9,1 0.0 P _part/16/_geo_xyz/19,9,2 0.0 P _part/16/_geo_xyz/2,0,0 -4.101049900 P _part/16/_geo_xyz/2,0,1 12.303149223 P _part/16/_geo_xyz/2,0,2 32.398292542 P _part/16/_geo_xyz/2,1,0 -4.101049900 P _part/16/_geo_xyz/2,1,1 12.332676888 P _part/16/_geo_xyz/2,1,2 32.447505951 P _part/16/_geo_xyz/2,10,0 -4.101049900 P _part/16/_geo_xyz/2,10,1 12.229330063 P _part/16/_geo_xyz/2,10,2 32.808399200 P _part/16/_geo_xyz/2,11,0 -4.101049900 P _part/16/_geo_xyz/2,11,1 12.244093895 P _part/16/_geo_xyz/2,11,2 32.562335968 P _part/16/_geo_xyz/2,12,0 -4.101049900 P _part/16/_geo_xyz/2,12,1 12.273621559 P _part/16/_geo_xyz/2,12,2 32.447505951 P _part/16/_geo_xyz/2,13,0 -4.101049900 P _part/16/_geo_xyz/2,13,1 12.303149223 P _part/16/_geo_xyz/2,13,2 32.398292542 P _part/16/_geo_xyz/2,14,0 0.0 P _part/16/_geo_xyz/2,14,1 0.0 P _part/16/_geo_xyz/2,14,2 0.0 P _part/16/_geo_xyz/2,15,0 0.0 P _part/16/_geo_xyz/2,15,1 0.0 P _part/16/_geo_xyz/2,15,2 0.0 P _part/16/_geo_xyz/2,16,0 0.0 P _part/16/_geo_xyz/2,16,1 0.0 P _part/16/_geo_xyz/2,16,2 0.0 P _part/16/_geo_xyz/2,17,0 0.0 P _part/16/_geo_xyz/2,17,1 0.0 P _part/16/_geo_xyz/2,17,2 0.0 P _part/16/_geo_xyz/2,2,0 -4.101049900 P _part/16/_geo_xyz/2,2,1 12.362204552 P _part/16/_geo_xyz/2,2,2 32.562335968 P _part/16/_geo_xyz/2,3,0 -4.101049900 P _part/16/_geo_xyz/2,3,1 12.376968384 P _part/16/_geo_xyz/2,3,2 32.808399200 P _part/16/_geo_xyz/2,4,0 -4.101049900 P _part/16/_geo_xyz/2,4,1 12.376968384 P _part/16/_geo_xyz/2,4,2 33.136482239 P _part/16/_geo_xyz/2,5,0 -4.101049900 P _part/16/_geo_xyz/2,5,1 12.336703300 P _part/16/_geo_xyz/2,5,2 33.628608704 P _part/16/_geo_xyz/2,6,0 -4.101049900 P _part/16/_geo_xyz/2,6,1 12.303149223 P _part/16/_geo_xyz/2,6,2 34.038715363 P _part/16/_geo_xyz/2,7,0 -4.101049900 P _part/16/_geo_xyz/2,7,1 12.303149223 P _part/16/_geo_xyz/2,7,2 34.038715363 P _part/16/_geo_xyz/2,8,0 -4.101049900 P _part/16/_geo_xyz/2,8,1 12.269595146 P _part/16/_geo_xyz/2,8,2 33.628608704 P _part/16/_geo_xyz/2,9,0 -4.101049900 P _part/16/_geo_xyz/2,9,1 12.229330063 P _part/16/_geo_xyz/2,9,2 33.136482239 P _part/16/_geo_xyz/3,0,0 -8.202099800 P _part/16/_geo_xyz/3,0,1 12.303149223 P _part/16/_geo_xyz/3,0,2 32.398292542 P _part/16/_geo_xyz/3,1,0 -8.202099800 P _part/16/_geo_xyz/3,1,1 12.332676888 P _part/16/_geo_xyz/3,1,2 32.447505951 P _part/16/_geo_xyz/3,10,0 -8.202099800 P _part/16/_geo_xyz/3,10,1 12.229330063 P _part/16/_geo_xyz/3,10,2 32.808399200 P _part/16/_geo_xyz/3,11,0 -8.202099800 P _part/16/_geo_xyz/3,11,1 12.244093895 P _part/16/_geo_xyz/3,11,2 32.562335968 P _part/16/_geo_xyz/3,12,0 -8.202099800 P _part/16/_geo_xyz/3,12,1 12.273621559 P _part/16/_geo_xyz/3,12,2 32.447505951 P _part/16/_geo_xyz/3,13,0 -8.202099800 P _part/16/_geo_xyz/3,13,1 12.303149223 P _part/16/_geo_xyz/3,13,2 32.398292542 P _part/16/_geo_xyz/3,14,0 0.0 P _part/16/_geo_xyz/3,14,1 0.0 P _part/16/_geo_xyz/3,14,2 0.0 P _part/16/_geo_xyz/3,15,0 0.0 P _part/16/_geo_xyz/3,15,1 0.0 P _part/16/_geo_xyz/3,15,2 0.0 P _part/16/_geo_xyz/3,16,0 0.0 P _part/16/_geo_xyz/3,16,1 0.0 P _part/16/_geo_xyz/3,16,2 0.0 P _part/16/_geo_xyz/3,17,0 0.0 P _part/16/_geo_xyz/3,17,1 0.0 P _part/16/_geo_xyz/3,17,2 0.0 P _part/16/_geo_xyz/3,2,0 -8.202099800 P _part/16/_geo_xyz/3,2,1 12.362204552 P _part/16/_geo_xyz/3,2,2 32.562335968 P _part/16/_geo_xyz/3,3,0 -8.202099800 P _part/16/_geo_xyz/3,3,1 12.376968384 P _part/16/_geo_xyz/3,3,2 32.808399200 P _part/16/_geo_xyz/3,4,0 -8.202099800 P _part/16/_geo_xyz/3,4,1 12.376968384 P _part/16/_geo_xyz/3,4,2 33.136482239 P _part/16/_geo_xyz/3,5,0 -8.202099800 P _part/16/_geo_xyz/3,5,1 12.336703300 P _part/16/_geo_xyz/3,5,2 33.628608704 P _part/16/_geo_xyz/3,6,0 -8.202099800 P _part/16/_geo_xyz/3,6,1 12.303149223 P _part/16/_geo_xyz/3,6,2 34.038715363 P _part/16/_geo_xyz/3,7,0 -8.202099800 P _part/16/_geo_xyz/3,7,1 12.303149223 P _part/16/_geo_xyz/3,7,2 34.038715363 P _part/16/_geo_xyz/3,8,0 -8.202099800 P _part/16/_geo_xyz/3,8,1 12.269595146 P _part/16/_geo_xyz/3,8,2 33.628608704 P _part/16/_geo_xyz/3,9,0 -8.202099800 P _part/16/_geo_xyz/3,9,1 12.229330063 P _part/16/_geo_xyz/3,9,2 33.136482239 P _part/16/_geo_xyz/4,0,0 -8.202099800 P _part/16/_geo_xyz/4,0,1 12.303149223 P _part/16/_geo_xyz/4,0,2 32.398292542 P _part/16/_geo_xyz/4,1,0 -8.202099800 P _part/16/_geo_xyz/4,1,1 12.332676888 P _part/16/_geo_xyz/4,1,2 32.447505951 P _part/16/_geo_xyz/4,10,0 -8.202099800 P _part/16/_geo_xyz/4,10,1 12.229330063 P _part/16/_geo_xyz/4,10,2 32.808399200 P _part/16/_geo_xyz/4,11,0 -8.202099800 P _part/16/_geo_xyz/4,11,1 12.244093895 P _part/16/_geo_xyz/4,11,2 32.562335968 P _part/16/_geo_xyz/4,12,0 -8.202099800 P _part/16/_geo_xyz/4,12,1 12.273621559 P _part/16/_geo_xyz/4,12,2 32.447505951 P _part/16/_geo_xyz/4,13,0 -8.202099800 P _part/16/_geo_xyz/4,13,1 12.303149223 P _part/16/_geo_xyz/4,13,2 32.398292542 P _part/16/_geo_xyz/4,14,0 0.0 P _part/16/_geo_xyz/4,14,1 0.0 P _part/16/_geo_xyz/4,14,2 0.0 P _part/16/_geo_xyz/4,15,0 0.0 P _part/16/_geo_xyz/4,15,1 0.0 P _part/16/_geo_xyz/4,15,2 0.0 P _part/16/_geo_xyz/4,16,0 0.0 P _part/16/_geo_xyz/4,16,1 0.0 P _part/16/_geo_xyz/4,16,2 0.0 P _part/16/_geo_xyz/4,17,0 0.0 P _part/16/_geo_xyz/4,17,1 0.0 P _part/16/_geo_xyz/4,17,2 0.0 P _part/16/_geo_xyz/4,2,0 -8.202099800 P _part/16/_geo_xyz/4,2,1 12.362204552 P _part/16/_geo_xyz/4,2,2 32.562335968 P _part/16/_geo_xyz/4,3,0 -8.202099800 P _part/16/_geo_xyz/4,3,1 12.376968384 P _part/16/_geo_xyz/4,3,2 32.808399200 P _part/16/_geo_xyz/4,4,0 -8.202099800 P _part/16/_geo_xyz/4,4,1 12.376968384 P _part/16/_geo_xyz/4,4,2 33.136482239 P _part/16/_geo_xyz/4,5,0 -8.202099800 P _part/16/_geo_xyz/4,5,1 12.336703300 P _part/16/_geo_xyz/4,5,2 33.628608704 P _part/16/_geo_xyz/4,6,0 -8.202099800 P _part/16/_geo_xyz/4,6,1 12.303149223 P _part/16/_geo_xyz/4,6,2 34.038715363 P _part/16/_geo_xyz/4,7,0 -8.202099800 P _part/16/_geo_xyz/4,7,1 12.303149223 P _part/16/_geo_xyz/4,7,2 34.038715363 P _part/16/_geo_xyz/4,8,0 -8.202099800 P _part/16/_geo_xyz/4,8,1 12.269595146 P _part/16/_geo_xyz/4,8,2 33.628608704 P _part/16/_geo_xyz/4,9,0 -8.202099800 P _part/16/_geo_xyz/4,9,1 12.229330063 P _part/16/_geo_xyz/4,9,2 33.136482239 P _part/16/_geo_xyz/5,0,0 -8.202099800 P _part/16/_geo_xyz/5,0,1 12.303149223 P _part/16/_geo_xyz/5,0,2 32.398292542 P _part/16/_geo_xyz/5,1,0 -8.202099800 P _part/16/_geo_xyz/5,1,1 12.332676888 P _part/16/_geo_xyz/5,1,2 32.447505951 P _part/16/_geo_xyz/5,10,0 -8.202099800 P _part/16/_geo_xyz/5,10,1 12.229330063 P _part/16/_geo_xyz/5,10,2 32.808399200 P _part/16/_geo_xyz/5,11,0 -8.202099800 P _part/16/_geo_xyz/5,11,1 12.244093895 P _part/16/_geo_xyz/5,11,2 32.562335968 P _part/16/_geo_xyz/5,12,0 -8.202099800 P _part/16/_geo_xyz/5,12,1 12.273621559 P _part/16/_geo_xyz/5,12,2 32.447505951 P _part/16/_geo_xyz/5,13,0 -8.202099800 P _part/16/_geo_xyz/5,13,1 12.303149223 P _part/16/_geo_xyz/5,13,2 32.398292542 P _part/16/_geo_xyz/5,14,0 0.0 P _part/16/_geo_xyz/5,14,1 0.0 P _part/16/_geo_xyz/5,14,2 0.0 P _part/16/_geo_xyz/5,15,0 0.0 P _part/16/_geo_xyz/5,15,1 0.0 P _part/16/_geo_xyz/5,15,2 0.0 P _part/16/_geo_xyz/5,16,0 0.0 P _part/16/_geo_xyz/5,16,1 0.0 P _part/16/_geo_xyz/5,16,2 0.0 P _part/16/_geo_xyz/5,17,0 0.0 P _part/16/_geo_xyz/5,17,1 0.0 P _part/16/_geo_xyz/5,17,2 0.0 P _part/16/_geo_xyz/5,2,0 -8.202099800 P _part/16/_geo_xyz/5,2,1 12.362204552 P _part/16/_geo_xyz/5,2,2 32.562335968 P _part/16/_geo_xyz/5,3,0 -8.202099800 P _part/16/_geo_xyz/5,3,1 12.376968384 P _part/16/_geo_xyz/5,3,2 32.808399200 P _part/16/_geo_xyz/5,4,0 -8.202099800 P _part/16/_geo_xyz/5,4,1 12.376968384 P _part/16/_geo_xyz/5,4,2 33.136482239 P _part/16/_geo_xyz/5,5,0 -8.202099800 P _part/16/_geo_xyz/5,5,1 12.303149223 P _part/16/_geo_xyz/5,5,2 34.038715363 P _part/16/_geo_xyz/5,6,0 -8.202099800 P _part/16/_geo_xyz/5,6,1 12.303149223 P _part/16/_geo_xyz/5,6,2 34.038715363 P _part/16/_geo_xyz/5,7,0 -8.202099800 P _part/16/_geo_xyz/5,7,1 12.303149223 P _part/16/_geo_xyz/5,7,2 34.038715363 P _part/16/_geo_xyz/5,8,0 -8.202099800 P _part/16/_geo_xyz/5,8,1 12.303149223 P _part/16/_geo_xyz/5,8,2 34.038715363 P _part/16/_geo_xyz/5,9,0 -8.202099800 P _part/16/_geo_xyz/5,9,1 12.229330063 P _part/16/_geo_xyz/5,9,2 33.136482239 P _part/16/_geo_xyz/6,0,0 -8.202099800 P _part/16/_geo_xyz/6,0,1 12.303149223 P _part/16/_geo_xyz/6,0,2 32.398292542 P _part/16/_geo_xyz/6,1,0 -8.202099800 P _part/16/_geo_xyz/6,1,1 12.332676888 P _part/16/_geo_xyz/6,1,2 32.447505951 P _part/16/_geo_xyz/6,10,0 -8.202099800 P _part/16/_geo_xyz/6,10,1 12.229330063 P _part/16/_geo_xyz/6,10,2 32.808399200 P _part/16/_geo_xyz/6,11,0 -8.202099800 P _part/16/_geo_xyz/6,11,1 12.244093895 P _part/16/_geo_xyz/6,11,2 32.562335968 P _part/16/_geo_xyz/6,12,0 -8.202099800 P _part/16/_geo_xyz/6,12,1 12.273621559 P _part/16/_geo_xyz/6,12,2 32.447505951 P _part/16/_geo_xyz/6,13,0 -8.202099800 P _part/16/_geo_xyz/6,13,1 12.303149223 P _part/16/_geo_xyz/6,13,2 32.398292542 P _part/16/_geo_xyz/6,14,0 0.0 P _part/16/_geo_xyz/6,14,1 0.0 P _part/16/_geo_xyz/6,14,2 0.0 P _part/16/_geo_xyz/6,15,0 0.0 P _part/16/_geo_xyz/6,15,1 0.0 P _part/16/_geo_xyz/6,15,2 0.0 P _part/16/_geo_xyz/6,16,0 0.0 P _part/16/_geo_xyz/6,16,1 0.0 P _part/16/_geo_xyz/6,16,2 0.0 P _part/16/_geo_xyz/6,17,0 0.0 P _part/16/_geo_xyz/6,17,1 0.0 P _part/16/_geo_xyz/6,17,2 0.0 P _part/16/_geo_xyz/6,2,0 -8.202099800 P _part/16/_geo_xyz/6,2,1 12.362204552 P _part/16/_geo_xyz/6,2,2 32.562335968 P _part/16/_geo_xyz/6,3,0 -8.202099800 P _part/16/_geo_xyz/6,3,1 12.376968384 P _part/16/_geo_xyz/6,3,2 32.808399200 P _part/16/_geo_xyz/6,4,0 -8.202099800 P _part/16/_geo_xyz/6,4,1 12.376968384 P _part/16/_geo_xyz/6,4,2 33.136482239 P _part/16/_geo_xyz/6,5,0 -8.202099800 P _part/16/_geo_xyz/6,5,1 12.303149223 P _part/16/_geo_xyz/6,5,2 34.038715363 P _part/16/_geo_xyz/6,6,0 -8.202099800 P _part/16/_geo_xyz/6,6,1 12.303149223 P _part/16/_geo_xyz/6,6,2 34.038715363 P _part/16/_geo_xyz/6,7,0 -8.202099800 P _part/16/_geo_xyz/6,7,1 12.303149223 P _part/16/_geo_xyz/6,7,2 34.038715363 P _part/16/_geo_xyz/6,8,0 -8.202099800 P _part/16/_geo_xyz/6,8,1 12.303149223 P _part/16/_geo_xyz/6,8,2 34.038715363 P _part/16/_geo_xyz/6,9,0 -8.202099800 P _part/16/_geo_xyz/6,9,1 12.229330063 P _part/16/_geo_xyz/6,9,2 33.136482239 P _part/16/_geo_xyz/7,0,0 -8.202099800 P _part/16/_geo_xyz/7,0,1 12.303149223 P _part/16/_geo_xyz/7,0,2 32.398292542 P _part/16/_geo_xyz/7,1,0 -8.202099800 P _part/16/_geo_xyz/7,1,1 12.332676888 P _part/16/_geo_xyz/7,1,2 32.447505951 P _part/16/_geo_xyz/7,10,0 -8.202099800 P _part/16/_geo_xyz/7,10,1 12.229330063 P _part/16/_geo_xyz/7,10,2 32.808399200 P _part/16/_geo_xyz/7,11,0 -8.202099800 P _part/16/_geo_xyz/7,11,1 12.244093895 P _part/16/_geo_xyz/7,11,2 32.562335968 P _part/16/_geo_xyz/7,12,0 -8.202099800 P _part/16/_geo_xyz/7,12,1 12.273621559 P _part/16/_geo_xyz/7,12,2 32.447505951 P _part/16/_geo_xyz/7,13,0 -8.202099800 P _part/16/_geo_xyz/7,13,1 12.303149223 P _part/16/_geo_xyz/7,13,2 32.398292542 P _part/16/_geo_xyz/7,14,0 0.0 P _part/16/_geo_xyz/7,14,1 0.0 P _part/16/_geo_xyz/7,14,2 0.0 P _part/16/_geo_xyz/7,15,0 0.0 P _part/16/_geo_xyz/7,15,1 0.0 P _part/16/_geo_xyz/7,15,2 0.0 P _part/16/_geo_xyz/7,16,0 0.0 P _part/16/_geo_xyz/7,16,1 0.0 P _part/16/_geo_xyz/7,16,2 0.0 P _part/16/_geo_xyz/7,17,0 0.0 P _part/16/_geo_xyz/7,17,1 0.0 P _part/16/_geo_xyz/7,17,2 0.0 P _part/16/_geo_xyz/7,2,0 -8.202099800 P _part/16/_geo_xyz/7,2,1 12.362204552 P _part/16/_geo_xyz/7,2,2 32.562335968 P _part/16/_geo_xyz/7,3,0 -8.202099800 P _part/16/_geo_xyz/7,3,1 12.376968384 P _part/16/_geo_xyz/7,3,2 32.808399200 P _part/16/_geo_xyz/7,4,0 -8.202099800 P _part/16/_geo_xyz/7,4,1 12.376968384 P _part/16/_geo_xyz/7,4,2 33.136482239 P _part/16/_geo_xyz/7,5,0 -8.202099800 P _part/16/_geo_xyz/7,5,1 12.303149223 P _part/16/_geo_xyz/7,5,2 34.038715363 P _part/16/_geo_xyz/7,6,0 -8.202099800 P _part/16/_geo_xyz/7,6,1 12.303149223 P _part/16/_geo_xyz/7,6,2 34.038715363 P _part/16/_geo_xyz/7,7,0 -8.202099800 P _part/16/_geo_xyz/7,7,1 12.303149223 P _part/16/_geo_xyz/7,7,2 34.038715363 P _part/16/_geo_xyz/7,8,0 -8.202099800 P _part/16/_geo_xyz/7,8,1 12.303149223 P _part/16/_geo_xyz/7,8,2 34.038715363 P _part/16/_geo_xyz/7,9,0 -8.202099800 P _part/16/_geo_xyz/7,9,1 12.229330063 P _part/16/_geo_xyz/7,9,2 33.136482239 P _part/16/_geo_xyz/8,0,0 -8.202099800 P _part/16/_geo_xyz/8,0,1 12.303149223 P _part/16/_geo_xyz/8,0,2 32.398292542 P _part/16/_geo_xyz/8,1,0 -8.202099800 P _part/16/_geo_xyz/8,1,1 12.332676888 P _part/16/_geo_xyz/8,1,2 32.447505951 P _part/16/_geo_xyz/8,10,0 -8.202099800 P _part/16/_geo_xyz/8,10,1 12.229330063 P _part/16/_geo_xyz/8,10,2 32.808399200 P _part/16/_geo_xyz/8,11,0 -8.202099800 P _part/16/_geo_xyz/8,11,1 12.244093895 P _part/16/_geo_xyz/8,11,2 32.562335968 P _part/16/_geo_xyz/8,12,0 -8.202099800 P _part/16/_geo_xyz/8,12,1 12.273621559 P _part/16/_geo_xyz/8,12,2 32.447505951 P _part/16/_geo_xyz/8,13,0 -8.202099800 P _part/16/_geo_xyz/8,13,1 12.303149223 P _part/16/_geo_xyz/8,13,2 32.398292542 P _part/16/_geo_xyz/8,14,0 0.0 P _part/16/_geo_xyz/8,14,1 0.0 P _part/16/_geo_xyz/8,14,2 0.0 P _part/16/_geo_xyz/8,15,0 0.0 P _part/16/_geo_xyz/8,15,1 0.0 P _part/16/_geo_xyz/8,15,2 0.0 P _part/16/_geo_xyz/8,16,0 0.0 P _part/16/_geo_xyz/8,16,1 0.0 P _part/16/_geo_xyz/8,16,2 0.0 P _part/16/_geo_xyz/8,17,0 0.0 P _part/16/_geo_xyz/8,17,1 0.0 P _part/16/_geo_xyz/8,17,2 0.0 P _part/16/_geo_xyz/8,2,0 -8.202099800 P _part/16/_geo_xyz/8,2,1 12.362204552 P _part/16/_geo_xyz/8,2,2 32.562335968 P _part/16/_geo_xyz/8,3,0 -8.202099800 P _part/16/_geo_xyz/8,3,1 12.376968384 P _part/16/_geo_xyz/8,3,2 32.808399200 P _part/16/_geo_xyz/8,4,0 -8.202099800 P _part/16/_geo_xyz/8,4,1 12.376968384 P _part/16/_geo_xyz/8,4,2 33.136482239 P _part/16/_geo_xyz/8,5,0 -8.202099800 P _part/16/_geo_xyz/8,5,1 12.303149223 P _part/16/_geo_xyz/8,5,2 34.038715363 P _part/16/_geo_xyz/8,6,0 -8.202099800 P _part/16/_geo_xyz/8,6,1 12.303149223 P _part/16/_geo_xyz/8,6,2 34.038715363 P _part/16/_geo_xyz/8,7,0 -8.202099800 P _part/16/_geo_xyz/8,7,1 12.303149223 P _part/16/_geo_xyz/8,7,2 34.038715363 P _part/16/_geo_xyz/8,8,0 -8.202099800 P _part/16/_geo_xyz/8,8,1 12.303149223 P _part/16/_geo_xyz/8,8,2 34.038715363 P _part/16/_geo_xyz/8,9,0 -8.202099800 P _part/16/_geo_xyz/8,9,1 12.229330063 P _part/16/_geo_xyz/8,9,2 33.136482239 P _part/16/_geo_xyz/9,0,0 0.0 P _part/16/_geo_xyz/9,0,1 0.0 P _part/16/_geo_xyz/9,0,2 0.0 P _part/16/_geo_xyz/9,1,0 0.0 P _part/16/_geo_xyz/9,1,1 0.0 P _part/16/_geo_xyz/9,1,2 0.0 P _part/16/_geo_xyz/9,10,0 0.0 P _part/16/_geo_xyz/9,10,1 0.0 P _part/16/_geo_xyz/9,10,2 0.0 P _part/16/_geo_xyz/9,11,0 0.0 P _part/16/_geo_xyz/9,11,1 0.0 P _part/16/_geo_xyz/9,11,2 0.0 P _part/16/_geo_xyz/9,12,0 0.0 P _part/16/_geo_xyz/9,12,1 0.0 P _part/16/_geo_xyz/9,12,2 0.0 P _part/16/_geo_xyz/9,13,0 0.0 P _part/16/_geo_xyz/9,13,1 0.0 P _part/16/_geo_xyz/9,13,2 0.0 P _part/16/_geo_xyz/9,14,0 0.0 P _part/16/_geo_xyz/9,14,1 0.0 P _part/16/_geo_xyz/9,14,2 0.0 P _part/16/_geo_xyz/9,15,0 0.0 P _part/16/_geo_xyz/9,15,1 0.0 P _part/16/_geo_xyz/9,15,2 0.0 P _part/16/_geo_xyz/9,16,0 0.0 P _part/16/_geo_xyz/9,16,1 0.0 P _part/16/_geo_xyz/9,16,2 0.0 P _part/16/_geo_xyz/9,17,0 0.0 P _part/16/_geo_xyz/9,17,1 0.0 P _part/16/_geo_xyz/9,17,2 0.0 P _part/16/_geo_xyz/9,2,0 0.0 P _part/16/_geo_xyz/9,2,1 0.0 P _part/16/_geo_xyz/9,2,2 0.0 P _part/16/_geo_xyz/9,3,0 0.0 P _part/16/_geo_xyz/9,3,1 0.0 P _part/16/_geo_xyz/9,3,2 0.0 P _part/16/_geo_xyz/9,4,0 0.0 P _part/16/_geo_xyz/9,4,1 0.0 P _part/16/_geo_xyz/9,4,2 0.0 P _part/16/_geo_xyz/9,5,0 0.0 P _part/16/_geo_xyz/9,5,1 0.0 P _part/16/_geo_xyz/9,5,2 0.0 P _part/16/_geo_xyz/9,6,0 0.0 P _part/16/_geo_xyz/9,6,1 0.0 P _part/16/_geo_xyz/9,6,2 0.0 P _part/16/_geo_xyz/9,7,0 0.0 P _part/16/_geo_xyz/9,7,1 0.0 P _part/16/_geo_xyz/9,7,2 0.0 P _part/16/_geo_xyz/9,8,0 0.0 P _part/16/_geo_xyz/9,8,1 0.0 P _part/16/_geo_xyz/9,8,2 0.0 P _part/16/_geo_xyz/9,9,0 0.0 P _part/16/_geo_xyz/9,9,1 0.0 P _part/16/_geo_xyz/9,9,2 0.0 P _part/16/_geo_xyz/i_count 20 P _part/16/_geo_xyz/j_count 18 P _part/16/_geo_xyz/k_count 3 P _part/16/_locked/0,0 0 P _part/16/_locked/0,1 0 P _part/16/_locked/0,10 0 P _part/16/_locked/0,11 0 P _part/16/_locked/0,12 0 P _part/16/_locked/0,13 0 P _part/16/_locked/0,14 0 P _part/16/_locked/0,15 0 P _part/16/_locked/0,16 0 P _part/16/_locked/0,17 0 P _part/16/_locked/0,2 0 P _part/16/_locked/0,3 0 P _part/16/_locked/0,4 0 P _part/16/_locked/0,5 0 P _part/16/_locked/0,6 0 P _part/16/_locked/0,7 0 P _part/16/_locked/0,8 0 P _part/16/_locked/0,9 0 P _part/16/_locked/1,0 0 P _part/16/_locked/1,1 0 P _part/16/_locked/1,10 0 P _part/16/_locked/1,11 0 P _part/16/_locked/1,12 0 P _part/16/_locked/1,13 0 P _part/16/_locked/1,14 0 P _part/16/_locked/1,15 0 P _part/16/_locked/1,16 0 P _part/16/_locked/1,17 0 P _part/16/_locked/1,2 0 P _part/16/_locked/1,3 0 P _part/16/_locked/1,4 0 P _part/16/_locked/1,5 0 P _part/16/_locked/1,6 0 P _part/16/_locked/1,7 0 P _part/16/_locked/1,8 0 P _part/16/_locked/1,9 0 P _part/16/_locked/10,0 0 P _part/16/_locked/10,1 0 P _part/16/_locked/10,10 0 P _part/16/_locked/10,11 0 P _part/16/_locked/10,12 0 P _part/16/_locked/10,13 0 P _part/16/_locked/10,14 0 P _part/16/_locked/10,15 0 P _part/16/_locked/10,16 0 P _part/16/_locked/10,17 0 P _part/16/_locked/10,2 0 P _part/16/_locked/10,3 0 P _part/16/_locked/10,4 0 P _part/16/_locked/10,5 0 P _part/16/_locked/10,6 0 P _part/16/_locked/10,7 0 P _part/16/_locked/10,8 0 P _part/16/_locked/10,9 0 P _part/16/_locked/11,0 0 P _part/16/_locked/11,1 0 P _part/16/_locked/11,10 0 P _part/16/_locked/11,11 0 P _part/16/_locked/11,12 0 P _part/16/_locked/11,13 0 P _part/16/_locked/11,14 0 P _part/16/_locked/11,15 0 P _part/16/_locked/11,16 0 P _part/16/_locked/11,17 0 P _part/16/_locked/11,2 0 P _part/16/_locked/11,3 0 P _part/16/_locked/11,4 0 P _part/16/_locked/11,5 0 P _part/16/_locked/11,6 0 P _part/16/_locked/11,7 0 P _part/16/_locked/11,8 0 P _part/16/_locked/11,9 0 P _part/16/_locked/12,0 0 P _part/16/_locked/12,1 0 P _part/16/_locked/12,10 0 P _part/16/_locked/12,11 0 P _part/16/_locked/12,12 0 P _part/16/_locked/12,13 0 P _part/16/_locked/12,14 0 P _part/16/_locked/12,15 0 P _part/16/_locked/12,16 0 P _part/16/_locked/12,17 0 P _part/16/_locked/12,2 0 P _part/16/_locked/12,3 0 P _part/16/_locked/12,4 0 P _part/16/_locked/12,5 0 P _part/16/_locked/12,6 0 P _part/16/_locked/12,7 0 P _part/16/_locked/12,8 0 P _part/16/_locked/12,9 0 P _part/16/_locked/13,0 0 P _part/16/_locked/13,1 0 P _part/16/_locked/13,10 0 P _part/16/_locked/13,11 0 P _part/16/_locked/13,12 0 P _part/16/_locked/13,13 0 P _part/16/_locked/13,14 0 P _part/16/_locked/13,15 0 P _part/16/_locked/13,16 0 P _part/16/_locked/13,17 0 P _part/16/_locked/13,2 0 P _part/16/_locked/13,3 0 P _part/16/_locked/13,4 0 P _part/16/_locked/13,5 0 P _part/16/_locked/13,6 0 P _part/16/_locked/13,7 0 P _part/16/_locked/13,8 0 P _part/16/_locked/13,9 0 P _part/16/_locked/14,0 0 P _part/16/_locked/14,1 0 P _part/16/_locked/14,10 0 P _part/16/_locked/14,11 0 P _part/16/_locked/14,12 0 P _part/16/_locked/14,13 0 P _part/16/_locked/14,14 0 P _part/16/_locked/14,15 0 P _part/16/_locked/14,16 0 P _part/16/_locked/14,17 0 P _part/16/_locked/14,2 0 P _part/16/_locked/14,3 0 P _part/16/_locked/14,4 0 P _part/16/_locked/14,5 0 P _part/16/_locked/14,6 0 P _part/16/_locked/14,7 0 P _part/16/_locked/14,8 0 P _part/16/_locked/14,9 0 P _part/16/_locked/15,0 0 P _part/16/_locked/15,1 0 P _part/16/_locked/15,10 0 P _part/16/_locked/15,11 0 P _part/16/_locked/15,12 0 P _part/16/_locked/15,13 0 P _part/16/_locked/15,14 0 P _part/16/_locked/15,15 0 P _part/16/_locked/15,16 0 P _part/16/_locked/15,17 0 P _part/16/_locked/15,2 0 P _part/16/_locked/15,3 0 P _part/16/_locked/15,4 0 P _part/16/_locked/15,5 0 P _part/16/_locked/15,6 0 P _part/16/_locked/15,7 0 P _part/16/_locked/15,8 0 P _part/16/_locked/15,9 0 P _part/16/_locked/16,0 0 P _part/16/_locked/16,1 0 P _part/16/_locked/16,10 0 P _part/16/_locked/16,11 0 P _part/16/_locked/16,12 0 P _part/16/_locked/16,13 0 P _part/16/_locked/16,14 0 P _part/16/_locked/16,15 0 P _part/16/_locked/16,16 0 P _part/16/_locked/16,17 0 P _part/16/_locked/16,2 0 P _part/16/_locked/16,3 0 P _part/16/_locked/16,4 0 P _part/16/_locked/16,5 0 P _part/16/_locked/16,6 0 P _part/16/_locked/16,7 0 P _part/16/_locked/16,8 0 P _part/16/_locked/16,9 0 P _part/16/_locked/17,0 0 P _part/16/_locked/17,1 0 P _part/16/_locked/17,10 0 P _part/16/_locked/17,11 0 P _part/16/_locked/17,12 0 P _part/16/_locked/17,13 0 P _part/16/_locked/17,14 0 P _part/16/_locked/17,15 0 P _part/16/_locked/17,16 0 P _part/16/_locked/17,17 0 P _part/16/_locked/17,2 0 P _part/16/_locked/17,3 0 P _part/16/_locked/17,4 0 P _part/16/_locked/17,5 0 P _part/16/_locked/17,6 0 P _part/16/_locked/17,7 0 P _part/16/_locked/17,8 0 P _part/16/_locked/17,9 0 P _part/16/_locked/18,0 0 P _part/16/_locked/18,1 0 P _part/16/_locked/18,10 0 P _part/16/_locked/18,11 0 P _part/16/_locked/18,12 0 P _part/16/_locked/18,13 0 P _part/16/_locked/18,14 0 P _part/16/_locked/18,15 0 P _part/16/_locked/18,16 0 P _part/16/_locked/18,17 0 P _part/16/_locked/18,2 0 P _part/16/_locked/18,3 0 P _part/16/_locked/18,4 0 P _part/16/_locked/18,5 0 P _part/16/_locked/18,6 0 P _part/16/_locked/18,7 0 P _part/16/_locked/18,8 0 P _part/16/_locked/18,9 0 P _part/16/_locked/19,0 0 P _part/16/_locked/19,1 0 P _part/16/_locked/19,10 0 P _part/16/_locked/19,11 0 P _part/16/_locked/19,12 0 P _part/16/_locked/19,13 0 P _part/16/_locked/19,14 0 P _part/16/_locked/19,15 0 P _part/16/_locked/19,16 0 P _part/16/_locked/19,17 0 P _part/16/_locked/19,2 0 P _part/16/_locked/19,3 0 P _part/16/_locked/19,4 0 P _part/16/_locked/19,5 0 P _part/16/_locked/19,6 0 P _part/16/_locked/19,7 0 P _part/16/_locked/19,8 0 P _part/16/_locked/19,9 0 P _part/16/_locked/2,0 0 P _part/16/_locked/2,1 0 P _part/16/_locked/2,10 0 P _part/16/_locked/2,11 0 P _part/16/_locked/2,12 0 P _part/16/_locked/2,13 0 P _part/16/_locked/2,14 0 P _part/16/_locked/2,15 0 P _part/16/_locked/2,16 0 P _part/16/_locked/2,17 0 P _part/16/_locked/2,2 0 P _part/16/_locked/2,3 0 P _part/16/_locked/2,4 0 P _part/16/_locked/2,5 0 P _part/16/_locked/2,6 0 P _part/16/_locked/2,7 0 P _part/16/_locked/2,8 0 P _part/16/_locked/2,9 0 P _part/16/_locked/3,0 0 P _part/16/_locked/3,1 0 P _part/16/_locked/3,10 0 P _part/16/_locked/3,11 0 P _part/16/_locked/3,12 0 P _part/16/_locked/3,13 0 P _part/16/_locked/3,14 0 P _part/16/_locked/3,15 0 P _part/16/_locked/3,16 0 P _part/16/_locked/3,17 0 P _part/16/_locked/3,2 0 P _part/16/_locked/3,3 0 P _part/16/_locked/3,4 0 P _part/16/_locked/3,5 0 P _part/16/_locked/3,6 0 P _part/16/_locked/3,7 0 P _part/16/_locked/3,8 0 P _part/16/_locked/3,9 0 P _part/16/_locked/4,0 0 P _part/16/_locked/4,1 0 P _part/16/_locked/4,10 0 P _part/16/_locked/4,11 0 P _part/16/_locked/4,12 0 P _part/16/_locked/4,13 0 P _part/16/_locked/4,14 0 P _part/16/_locked/4,15 0 P _part/16/_locked/4,16 0 P _part/16/_locked/4,17 0 P _part/16/_locked/4,2 0 P _part/16/_locked/4,3 0 P _part/16/_locked/4,4 0 P _part/16/_locked/4,5 0 P _part/16/_locked/4,6 0 P _part/16/_locked/4,7 0 P _part/16/_locked/4,8 0 P _part/16/_locked/4,9 0 P _part/16/_locked/5,0 0 P _part/16/_locked/5,1 0 P _part/16/_locked/5,10 0 P _part/16/_locked/5,11 0 P _part/16/_locked/5,12 0 P _part/16/_locked/5,13 0 P _part/16/_locked/5,14 0 P _part/16/_locked/5,15 0 P _part/16/_locked/5,16 0 P _part/16/_locked/5,17 0 P _part/16/_locked/5,2 0 P _part/16/_locked/5,3 0 P _part/16/_locked/5,4 0 P _part/16/_locked/5,5 0 P _part/16/_locked/5,6 0 P _part/16/_locked/5,7 0 P _part/16/_locked/5,8 0 P _part/16/_locked/5,9 0 P _part/16/_locked/6,0 0 P _part/16/_locked/6,1 0 P _part/16/_locked/6,10 0 P _part/16/_locked/6,11 0 P _part/16/_locked/6,12 0 P _part/16/_locked/6,13 0 P _part/16/_locked/6,14 0 P _part/16/_locked/6,15 0 P _part/16/_locked/6,16 0 P _part/16/_locked/6,17 0 P _part/16/_locked/6,2 0 P _part/16/_locked/6,3 0 P _part/16/_locked/6,4 0 P _part/16/_locked/6,5 0 P _part/16/_locked/6,6 0 P _part/16/_locked/6,7 0 P _part/16/_locked/6,8 0 P _part/16/_locked/6,9 0 P _part/16/_locked/7,0 0 P _part/16/_locked/7,1 0 P _part/16/_locked/7,10 0 P _part/16/_locked/7,11 0 P _part/16/_locked/7,12 0 P _part/16/_locked/7,13 0 P _part/16/_locked/7,14 0 P _part/16/_locked/7,15 0 P _part/16/_locked/7,16 0 P _part/16/_locked/7,17 0 P _part/16/_locked/7,2 0 P _part/16/_locked/7,3 0 P _part/16/_locked/7,4 0 P _part/16/_locked/7,5 0 P _part/16/_locked/7,6 0 P _part/16/_locked/7,7 0 P _part/16/_locked/7,8 0 P _part/16/_locked/7,9 0 P _part/16/_locked/8,0 0 P _part/16/_locked/8,1 0 P _part/16/_locked/8,10 0 P _part/16/_locked/8,11 0 P _part/16/_locked/8,12 0 P _part/16/_locked/8,13 0 P _part/16/_locked/8,14 0 P _part/16/_locked/8,15 0 P _part/16/_locked/8,16 0 P _part/16/_locked/8,17 0 P _part/16/_locked/8,2 0 P _part/16/_locked/8,3 0 P _part/16/_locked/8,4 0 P _part/16/_locked/8,5 0 P _part/16/_locked/8,6 0 P _part/16/_locked/8,7 0 P _part/16/_locked/8,8 0 P _part/16/_locked/8,9 0 P _part/16/_locked/9,0 0 P _part/16/_locked/9,1 0 P _part/16/_locked/9,10 0 P _part/16/_locked/9,11 0 P _part/16/_locked/9,12 0 P _part/16/_locked/9,13 0 P _part/16/_locked/9,14 0 P _part/16/_locked/9,15 0 P _part/16/_locked/9,16 0 P _part/16/_locked/9,17 0 P _part/16/_locked/9,2 0 P _part/16/_locked/9,3 0 P _part/16/_locked/9,4 0 P _part/16/_locked/9,5 0 P _part/16/_locked/9,6 0 P _part/16/_locked/9,7 0 P _part/16/_locked/9,8 0 P _part/16/_locked/9,9 0 P _part/16/_locked/i_count 20 P _part/16/_locked/j_count 18 P _part/16/_part_cd 0.075000003 P _part/16/_part_phi 0.0 P _part/16/_part_psi 0.0 P _part/16/_part_rad 2.0 P _part/16/_part_specs_eq 1 P _part/16/_part_specs_invis 0 P _part/16/_part_specs_unused1 0 P _part/16/_part_specs_unused2 0 P _part/16/_part_tex 0 P _part/16/_part_the 0.0 P _part/16/_part_x -0.0 P _part/16/_part_y 12.303149223 P _part/16/_part_z 32.808399200 P _part/16/_patt_con 0 P _part/16/_patt_prt 0 P _part/16/_patt_rat 0.0 P _part/16/_r_dim 14 P _part/16/_s_dim 4 P _part/16/_scon 37.676635742 P _part/16/_top_s1 0.755999982 P _part/16/_top_s2 1.0 P _part/16/_top_t1 0.509999990 P _part/16/_top_t2 0.754000008 P _part/17/_aero_x_os 0.0 P _part/17/_aero_y_os 0.0 P _part/17/_aero_z_os 0.0 P _part/17/_area_frnt 0.0 P _part/17/_area_side 0.0 P _part/17/_area_vert 0.0 P _part/17/_bot_s1 0.755999982 P _part/17/_bot_s2 1.0 P _part/17/_bot_t1 0.509999990 P _part/17/_bot_t2 0.754000008 P _part/17/_damp 1.883831739 P _part/17/_geo_xyz/0,0,0 0.0 P _part/17/_geo_xyz/0,0,1 12.303149223 P _part/17/_geo_xyz/0,0,2 32.398292542 P _part/17/_geo_xyz/0,1,0 0.0 P _part/17/_geo_xyz/0,1,1 12.332676888 P _part/17/_geo_xyz/0,1,2 32.447505951 P _part/17/_geo_xyz/0,10,0 0.0 P _part/17/_geo_xyz/0,10,1 12.229330063 P _part/17/_geo_xyz/0,10,2 32.808399200 P _part/17/_geo_xyz/0,11,0 0.0 P _part/17/_geo_xyz/0,11,1 12.244093895 P _part/17/_geo_xyz/0,11,2 32.562335968 P _part/17/_geo_xyz/0,12,0 0.0 P _part/17/_geo_xyz/0,12,1 12.273621559 P _part/17/_geo_xyz/0,12,2 32.447505951 P _part/17/_geo_xyz/0,13,0 0.0 P _part/17/_geo_xyz/0,13,1 12.303149223 P _part/17/_geo_xyz/0,13,2 32.398292542 P _part/17/_geo_xyz/0,14,0 0.0 P _part/17/_geo_xyz/0,14,1 0.0 P _part/17/_geo_xyz/0,14,2 0.0 P _part/17/_geo_xyz/0,15,0 0.0 P _part/17/_geo_xyz/0,15,1 0.0 P _part/17/_geo_xyz/0,15,2 0.0 P _part/17/_geo_xyz/0,16,0 0.0 P _part/17/_geo_xyz/0,16,1 0.0 P _part/17/_geo_xyz/0,16,2 0.0 P _part/17/_geo_xyz/0,17,0 0.0 P _part/17/_geo_xyz/0,17,1 0.0 P _part/17/_geo_xyz/0,17,2 0.0 P _part/17/_geo_xyz/0,2,0 0.0 P _part/17/_geo_xyz/0,2,1 12.362204552 P _part/17/_geo_xyz/0,2,2 32.562335968 P _part/17/_geo_xyz/0,3,0 0.0 P _part/17/_geo_xyz/0,3,1 12.376968384 P _part/17/_geo_xyz/0,3,2 32.808399200 P _part/17/_geo_xyz/0,4,0 0.0 P _part/17/_geo_xyz/0,4,1 12.376968384 P _part/17/_geo_xyz/0,4,2 33.136482239 P _part/17/_geo_xyz/0,5,0 0.0 P _part/17/_geo_xyz/0,5,1 12.336703300 P _part/17/_geo_xyz/0,5,2 33.628608704 P _part/17/_geo_xyz/0,6,0 0.0 P _part/17/_geo_xyz/0,6,1 12.303149223 P _part/17/_geo_xyz/0,6,2 34.038715363 P _part/17/_geo_xyz/0,7,0 0.0 P _part/17/_geo_xyz/0,7,1 12.303149223 P _part/17/_geo_xyz/0,7,2 34.038715363 P _part/17/_geo_xyz/0,8,0 0.0 P _part/17/_geo_xyz/0,8,1 12.269595146 P _part/17/_geo_xyz/0,8,2 33.628608704 P _part/17/_geo_xyz/0,9,0 0.0 P _part/17/_geo_xyz/0,9,1 12.229330063 P _part/17/_geo_xyz/0,9,2 33.136482239 P _part/17/_geo_xyz/1,0,0 4.101049900 P _part/17/_geo_xyz/1,0,1 12.303149223 P _part/17/_geo_xyz/1,0,2 32.398292542 P _part/17/_geo_xyz/1,1,0 4.101049900 P _part/17/_geo_xyz/1,1,1 12.332676888 P _part/17/_geo_xyz/1,1,2 32.447505951 P _part/17/_geo_xyz/1,10,0 4.101049900 P _part/17/_geo_xyz/1,10,1 12.229330063 P _part/17/_geo_xyz/1,10,2 32.808399200 P _part/17/_geo_xyz/1,11,0 4.101049900 P _part/17/_geo_xyz/1,11,1 12.244093895 P _part/17/_geo_xyz/1,11,2 32.562335968 P _part/17/_geo_xyz/1,12,0 4.101049900 P _part/17/_geo_xyz/1,12,1 12.273621559 P _part/17/_geo_xyz/1,12,2 32.447505951 P _part/17/_geo_xyz/1,13,0 4.101049900 P _part/17/_geo_xyz/1,13,1 12.303149223 P _part/17/_geo_xyz/1,13,2 32.398292542 P _part/17/_geo_xyz/1,14,0 0.0 P _part/17/_geo_xyz/1,14,1 0.0 P _part/17/_geo_xyz/1,14,2 0.0 P _part/17/_geo_xyz/1,15,0 0.0 P _part/17/_geo_xyz/1,15,1 0.0 P _part/17/_geo_xyz/1,15,2 0.0 P _part/17/_geo_xyz/1,16,0 0.0 P _part/17/_geo_xyz/1,16,1 0.0 P _part/17/_geo_xyz/1,16,2 0.0 P _part/17/_geo_xyz/1,17,0 0.0 P _part/17/_geo_xyz/1,17,1 0.0 P _part/17/_geo_xyz/1,17,2 0.0 P _part/17/_geo_xyz/1,2,0 4.101049900 P _part/17/_geo_xyz/1,2,1 12.362204552 P _part/17/_geo_xyz/1,2,2 32.562335968 P _part/17/_geo_xyz/1,3,0 4.101049900 P _part/17/_geo_xyz/1,3,1 12.376968384 P _part/17/_geo_xyz/1,3,2 32.808399200 P _part/17/_geo_xyz/1,4,0 4.101049900 P _part/17/_geo_xyz/1,4,1 12.376968384 P _part/17/_geo_xyz/1,4,2 33.136482239 P _part/17/_geo_xyz/1,5,0 4.101049900 P _part/17/_geo_xyz/1,5,1 12.336703300 P _part/17/_geo_xyz/1,5,2 33.628608704 P _part/17/_geo_xyz/1,6,0 4.101049900 P _part/17/_geo_xyz/1,6,1 12.303149223 P _part/17/_geo_xyz/1,6,2 34.038715363 P _part/17/_geo_xyz/1,7,0 4.101049900 P _part/17/_geo_xyz/1,7,1 12.303149223 P _part/17/_geo_xyz/1,7,2 34.038715363 P _part/17/_geo_xyz/1,8,0 4.101049900 P _part/17/_geo_xyz/1,8,1 12.269595146 P _part/17/_geo_xyz/1,8,2 33.628608704 P _part/17/_geo_xyz/1,9,0 4.101049900 P _part/17/_geo_xyz/1,9,1 12.229330063 P _part/17/_geo_xyz/1,9,2 33.136482239 P _part/17/_geo_xyz/10,0,0 0.0 P _part/17/_geo_xyz/10,0,1 0.0 P _part/17/_geo_xyz/10,0,2 0.0 P _part/17/_geo_xyz/10,1,0 0.0 P _part/17/_geo_xyz/10,1,1 0.0 P _part/17/_geo_xyz/10,1,2 0.0 P _part/17/_geo_xyz/10,10,0 0.0 P _part/17/_geo_xyz/10,10,1 0.0 P _part/17/_geo_xyz/10,10,2 0.0 P _part/17/_geo_xyz/10,11,0 0.0 P _part/17/_geo_xyz/10,11,1 0.0 P _part/17/_geo_xyz/10,11,2 0.0 P _part/17/_geo_xyz/10,12,0 0.0 P _part/17/_geo_xyz/10,12,1 0.0 P _part/17/_geo_xyz/10,12,2 0.0 P _part/17/_geo_xyz/10,13,0 0.0 P _part/17/_geo_xyz/10,13,1 0.0 P _part/17/_geo_xyz/10,13,2 0.0 P _part/17/_geo_xyz/10,14,0 0.0 P _part/17/_geo_xyz/10,14,1 0.0 P _part/17/_geo_xyz/10,14,2 0.0 P _part/17/_geo_xyz/10,15,0 0.0 P _part/17/_geo_xyz/10,15,1 0.0 P _part/17/_geo_xyz/10,15,2 0.0 P _part/17/_geo_xyz/10,16,0 0.0 P _part/17/_geo_xyz/10,16,1 0.0 P _part/17/_geo_xyz/10,16,2 0.0 P _part/17/_geo_xyz/10,17,0 0.0 P _part/17/_geo_xyz/10,17,1 0.0 P _part/17/_geo_xyz/10,17,2 0.0 P _part/17/_geo_xyz/10,2,0 0.0 P _part/17/_geo_xyz/10,2,1 0.0 P _part/17/_geo_xyz/10,2,2 0.0 P _part/17/_geo_xyz/10,3,0 0.0 P _part/17/_geo_xyz/10,3,1 0.0 P _part/17/_geo_xyz/10,3,2 0.0 P _part/17/_geo_xyz/10,4,0 0.0 P _part/17/_geo_xyz/10,4,1 0.0 P _part/17/_geo_xyz/10,4,2 0.0 P _part/17/_geo_xyz/10,5,0 0.0 P _part/17/_geo_xyz/10,5,1 0.0 P _part/17/_geo_xyz/10,5,2 0.0 P _part/17/_geo_xyz/10,6,0 0.0 P _part/17/_geo_xyz/10,6,1 0.0 P _part/17/_geo_xyz/10,6,2 0.0 P _part/17/_geo_xyz/10,7,0 0.0 P _part/17/_geo_xyz/10,7,1 0.0 P _part/17/_geo_xyz/10,7,2 0.0 P _part/17/_geo_xyz/10,8,0 0.0 P _part/17/_geo_xyz/10,8,1 0.0 P _part/17/_geo_xyz/10,8,2 0.0 P _part/17/_geo_xyz/10,9,0 0.0 P _part/17/_geo_xyz/10,9,1 0.0 P _part/17/_geo_xyz/10,9,2 0.0 P _part/17/_geo_xyz/11,0,0 0.0 P _part/17/_geo_xyz/11,0,1 0.0 P _part/17/_geo_xyz/11,0,2 0.0 P _part/17/_geo_xyz/11,1,0 0.0 P _part/17/_geo_xyz/11,1,1 0.0 P _part/17/_geo_xyz/11,1,2 0.0 P _part/17/_geo_xyz/11,10,0 0.0 P _part/17/_geo_xyz/11,10,1 0.0 P _part/17/_geo_xyz/11,10,2 0.0 P _part/17/_geo_xyz/11,11,0 0.0 P _part/17/_geo_xyz/11,11,1 0.0 P _part/17/_geo_xyz/11,11,2 0.0 P _part/17/_geo_xyz/11,12,0 0.0 P _part/17/_geo_xyz/11,12,1 0.0 P _part/17/_geo_xyz/11,12,2 0.0 P _part/17/_geo_xyz/11,13,0 0.0 P _part/17/_geo_xyz/11,13,1 0.0 P _part/17/_geo_xyz/11,13,2 0.0 P _part/17/_geo_xyz/11,14,0 0.0 P _part/17/_geo_xyz/11,14,1 0.0 P _part/17/_geo_xyz/11,14,2 0.0 P _part/17/_geo_xyz/11,15,0 0.0 P _part/17/_geo_xyz/11,15,1 0.0 P _part/17/_geo_xyz/11,15,2 0.0 P _part/17/_geo_xyz/11,16,0 0.0 P _part/17/_geo_xyz/11,16,1 0.0 P _part/17/_geo_xyz/11,16,2 0.0 P _part/17/_geo_xyz/11,17,0 0.0 P _part/17/_geo_xyz/11,17,1 0.0 P _part/17/_geo_xyz/11,17,2 0.0 P _part/17/_geo_xyz/11,2,0 0.0 P _part/17/_geo_xyz/11,2,1 0.0 P _part/17/_geo_xyz/11,2,2 0.0 P _part/17/_geo_xyz/11,3,0 0.0 P _part/17/_geo_xyz/11,3,1 0.0 P _part/17/_geo_xyz/11,3,2 0.0 P _part/17/_geo_xyz/11,4,0 0.0 P _part/17/_geo_xyz/11,4,1 0.0 P _part/17/_geo_xyz/11,4,2 0.0 P _part/17/_geo_xyz/11,5,0 0.0 P _part/17/_geo_xyz/11,5,1 0.0 P _part/17/_geo_xyz/11,5,2 0.0 P _part/17/_geo_xyz/11,6,0 0.0 P _part/17/_geo_xyz/11,6,1 0.0 P _part/17/_geo_xyz/11,6,2 0.0 P _part/17/_geo_xyz/11,7,0 0.0 P _part/17/_geo_xyz/11,7,1 0.0 P _part/17/_geo_xyz/11,7,2 0.0 P _part/17/_geo_xyz/11,8,0 0.0 P _part/17/_geo_xyz/11,8,1 0.0 P _part/17/_geo_xyz/11,8,2 0.0 P _part/17/_geo_xyz/11,9,0 0.0 P _part/17/_geo_xyz/11,9,1 0.0 P _part/17/_geo_xyz/11,9,2 0.0 P _part/17/_geo_xyz/12,0,0 0.0 P _part/17/_geo_xyz/12,0,1 0.0 P _part/17/_geo_xyz/12,0,2 0.0 P _part/17/_geo_xyz/12,1,0 0.0 P _part/17/_geo_xyz/12,1,1 0.0 P _part/17/_geo_xyz/12,1,2 0.0 P _part/17/_geo_xyz/12,10,0 0.0 P _part/17/_geo_xyz/12,10,1 0.0 P _part/17/_geo_xyz/12,10,2 0.0 P _part/17/_geo_xyz/12,11,0 0.0 P _part/17/_geo_xyz/12,11,1 0.0 P _part/17/_geo_xyz/12,11,2 0.0 P _part/17/_geo_xyz/12,12,0 0.0 P _part/17/_geo_xyz/12,12,1 0.0 P _part/17/_geo_xyz/12,12,2 0.0 P _part/17/_geo_xyz/12,13,0 0.0 P _part/17/_geo_xyz/12,13,1 0.0 P _part/17/_geo_xyz/12,13,2 0.0 P _part/17/_geo_xyz/12,14,0 0.0 P _part/17/_geo_xyz/12,14,1 0.0 P _part/17/_geo_xyz/12,14,2 0.0 P _part/17/_geo_xyz/12,15,0 0.0 P _part/17/_geo_xyz/12,15,1 0.0 P _part/17/_geo_xyz/12,15,2 0.0 P _part/17/_geo_xyz/12,16,0 0.0 P _part/17/_geo_xyz/12,16,1 0.0 P _part/17/_geo_xyz/12,16,2 0.0 P _part/17/_geo_xyz/12,17,0 0.0 P _part/17/_geo_xyz/12,17,1 0.0 P _part/17/_geo_xyz/12,17,2 0.0 P _part/17/_geo_xyz/12,2,0 0.0 P _part/17/_geo_xyz/12,2,1 0.0 P _part/17/_geo_xyz/12,2,2 0.0 P _part/17/_geo_xyz/12,3,0 0.0 P _part/17/_geo_xyz/12,3,1 0.0 P _part/17/_geo_xyz/12,3,2 0.0 P _part/17/_geo_xyz/12,4,0 0.0 P _part/17/_geo_xyz/12,4,1 0.0 P _part/17/_geo_xyz/12,4,2 0.0 P _part/17/_geo_xyz/12,5,0 0.0 P _part/17/_geo_xyz/12,5,1 0.0 P _part/17/_geo_xyz/12,5,2 0.0 P _part/17/_geo_xyz/12,6,0 0.0 P _part/17/_geo_xyz/12,6,1 0.0 P _part/17/_geo_xyz/12,6,2 0.0 P _part/17/_geo_xyz/12,7,0 0.0 P _part/17/_geo_xyz/12,7,1 0.0 P _part/17/_geo_xyz/12,7,2 0.0 P _part/17/_geo_xyz/12,8,0 0.0 P _part/17/_geo_xyz/12,8,1 0.0 P _part/17/_geo_xyz/12,8,2 0.0 P _part/17/_geo_xyz/12,9,0 0.0 P _part/17/_geo_xyz/12,9,1 0.0 P _part/17/_geo_xyz/12,9,2 0.0 P _part/17/_geo_xyz/13,0,0 0.0 P _part/17/_geo_xyz/13,0,1 0.0 P _part/17/_geo_xyz/13,0,2 0.0 P _part/17/_geo_xyz/13,1,0 0.0 P _part/17/_geo_xyz/13,1,1 0.0 P _part/17/_geo_xyz/13,1,2 0.0 P _part/17/_geo_xyz/13,10,0 0.0 P _part/17/_geo_xyz/13,10,1 0.0 P _part/17/_geo_xyz/13,10,2 0.0 P _part/17/_geo_xyz/13,11,0 0.0 P _part/17/_geo_xyz/13,11,1 0.0 P _part/17/_geo_xyz/13,11,2 0.0 P _part/17/_geo_xyz/13,12,0 0.0 P _part/17/_geo_xyz/13,12,1 0.0 P _part/17/_geo_xyz/13,12,2 0.0 P _part/17/_geo_xyz/13,13,0 0.0 P _part/17/_geo_xyz/13,13,1 0.0 P _part/17/_geo_xyz/13,13,2 0.0 P _part/17/_geo_xyz/13,14,0 0.0 P _part/17/_geo_xyz/13,14,1 0.0 P _part/17/_geo_xyz/13,14,2 0.0 P _part/17/_geo_xyz/13,15,0 0.0 P _part/17/_geo_xyz/13,15,1 0.0 P _part/17/_geo_xyz/13,15,2 0.0 P _part/17/_geo_xyz/13,16,0 0.0 P _part/17/_geo_xyz/13,16,1 0.0 P _part/17/_geo_xyz/13,16,2 0.0 P _part/17/_geo_xyz/13,17,0 0.0 P _part/17/_geo_xyz/13,17,1 0.0 P _part/17/_geo_xyz/13,17,2 0.0 P _part/17/_geo_xyz/13,2,0 0.0 P _part/17/_geo_xyz/13,2,1 0.0 P _part/17/_geo_xyz/13,2,2 0.0 P _part/17/_geo_xyz/13,3,0 0.0 P _part/17/_geo_xyz/13,3,1 0.0 P _part/17/_geo_xyz/13,3,2 0.0 P _part/17/_geo_xyz/13,4,0 0.0 P _part/17/_geo_xyz/13,4,1 0.0 P _part/17/_geo_xyz/13,4,2 0.0 P _part/17/_geo_xyz/13,5,0 0.0 P _part/17/_geo_xyz/13,5,1 0.0 P _part/17/_geo_xyz/13,5,2 0.0 P _part/17/_geo_xyz/13,6,0 0.0 P _part/17/_geo_xyz/13,6,1 0.0 P _part/17/_geo_xyz/13,6,2 0.0 P _part/17/_geo_xyz/13,7,0 0.0 P _part/17/_geo_xyz/13,7,1 0.0 P _part/17/_geo_xyz/13,7,2 0.0 P _part/17/_geo_xyz/13,8,0 0.0 P _part/17/_geo_xyz/13,8,1 0.0 P _part/17/_geo_xyz/13,8,2 0.0 P _part/17/_geo_xyz/13,9,0 0.0 P _part/17/_geo_xyz/13,9,1 0.0 P _part/17/_geo_xyz/13,9,2 0.0 P _part/17/_geo_xyz/14,0,0 0.0 P _part/17/_geo_xyz/14,0,1 0.0 P _part/17/_geo_xyz/14,0,2 0.0 P _part/17/_geo_xyz/14,1,0 0.0 P _part/17/_geo_xyz/14,1,1 0.0 P _part/17/_geo_xyz/14,1,2 0.0 P _part/17/_geo_xyz/14,10,0 0.0 P _part/17/_geo_xyz/14,10,1 0.0 P _part/17/_geo_xyz/14,10,2 0.0 P _part/17/_geo_xyz/14,11,0 0.0 P _part/17/_geo_xyz/14,11,1 0.0 P _part/17/_geo_xyz/14,11,2 0.0 P _part/17/_geo_xyz/14,12,0 0.0 P _part/17/_geo_xyz/14,12,1 0.0 P _part/17/_geo_xyz/14,12,2 0.0 P _part/17/_geo_xyz/14,13,0 0.0 P _part/17/_geo_xyz/14,13,1 0.0 P _part/17/_geo_xyz/14,13,2 0.0 P _part/17/_geo_xyz/14,14,0 0.0 P _part/17/_geo_xyz/14,14,1 0.0 P _part/17/_geo_xyz/14,14,2 0.0 P _part/17/_geo_xyz/14,15,0 0.0 P _part/17/_geo_xyz/14,15,1 0.0 P _part/17/_geo_xyz/14,15,2 0.0 P _part/17/_geo_xyz/14,16,0 0.0 P _part/17/_geo_xyz/14,16,1 0.0 P _part/17/_geo_xyz/14,16,2 0.0 P _part/17/_geo_xyz/14,17,0 0.0 P _part/17/_geo_xyz/14,17,1 0.0 P _part/17/_geo_xyz/14,17,2 0.0 P _part/17/_geo_xyz/14,2,0 0.0 P _part/17/_geo_xyz/14,2,1 0.0 P _part/17/_geo_xyz/14,2,2 0.0 P _part/17/_geo_xyz/14,3,0 0.0 P _part/17/_geo_xyz/14,3,1 0.0 P _part/17/_geo_xyz/14,3,2 0.0 P _part/17/_geo_xyz/14,4,0 0.0 P _part/17/_geo_xyz/14,4,1 0.0 P _part/17/_geo_xyz/14,4,2 0.0 P _part/17/_geo_xyz/14,5,0 0.0 P _part/17/_geo_xyz/14,5,1 0.0 P _part/17/_geo_xyz/14,5,2 0.0 P _part/17/_geo_xyz/14,6,0 0.0 P _part/17/_geo_xyz/14,6,1 0.0 P _part/17/_geo_xyz/14,6,2 0.0 P _part/17/_geo_xyz/14,7,0 0.0 P _part/17/_geo_xyz/14,7,1 0.0 P _part/17/_geo_xyz/14,7,2 0.0 P _part/17/_geo_xyz/14,8,0 0.0 P _part/17/_geo_xyz/14,8,1 0.0 P _part/17/_geo_xyz/14,8,2 0.0 P _part/17/_geo_xyz/14,9,0 0.0 P _part/17/_geo_xyz/14,9,1 0.0 P _part/17/_geo_xyz/14,9,2 0.0 P _part/17/_geo_xyz/15,0,0 0.0 P _part/17/_geo_xyz/15,0,1 0.0 P _part/17/_geo_xyz/15,0,2 0.0 P _part/17/_geo_xyz/15,1,0 0.0 P _part/17/_geo_xyz/15,1,1 0.0 P _part/17/_geo_xyz/15,1,2 0.0 P _part/17/_geo_xyz/15,10,0 0.0 P _part/17/_geo_xyz/15,10,1 0.0 P _part/17/_geo_xyz/15,10,2 0.0 P _part/17/_geo_xyz/15,11,0 0.0 P _part/17/_geo_xyz/15,11,1 0.0 P _part/17/_geo_xyz/15,11,2 0.0 P _part/17/_geo_xyz/15,12,0 0.0 P _part/17/_geo_xyz/15,12,1 0.0 P _part/17/_geo_xyz/15,12,2 0.0 P _part/17/_geo_xyz/15,13,0 0.0 P _part/17/_geo_xyz/15,13,1 0.0 P _part/17/_geo_xyz/15,13,2 0.0 P _part/17/_geo_xyz/15,14,0 0.0 P _part/17/_geo_xyz/15,14,1 0.0 P _part/17/_geo_xyz/15,14,2 0.0 P _part/17/_geo_xyz/15,15,0 0.0 P _part/17/_geo_xyz/15,15,1 0.0 P _part/17/_geo_xyz/15,15,2 0.0 P _part/17/_geo_xyz/15,16,0 0.0 P _part/17/_geo_xyz/15,16,1 0.0 P _part/17/_geo_xyz/15,16,2 0.0 P _part/17/_geo_xyz/15,17,0 0.0 P _part/17/_geo_xyz/15,17,1 0.0 P _part/17/_geo_xyz/15,17,2 0.0 P _part/17/_geo_xyz/15,2,0 0.0 P _part/17/_geo_xyz/15,2,1 0.0 P _part/17/_geo_xyz/15,2,2 0.0 P _part/17/_geo_xyz/15,3,0 0.0 P _part/17/_geo_xyz/15,3,1 0.0 P _part/17/_geo_xyz/15,3,2 0.0 P _part/17/_geo_xyz/15,4,0 0.0 P _part/17/_geo_xyz/15,4,1 0.0 P _part/17/_geo_xyz/15,4,2 0.0 P _part/17/_geo_xyz/15,5,0 0.0 P _part/17/_geo_xyz/15,5,1 0.0 P _part/17/_geo_xyz/15,5,2 0.0 P _part/17/_geo_xyz/15,6,0 0.0 P _part/17/_geo_xyz/15,6,1 0.0 P _part/17/_geo_xyz/15,6,2 0.0 P _part/17/_geo_xyz/15,7,0 0.0 P _part/17/_geo_xyz/15,7,1 0.0 P _part/17/_geo_xyz/15,7,2 0.0 P _part/17/_geo_xyz/15,8,0 0.0 P _part/17/_geo_xyz/15,8,1 0.0 P _part/17/_geo_xyz/15,8,2 0.0 P _part/17/_geo_xyz/15,9,0 0.0 P _part/17/_geo_xyz/15,9,1 0.0 P _part/17/_geo_xyz/15,9,2 0.0 P _part/17/_geo_xyz/16,0,0 0.0 P _part/17/_geo_xyz/16,0,1 0.0 P _part/17/_geo_xyz/16,0,2 0.0 P _part/17/_geo_xyz/16,1,0 0.0 P _part/17/_geo_xyz/16,1,1 0.0 P _part/17/_geo_xyz/16,1,2 0.0 P _part/17/_geo_xyz/16,10,0 0.0 P _part/17/_geo_xyz/16,10,1 0.0 P _part/17/_geo_xyz/16,10,2 0.0 P _part/17/_geo_xyz/16,11,0 0.0 P _part/17/_geo_xyz/16,11,1 0.0 P _part/17/_geo_xyz/16,11,2 0.0 P _part/17/_geo_xyz/16,12,0 0.0 P _part/17/_geo_xyz/16,12,1 0.0 P _part/17/_geo_xyz/16,12,2 0.0 P _part/17/_geo_xyz/16,13,0 0.0 P _part/17/_geo_xyz/16,13,1 0.0 P _part/17/_geo_xyz/16,13,2 0.0 P _part/17/_geo_xyz/16,14,0 0.0 P _part/17/_geo_xyz/16,14,1 0.0 P _part/17/_geo_xyz/16,14,2 0.0 P _part/17/_geo_xyz/16,15,0 0.0 P _part/17/_geo_xyz/16,15,1 0.0 P _part/17/_geo_xyz/16,15,2 0.0 P _part/17/_geo_xyz/16,16,0 0.0 P _part/17/_geo_xyz/16,16,1 0.0 P _part/17/_geo_xyz/16,16,2 0.0 P _part/17/_geo_xyz/16,17,0 0.0 P _part/17/_geo_xyz/16,17,1 0.0 P _part/17/_geo_xyz/16,17,2 0.0 P _part/17/_geo_xyz/16,2,0 0.0 P _part/17/_geo_xyz/16,2,1 0.0 P _part/17/_geo_xyz/16,2,2 0.0 P _part/17/_geo_xyz/16,3,0 0.0 P _part/17/_geo_xyz/16,3,1 0.0 P _part/17/_geo_xyz/16,3,2 0.0 P _part/17/_geo_xyz/16,4,0 0.0 P _part/17/_geo_xyz/16,4,1 0.0 P _part/17/_geo_xyz/16,4,2 0.0 P _part/17/_geo_xyz/16,5,0 0.0 P _part/17/_geo_xyz/16,5,1 0.0 P _part/17/_geo_xyz/16,5,2 0.0 P _part/17/_geo_xyz/16,6,0 0.0 P _part/17/_geo_xyz/16,6,1 0.0 P _part/17/_geo_xyz/16,6,2 0.0 P _part/17/_geo_xyz/16,7,0 0.0 P _part/17/_geo_xyz/16,7,1 0.0 P _part/17/_geo_xyz/16,7,2 0.0 P _part/17/_geo_xyz/16,8,0 0.0 P _part/17/_geo_xyz/16,8,1 0.0 P _part/17/_geo_xyz/16,8,2 0.0 P _part/17/_geo_xyz/16,9,0 0.0 P _part/17/_geo_xyz/16,9,1 0.0 P _part/17/_geo_xyz/16,9,2 0.0 P _part/17/_geo_xyz/17,0,0 0.0 P _part/17/_geo_xyz/17,0,1 0.0 P _part/17/_geo_xyz/17,0,2 0.0 P _part/17/_geo_xyz/17,1,0 0.0 P _part/17/_geo_xyz/17,1,1 0.0 P _part/17/_geo_xyz/17,1,2 0.0 P _part/17/_geo_xyz/17,10,0 0.0 P _part/17/_geo_xyz/17,10,1 0.0 P _part/17/_geo_xyz/17,10,2 0.0 P _part/17/_geo_xyz/17,11,0 0.0 P _part/17/_geo_xyz/17,11,1 0.0 P _part/17/_geo_xyz/17,11,2 0.0 P _part/17/_geo_xyz/17,12,0 0.0 P _part/17/_geo_xyz/17,12,1 0.0 P _part/17/_geo_xyz/17,12,2 0.0 P _part/17/_geo_xyz/17,13,0 0.0 P _part/17/_geo_xyz/17,13,1 0.0 P _part/17/_geo_xyz/17,13,2 0.0 P _part/17/_geo_xyz/17,14,0 0.0 P _part/17/_geo_xyz/17,14,1 0.0 P _part/17/_geo_xyz/17,14,2 0.0 P _part/17/_geo_xyz/17,15,0 0.0 P _part/17/_geo_xyz/17,15,1 0.0 P _part/17/_geo_xyz/17,15,2 0.0 P _part/17/_geo_xyz/17,16,0 0.0 P _part/17/_geo_xyz/17,16,1 0.0 P _part/17/_geo_xyz/17,16,2 0.0 P _part/17/_geo_xyz/17,17,0 0.0 P _part/17/_geo_xyz/17,17,1 0.0 P _part/17/_geo_xyz/17,17,2 0.0 P _part/17/_geo_xyz/17,2,0 0.0 P _part/17/_geo_xyz/17,2,1 0.0 P _part/17/_geo_xyz/17,2,2 0.0 P _part/17/_geo_xyz/17,3,0 0.0 P _part/17/_geo_xyz/17,3,1 0.0 P _part/17/_geo_xyz/17,3,2 0.0 P _part/17/_geo_xyz/17,4,0 0.0 P _part/17/_geo_xyz/17,4,1 0.0 P _part/17/_geo_xyz/17,4,2 0.0 P _part/17/_geo_xyz/17,5,0 0.0 P _part/17/_geo_xyz/17,5,1 0.0 P _part/17/_geo_xyz/17,5,2 0.0 P _part/17/_geo_xyz/17,6,0 0.0 P _part/17/_geo_xyz/17,6,1 0.0 P _part/17/_geo_xyz/17,6,2 0.0 P _part/17/_geo_xyz/17,7,0 0.0 P _part/17/_geo_xyz/17,7,1 0.0 P _part/17/_geo_xyz/17,7,2 0.0 P _part/17/_geo_xyz/17,8,0 0.0 P _part/17/_geo_xyz/17,8,1 0.0 P _part/17/_geo_xyz/17,8,2 0.0 P _part/17/_geo_xyz/17,9,0 0.0 P _part/17/_geo_xyz/17,9,1 0.0 P _part/17/_geo_xyz/17,9,2 0.0 P _part/17/_geo_xyz/18,0,0 0.0 P _part/17/_geo_xyz/18,0,1 0.0 P _part/17/_geo_xyz/18,0,2 0.0 P _part/17/_geo_xyz/18,1,0 0.0 P _part/17/_geo_xyz/18,1,1 0.0 P _part/17/_geo_xyz/18,1,2 0.0 P _part/17/_geo_xyz/18,10,0 0.0 P _part/17/_geo_xyz/18,10,1 0.0 P _part/17/_geo_xyz/18,10,2 0.0 P _part/17/_geo_xyz/18,11,0 0.0 P _part/17/_geo_xyz/18,11,1 0.0 P _part/17/_geo_xyz/18,11,2 0.0 P _part/17/_geo_xyz/18,12,0 0.0 P _part/17/_geo_xyz/18,12,1 0.0 P _part/17/_geo_xyz/18,12,2 0.0 P _part/17/_geo_xyz/18,13,0 0.0 P _part/17/_geo_xyz/18,13,1 0.0 P _part/17/_geo_xyz/18,13,2 0.0 P _part/17/_geo_xyz/18,14,0 0.0 P _part/17/_geo_xyz/18,14,1 0.0 P _part/17/_geo_xyz/18,14,2 0.0 P _part/17/_geo_xyz/18,15,0 0.0 P _part/17/_geo_xyz/18,15,1 0.0 P _part/17/_geo_xyz/18,15,2 0.0 P _part/17/_geo_xyz/18,16,0 0.0 P _part/17/_geo_xyz/18,16,1 0.0 P _part/17/_geo_xyz/18,16,2 0.0 P _part/17/_geo_xyz/18,17,0 0.0 P _part/17/_geo_xyz/18,17,1 0.0 P _part/17/_geo_xyz/18,17,2 0.0 P _part/17/_geo_xyz/18,2,0 0.0 P _part/17/_geo_xyz/18,2,1 0.0 P _part/17/_geo_xyz/18,2,2 0.0 P _part/17/_geo_xyz/18,3,0 0.0 P _part/17/_geo_xyz/18,3,1 0.0 P _part/17/_geo_xyz/18,3,2 0.0 P _part/17/_geo_xyz/18,4,0 0.0 P _part/17/_geo_xyz/18,4,1 0.0 P _part/17/_geo_xyz/18,4,2 0.0 P _part/17/_geo_xyz/18,5,0 0.0 P _part/17/_geo_xyz/18,5,1 0.0 P _part/17/_geo_xyz/18,5,2 0.0 P _part/17/_geo_xyz/18,6,0 0.0 P _part/17/_geo_xyz/18,6,1 0.0 P _part/17/_geo_xyz/18,6,2 0.0 P _part/17/_geo_xyz/18,7,0 0.0 P _part/17/_geo_xyz/18,7,1 0.0 P _part/17/_geo_xyz/18,7,2 0.0 P _part/17/_geo_xyz/18,8,0 0.0 P _part/17/_geo_xyz/18,8,1 0.0 P _part/17/_geo_xyz/18,8,2 0.0 P _part/17/_geo_xyz/18,9,0 0.0 P _part/17/_geo_xyz/18,9,1 0.0 P _part/17/_geo_xyz/18,9,2 0.0 P _part/17/_geo_xyz/19,0,0 0.0 P _part/17/_geo_xyz/19,0,1 0.0 P _part/17/_geo_xyz/19,0,2 0.0 P _part/17/_geo_xyz/19,1,0 0.0 P _part/17/_geo_xyz/19,1,1 0.0 P _part/17/_geo_xyz/19,1,2 0.0 P _part/17/_geo_xyz/19,10,0 0.0 P _part/17/_geo_xyz/19,10,1 0.0 P _part/17/_geo_xyz/19,10,2 0.0 P _part/17/_geo_xyz/19,11,0 0.0 P _part/17/_geo_xyz/19,11,1 0.0 P _part/17/_geo_xyz/19,11,2 0.0 P _part/17/_geo_xyz/19,12,0 0.0 P _part/17/_geo_xyz/19,12,1 0.0 P _part/17/_geo_xyz/19,12,2 0.0 P _part/17/_geo_xyz/19,13,0 0.0 P _part/17/_geo_xyz/19,13,1 0.0 P _part/17/_geo_xyz/19,13,2 0.0 P _part/17/_geo_xyz/19,14,0 0.0 P _part/17/_geo_xyz/19,14,1 0.0 P _part/17/_geo_xyz/19,14,2 0.0 P _part/17/_geo_xyz/19,15,0 0.0 P _part/17/_geo_xyz/19,15,1 0.0 P _part/17/_geo_xyz/19,15,2 0.0 P _part/17/_geo_xyz/19,16,0 0.0 P _part/17/_geo_xyz/19,16,1 0.0 P _part/17/_geo_xyz/19,16,2 0.0 P _part/17/_geo_xyz/19,17,0 0.0 P _part/17/_geo_xyz/19,17,1 0.0 P _part/17/_geo_xyz/19,17,2 0.0 P _part/17/_geo_xyz/19,2,0 0.0 P _part/17/_geo_xyz/19,2,1 0.0 P _part/17/_geo_xyz/19,2,2 0.0 P _part/17/_geo_xyz/19,3,0 0.0 P _part/17/_geo_xyz/19,3,1 0.0 P _part/17/_geo_xyz/19,3,2 0.0 P _part/17/_geo_xyz/19,4,0 0.0 P _part/17/_geo_xyz/19,4,1 0.0 P _part/17/_geo_xyz/19,4,2 0.0 P _part/17/_geo_xyz/19,5,0 0.0 P _part/17/_geo_xyz/19,5,1 0.0 P _part/17/_geo_xyz/19,5,2 0.0 P _part/17/_geo_xyz/19,6,0 0.0 P _part/17/_geo_xyz/19,6,1 0.0 P _part/17/_geo_xyz/19,6,2 0.0 P _part/17/_geo_xyz/19,7,0 0.0 P _part/17/_geo_xyz/19,7,1 0.0 P _part/17/_geo_xyz/19,7,2 0.0 P _part/17/_geo_xyz/19,8,0 0.0 P _part/17/_geo_xyz/19,8,1 0.0 P _part/17/_geo_xyz/19,8,2 0.0 P _part/17/_geo_xyz/19,9,0 0.0 P _part/17/_geo_xyz/19,9,1 0.0 P _part/17/_geo_xyz/19,9,2 0.0 P _part/17/_geo_xyz/2,0,0 4.101049900 P _part/17/_geo_xyz/2,0,1 12.303149223 P _part/17/_geo_xyz/2,0,2 32.398292542 P _part/17/_geo_xyz/2,1,0 4.101049900 P _part/17/_geo_xyz/2,1,1 12.332676888 P _part/17/_geo_xyz/2,1,2 32.447505951 P _part/17/_geo_xyz/2,10,0 4.101049900 P _part/17/_geo_xyz/2,10,1 12.229330063 P _part/17/_geo_xyz/2,10,2 32.808399200 P _part/17/_geo_xyz/2,11,0 4.101049900 P _part/17/_geo_xyz/2,11,1 12.244093895 P _part/17/_geo_xyz/2,11,2 32.562335968 P _part/17/_geo_xyz/2,12,0 4.101049900 P _part/17/_geo_xyz/2,12,1 12.273621559 P _part/17/_geo_xyz/2,12,2 32.447505951 P _part/17/_geo_xyz/2,13,0 4.101049900 P _part/17/_geo_xyz/2,13,1 12.303149223 P _part/17/_geo_xyz/2,13,2 32.398292542 P _part/17/_geo_xyz/2,14,0 0.0 P _part/17/_geo_xyz/2,14,1 0.0 P _part/17/_geo_xyz/2,14,2 0.0 P _part/17/_geo_xyz/2,15,0 0.0 P _part/17/_geo_xyz/2,15,1 0.0 P _part/17/_geo_xyz/2,15,2 0.0 P _part/17/_geo_xyz/2,16,0 0.0 P _part/17/_geo_xyz/2,16,1 0.0 P _part/17/_geo_xyz/2,16,2 0.0 P _part/17/_geo_xyz/2,17,0 0.0 P _part/17/_geo_xyz/2,17,1 0.0 P _part/17/_geo_xyz/2,17,2 0.0 P _part/17/_geo_xyz/2,2,0 4.101049900 P _part/17/_geo_xyz/2,2,1 12.362204552 P _part/17/_geo_xyz/2,2,2 32.562335968 P _part/17/_geo_xyz/2,3,0 4.101049900 P _part/17/_geo_xyz/2,3,1 12.376968384 P _part/17/_geo_xyz/2,3,2 32.808399200 P _part/17/_geo_xyz/2,4,0 4.101049900 P _part/17/_geo_xyz/2,4,1 12.376968384 P _part/17/_geo_xyz/2,4,2 33.136482239 P _part/17/_geo_xyz/2,5,0 4.101049900 P _part/17/_geo_xyz/2,5,1 12.336703300 P _part/17/_geo_xyz/2,5,2 33.628608704 P _part/17/_geo_xyz/2,6,0 4.101049900 P _part/17/_geo_xyz/2,6,1 12.303149223 P _part/17/_geo_xyz/2,6,2 34.038715363 P _part/17/_geo_xyz/2,7,0 4.101049900 P _part/17/_geo_xyz/2,7,1 12.303149223 P _part/17/_geo_xyz/2,7,2 34.038715363 P _part/17/_geo_xyz/2,8,0 4.101049900 P _part/17/_geo_xyz/2,8,1 12.269595146 P _part/17/_geo_xyz/2,8,2 33.628608704 P _part/17/_geo_xyz/2,9,0 4.101049900 P _part/17/_geo_xyz/2,9,1 12.229330063 P _part/17/_geo_xyz/2,9,2 33.136482239 P _part/17/_geo_xyz/3,0,0 8.202099800 P _part/17/_geo_xyz/3,0,1 12.303149223 P _part/17/_geo_xyz/3,0,2 32.398292542 P _part/17/_geo_xyz/3,1,0 8.202099800 P _part/17/_geo_xyz/3,1,1 12.332676888 P _part/17/_geo_xyz/3,1,2 32.447505951 P _part/17/_geo_xyz/3,10,0 8.202099800 P _part/17/_geo_xyz/3,10,1 12.229330063 P _part/17/_geo_xyz/3,10,2 32.808399200 P _part/17/_geo_xyz/3,11,0 8.202099800 P _part/17/_geo_xyz/3,11,1 12.244093895 P _part/17/_geo_xyz/3,11,2 32.562335968 P _part/17/_geo_xyz/3,12,0 8.202099800 P _part/17/_geo_xyz/3,12,1 12.273621559 P _part/17/_geo_xyz/3,12,2 32.447505951 P _part/17/_geo_xyz/3,13,0 8.202099800 P _part/17/_geo_xyz/3,13,1 12.303149223 P _part/17/_geo_xyz/3,13,2 32.398292542 P _part/17/_geo_xyz/3,14,0 0.0 P _part/17/_geo_xyz/3,14,1 0.0 P _part/17/_geo_xyz/3,14,2 0.0 P _part/17/_geo_xyz/3,15,0 0.0 P _part/17/_geo_xyz/3,15,1 0.0 P _part/17/_geo_xyz/3,15,2 0.0 P _part/17/_geo_xyz/3,16,0 0.0 P _part/17/_geo_xyz/3,16,1 0.0 P _part/17/_geo_xyz/3,16,2 0.0 P _part/17/_geo_xyz/3,17,0 0.0 P _part/17/_geo_xyz/3,17,1 0.0 P _part/17/_geo_xyz/3,17,2 0.0 P _part/17/_geo_xyz/3,2,0 8.202099800 P _part/17/_geo_xyz/3,2,1 12.362204552 P _part/17/_geo_xyz/3,2,2 32.562335968 P _part/17/_geo_xyz/3,3,0 8.202099800 P _part/17/_geo_xyz/3,3,1 12.376968384 P _part/17/_geo_xyz/3,3,2 32.808399200 P _part/17/_geo_xyz/3,4,0 8.202099800 P _part/17/_geo_xyz/3,4,1 12.376968384 P _part/17/_geo_xyz/3,4,2 33.136482239 P _part/17/_geo_xyz/3,5,0 8.202099800 P _part/17/_geo_xyz/3,5,1 12.336703300 P _part/17/_geo_xyz/3,5,2 33.628608704 P _part/17/_geo_xyz/3,6,0 8.202099800 P _part/17/_geo_xyz/3,6,1 12.303149223 P _part/17/_geo_xyz/3,6,2 34.038715363 P _part/17/_geo_xyz/3,7,0 8.202099800 P _part/17/_geo_xyz/3,7,1 12.303149223 P _part/17/_geo_xyz/3,7,2 34.038715363 P _part/17/_geo_xyz/3,8,0 8.202099800 P _part/17/_geo_xyz/3,8,1 12.269595146 P _part/17/_geo_xyz/3,8,2 33.628608704 P _part/17/_geo_xyz/3,9,0 8.202099800 P _part/17/_geo_xyz/3,9,1 12.229330063 P _part/17/_geo_xyz/3,9,2 33.136482239 P _part/17/_geo_xyz/4,0,0 8.202099800 P _part/17/_geo_xyz/4,0,1 12.303149223 P _part/17/_geo_xyz/4,0,2 32.398292542 P _part/17/_geo_xyz/4,1,0 8.202099800 P _part/17/_geo_xyz/4,1,1 12.332676888 P _part/17/_geo_xyz/4,1,2 32.447505951 P _part/17/_geo_xyz/4,10,0 8.202099800 P _part/17/_geo_xyz/4,10,1 12.229330063 P _part/17/_geo_xyz/4,10,2 32.808399200 P _part/17/_geo_xyz/4,11,0 8.202099800 P _part/17/_geo_xyz/4,11,1 12.244093895 P _part/17/_geo_xyz/4,11,2 32.562335968 P _part/17/_geo_xyz/4,12,0 8.202099800 P _part/17/_geo_xyz/4,12,1 12.273621559 P _part/17/_geo_xyz/4,12,2 32.447505951 P _part/17/_geo_xyz/4,13,0 8.202099800 P _part/17/_geo_xyz/4,13,1 12.303149223 P _part/17/_geo_xyz/4,13,2 32.398292542 P _part/17/_geo_xyz/4,14,0 0.0 P _part/17/_geo_xyz/4,14,1 0.0 P _part/17/_geo_xyz/4,14,2 0.0 P _part/17/_geo_xyz/4,15,0 0.0 P _part/17/_geo_xyz/4,15,1 0.0 P _part/17/_geo_xyz/4,15,2 0.0 P _part/17/_geo_xyz/4,16,0 0.0 P _part/17/_geo_xyz/4,16,1 0.0 P _part/17/_geo_xyz/4,16,2 0.0 P _part/17/_geo_xyz/4,17,0 0.0 P _part/17/_geo_xyz/4,17,1 0.0 P _part/17/_geo_xyz/4,17,2 0.0 P _part/17/_geo_xyz/4,2,0 8.202099800 P _part/17/_geo_xyz/4,2,1 12.362204552 P _part/17/_geo_xyz/4,2,2 32.562335968 P _part/17/_geo_xyz/4,3,0 8.202099800 P _part/17/_geo_xyz/4,3,1 12.376968384 P _part/17/_geo_xyz/4,3,2 32.808399200 P _part/17/_geo_xyz/4,4,0 8.202099800 P _part/17/_geo_xyz/4,4,1 12.376968384 P _part/17/_geo_xyz/4,4,2 33.136482239 P _part/17/_geo_xyz/4,5,0 8.202099800 P _part/17/_geo_xyz/4,5,1 12.336703300 P _part/17/_geo_xyz/4,5,2 33.628608704 P _part/17/_geo_xyz/4,6,0 8.202099800 P _part/17/_geo_xyz/4,6,1 12.303149223 P _part/17/_geo_xyz/4,6,2 34.038715363 P _part/17/_geo_xyz/4,7,0 8.202099800 P _part/17/_geo_xyz/4,7,1 12.303149223 P _part/17/_geo_xyz/4,7,2 34.038715363 P _part/17/_geo_xyz/4,8,0 8.202099800 P _part/17/_geo_xyz/4,8,1 12.269595146 P _part/17/_geo_xyz/4,8,2 33.628608704 P _part/17/_geo_xyz/4,9,0 8.202099800 P _part/17/_geo_xyz/4,9,1 12.229330063 P _part/17/_geo_xyz/4,9,2 33.136482239 P _part/17/_geo_xyz/5,0,0 8.202099800 P _part/17/_geo_xyz/5,0,1 12.303149223 P _part/17/_geo_xyz/5,0,2 32.398292542 P _part/17/_geo_xyz/5,1,0 8.202099800 P _part/17/_geo_xyz/5,1,1 12.332676888 P _part/17/_geo_xyz/5,1,2 32.447505951 P _part/17/_geo_xyz/5,10,0 8.202099800 P _part/17/_geo_xyz/5,10,1 12.229330063 P _part/17/_geo_xyz/5,10,2 32.808399200 P _part/17/_geo_xyz/5,11,0 8.202099800 P _part/17/_geo_xyz/5,11,1 12.244093895 P _part/17/_geo_xyz/5,11,2 32.562335968 P _part/17/_geo_xyz/5,12,0 8.202099800 P _part/17/_geo_xyz/5,12,1 12.273621559 P _part/17/_geo_xyz/5,12,2 32.447505951 P _part/17/_geo_xyz/5,13,0 8.202099800 P _part/17/_geo_xyz/5,13,1 12.303149223 P _part/17/_geo_xyz/5,13,2 32.398292542 P _part/17/_geo_xyz/5,14,0 0.0 P _part/17/_geo_xyz/5,14,1 0.0 P _part/17/_geo_xyz/5,14,2 0.0 P _part/17/_geo_xyz/5,15,0 0.0 P _part/17/_geo_xyz/5,15,1 0.0 P _part/17/_geo_xyz/5,15,2 0.0 P _part/17/_geo_xyz/5,16,0 0.0 P _part/17/_geo_xyz/5,16,1 0.0 P _part/17/_geo_xyz/5,16,2 0.0 P _part/17/_geo_xyz/5,17,0 0.0 P _part/17/_geo_xyz/5,17,1 0.0 P _part/17/_geo_xyz/5,17,2 0.0 P _part/17/_geo_xyz/5,2,0 8.202099800 P _part/17/_geo_xyz/5,2,1 12.362204552 P _part/17/_geo_xyz/5,2,2 32.562335968 P _part/17/_geo_xyz/5,3,0 8.202099800 P _part/17/_geo_xyz/5,3,1 12.376968384 P _part/17/_geo_xyz/5,3,2 32.808399200 P _part/17/_geo_xyz/5,4,0 8.202099800 P _part/17/_geo_xyz/5,4,1 12.376968384 P _part/17/_geo_xyz/5,4,2 33.136482239 P _part/17/_geo_xyz/5,5,0 8.202099800 P _part/17/_geo_xyz/5,5,1 12.303149223 P _part/17/_geo_xyz/5,5,2 34.038715363 P _part/17/_geo_xyz/5,6,0 8.202099800 P _part/17/_geo_xyz/5,6,1 12.303149223 P _part/17/_geo_xyz/5,6,2 34.038715363 P _part/17/_geo_xyz/5,7,0 8.202099800 P _part/17/_geo_xyz/5,7,1 12.303149223 P _part/17/_geo_xyz/5,7,2 34.038715363 P _part/17/_geo_xyz/5,8,0 8.202099800 P _part/17/_geo_xyz/5,8,1 12.303149223 P _part/17/_geo_xyz/5,8,2 34.038715363 P _part/17/_geo_xyz/5,9,0 8.202099800 P _part/17/_geo_xyz/5,9,1 12.229330063 P _part/17/_geo_xyz/5,9,2 33.136482239 P _part/17/_geo_xyz/6,0,0 8.202099800 P _part/17/_geo_xyz/6,0,1 12.303149223 P _part/17/_geo_xyz/6,0,2 32.398292542 P _part/17/_geo_xyz/6,1,0 8.202099800 P _part/17/_geo_xyz/6,1,1 12.332676888 P _part/17/_geo_xyz/6,1,2 32.447505951 P _part/17/_geo_xyz/6,10,0 8.202099800 P _part/17/_geo_xyz/6,10,1 12.229330063 P _part/17/_geo_xyz/6,10,2 32.808399200 P _part/17/_geo_xyz/6,11,0 8.202099800 P _part/17/_geo_xyz/6,11,1 12.244093895 P _part/17/_geo_xyz/6,11,2 32.562335968 P _part/17/_geo_xyz/6,12,0 8.202099800 P _part/17/_geo_xyz/6,12,1 12.273621559 P _part/17/_geo_xyz/6,12,2 32.447505951 P _part/17/_geo_xyz/6,13,0 8.202099800 P _part/17/_geo_xyz/6,13,1 12.303149223 P _part/17/_geo_xyz/6,13,2 32.398292542 P _part/17/_geo_xyz/6,14,0 0.0 P _part/17/_geo_xyz/6,14,1 0.0 P _part/17/_geo_xyz/6,14,2 0.0 P _part/17/_geo_xyz/6,15,0 0.0 P _part/17/_geo_xyz/6,15,1 0.0 P _part/17/_geo_xyz/6,15,2 0.0 P _part/17/_geo_xyz/6,16,0 0.0 P _part/17/_geo_xyz/6,16,1 0.0 P _part/17/_geo_xyz/6,16,2 0.0 P _part/17/_geo_xyz/6,17,0 0.0 P _part/17/_geo_xyz/6,17,1 0.0 P _part/17/_geo_xyz/6,17,2 0.0 P _part/17/_geo_xyz/6,2,0 8.202099800 P _part/17/_geo_xyz/6,2,1 12.362204552 P _part/17/_geo_xyz/6,2,2 32.562335968 P _part/17/_geo_xyz/6,3,0 8.202099800 P _part/17/_geo_xyz/6,3,1 12.376968384 P _part/17/_geo_xyz/6,3,2 32.808399200 P _part/17/_geo_xyz/6,4,0 8.202099800 P _part/17/_geo_xyz/6,4,1 12.376968384 P _part/17/_geo_xyz/6,4,2 33.136482239 P _part/17/_geo_xyz/6,5,0 8.202099800 P _part/17/_geo_xyz/6,5,1 12.303149223 P _part/17/_geo_xyz/6,5,2 34.038715363 P _part/17/_geo_xyz/6,6,0 8.202099800 P _part/17/_geo_xyz/6,6,1 12.303149223 P _part/17/_geo_xyz/6,6,2 34.038715363 P _part/17/_geo_xyz/6,7,0 8.202099800 P _part/17/_geo_xyz/6,7,1 12.303149223 P _part/17/_geo_xyz/6,7,2 34.038715363 P _part/17/_geo_xyz/6,8,0 8.202099800 P _part/17/_geo_xyz/6,8,1 12.303149223 P _part/17/_geo_xyz/6,8,2 34.038715363 P _part/17/_geo_xyz/6,9,0 8.202099800 P _part/17/_geo_xyz/6,9,1 12.229330063 P _part/17/_geo_xyz/6,9,2 33.136482239 P _part/17/_geo_xyz/7,0,0 8.202099800 P _part/17/_geo_xyz/7,0,1 12.303149223 P _part/17/_geo_xyz/7,0,2 32.398292542 P _part/17/_geo_xyz/7,1,0 8.202099800 P _part/17/_geo_xyz/7,1,1 12.332676888 P _part/17/_geo_xyz/7,1,2 32.447505951 P _part/17/_geo_xyz/7,10,0 8.202099800 P _part/17/_geo_xyz/7,10,1 12.229330063 P _part/17/_geo_xyz/7,10,2 32.808399200 P _part/17/_geo_xyz/7,11,0 8.202099800 P _part/17/_geo_xyz/7,11,1 12.244093895 P _part/17/_geo_xyz/7,11,2 32.562335968 P _part/17/_geo_xyz/7,12,0 8.202099800 P _part/17/_geo_xyz/7,12,1 12.273621559 P _part/17/_geo_xyz/7,12,2 32.447505951 P _part/17/_geo_xyz/7,13,0 8.202099800 P _part/17/_geo_xyz/7,13,1 12.303149223 P _part/17/_geo_xyz/7,13,2 32.398292542 P _part/17/_geo_xyz/7,14,0 0.0 P _part/17/_geo_xyz/7,14,1 0.0 P _part/17/_geo_xyz/7,14,2 0.0 P _part/17/_geo_xyz/7,15,0 0.0 P _part/17/_geo_xyz/7,15,1 0.0 P _part/17/_geo_xyz/7,15,2 0.0 P _part/17/_geo_xyz/7,16,0 0.0 P _part/17/_geo_xyz/7,16,1 0.0 P _part/17/_geo_xyz/7,16,2 0.0 P _part/17/_geo_xyz/7,17,0 0.0 P _part/17/_geo_xyz/7,17,1 0.0 P _part/17/_geo_xyz/7,17,2 0.0 P _part/17/_geo_xyz/7,2,0 8.202099800 P _part/17/_geo_xyz/7,2,1 12.362204552 P _part/17/_geo_xyz/7,2,2 32.562335968 P _part/17/_geo_xyz/7,3,0 8.202099800 P _part/17/_geo_xyz/7,3,1 12.376968384 P _part/17/_geo_xyz/7,3,2 32.808399200 P _part/17/_geo_xyz/7,4,0 8.202099800 P _part/17/_geo_xyz/7,4,1 12.376968384 P _part/17/_geo_xyz/7,4,2 33.136482239 P _part/17/_geo_xyz/7,5,0 8.202099800 P _part/17/_geo_xyz/7,5,1 12.303149223 P _part/17/_geo_xyz/7,5,2 34.038715363 P _part/17/_geo_xyz/7,6,0 8.202099800 P _part/17/_geo_xyz/7,6,1 12.303149223 P _part/17/_geo_xyz/7,6,2 34.038715363 P _part/17/_geo_xyz/7,7,0 8.202099800 P _part/17/_geo_xyz/7,7,1 12.303149223 P _part/17/_geo_xyz/7,7,2 34.038715363 P _part/17/_geo_xyz/7,8,0 8.202099800 P _part/17/_geo_xyz/7,8,1 12.303149223 P _part/17/_geo_xyz/7,8,2 34.038715363 P _part/17/_geo_xyz/7,9,0 8.202099800 P _part/17/_geo_xyz/7,9,1 12.229330063 P _part/17/_geo_xyz/7,9,2 33.136482239 P _part/17/_geo_xyz/8,0,0 8.202099800 P _part/17/_geo_xyz/8,0,1 12.303149223 P _part/17/_geo_xyz/8,0,2 32.398292542 P _part/17/_geo_xyz/8,1,0 8.202099800 P _part/17/_geo_xyz/8,1,1 12.332676888 P _part/17/_geo_xyz/8,1,2 32.447505951 P _part/17/_geo_xyz/8,10,0 8.202099800 P _part/17/_geo_xyz/8,10,1 12.229330063 P _part/17/_geo_xyz/8,10,2 32.808399200 P _part/17/_geo_xyz/8,11,0 8.202099800 P _part/17/_geo_xyz/8,11,1 12.244093895 P _part/17/_geo_xyz/8,11,2 32.562335968 P _part/17/_geo_xyz/8,12,0 8.202099800 P _part/17/_geo_xyz/8,12,1 12.273621559 P _part/17/_geo_xyz/8,12,2 32.447505951 P _part/17/_geo_xyz/8,13,0 8.202099800 P _part/17/_geo_xyz/8,13,1 12.303149223 P _part/17/_geo_xyz/8,13,2 32.398292542 P _part/17/_geo_xyz/8,14,0 0.0 P _part/17/_geo_xyz/8,14,1 0.0 P _part/17/_geo_xyz/8,14,2 0.0 P _part/17/_geo_xyz/8,15,0 0.0 P _part/17/_geo_xyz/8,15,1 0.0 P _part/17/_geo_xyz/8,15,2 0.0 P _part/17/_geo_xyz/8,16,0 0.0 P _part/17/_geo_xyz/8,16,1 0.0 P _part/17/_geo_xyz/8,16,2 0.0 P _part/17/_geo_xyz/8,17,0 0.0 P _part/17/_geo_xyz/8,17,1 0.0 P _part/17/_geo_xyz/8,17,2 0.0 P _part/17/_geo_xyz/8,2,0 8.202099800 P _part/17/_geo_xyz/8,2,1 12.362204552 P _part/17/_geo_xyz/8,2,2 32.562335968 P _part/17/_geo_xyz/8,3,0 8.202099800 P _part/17/_geo_xyz/8,3,1 12.376968384 P _part/17/_geo_xyz/8,3,2 32.808399200 P _part/17/_geo_xyz/8,4,0 8.202099800 P _part/17/_geo_xyz/8,4,1 12.376968384 P _part/17/_geo_xyz/8,4,2 33.136482239 P _part/17/_geo_xyz/8,5,0 8.202099800 P _part/17/_geo_xyz/8,5,1 12.303149223 P _part/17/_geo_xyz/8,5,2 34.038715363 P _part/17/_geo_xyz/8,6,0 8.202099800 P _part/17/_geo_xyz/8,6,1 12.303149223 P _part/17/_geo_xyz/8,6,2 34.038715363 P _part/17/_geo_xyz/8,7,0 8.202099800 P _part/17/_geo_xyz/8,7,1 12.303149223 P _part/17/_geo_xyz/8,7,2 34.038715363 P _part/17/_geo_xyz/8,8,0 8.202099800 P _part/17/_geo_xyz/8,8,1 12.303149223 P _part/17/_geo_xyz/8,8,2 34.038715363 P _part/17/_geo_xyz/8,9,0 8.202099800 P _part/17/_geo_xyz/8,9,1 12.229330063 P _part/17/_geo_xyz/8,9,2 33.136482239 P _part/17/_geo_xyz/9,0,0 0.0 P _part/17/_geo_xyz/9,0,1 0.0 P _part/17/_geo_xyz/9,0,2 0.0 P _part/17/_geo_xyz/9,1,0 0.0 P _part/17/_geo_xyz/9,1,1 0.0 P _part/17/_geo_xyz/9,1,2 0.0 P _part/17/_geo_xyz/9,10,0 0.0 P _part/17/_geo_xyz/9,10,1 0.0 P _part/17/_geo_xyz/9,10,2 0.0 P _part/17/_geo_xyz/9,11,0 0.0 P _part/17/_geo_xyz/9,11,1 0.0 P _part/17/_geo_xyz/9,11,2 0.0 P _part/17/_geo_xyz/9,12,0 0.0 P _part/17/_geo_xyz/9,12,1 0.0 P _part/17/_geo_xyz/9,12,2 0.0 P _part/17/_geo_xyz/9,13,0 0.0 P _part/17/_geo_xyz/9,13,1 0.0 P _part/17/_geo_xyz/9,13,2 0.0 P _part/17/_geo_xyz/9,14,0 0.0 P _part/17/_geo_xyz/9,14,1 0.0 P _part/17/_geo_xyz/9,14,2 0.0 P _part/17/_geo_xyz/9,15,0 0.0 P _part/17/_geo_xyz/9,15,1 0.0 P _part/17/_geo_xyz/9,15,2 0.0 P _part/17/_geo_xyz/9,16,0 0.0 P _part/17/_geo_xyz/9,16,1 0.0 P _part/17/_geo_xyz/9,16,2 0.0 P _part/17/_geo_xyz/9,17,0 0.0 P _part/17/_geo_xyz/9,17,1 0.0 P _part/17/_geo_xyz/9,17,2 0.0 P _part/17/_geo_xyz/9,2,0 0.0 P _part/17/_geo_xyz/9,2,1 0.0 P _part/17/_geo_xyz/9,2,2 0.0 P _part/17/_geo_xyz/9,3,0 0.0 P _part/17/_geo_xyz/9,3,1 0.0 P _part/17/_geo_xyz/9,3,2 0.0 P _part/17/_geo_xyz/9,4,0 0.0 P _part/17/_geo_xyz/9,4,1 0.0 P _part/17/_geo_xyz/9,4,2 0.0 P _part/17/_geo_xyz/9,5,0 0.0 P _part/17/_geo_xyz/9,5,1 0.0 P _part/17/_geo_xyz/9,5,2 0.0 P _part/17/_geo_xyz/9,6,0 0.0 P _part/17/_geo_xyz/9,6,1 0.0 P _part/17/_geo_xyz/9,6,2 0.0 P _part/17/_geo_xyz/9,7,0 0.0 P _part/17/_geo_xyz/9,7,1 0.0 P _part/17/_geo_xyz/9,7,2 0.0 P _part/17/_geo_xyz/9,8,0 0.0 P _part/17/_geo_xyz/9,8,1 0.0 P _part/17/_geo_xyz/9,8,2 0.0 P _part/17/_geo_xyz/9,9,0 0.0 P _part/17/_geo_xyz/9,9,1 0.0 P _part/17/_geo_xyz/9,9,2 0.0 P _part/17/_geo_xyz/i_count 20 P _part/17/_geo_xyz/j_count 18 P _part/17/_geo_xyz/k_count 3 P _part/17/_locked/0,0 0 P _part/17/_locked/0,1 0 P _part/17/_locked/0,10 0 P _part/17/_locked/0,11 0 P _part/17/_locked/0,12 0 P _part/17/_locked/0,13 0 P _part/17/_locked/0,14 0 P _part/17/_locked/0,15 0 P _part/17/_locked/0,16 0 P _part/17/_locked/0,17 0 P _part/17/_locked/0,2 0 P _part/17/_locked/0,3 0 P _part/17/_locked/0,4 0 P _part/17/_locked/0,5 0 P _part/17/_locked/0,6 0 P _part/17/_locked/0,7 0 P _part/17/_locked/0,8 0 P _part/17/_locked/0,9 0 P _part/17/_locked/1,0 0 P _part/17/_locked/1,1 0 P _part/17/_locked/1,10 0 P _part/17/_locked/1,11 0 P _part/17/_locked/1,12 0 P _part/17/_locked/1,13 0 P _part/17/_locked/1,14 0 P _part/17/_locked/1,15 0 P _part/17/_locked/1,16 0 P _part/17/_locked/1,17 0 P _part/17/_locked/1,2 0 P _part/17/_locked/1,3 0 P _part/17/_locked/1,4 0 P _part/17/_locked/1,5 0 P _part/17/_locked/1,6 0 P _part/17/_locked/1,7 0 P _part/17/_locked/1,8 0 P _part/17/_locked/1,9 0 P _part/17/_locked/10,0 0 P _part/17/_locked/10,1 0 P _part/17/_locked/10,10 0 P _part/17/_locked/10,11 0 P _part/17/_locked/10,12 0 P _part/17/_locked/10,13 0 P _part/17/_locked/10,14 0 P _part/17/_locked/10,15 0 P _part/17/_locked/10,16 0 P _part/17/_locked/10,17 0 P _part/17/_locked/10,2 0 P _part/17/_locked/10,3 0 P _part/17/_locked/10,4 0 P _part/17/_locked/10,5 0 P _part/17/_locked/10,6 0 P _part/17/_locked/10,7 0 P _part/17/_locked/10,8 0 P _part/17/_locked/10,9 0 P _part/17/_locked/11,0 0 P _part/17/_locked/11,1 0 P _part/17/_locked/11,10 0 P _part/17/_locked/11,11 0 P _part/17/_locked/11,12 0 P _part/17/_locked/11,13 0 P _part/17/_locked/11,14 0 P _part/17/_locked/11,15 0 P _part/17/_locked/11,16 0 P _part/17/_locked/11,17 0 P _part/17/_locked/11,2 0 P _part/17/_locked/11,3 0 P _part/17/_locked/11,4 0 P _part/17/_locked/11,5 0 P _part/17/_locked/11,6 0 P _part/17/_locked/11,7 0 P _part/17/_locked/11,8 0 P _part/17/_locked/11,9 0 P _part/17/_locked/12,0 0 P _part/17/_locked/12,1 0 P _part/17/_locked/12,10 0 P _part/17/_locked/12,11 0 P _part/17/_locked/12,12 0 P _part/17/_locked/12,13 0 P _part/17/_locked/12,14 0 P _part/17/_locked/12,15 0 P _part/17/_locked/12,16 0 P _part/17/_locked/12,17 0 P _part/17/_locked/12,2 0 P _part/17/_locked/12,3 0 P _part/17/_locked/12,4 0 P _part/17/_locked/12,5 0 P _part/17/_locked/12,6 0 P _part/17/_locked/12,7 0 P _part/17/_locked/12,8 0 P _part/17/_locked/12,9 0 P _part/17/_locked/13,0 0 P _part/17/_locked/13,1 0 P _part/17/_locked/13,10 0 P _part/17/_locked/13,11 0 P _part/17/_locked/13,12 0 P _part/17/_locked/13,13 0 P _part/17/_locked/13,14 0 P _part/17/_locked/13,15 0 P _part/17/_locked/13,16 0 P _part/17/_locked/13,17 0 P _part/17/_locked/13,2 0 P _part/17/_locked/13,3 0 P _part/17/_locked/13,4 0 P _part/17/_locked/13,5 0 P _part/17/_locked/13,6 0 P _part/17/_locked/13,7 0 P _part/17/_locked/13,8 0 P _part/17/_locked/13,9 0 P _part/17/_locked/14,0 0 P _part/17/_locked/14,1 0 P _part/17/_locked/14,10 0 P _part/17/_locked/14,11 0 P _part/17/_locked/14,12 0 P _part/17/_locked/14,13 0 P _part/17/_locked/14,14 0 P _part/17/_locked/14,15 0 P _part/17/_locked/14,16 0 P _part/17/_locked/14,17 0 P _part/17/_locked/14,2 0 P _part/17/_locked/14,3 0 P _part/17/_locked/14,4 0 P _part/17/_locked/14,5 0 P _part/17/_locked/14,6 0 P _part/17/_locked/14,7 0 P _part/17/_locked/14,8 0 P _part/17/_locked/14,9 0 P _part/17/_locked/15,0 0 P _part/17/_locked/15,1 0 P _part/17/_locked/15,10 0 P _part/17/_locked/15,11 0 P _part/17/_locked/15,12 0 P _part/17/_locked/15,13 0 P _part/17/_locked/15,14 0 P _part/17/_locked/15,15 0 P _part/17/_locked/15,16 0 P _part/17/_locked/15,17 0 P _part/17/_locked/15,2 0 P _part/17/_locked/15,3 0 P _part/17/_locked/15,4 0 P _part/17/_locked/15,5 0 P _part/17/_locked/15,6 0 P _part/17/_locked/15,7 0 P _part/17/_locked/15,8 0 P _part/17/_locked/15,9 0 P _part/17/_locked/16,0 0 P _part/17/_locked/16,1 0 P _part/17/_locked/16,10 0 P _part/17/_locked/16,11 0 P _part/17/_locked/16,12 0 P _part/17/_locked/16,13 0 P _part/17/_locked/16,14 0 P _part/17/_locked/16,15 0 P _part/17/_locked/16,16 0 P _part/17/_locked/16,17 0 P _part/17/_locked/16,2 0 P _part/17/_locked/16,3 0 P _part/17/_locked/16,4 0 P _part/17/_locked/16,5 0 P _part/17/_locked/16,6 0 P _part/17/_locked/16,7 0 P _part/17/_locked/16,8 0 P _part/17/_locked/16,9 0 P _part/17/_locked/17,0 0 P _part/17/_locked/17,1 0 P _part/17/_locked/17,10 0 P _part/17/_locked/17,11 0 P _part/17/_locked/17,12 0 P _part/17/_locked/17,13 0 P _part/17/_locked/17,14 0 P _part/17/_locked/17,15 0 P _part/17/_locked/17,16 0 P _part/17/_locked/17,17 0 P _part/17/_locked/17,2 0 P _part/17/_locked/17,3 0 P _part/17/_locked/17,4 0 P _part/17/_locked/17,5 0 P _part/17/_locked/17,6 0 P _part/17/_locked/17,7 0 P _part/17/_locked/17,8 0 P _part/17/_locked/17,9 0 P _part/17/_locked/18,0 0 P _part/17/_locked/18,1 0 P _part/17/_locked/18,10 0 P _part/17/_locked/18,11 0 P _part/17/_locked/18,12 0 P _part/17/_locked/18,13 0 P _part/17/_locked/18,14 0 P _part/17/_locked/18,15 0 P _part/17/_locked/18,16 0 P _part/17/_locked/18,17 0 P _part/17/_locked/18,2 0 P _part/17/_locked/18,3 0 P _part/17/_locked/18,4 0 P _part/17/_locked/18,5 0 P _part/17/_locked/18,6 0 P _part/17/_locked/18,7 0 P _part/17/_locked/18,8 0 P _part/17/_locked/18,9 0 P _part/17/_locked/19,0 0 P _part/17/_locked/19,1 0 P _part/17/_locked/19,10 0 P _part/17/_locked/19,11 0 P _part/17/_locked/19,12 0 P _part/17/_locked/19,13 0 P _part/17/_locked/19,14 0 P _part/17/_locked/19,15 0 P _part/17/_locked/19,16 0 P _part/17/_locked/19,17 0 P _part/17/_locked/19,2 0 P _part/17/_locked/19,3 0 P _part/17/_locked/19,4 0 P _part/17/_locked/19,5 0 P _part/17/_locked/19,6 0 P _part/17/_locked/19,7 0 P _part/17/_locked/19,8 0 P _part/17/_locked/19,9 0 P _part/17/_locked/2,0 0 P _part/17/_locked/2,1 0 P _part/17/_locked/2,10 0 P _part/17/_locked/2,11 0 P _part/17/_locked/2,12 0 P _part/17/_locked/2,13 0 P _part/17/_locked/2,14 0 P _part/17/_locked/2,15 0 P _part/17/_locked/2,16 0 P _part/17/_locked/2,17 0 P _part/17/_locked/2,2 0 P _part/17/_locked/2,3 0 P _part/17/_locked/2,4 0 P _part/17/_locked/2,5 0 P _part/17/_locked/2,6 0 P _part/17/_locked/2,7 0 P _part/17/_locked/2,8 0 P _part/17/_locked/2,9 0 P _part/17/_locked/3,0 0 P _part/17/_locked/3,1 0 P _part/17/_locked/3,10 0 P _part/17/_locked/3,11 0 P _part/17/_locked/3,12 0 P _part/17/_locked/3,13 0 P _part/17/_locked/3,14 0 P _part/17/_locked/3,15 0 P _part/17/_locked/3,16 0 P _part/17/_locked/3,17 0 P _part/17/_locked/3,2 0 P _part/17/_locked/3,3 0 P _part/17/_locked/3,4 0 P _part/17/_locked/3,5 0 P _part/17/_locked/3,6 0 P _part/17/_locked/3,7 0 P _part/17/_locked/3,8 0 P _part/17/_locked/3,9 0 P _part/17/_locked/4,0 0 P _part/17/_locked/4,1 0 P _part/17/_locked/4,10 0 P _part/17/_locked/4,11 0 P _part/17/_locked/4,12 0 P _part/17/_locked/4,13 0 P _part/17/_locked/4,14 0 P _part/17/_locked/4,15 0 P _part/17/_locked/4,16 0 P _part/17/_locked/4,17 0 P _part/17/_locked/4,2 0 P _part/17/_locked/4,3 0 P _part/17/_locked/4,4 0 P _part/17/_locked/4,5 0 P _part/17/_locked/4,6 0 P _part/17/_locked/4,7 0 P _part/17/_locked/4,8 0 P _part/17/_locked/4,9 0 P _part/17/_locked/5,0 0 P _part/17/_locked/5,1 0 P _part/17/_locked/5,10 0 P _part/17/_locked/5,11 0 P _part/17/_locked/5,12 0 P _part/17/_locked/5,13 0 P _part/17/_locked/5,14 0 P _part/17/_locked/5,15 0 P _part/17/_locked/5,16 0 P _part/17/_locked/5,17 0 P _part/17/_locked/5,2 0 P _part/17/_locked/5,3 0 P _part/17/_locked/5,4 0 P _part/17/_locked/5,5 0 P _part/17/_locked/5,6 0 P _part/17/_locked/5,7 0 P _part/17/_locked/5,8 0 P _part/17/_locked/5,9 0 P _part/17/_locked/6,0 0 P _part/17/_locked/6,1 0 P _part/17/_locked/6,10 0 P _part/17/_locked/6,11 0 P _part/17/_locked/6,12 0 P _part/17/_locked/6,13 0 P _part/17/_locked/6,14 0 P _part/17/_locked/6,15 0 P _part/17/_locked/6,16 0 P _part/17/_locked/6,17 0 P _part/17/_locked/6,2 0 P _part/17/_locked/6,3 0 P _part/17/_locked/6,4 0 P _part/17/_locked/6,5 0 P _part/17/_locked/6,6 0 P _part/17/_locked/6,7 0 P _part/17/_locked/6,8 0 P _part/17/_locked/6,9 0 P _part/17/_locked/7,0 0 P _part/17/_locked/7,1 0 P _part/17/_locked/7,10 0 P _part/17/_locked/7,11 0 P _part/17/_locked/7,12 0 P _part/17/_locked/7,13 0 P _part/17/_locked/7,14 0 P _part/17/_locked/7,15 0 P _part/17/_locked/7,16 0 P _part/17/_locked/7,17 0 P _part/17/_locked/7,2 0 P _part/17/_locked/7,3 0 P _part/17/_locked/7,4 0 P _part/17/_locked/7,5 0 P _part/17/_locked/7,6 0 P _part/17/_locked/7,7 0 P _part/17/_locked/7,8 0 P _part/17/_locked/7,9 0 P _part/17/_locked/8,0 0 P _part/17/_locked/8,1 0 P _part/17/_locked/8,10 0 P _part/17/_locked/8,11 0 P _part/17/_locked/8,12 0 P _part/17/_locked/8,13 0 P _part/17/_locked/8,14 0 P _part/17/_locked/8,15 0 P _part/17/_locked/8,16 0 P _part/17/_locked/8,17 0 P _part/17/_locked/8,2 0 P _part/17/_locked/8,3 0 P _part/17/_locked/8,4 0 P _part/17/_locked/8,5 0 P _part/17/_locked/8,6 0 P _part/17/_locked/8,7 0 P _part/17/_locked/8,8 0 P _part/17/_locked/8,9 0 P _part/17/_locked/9,0 0 P _part/17/_locked/9,1 0 P _part/17/_locked/9,10 0 P _part/17/_locked/9,11 0 P _part/17/_locked/9,12 0 P _part/17/_locked/9,13 0 P _part/17/_locked/9,14 0 P _part/17/_locked/9,15 0 P _part/17/_locked/9,16 0 P _part/17/_locked/9,17 0 P _part/17/_locked/9,2 0 P _part/17/_locked/9,3 0 P _part/17/_locked/9,4 0 P _part/17/_locked/9,5 0 P _part/17/_locked/9,6 0 P _part/17/_locked/9,7 0 P _part/17/_locked/9,8 0 P _part/17/_locked/9,9 0 P _part/17/_locked/i_count 20 P _part/17/_locked/j_count 18 P _part/17/_part_cd 0.075000003 P _part/17/_part_phi 0.0 P _part/17/_part_psi 0.0 P _part/17/_part_rad 2.0 P _part/17/_part_specs_eq 1 P _part/17/_part_specs_invis 0 P _part/17/_part_specs_unused1 0 P _part/17/_part_specs_unused2 0 P _part/17/_part_tex 0 P _part/17/_part_the 0.0 P _part/17/_part_x 0.0 P _part/17/_part_y 12.303149223 P _part/17/_part_z 32.808399200 P _part/17/_patt_con 0 P _part/17/_patt_prt 0 P _part/17/_patt_rat 0.0 P _part/17/_r_dim 14 P _part/17/_s_dim 4 P _part/17/_scon 37.676635742 P _part/17/_top_s1 0.755999982 P _part/17/_top_s2 1.0 P _part/17/_top_t1 0.509999990 P _part/17/_top_t2 0.754000008 P _part/18/_aero_x_os 0.0 P _part/18/_aero_y_os 0.0 P _part/18/_aero_z_os 0.0 P _part/18/_area_frnt 0.0 P _part/18/_area_side 0.0 P _part/18/_area_vert 0.0 P _part/18/_bot_s1 0.263999999 P _part/18/_bot_s2 0.508000016 P _part/18/_bot_t1 0.0 P _part/18/_bot_t2 0.252999991 P _part/18/_damp 1.883831739 P _part/18/_geo_xyz/0,0,0 0.0 P _part/18/_geo_xyz/0,0,1 0.0 P _part/18/_geo_xyz/0,0,2 32.800197601 P _part/18/_geo_xyz/0,1,0 -0.000590551 P _part/18/_geo_xyz/0,1,1 0.000000000 P _part/18/_geo_xyz/0,1,2 32.801181793 P _part/18/_geo_xyz/0,10,0 0.001476378 P _part/18/_geo_xyz/0,10,1 -0.000000000 P _part/18/_geo_xyz/0,10,2 32.808399200 P _part/18/_geo_xyz/0,11,0 0.001181102 P _part/18/_geo_xyz/0,11,1 -0.000000000 P _part/18/_geo_xyz/0,11,2 32.803478241 P _part/18/_geo_xyz/0,12,0 0.000590551 P _part/18/_geo_xyz/0,12,1 -0.000000000 P _part/18/_geo_xyz/0,12,2 32.801181793 P _part/18/_geo_xyz/0,13,0 0.0 P _part/18/_geo_xyz/0,13,1 0.0 P _part/18/_geo_xyz/0,13,2 32.800197601 P _part/18/_geo_xyz/0,14,0 0.0 P _part/18/_geo_xyz/0,14,1 0.0 P _part/18/_geo_xyz/0,14,2 0.0 P _part/18/_geo_xyz/0,15,0 0.0 P _part/18/_geo_xyz/0,15,1 0.0 P _part/18/_geo_xyz/0,15,2 0.0 P _part/18/_geo_xyz/0,16,0 0.0 P _part/18/_geo_xyz/0,16,1 0.0 P _part/18/_geo_xyz/0,16,2 0.0 P _part/18/_geo_xyz/0,17,0 0.0 P _part/18/_geo_xyz/0,17,1 0.0 P _part/18/_geo_xyz/0,17,2 0.0 P _part/18/_geo_xyz/0,2,0 -0.001181102 P _part/18/_geo_xyz/0,2,1 0.000000000 P _part/18/_geo_xyz/0,2,2 32.803478241 P _part/18/_geo_xyz/0,3,0 -0.001476378 P _part/18/_geo_xyz/0,3,1 0.000000000 P _part/18/_geo_xyz/0,3,2 32.808399200 P _part/18/_geo_xyz/0,4,0 -0.001476378 P _part/18/_geo_xyz/0,4,1 0.000000000 P _part/18/_geo_xyz/0,4,2 32.814960480 P _part/18/_geo_xyz/0,5,0 -0.000671081 P _part/18/_geo_xyz/0,5,1 0.000000000 P _part/18/_geo_xyz/0,5,2 32.824802399 P _part/18/_geo_xyz/0,6,0 0.0 P _part/18/_geo_xyz/0,6,1 0.0 P _part/18/_geo_xyz/0,6,2 32.833003998 P _part/18/_geo_xyz/0,7,0 0.0 P _part/18/_geo_xyz/0,7,1 0.0 P _part/18/_geo_xyz/0,7,2 32.833003998 P _part/18/_geo_xyz/0,8,0 0.000671081 P _part/18/_geo_xyz/0,8,1 -0.000000000 P _part/18/_geo_xyz/0,8,2 32.824802399 P _part/18/_geo_xyz/0,9,0 0.001476378 P _part/18/_geo_xyz/0,9,1 -0.000000000 P _part/18/_geo_xyz/0,9,2 32.814960480 P _part/18/_geo_xyz/1,0,0 -0.000000501 P _part/18/_geo_xyz/1,0,1 3.075787306 P _part/18/_geo_xyz/1,0,2 32.800197601 P _part/18/_geo_xyz/1,1,0 -0.000591052 P _part/18/_geo_xyz/1,1,1 3.075787306 P _part/18/_geo_xyz/1,1,2 32.801181793 P _part/18/_geo_xyz/1,10,0 0.001475877 P _part/18/_geo_xyz/1,10,1 3.075787306 P _part/18/_geo_xyz/1,10,2 32.808399200 P _part/18/_geo_xyz/1,11,0 0.001180601 P _part/18/_geo_xyz/1,11,1 3.075787306 P _part/18/_geo_xyz/1,11,2 32.803478241 P _part/18/_geo_xyz/1,12,0 0.000590050 P _part/18/_geo_xyz/1,12,1 3.075787306 P _part/18/_geo_xyz/1,12,2 32.801181793 P _part/18/_geo_xyz/1,13,0 -0.000000501 P _part/18/_geo_xyz/1,13,1 3.075787306 P _part/18/_geo_xyz/1,13,2 32.800197601 P _part/18/_geo_xyz/1,14,0 0.0 P _part/18/_geo_xyz/1,14,1 0.0 P _part/18/_geo_xyz/1,14,2 0.0 P _part/18/_geo_xyz/1,15,0 0.0 P _part/18/_geo_xyz/1,15,1 0.0 P _part/18/_geo_xyz/1,15,2 0.0 P _part/18/_geo_xyz/1,16,0 0.0 P _part/18/_geo_xyz/1,16,1 0.0 P _part/18/_geo_xyz/1,16,2 0.0 P _part/18/_geo_xyz/1,17,0 0.0 P _part/18/_geo_xyz/1,17,1 0.0 P _part/18/_geo_xyz/1,17,2 0.0 P _part/18/_geo_xyz/1,2,0 -0.001181603 P _part/18/_geo_xyz/1,2,1 3.075787306 P _part/18/_geo_xyz/1,2,2 32.803478241 P _part/18/_geo_xyz/1,3,0 -0.001476879 P _part/18/_geo_xyz/1,3,1 3.075787306 P _part/18/_geo_xyz/1,3,2 32.808399200 P _part/18/_geo_xyz/1,4,0 -0.001476879 P _part/18/_geo_xyz/1,4,1 3.075787306 P _part/18/_geo_xyz/1,4,2 32.814960480 P _part/18/_geo_xyz/1,5,0 -0.000671582 P _part/18/_geo_xyz/1,5,1 3.075787306 P _part/18/_geo_xyz/1,5,2 32.824802399 P _part/18/_geo_xyz/1,6,0 -0.000000501 P _part/18/_geo_xyz/1,6,1 3.075787306 P _part/18/_geo_xyz/1,6,2 32.833003998 P _part/18/_geo_xyz/1,7,0 -0.000000501 P _part/18/_geo_xyz/1,7,1 3.075787306 P _part/18/_geo_xyz/1,7,2 32.833003998 P _part/18/_geo_xyz/1,8,0 0.000670580 P _part/18/_geo_xyz/1,8,1 3.075787306 P _part/18/_geo_xyz/1,8,2 32.824802399 P _part/18/_geo_xyz/1,9,0 0.001475877 P _part/18/_geo_xyz/1,9,1 3.075787306 P _part/18/_geo_xyz/1,9,2 32.814960480 P _part/18/_geo_xyz/10,0,0 0.0 P _part/18/_geo_xyz/10,0,1 0.0 P _part/18/_geo_xyz/10,0,2 0.0 P _part/18/_geo_xyz/10,1,0 0.0 P _part/18/_geo_xyz/10,1,1 0.0 P _part/18/_geo_xyz/10,1,2 0.0 P _part/18/_geo_xyz/10,10,0 0.0 P _part/18/_geo_xyz/10,10,1 0.0 P _part/18/_geo_xyz/10,10,2 0.0 P _part/18/_geo_xyz/10,11,0 0.0 P _part/18/_geo_xyz/10,11,1 0.0 P _part/18/_geo_xyz/10,11,2 0.0 P _part/18/_geo_xyz/10,12,0 0.0 P _part/18/_geo_xyz/10,12,1 0.0 P _part/18/_geo_xyz/10,12,2 0.0 P _part/18/_geo_xyz/10,13,0 0.0 P _part/18/_geo_xyz/10,13,1 0.0 P _part/18/_geo_xyz/10,13,2 0.0 P _part/18/_geo_xyz/10,14,0 0.0 P _part/18/_geo_xyz/10,14,1 0.0 P _part/18/_geo_xyz/10,14,2 0.0 P _part/18/_geo_xyz/10,15,0 0.0 P _part/18/_geo_xyz/10,15,1 0.0 P _part/18/_geo_xyz/10,15,2 0.0 P _part/18/_geo_xyz/10,16,0 0.0 P _part/18/_geo_xyz/10,16,1 0.0 P _part/18/_geo_xyz/10,16,2 0.0 P _part/18/_geo_xyz/10,17,0 0.0 P _part/18/_geo_xyz/10,17,1 0.0 P _part/18/_geo_xyz/10,17,2 0.0 P _part/18/_geo_xyz/10,2,0 0.0 P _part/18/_geo_xyz/10,2,1 0.0 P _part/18/_geo_xyz/10,2,2 0.0 P _part/18/_geo_xyz/10,3,0 0.0 P _part/18/_geo_xyz/10,3,1 0.0 P _part/18/_geo_xyz/10,3,2 0.0 P _part/18/_geo_xyz/10,4,0 0.0 P _part/18/_geo_xyz/10,4,1 0.0 P _part/18/_geo_xyz/10,4,2 0.0 P _part/18/_geo_xyz/10,5,0 0.0 P _part/18/_geo_xyz/10,5,1 0.0 P _part/18/_geo_xyz/10,5,2 0.0 P _part/18/_geo_xyz/10,6,0 0.0 P _part/18/_geo_xyz/10,6,1 0.0 P _part/18/_geo_xyz/10,6,2 0.0 P _part/18/_geo_xyz/10,7,0 0.0 P _part/18/_geo_xyz/10,7,1 0.0 P _part/18/_geo_xyz/10,7,2 0.0 P _part/18/_geo_xyz/10,8,0 0.0 P _part/18/_geo_xyz/10,8,1 0.0 P _part/18/_geo_xyz/10,8,2 0.0 P _part/18/_geo_xyz/10,9,0 0.0 P _part/18/_geo_xyz/10,9,1 0.0 P _part/18/_geo_xyz/10,9,2 0.0 P _part/18/_geo_xyz/11,0,0 0.0 P _part/18/_geo_xyz/11,0,1 0.0 P _part/18/_geo_xyz/11,0,2 0.0 P _part/18/_geo_xyz/11,1,0 0.0 P _part/18/_geo_xyz/11,1,1 0.0 P _part/18/_geo_xyz/11,1,2 0.0 P _part/18/_geo_xyz/11,10,0 0.0 P _part/18/_geo_xyz/11,10,1 0.0 P _part/18/_geo_xyz/11,10,2 0.0 P _part/18/_geo_xyz/11,11,0 0.0 P _part/18/_geo_xyz/11,11,1 0.0 P _part/18/_geo_xyz/11,11,2 0.0 P _part/18/_geo_xyz/11,12,0 0.0 P _part/18/_geo_xyz/11,12,1 0.0 P _part/18/_geo_xyz/11,12,2 0.0 P _part/18/_geo_xyz/11,13,0 0.0 P _part/18/_geo_xyz/11,13,1 0.0 P _part/18/_geo_xyz/11,13,2 0.0 P _part/18/_geo_xyz/11,14,0 0.0 P _part/18/_geo_xyz/11,14,1 0.0 P _part/18/_geo_xyz/11,14,2 0.0 P _part/18/_geo_xyz/11,15,0 0.0 P _part/18/_geo_xyz/11,15,1 0.0 P _part/18/_geo_xyz/11,15,2 0.0 P _part/18/_geo_xyz/11,16,0 0.0 P _part/18/_geo_xyz/11,16,1 0.0 P _part/18/_geo_xyz/11,16,2 0.0 P _part/18/_geo_xyz/11,17,0 0.0 P _part/18/_geo_xyz/11,17,1 0.0 P _part/18/_geo_xyz/11,17,2 0.0 P _part/18/_geo_xyz/11,2,0 0.0 P _part/18/_geo_xyz/11,2,1 0.0 P _part/18/_geo_xyz/11,2,2 0.0 P _part/18/_geo_xyz/11,3,0 0.0 P _part/18/_geo_xyz/11,3,1 0.0 P _part/18/_geo_xyz/11,3,2 0.0 P _part/18/_geo_xyz/11,4,0 0.0 P _part/18/_geo_xyz/11,4,1 0.0 P _part/18/_geo_xyz/11,4,2 0.0 P _part/18/_geo_xyz/11,5,0 0.0 P _part/18/_geo_xyz/11,5,1 0.0 P _part/18/_geo_xyz/11,5,2 0.0 P _part/18/_geo_xyz/11,6,0 0.0 P _part/18/_geo_xyz/11,6,1 0.0 P _part/18/_geo_xyz/11,6,2 0.0 P _part/18/_geo_xyz/11,7,0 0.0 P _part/18/_geo_xyz/11,7,1 0.0 P _part/18/_geo_xyz/11,7,2 0.0 P _part/18/_geo_xyz/11,8,0 0.0 P _part/18/_geo_xyz/11,8,1 0.0 P _part/18/_geo_xyz/11,8,2 0.0 P _part/18/_geo_xyz/11,9,0 0.0 P _part/18/_geo_xyz/11,9,1 0.0 P _part/18/_geo_xyz/11,9,2 0.0 P _part/18/_geo_xyz/12,0,0 0.0 P _part/18/_geo_xyz/12,0,1 0.0 P _part/18/_geo_xyz/12,0,2 0.0 P _part/18/_geo_xyz/12,1,0 0.0 P _part/18/_geo_xyz/12,1,1 0.0 P _part/18/_geo_xyz/12,1,2 0.0 P _part/18/_geo_xyz/12,10,0 0.0 P _part/18/_geo_xyz/12,10,1 0.0 P _part/18/_geo_xyz/12,10,2 0.0 P _part/18/_geo_xyz/12,11,0 0.0 P _part/18/_geo_xyz/12,11,1 0.0 P _part/18/_geo_xyz/12,11,2 0.0 P _part/18/_geo_xyz/12,12,0 0.0 P _part/18/_geo_xyz/12,12,1 0.0 P _part/18/_geo_xyz/12,12,2 0.0 P _part/18/_geo_xyz/12,13,0 0.0 P _part/18/_geo_xyz/12,13,1 0.0 P _part/18/_geo_xyz/12,13,2 0.0 P _part/18/_geo_xyz/12,14,0 0.0 P _part/18/_geo_xyz/12,14,1 0.0 P _part/18/_geo_xyz/12,14,2 0.0 P _part/18/_geo_xyz/12,15,0 0.0 P _part/18/_geo_xyz/12,15,1 0.0 P _part/18/_geo_xyz/12,15,2 0.0 P _part/18/_geo_xyz/12,16,0 0.0 P _part/18/_geo_xyz/12,16,1 0.0 P _part/18/_geo_xyz/12,16,2 0.0 P _part/18/_geo_xyz/12,17,0 0.0 P _part/18/_geo_xyz/12,17,1 0.0 P _part/18/_geo_xyz/12,17,2 0.0 P _part/18/_geo_xyz/12,2,0 0.0 P _part/18/_geo_xyz/12,2,1 0.0 P _part/18/_geo_xyz/12,2,2 0.0 P _part/18/_geo_xyz/12,3,0 0.0 P _part/18/_geo_xyz/12,3,1 0.0 P _part/18/_geo_xyz/12,3,2 0.0 P _part/18/_geo_xyz/12,4,0 0.0 P _part/18/_geo_xyz/12,4,1 0.0 P _part/18/_geo_xyz/12,4,2 0.0 P _part/18/_geo_xyz/12,5,0 0.0 P _part/18/_geo_xyz/12,5,1 0.0 P _part/18/_geo_xyz/12,5,2 0.0 P _part/18/_geo_xyz/12,6,0 0.0 P _part/18/_geo_xyz/12,6,1 0.0 P _part/18/_geo_xyz/12,6,2 0.0 P _part/18/_geo_xyz/12,7,0 0.0 P _part/18/_geo_xyz/12,7,1 0.0 P _part/18/_geo_xyz/12,7,2 0.0 P _part/18/_geo_xyz/12,8,0 0.0 P _part/18/_geo_xyz/12,8,1 0.0 P _part/18/_geo_xyz/12,8,2 0.0 P _part/18/_geo_xyz/12,9,0 0.0 P _part/18/_geo_xyz/12,9,1 0.0 P _part/18/_geo_xyz/12,9,2 0.0 P _part/18/_geo_xyz/13,0,0 0.0 P _part/18/_geo_xyz/13,0,1 0.0 P _part/18/_geo_xyz/13,0,2 0.0 P _part/18/_geo_xyz/13,1,0 0.0 P _part/18/_geo_xyz/13,1,1 0.0 P _part/18/_geo_xyz/13,1,2 0.0 P _part/18/_geo_xyz/13,10,0 0.0 P _part/18/_geo_xyz/13,10,1 0.0 P _part/18/_geo_xyz/13,10,2 0.0 P _part/18/_geo_xyz/13,11,0 0.0 P _part/18/_geo_xyz/13,11,1 0.0 P _part/18/_geo_xyz/13,11,2 0.0 P _part/18/_geo_xyz/13,12,0 0.0 P _part/18/_geo_xyz/13,12,1 0.0 P _part/18/_geo_xyz/13,12,2 0.0 P _part/18/_geo_xyz/13,13,0 0.0 P _part/18/_geo_xyz/13,13,1 0.0 P _part/18/_geo_xyz/13,13,2 0.0 P _part/18/_geo_xyz/13,14,0 0.0 P _part/18/_geo_xyz/13,14,1 0.0 P _part/18/_geo_xyz/13,14,2 0.0 P _part/18/_geo_xyz/13,15,0 0.0 P _part/18/_geo_xyz/13,15,1 0.0 P _part/18/_geo_xyz/13,15,2 0.0 P _part/18/_geo_xyz/13,16,0 0.0 P _part/18/_geo_xyz/13,16,1 0.0 P _part/18/_geo_xyz/13,16,2 0.0 P _part/18/_geo_xyz/13,17,0 0.0 P _part/18/_geo_xyz/13,17,1 0.0 P _part/18/_geo_xyz/13,17,2 0.0 P _part/18/_geo_xyz/13,2,0 0.0 P _part/18/_geo_xyz/13,2,1 0.0 P _part/18/_geo_xyz/13,2,2 0.0 P _part/18/_geo_xyz/13,3,0 0.0 P _part/18/_geo_xyz/13,3,1 0.0 P _part/18/_geo_xyz/13,3,2 0.0 P _part/18/_geo_xyz/13,4,0 0.0 P _part/18/_geo_xyz/13,4,1 0.0 P _part/18/_geo_xyz/13,4,2 0.0 P _part/18/_geo_xyz/13,5,0 0.0 P _part/18/_geo_xyz/13,5,1 0.0 P _part/18/_geo_xyz/13,5,2 0.0 P _part/18/_geo_xyz/13,6,0 0.0 P _part/18/_geo_xyz/13,6,1 0.0 P _part/18/_geo_xyz/13,6,2 0.0 P _part/18/_geo_xyz/13,7,0 0.0 P _part/18/_geo_xyz/13,7,1 0.0 P _part/18/_geo_xyz/13,7,2 0.0 P _part/18/_geo_xyz/13,8,0 0.0 P _part/18/_geo_xyz/13,8,1 0.0 P _part/18/_geo_xyz/13,8,2 0.0 P _part/18/_geo_xyz/13,9,0 0.0 P _part/18/_geo_xyz/13,9,1 0.0 P _part/18/_geo_xyz/13,9,2 0.0 P _part/18/_geo_xyz/14,0,0 0.0 P _part/18/_geo_xyz/14,0,1 0.0 P _part/18/_geo_xyz/14,0,2 0.0 P _part/18/_geo_xyz/14,1,0 0.0 P _part/18/_geo_xyz/14,1,1 0.0 P _part/18/_geo_xyz/14,1,2 0.0 P _part/18/_geo_xyz/14,10,0 0.0 P _part/18/_geo_xyz/14,10,1 0.0 P _part/18/_geo_xyz/14,10,2 0.0 P _part/18/_geo_xyz/14,11,0 0.0 P _part/18/_geo_xyz/14,11,1 0.0 P _part/18/_geo_xyz/14,11,2 0.0 P _part/18/_geo_xyz/14,12,0 0.0 P _part/18/_geo_xyz/14,12,1 0.0 P _part/18/_geo_xyz/14,12,2 0.0 P _part/18/_geo_xyz/14,13,0 0.0 P _part/18/_geo_xyz/14,13,1 0.0 P _part/18/_geo_xyz/14,13,2 0.0 P _part/18/_geo_xyz/14,14,0 0.0 P _part/18/_geo_xyz/14,14,1 0.0 P _part/18/_geo_xyz/14,14,2 0.0 P _part/18/_geo_xyz/14,15,0 0.0 P _part/18/_geo_xyz/14,15,1 0.0 P _part/18/_geo_xyz/14,15,2 0.0 P _part/18/_geo_xyz/14,16,0 0.0 P _part/18/_geo_xyz/14,16,1 0.0 P _part/18/_geo_xyz/14,16,2 0.0 P _part/18/_geo_xyz/14,17,0 0.0 P _part/18/_geo_xyz/14,17,1 0.0 P _part/18/_geo_xyz/14,17,2 0.0 P _part/18/_geo_xyz/14,2,0 0.0 P _part/18/_geo_xyz/14,2,1 0.0 P _part/18/_geo_xyz/14,2,2 0.0 P _part/18/_geo_xyz/14,3,0 0.0 P _part/18/_geo_xyz/14,3,1 0.0 P _part/18/_geo_xyz/14,3,2 0.0 P _part/18/_geo_xyz/14,4,0 0.0 P _part/18/_geo_xyz/14,4,1 0.0 P _part/18/_geo_xyz/14,4,2 0.0 P _part/18/_geo_xyz/14,5,0 0.0 P _part/18/_geo_xyz/14,5,1 0.0 P _part/18/_geo_xyz/14,5,2 0.0 P _part/18/_geo_xyz/14,6,0 0.0 P _part/18/_geo_xyz/14,6,1 0.0 P _part/18/_geo_xyz/14,6,2 0.0 P _part/18/_geo_xyz/14,7,0 0.0 P _part/18/_geo_xyz/14,7,1 0.0 P _part/18/_geo_xyz/14,7,2 0.0 P _part/18/_geo_xyz/14,8,0 0.0 P _part/18/_geo_xyz/14,8,1 0.0 P _part/18/_geo_xyz/14,8,2 0.0 P _part/18/_geo_xyz/14,9,0 0.0 P _part/18/_geo_xyz/14,9,1 0.0 P _part/18/_geo_xyz/14,9,2 0.0 P _part/18/_geo_xyz/15,0,0 0.0 P _part/18/_geo_xyz/15,0,1 0.0 P _part/18/_geo_xyz/15,0,2 0.0 P _part/18/_geo_xyz/15,1,0 0.0 P _part/18/_geo_xyz/15,1,1 0.0 P _part/18/_geo_xyz/15,1,2 0.0 P _part/18/_geo_xyz/15,10,0 0.0 P _part/18/_geo_xyz/15,10,1 0.0 P _part/18/_geo_xyz/15,10,2 0.0 P _part/18/_geo_xyz/15,11,0 0.0 P _part/18/_geo_xyz/15,11,1 0.0 P _part/18/_geo_xyz/15,11,2 0.0 P _part/18/_geo_xyz/15,12,0 0.0 P _part/18/_geo_xyz/15,12,1 0.0 P _part/18/_geo_xyz/15,12,2 0.0 P _part/18/_geo_xyz/15,13,0 0.0 P _part/18/_geo_xyz/15,13,1 0.0 P _part/18/_geo_xyz/15,13,2 0.0 P _part/18/_geo_xyz/15,14,0 0.0 P _part/18/_geo_xyz/15,14,1 0.0 P _part/18/_geo_xyz/15,14,2 0.0 P _part/18/_geo_xyz/15,15,0 0.0 P _part/18/_geo_xyz/15,15,1 0.0 P _part/18/_geo_xyz/15,15,2 0.0 P _part/18/_geo_xyz/15,16,0 0.0 P _part/18/_geo_xyz/15,16,1 0.0 P _part/18/_geo_xyz/15,16,2 0.0 P _part/18/_geo_xyz/15,17,0 0.0 P _part/18/_geo_xyz/15,17,1 0.0 P _part/18/_geo_xyz/15,17,2 0.0 P _part/18/_geo_xyz/15,2,0 0.0 P _part/18/_geo_xyz/15,2,1 0.0 P _part/18/_geo_xyz/15,2,2 0.0 P _part/18/_geo_xyz/15,3,0 0.0 P _part/18/_geo_xyz/15,3,1 0.0 P _part/18/_geo_xyz/15,3,2 0.0 P _part/18/_geo_xyz/15,4,0 0.0 P _part/18/_geo_xyz/15,4,1 0.0 P _part/18/_geo_xyz/15,4,2 0.0 P _part/18/_geo_xyz/15,5,0 0.0 P _part/18/_geo_xyz/15,5,1 0.0 P _part/18/_geo_xyz/15,5,2 0.0 P _part/18/_geo_xyz/15,6,0 0.0 P _part/18/_geo_xyz/15,6,1 0.0 P _part/18/_geo_xyz/15,6,2 0.0 P _part/18/_geo_xyz/15,7,0 0.0 P _part/18/_geo_xyz/15,7,1 0.0 P _part/18/_geo_xyz/15,7,2 0.0 P _part/18/_geo_xyz/15,8,0 0.0 P _part/18/_geo_xyz/15,8,1 0.0 P _part/18/_geo_xyz/15,8,2 0.0 P _part/18/_geo_xyz/15,9,0 0.0 P _part/18/_geo_xyz/15,9,1 0.0 P _part/18/_geo_xyz/15,9,2 0.0 P _part/18/_geo_xyz/16,0,0 0.0 P _part/18/_geo_xyz/16,0,1 0.0 P _part/18/_geo_xyz/16,0,2 0.0 P _part/18/_geo_xyz/16,1,0 0.0 P _part/18/_geo_xyz/16,1,1 0.0 P _part/18/_geo_xyz/16,1,2 0.0 P _part/18/_geo_xyz/16,10,0 0.0 P _part/18/_geo_xyz/16,10,1 0.0 P _part/18/_geo_xyz/16,10,2 0.0 P _part/18/_geo_xyz/16,11,0 0.0 P _part/18/_geo_xyz/16,11,1 0.0 P _part/18/_geo_xyz/16,11,2 0.0 P _part/18/_geo_xyz/16,12,0 0.0 P _part/18/_geo_xyz/16,12,1 0.0 P _part/18/_geo_xyz/16,12,2 0.0 P _part/18/_geo_xyz/16,13,0 0.0 P _part/18/_geo_xyz/16,13,1 0.0 P _part/18/_geo_xyz/16,13,2 0.0 P _part/18/_geo_xyz/16,14,0 0.0 P _part/18/_geo_xyz/16,14,1 0.0 P _part/18/_geo_xyz/16,14,2 0.0 P _part/18/_geo_xyz/16,15,0 0.0 P _part/18/_geo_xyz/16,15,1 0.0 P _part/18/_geo_xyz/16,15,2 0.0 P _part/18/_geo_xyz/16,16,0 0.0 P _part/18/_geo_xyz/16,16,1 0.0 P _part/18/_geo_xyz/16,16,2 0.0 P _part/18/_geo_xyz/16,17,0 0.0 P _part/18/_geo_xyz/16,17,1 0.0 P _part/18/_geo_xyz/16,17,2 0.0 P _part/18/_geo_xyz/16,2,0 0.0 P _part/18/_geo_xyz/16,2,1 0.0 P _part/18/_geo_xyz/16,2,2 0.0 P _part/18/_geo_xyz/16,3,0 0.0 P _part/18/_geo_xyz/16,3,1 0.0 P _part/18/_geo_xyz/16,3,2 0.0 P _part/18/_geo_xyz/16,4,0 0.0 P _part/18/_geo_xyz/16,4,1 0.0 P _part/18/_geo_xyz/16,4,2 0.0 P _part/18/_geo_xyz/16,5,0 0.0 P _part/18/_geo_xyz/16,5,1 0.0 P _part/18/_geo_xyz/16,5,2 0.0 P _part/18/_geo_xyz/16,6,0 0.0 P _part/18/_geo_xyz/16,6,1 0.0 P _part/18/_geo_xyz/16,6,2 0.0 P _part/18/_geo_xyz/16,7,0 0.0 P _part/18/_geo_xyz/16,7,1 0.0 P _part/18/_geo_xyz/16,7,2 0.0 P _part/18/_geo_xyz/16,8,0 0.0 P _part/18/_geo_xyz/16,8,1 0.0 P _part/18/_geo_xyz/16,8,2 0.0 P _part/18/_geo_xyz/16,9,0 0.0 P _part/18/_geo_xyz/16,9,1 0.0 P _part/18/_geo_xyz/16,9,2 0.0 P _part/18/_geo_xyz/17,0,0 0.0 P _part/18/_geo_xyz/17,0,1 0.0 P _part/18/_geo_xyz/17,0,2 0.0 P _part/18/_geo_xyz/17,1,0 0.0 P _part/18/_geo_xyz/17,1,1 0.0 P _part/18/_geo_xyz/17,1,2 0.0 P _part/18/_geo_xyz/17,10,0 0.0 P _part/18/_geo_xyz/17,10,1 0.0 P _part/18/_geo_xyz/17,10,2 0.0 P _part/18/_geo_xyz/17,11,0 0.0 P _part/18/_geo_xyz/17,11,1 0.0 P _part/18/_geo_xyz/17,11,2 0.0 P _part/18/_geo_xyz/17,12,0 0.0 P _part/18/_geo_xyz/17,12,1 0.0 P _part/18/_geo_xyz/17,12,2 0.0 P _part/18/_geo_xyz/17,13,0 0.0 P _part/18/_geo_xyz/17,13,1 0.0 P _part/18/_geo_xyz/17,13,2 0.0 P _part/18/_geo_xyz/17,14,0 0.0 P _part/18/_geo_xyz/17,14,1 0.0 P _part/18/_geo_xyz/17,14,2 0.0 P _part/18/_geo_xyz/17,15,0 0.0 P _part/18/_geo_xyz/17,15,1 0.0 P _part/18/_geo_xyz/17,15,2 0.0 P _part/18/_geo_xyz/17,16,0 0.0 P _part/18/_geo_xyz/17,16,1 0.0 P _part/18/_geo_xyz/17,16,2 0.0 P _part/18/_geo_xyz/17,17,0 0.0 P _part/18/_geo_xyz/17,17,1 0.0 P _part/18/_geo_xyz/17,17,2 0.0 P _part/18/_geo_xyz/17,2,0 0.0 P _part/18/_geo_xyz/17,2,1 0.0 P _part/18/_geo_xyz/17,2,2 0.0 P _part/18/_geo_xyz/17,3,0 0.0 P _part/18/_geo_xyz/17,3,1 0.0 P _part/18/_geo_xyz/17,3,2 0.0 P _part/18/_geo_xyz/17,4,0 0.0 P _part/18/_geo_xyz/17,4,1 0.0 P _part/18/_geo_xyz/17,4,2 0.0 P _part/18/_geo_xyz/17,5,0 0.0 P _part/18/_geo_xyz/17,5,1 0.0 P _part/18/_geo_xyz/17,5,2 0.0 P _part/18/_geo_xyz/17,6,0 0.0 P _part/18/_geo_xyz/17,6,1 0.0 P _part/18/_geo_xyz/17,6,2 0.0 P _part/18/_geo_xyz/17,7,0 0.0 P _part/18/_geo_xyz/17,7,1 0.0 P _part/18/_geo_xyz/17,7,2 0.0 P _part/18/_geo_xyz/17,8,0 0.0 P _part/18/_geo_xyz/17,8,1 0.0 P _part/18/_geo_xyz/17,8,2 0.0 P _part/18/_geo_xyz/17,9,0 0.0 P _part/18/_geo_xyz/17,9,1 0.0 P _part/18/_geo_xyz/17,9,2 0.0 P _part/18/_geo_xyz/18,0,0 0.0 P _part/18/_geo_xyz/18,0,1 0.0 P _part/18/_geo_xyz/18,0,2 0.0 P _part/18/_geo_xyz/18,1,0 0.0 P _part/18/_geo_xyz/18,1,1 0.0 P _part/18/_geo_xyz/18,1,2 0.0 P _part/18/_geo_xyz/18,10,0 0.0 P _part/18/_geo_xyz/18,10,1 0.0 P _part/18/_geo_xyz/18,10,2 0.0 P _part/18/_geo_xyz/18,11,0 0.0 P _part/18/_geo_xyz/18,11,1 0.0 P _part/18/_geo_xyz/18,11,2 0.0 P _part/18/_geo_xyz/18,12,0 0.0 P _part/18/_geo_xyz/18,12,1 0.0 P _part/18/_geo_xyz/18,12,2 0.0 P _part/18/_geo_xyz/18,13,0 0.0 P _part/18/_geo_xyz/18,13,1 0.0 P _part/18/_geo_xyz/18,13,2 0.0 P _part/18/_geo_xyz/18,14,0 0.0 P _part/18/_geo_xyz/18,14,1 0.0 P _part/18/_geo_xyz/18,14,2 0.0 P _part/18/_geo_xyz/18,15,0 0.0 P _part/18/_geo_xyz/18,15,1 0.0 P _part/18/_geo_xyz/18,15,2 0.0 P _part/18/_geo_xyz/18,16,0 0.0 P _part/18/_geo_xyz/18,16,1 0.0 P _part/18/_geo_xyz/18,16,2 0.0 P _part/18/_geo_xyz/18,17,0 0.0 P _part/18/_geo_xyz/18,17,1 0.0 P _part/18/_geo_xyz/18,17,2 0.0 P _part/18/_geo_xyz/18,2,0 0.0 P _part/18/_geo_xyz/18,2,1 0.0 P _part/18/_geo_xyz/18,2,2 0.0 P _part/18/_geo_xyz/18,3,0 0.0 P _part/18/_geo_xyz/18,3,1 0.0 P _part/18/_geo_xyz/18,3,2 0.0 P _part/18/_geo_xyz/18,4,0 0.0 P _part/18/_geo_xyz/18,4,1 0.0 P _part/18/_geo_xyz/18,4,2 0.0 P _part/18/_geo_xyz/18,5,0 0.0 P _part/18/_geo_xyz/18,5,1 0.0 P _part/18/_geo_xyz/18,5,2 0.0 P _part/18/_geo_xyz/18,6,0 0.0 P _part/18/_geo_xyz/18,6,1 0.0 P _part/18/_geo_xyz/18,6,2 0.0 P _part/18/_geo_xyz/18,7,0 0.0 P _part/18/_geo_xyz/18,7,1 0.0 P _part/18/_geo_xyz/18,7,2 0.0 P _part/18/_geo_xyz/18,8,0 0.0 P _part/18/_geo_xyz/18,8,1 0.0 P _part/18/_geo_xyz/18,8,2 0.0 P _part/18/_geo_xyz/18,9,0 0.0 P _part/18/_geo_xyz/18,9,1 0.0 P _part/18/_geo_xyz/18,9,2 0.0 P _part/18/_geo_xyz/19,0,0 0.0 P _part/18/_geo_xyz/19,0,1 0.0 P _part/18/_geo_xyz/19,0,2 0.0 P _part/18/_geo_xyz/19,1,0 0.0 P _part/18/_geo_xyz/19,1,1 0.0 P _part/18/_geo_xyz/19,1,2 0.0 P _part/18/_geo_xyz/19,10,0 0.0 P _part/18/_geo_xyz/19,10,1 0.0 P _part/18/_geo_xyz/19,10,2 0.0 P _part/18/_geo_xyz/19,11,0 0.0 P _part/18/_geo_xyz/19,11,1 0.0 P _part/18/_geo_xyz/19,11,2 0.0 P _part/18/_geo_xyz/19,12,0 0.0 P _part/18/_geo_xyz/19,12,1 0.0 P _part/18/_geo_xyz/19,12,2 0.0 P _part/18/_geo_xyz/19,13,0 0.0 P _part/18/_geo_xyz/19,13,1 0.0 P _part/18/_geo_xyz/19,13,2 0.0 P _part/18/_geo_xyz/19,14,0 0.0 P _part/18/_geo_xyz/19,14,1 0.0 P _part/18/_geo_xyz/19,14,2 0.0 P _part/18/_geo_xyz/19,15,0 0.0 P _part/18/_geo_xyz/19,15,1 0.0 P _part/18/_geo_xyz/19,15,2 0.0 P _part/18/_geo_xyz/19,16,0 0.0 P _part/18/_geo_xyz/19,16,1 0.0 P _part/18/_geo_xyz/19,16,2 0.0 P _part/18/_geo_xyz/19,17,0 0.0 P _part/18/_geo_xyz/19,17,1 0.0 P _part/18/_geo_xyz/19,17,2 0.0 P _part/18/_geo_xyz/19,2,0 0.0 P _part/18/_geo_xyz/19,2,1 0.0 P _part/18/_geo_xyz/19,2,2 0.0 P _part/18/_geo_xyz/19,3,0 0.0 P _part/18/_geo_xyz/19,3,1 0.0 P _part/18/_geo_xyz/19,3,2 0.0 P _part/18/_geo_xyz/19,4,0 0.0 P _part/18/_geo_xyz/19,4,1 0.0 P _part/18/_geo_xyz/19,4,2 0.0 P _part/18/_geo_xyz/19,5,0 0.0 P _part/18/_geo_xyz/19,5,1 0.0 P _part/18/_geo_xyz/19,5,2 0.0 P _part/18/_geo_xyz/19,6,0 0.0 P _part/18/_geo_xyz/19,6,1 0.0 P _part/18/_geo_xyz/19,6,2 0.0 P _part/18/_geo_xyz/19,7,0 0.0 P _part/18/_geo_xyz/19,7,1 0.0 P _part/18/_geo_xyz/19,7,2 0.0 P _part/18/_geo_xyz/19,8,0 0.0 P _part/18/_geo_xyz/19,8,1 0.0 P _part/18/_geo_xyz/19,8,2 0.0 P _part/18/_geo_xyz/19,9,0 0.0 P _part/18/_geo_xyz/19,9,1 0.0 P _part/18/_geo_xyz/19,9,2 0.0 P _part/18/_geo_xyz/2,0,0 -0.000000501 P _part/18/_geo_xyz/2,0,1 3.075787306 P _part/18/_geo_xyz/2,0,2 32.800197601 P _part/18/_geo_xyz/2,1,0 -0.000591052 P _part/18/_geo_xyz/2,1,1 3.075787306 P _part/18/_geo_xyz/2,1,2 32.801181793 P _part/18/_geo_xyz/2,10,0 0.001475877 P _part/18/_geo_xyz/2,10,1 3.075787306 P _part/18/_geo_xyz/2,10,2 32.808399200 P _part/18/_geo_xyz/2,11,0 0.001180601 P _part/18/_geo_xyz/2,11,1 3.075787306 P _part/18/_geo_xyz/2,11,2 32.803478241 P _part/18/_geo_xyz/2,12,0 0.000590050 P _part/18/_geo_xyz/2,12,1 3.075787306 P _part/18/_geo_xyz/2,12,2 32.801181793 P _part/18/_geo_xyz/2,13,0 -0.000000501 P _part/18/_geo_xyz/2,13,1 3.075787306 P _part/18/_geo_xyz/2,13,2 32.800197601 P _part/18/_geo_xyz/2,14,0 0.0 P _part/18/_geo_xyz/2,14,1 0.0 P _part/18/_geo_xyz/2,14,2 0.0 P _part/18/_geo_xyz/2,15,0 0.0 P _part/18/_geo_xyz/2,15,1 0.0 P _part/18/_geo_xyz/2,15,2 0.0 P _part/18/_geo_xyz/2,16,0 0.0 P _part/18/_geo_xyz/2,16,1 0.0 P _part/18/_geo_xyz/2,16,2 0.0 P _part/18/_geo_xyz/2,17,0 0.0 P _part/18/_geo_xyz/2,17,1 0.0 P _part/18/_geo_xyz/2,17,2 0.0 P _part/18/_geo_xyz/2,2,0 -0.001181603 P _part/18/_geo_xyz/2,2,1 3.075787306 P _part/18/_geo_xyz/2,2,2 32.803478241 P _part/18/_geo_xyz/2,3,0 -0.001476879 P _part/18/_geo_xyz/2,3,1 3.075787306 P _part/18/_geo_xyz/2,3,2 32.808399200 P _part/18/_geo_xyz/2,4,0 -0.001476879 P _part/18/_geo_xyz/2,4,1 3.075787306 P _part/18/_geo_xyz/2,4,2 32.814960480 P _part/18/_geo_xyz/2,5,0 -0.000671582 P _part/18/_geo_xyz/2,5,1 3.075787306 P _part/18/_geo_xyz/2,5,2 32.824802399 P _part/18/_geo_xyz/2,6,0 -0.000000501 P _part/18/_geo_xyz/2,6,1 3.075787306 P _part/18/_geo_xyz/2,6,2 32.833003998 P _part/18/_geo_xyz/2,7,0 -0.000000501 P _part/18/_geo_xyz/2,7,1 3.075787306 P _part/18/_geo_xyz/2,7,2 32.833003998 P _part/18/_geo_xyz/2,8,0 0.000670580 P _part/18/_geo_xyz/2,8,1 3.075787306 P _part/18/_geo_xyz/2,8,2 32.824802399 P _part/18/_geo_xyz/2,9,0 0.001475877 P _part/18/_geo_xyz/2,9,1 3.075787306 P _part/18/_geo_xyz/2,9,2 32.814960480 P _part/18/_geo_xyz/3,0,0 -0.000001002 P _part/18/_geo_xyz/3,0,1 6.151574612 P _part/18/_geo_xyz/3,0,2 32.800197601 P _part/18/_geo_xyz/3,1,0 -0.000591553 P _part/18/_geo_xyz/3,1,1 6.151574612 P _part/18/_geo_xyz/3,1,2 32.801181793 P _part/18/_geo_xyz/3,10,0 0.001475376 P _part/18/_geo_xyz/3,10,1 6.151574612 P _part/18/_geo_xyz/3,10,2 32.808399200 P _part/18/_geo_xyz/3,11,0 0.001180100 P _part/18/_geo_xyz/3,11,1 6.151574612 P _part/18/_geo_xyz/3,11,2 32.803478241 P _part/18/_geo_xyz/3,12,0 0.000589549 P _part/18/_geo_xyz/3,12,1 6.151574612 P _part/18/_geo_xyz/3,12,2 32.801181793 P _part/18/_geo_xyz/3,13,0 -0.000001002 P _part/18/_geo_xyz/3,13,1 6.151574612 P _part/18/_geo_xyz/3,13,2 32.800197601 P _part/18/_geo_xyz/3,14,0 0.0 P _part/18/_geo_xyz/3,14,1 0.0 P _part/18/_geo_xyz/3,14,2 0.0 P _part/18/_geo_xyz/3,15,0 0.0 P _part/18/_geo_xyz/3,15,1 0.0 P _part/18/_geo_xyz/3,15,2 0.0 P _part/18/_geo_xyz/3,16,0 0.0 P _part/18/_geo_xyz/3,16,1 0.0 P _part/18/_geo_xyz/3,16,2 0.0 P _part/18/_geo_xyz/3,17,0 0.0 P _part/18/_geo_xyz/3,17,1 0.0 P _part/18/_geo_xyz/3,17,2 0.0 P _part/18/_geo_xyz/3,2,0 -0.001182105 P _part/18/_geo_xyz/3,2,1 6.151574612 P _part/18/_geo_xyz/3,2,2 32.803478241 P _part/18/_geo_xyz/3,3,0 -0.001477380 P _part/18/_geo_xyz/3,3,1 6.151574612 P _part/18/_geo_xyz/3,3,2 32.808399200 P _part/18/_geo_xyz/3,4,0 -0.001477380 P _part/18/_geo_xyz/3,4,1 6.151574612 P _part/18/_geo_xyz/3,4,2 32.814960480 P _part/18/_geo_xyz/3,5,0 -0.000672083 P _part/18/_geo_xyz/3,5,1 6.151574612 P _part/18/_geo_xyz/3,5,2 32.824802399 P _part/18/_geo_xyz/3,6,0 -0.000001002 P _part/18/_geo_xyz/3,6,1 6.151574612 P _part/18/_geo_xyz/3,6,2 32.833003998 P _part/18/_geo_xyz/3,7,0 -0.000001002 P _part/18/_geo_xyz/3,7,1 6.151574612 P _part/18/_geo_xyz/3,7,2 32.833003998 P _part/18/_geo_xyz/3,8,0 0.000670079 P _part/18/_geo_xyz/3,8,1 6.151574612 P _part/18/_geo_xyz/3,8,2 32.824802399 P _part/18/_geo_xyz/3,9,0 0.001475376 P _part/18/_geo_xyz/3,9,1 6.151574612 P _part/18/_geo_xyz/3,9,2 32.814960480 P _part/18/_geo_xyz/4,0,0 -0.000001002 P _part/18/_geo_xyz/4,0,1 6.151574612 P _part/18/_geo_xyz/4,0,2 32.800197601 P _part/18/_geo_xyz/4,1,0 -0.000591553 P _part/18/_geo_xyz/4,1,1 6.151574612 P _part/18/_geo_xyz/4,1,2 32.801181793 P _part/18/_geo_xyz/4,10,0 0.001475376 P _part/18/_geo_xyz/4,10,1 6.151574612 P _part/18/_geo_xyz/4,10,2 32.808399200 P _part/18/_geo_xyz/4,11,0 0.001180100 P _part/18/_geo_xyz/4,11,1 6.151574612 P _part/18/_geo_xyz/4,11,2 32.803478241 P _part/18/_geo_xyz/4,12,0 0.000589549 P _part/18/_geo_xyz/4,12,1 6.151574612 P _part/18/_geo_xyz/4,12,2 32.801181793 P _part/18/_geo_xyz/4,13,0 -0.000001002 P _part/18/_geo_xyz/4,13,1 6.151574612 P _part/18/_geo_xyz/4,13,2 32.800197601 P _part/18/_geo_xyz/4,14,0 0.0 P _part/18/_geo_xyz/4,14,1 0.0 P _part/18/_geo_xyz/4,14,2 0.0 P _part/18/_geo_xyz/4,15,0 0.0 P _part/18/_geo_xyz/4,15,1 0.0 P _part/18/_geo_xyz/4,15,2 0.0 P _part/18/_geo_xyz/4,16,0 0.0 P _part/18/_geo_xyz/4,16,1 0.0 P _part/18/_geo_xyz/4,16,2 0.0 P _part/18/_geo_xyz/4,17,0 0.0 P _part/18/_geo_xyz/4,17,1 0.0 P _part/18/_geo_xyz/4,17,2 0.0 P _part/18/_geo_xyz/4,2,0 -0.001182105 P _part/18/_geo_xyz/4,2,1 6.151574612 P _part/18/_geo_xyz/4,2,2 32.803478241 P _part/18/_geo_xyz/4,3,0 -0.001477380 P _part/18/_geo_xyz/4,3,1 6.151574612 P _part/18/_geo_xyz/4,3,2 32.808399200 P _part/18/_geo_xyz/4,4,0 -0.001477380 P _part/18/_geo_xyz/4,4,1 6.151574612 P _part/18/_geo_xyz/4,4,2 32.814960480 P _part/18/_geo_xyz/4,5,0 -0.000672083 P _part/18/_geo_xyz/4,5,1 6.151574612 P _part/18/_geo_xyz/4,5,2 32.824802399 P _part/18/_geo_xyz/4,6,0 -0.000001002 P _part/18/_geo_xyz/4,6,1 6.151574612 P _part/18/_geo_xyz/4,6,2 32.833003998 P _part/18/_geo_xyz/4,7,0 -0.000001002 P _part/18/_geo_xyz/4,7,1 6.151574612 P _part/18/_geo_xyz/4,7,2 32.833003998 P _part/18/_geo_xyz/4,8,0 0.000670079 P _part/18/_geo_xyz/4,8,1 6.151574612 P _part/18/_geo_xyz/4,8,2 32.824802399 P _part/18/_geo_xyz/4,9,0 0.001475376 P _part/18/_geo_xyz/4,9,1 6.151574612 P _part/18/_geo_xyz/4,9,2 32.814960480 P _part/18/_geo_xyz/5,0,0 -0.000001503 P _part/18/_geo_xyz/5,0,1 9.227361679 P _part/18/_geo_xyz/5,0,2 32.800197601 P _part/18/_geo_xyz/5,1,0 -0.000592055 P _part/18/_geo_xyz/5,1,1 9.227361679 P _part/18/_geo_xyz/5,1,2 32.801181793 P _part/18/_geo_xyz/5,10,0 0.001474875 P _part/18/_geo_xyz/5,10,1 9.227361679 P _part/18/_geo_xyz/5,10,2 32.808399200 P _part/18/_geo_xyz/5,11,0 0.001179599 P _part/18/_geo_xyz/5,11,1 9.227361679 P _part/18/_geo_xyz/5,11,2 32.803478241 P _part/18/_geo_xyz/5,12,0 0.000589048 P _part/18/_geo_xyz/5,12,1 9.227361679 P _part/18/_geo_xyz/5,12,2 32.801181793 P _part/18/_geo_xyz/5,13,0 -0.000001503 P _part/18/_geo_xyz/5,13,1 9.227361679 P _part/18/_geo_xyz/5,13,2 32.800197601 P _part/18/_geo_xyz/5,14,0 0.0 P _part/18/_geo_xyz/5,14,1 0.0 P _part/18/_geo_xyz/5,14,2 0.0 P _part/18/_geo_xyz/5,15,0 0.0 P _part/18/_geo_xyz/5,15,1 0.0 P _part/18/_geo_xyz/5,15,2 0.0 P _part/18/_geo_xyz/5,16,0 0.0 P _part/18/_geo_xyz/5,16,1 0.0 P _part/18/_geo_xyz/5,16,2 0.0 P _part/18/_geo_xyz/5,17,0 0.0 P _part/18/_geo_xyz/5,17,1 0.0 P _part/18/_geo_xyz/5,17,2 0.0 P _part/18/_geo_xyz/5,2,0 -0.001182606 P _part/18/_geo_xyz/5,2,1 9.227361679 P _part/18/_geo_xyz/5,2,2 32.803478241 P _part/18/_geo_xyz/5,3,0 -0.001477881 P _part/18/_geo_xyz/5,3,1 9.227361679 P _part/18/_geo_xyz/5,3,2 32.808399200 P _part/18/_geo_xyz/5,4,0 -0.001477881 P _part/18/_geo_xyz/5,4,1 9.227361679 P _part/18/_geo_xyz/5,4,2 32.814960480 P _part/18/_geo_xyz/5,5,0 -0.000672584 P _part/18/_geo_xyz/5,5,1 9.227361679 P _part/18/_geo_xyz/5,5,2 32.824802399 P _part/18/_geo_xyz/5,6,0 -0.000001503 P _part/18/_geo_xyz/5,6,1 9.227361679 P _part/18/_geo_xyz/5,6,2 32.833003998 P _part/18/_geo_xyz/5,7,0 -0.000001503 P _part/18/_geo_xyz/5,7,1 9.227361679 P _part/18/_geo_xyz/5,7,2 32.833003998 P _part/18/_geo_xyz/5,8,0 0.000669578 P _part/18/_geo_xyz/5,8,1 9.227361679 P _part/18/_geo_xyz/5,8,2 32.824802399 P _part/18/_geo_xyz/5,9,0 0.001474875 P _part/18/_geo_xyz/5,9,1 9.227361679 P _part/18/_geo_xyz/5,9,2 32.814960480 P _part/18/_geo_xyz/6,0,0 -0.000001503 P _part/18/_geo_xyz/6,0,1 9.227361679 P _part/18/_geo_xyz/6,0,2 32.800197601 P _part/18/_geo_xyz/6,1,0 -0.000592055 P _part/18/_geo_xyz/6,1,1 9.227361679 P _part/18/_geo_xyz/6,1,2 32.801181793 P _part/18/_geo_xyz/6,10,0 0.001474875 P _part/18/_geo_xyz/6,10,1 9.227361679 P _part/18/_geo_xyz/6,10,2 32.808399200 P _part/18/_geo_xyz/6,11,0 0.001179599 P _part/18/_geo_xyz/6,11,1 9.227361679 P _part/18/_geo_xyz/6,11,2 32.803478241 P _part/18/_geo_xyz/6,12,0 0.000589048 P _part/18/_geo_xyz/6,12,1 9.227361679 P _part/18/_geo_xyz/6,12,2 32.801181793 P _part/18/_geo_xyz/6,13,0 -0.000001503 P _part/18/_geo_xyz/6,13,1 9.227361679 P _part/18/_geo_xyz/6,13,2 32.800197601 P _part/18/_geo_xyz/6,14,0 0.0 P _part/18/_geo_xyz/6,14,1 0.0 P _part/18/_geo_xyz/6,14,2 0.0 P _part/18/_geo_xyz/6,15,0 0.0 P _part/18/_geo_xyz/6,15,1 0.0 P _part/18/_geo_xyz/6,15,2 0.0 P _part/18/_geo_xyz/6,16,0 0.0 P _part/18/_geo_xyz/6,16,1 0.0 P _part/18/_geo_xyz/6,16,2 0.0 P _part/18/_geo_xyz/6,17,0 0.0 P _part/18/_geo_xyz/6,17,1 0.0 P _part/18/_geo_xyz/6,17,2 0.0 P _part/18/_geo_xyz/6,2,0 -0.001182606 P _part/18/_geo_xyz/6,2,1 9.227361679 P _part/18/_geo_xyz/6,2,2 32.803478241 P _part/18/_geo_xyz/6,3,0 -0.001477881 P _part/18/_geo_xyz/6,3,1 9.227361679 P _part/18/_geo_xyz/6,3,2 32.808399200 P _part/18/_geo_xyz/6,4,0 -0.001477881 P _part/18/_geo_xyz/6,4,1 9.227361679 P _part/18/_geo_xyz/6,4,2 32.814960480 P _part/18/_geo_xyz/6,5,0 -0.000672584 P _part/18/_geo_xyz/6,5,1 9.227361679 P _part/18/_geo_xyz/6,5,2 32.824802399 P _part/18/_geo_xyz/6,6,0 -0.000001503 P _part/18/_geo_xyz/6,6,1 9.227361679 P _part/18/_geo_xyz/6,6,2 32.833003998 P _part/18/_geo_xyz/6,7,0 -0.000001503 P _part/18/_geo_xyz/6,7,1 9.227361679 P _part/18/_geo_xyz/6,7,2 32.833003998 P _part/18/_geo_xyz/6,8,0 0.000669578 P _part/18/_geo_xyz/6,8,1 9.227361679 P _part/18/_geo_xyz/6,8,2 32.824802399 P _part/18/_geo_xyz/6,9,0 0.001474875 P _part/18/_geo_xyz/6,9,1 9.227361679 P _part/18/_geo_xyz/6,9,2 32.814960480 P _part/18/_geo_xyz/7,0,0 -0.000002004 P _part/18/_geo_xyz/7,0,1 12.303149223 P _part/18/_geo_xyz/7,0,2 32.800197601 P _part/18/_geo_xyz/7,1,0 -0.000592556 P _part/18/_geo_xyz/7,1,1 12.303149223 P _part/18/_geo_xyz/7,1,2 32.801181793 P _part/18/_geo_xyz/7,10,0 0.001474374 P _part/18/_geo_xyz/7,10,1 12.303149223 P _part/18/_geo_xyz/7,10,2 32.808399200 P _part/18/_geo_xyz/7,11,0 0.001179098 P _part/18/_geo_xyz/7,11,1 12.303149223 P _part/18/_geo_xyz/7,11,2 32.803478241 P _part/18/_geo_xyz/7,12,0 0.000588547 P _part/18/_geo_xyz/7,12,1 12.303149223 P _part/18/_geo_xyz/7,12,2 32.801181793 P _part/18/_geo_xyz/7,13,0 -0.000002004 P _part/18/_geo_xyz/7,13,1 12.303149223 P _part/18/_geo_xyz/7,13,2 32.800197601 P _part/18/_geo_xyz/7,14,0 0.0 P _part/18/_geo_xyz/7,14,1 0.0 P _part/18/_geo_xyz/7,14,2 0.0 P _part/18/_geo_xyz/7,15,0 0.0 P _part/18/_geo_xyz/7,15,1 0.0 P _part/18/_geo_xyz/7,15,2 0.0 P _part/18/_geo_xyz/7,16,0 0.0 P _part/18/_geo_xyz/7,16,1 0.0 P _part/18/_geo_xyz/7,16,2 0.0 P _part/18/_geo_xyz/7,17,0 0.0 P _part/18/_geo_xyz/7,17,1 0.0 P _part/18/_geo_xyz/7,17,2 0.0 P _part/18/_geo_xyz/7,2,0 -0.001183107 P _part/18/_geo_xyz/7,2,1 12.303149223 P _part/18/_geo_xyz/7,2,2 32.803478241 P _part/18/_geo_xyz/7,3,0 -0.001478382 P _part/18/_geo_xyz/7,3,1 12.303149223 P _part/18/_geo_xyz/7,3,2 32.808399200 P _part/18/_geo_xyz/7,4,0 -0.001478382 P _part/18/_geo_xyz/7,4,1 12.303149223 P _part/18/_geo_xyz/7,4,2 32.814960480 P _part/18/_geo_xyz/7,5,0 -0.000673085 P _part/18/_geo_xyz/7,5,1 12.303149223 P _part/18/_geo_xyz/7,5,2 32.824802399 P _part/18/_geo_xyz/7,6,0 -0.000002004 P _part/18/_geo_xyz/7,6,1 12.303149223 P _part/18/_geo_xyz/7,6,2 32.833003998 P _part/18/_geo_xyz/7,7,0 -0.000002004 P _part/18/_geo_xyz/7,7,1 12.303149223 P _part/18/_geo_xyz/7,7,2 32.833003998 P _part/18/_geo_xyz/7,8,0 0.000669076 P _part/18/_geo_xyz/7,8,1 12.303149223 P _part/18/_geo_xyz/7,8,2 32.824802399 P _part/18/_geo_xyz/7,9,0 0.001474374 P _part/18/_geo_xyz/7,9,1 12.303149223 P _part/18/_geo_xyz/7,9,2 32.814960480 P _part/18/_geo_xyz/8,0,0 -0.000002004 P _part/18/_geo_xyz/8,0,1 12.303149223 P _part/18/_geo_xyz/8,0,2 32.800197601 P _part/18/_geo_xyz/8,1,0 -0.000592556 P _part/18/_geo_xyz/8,1,1 12.303149223 P _part/18/_geo_xyz/8,1,2 32.801181793 P _part/18/_geo_xyz/8,10,0 0.001474374 P _part/18/_geo_xyz/8,10,1 12.303149223 P _part/18/_geo_xyz/8,10,2 32.808399200 P _part/18/_geo_xyz/8,11,0 0.001179098 P _part/18/_geo_xyz/8,11,1 12.303149223 P _part/18/_geo_xyz/8,11,2 32.803478241 P _part/18/_geo_xyz/8,12,0 0.000588547 P _part/18/_geo_xyz/8,12,1 12.303149223 P _part/18/_geo_xyz/8,12,2 32.801181793 P _part/18/_geo_xyz/8,13,0 -0.000002004 P _part/18/_geo_xyz/8,13,1 12.303149223 P _part/18/_geo_xyz/8,13,2 32.800197601 P _part/18/_geo_xyz/8,14,0 0.0 P _part/18/_geo_xyz/8,14,1 0.0 P _part/18/_geo_xyz/8,14,2 0.0 P _part/18/_geo_xyz/8,15,0 0.0 P _part/18/_geo_xyz/8,15,1 0.0 P _part/18/_geo_xyz/8,15,2 0.0 P _part/18/_geo_xyz/8,16,0 0.0 P _part/18/_geo_xyz/8,16,1 0.0 P _part/18/_geo_xyz/8,16,2 0.0 P _part/18/_geo_xyz/8,17,0 0.0 P _part/18/_geo_xyz/8,17,1 0.0 P _part/18/_geo_xyz/8,17,2 0.0 P _part/18/_geo_xyz/8,2,0 -0.001183107 P _part/18/_geo_xyz/8,2,1 12.303149223 P _part/18/_geo_xyz/8,2,2 32.803478241 P _part/18/_geo_xyz/8,3,0 -0.001478382 P _part/18/_geo_xyz/8,3,1 12.303149223 P _part/18/_geo_xyz/8,3,2 32.808399200 P _part/18/_geo_xyz/8,4,0 -0.001478382 P _part/18/_geo_xyz/8,4,1 12.303149223 P _part/18/_geo_xyz/8,4,2 32.814960480 P _part/18/_geo_xyz/8,5,0 -0.000673085 P _part/18/_geo_xyz/8,5,1 12.303149223 P _part/18/_geo_xyz/8,5,2 32.824802399 P _part/18/_geo_xyz/8,6,0 -0.000002004 P _part/18/_geo_xyz/8,6,1 12.303149223 P _part/18/_geo_xyz/8,6,2 32.833003998 P _part/18/_geo_xyz/8,7,0 -0.000002004 P _part/18/_geo_xyz/8,7,1 12.303149223 P _part/18/_geo_xyz/8,7,2 32.833003998 P _part/18/_geo_xyz/8,8,0 0.000669076 P _part/18/_geo_xyz/8,8,1 12.303149223 P _part/18/_geo_xyz/8,8,2 32.824802399 P _part/18/_geo_xyz/8,9,0 0.001474374 P _part/18/_geo_xyz/8,9,1 12.303149223 P _part/18/_geo_xyz/8,9,2 32.814960480 P _part/18/_geo_xyz/9,0,0 0.0 P _part/18/_geo_xyz/9,0,1 0.0 P _part/18/_geo_xyz/9,0,2 0.0 P _part/18/_geo_xyz/9,1,0 0.0 P _part/18/_geo_xyz/9,1,1 0.0 P _part/18/_geo_xyz/9,1,2 0.0 P _part/18/_geo_xyz/9,10,0 0.0 P _part/18/_geo_xyz/9,10,1 0.0 P _part/18/_geo_xyz/9,10,2 0.0 P _part/18/_geo_xyz/9,11,0 0.0 P _part/18/_geo_xyz/9,11,1 0.0 P _part/18/_geo_xyz/9,11,2 0.0 P _part/18/_geo_xyz/9,12,0 0.0 P _part/18/_geo_xyz/9,12,1 0.0 P _part/18/_geo_xyz/9,12,2 0.0 P _part/18/_geo_xyz/9,13,0 0.0 P _part/18/_geo_xyz/9,13,1 0.0 P _part/18/_geo_xyz/9,13,2 0.0 P _part/18/_geo_xyz/9,14,0 0.0 P _part/18/_geo_xyz/9,14,1 0.0 P _part/18/_geo_xyz/9,14,2 0.0 P _part/18/_geo_xyz/9,15,0 0.0 P _part/18/_geo_xyz/9,15,1 0.0 P _part/18/_geo_xyz/9,15,2 0.0 P _part/18/_geo_xyz/9,16,0 0.0 P _part/18/_geo_xyz/9,16,1 0.0 P _part/18/_geo_xyz/9,16,2 0.0 P _part/18/_geo_xyz/9,17,0 0.0 P _part/18/_geo_xyz/9,17,1 0.0 P _part/18/_geo_xyz/9,17,2 0.0 P _part/18/_geo_xyz/9,2,0 0.0 P _part/18/_geo_xyz/9,2,1 0.0 P _part/18/_geo_xyz/9,2,2 0.0 P _part/18/_geo_xyz/9,3,0 0.0 P _part/18/_geo_xyz/9,3,1 0.0 P _part/18/_geo_xyz/9,3,2 0.0 P _part/18/_geo_xyz/9,4,0 0.0 P _part/18/_geo_xyz/9,4,1 0.0 P _part/18/_geo_xyz/9,4,2 0.0 P _part/18/_geo_xyz/9,5,0 0.0 P _part/18/_geo_xyz/9,5,1 0.0 P _part/18/_geo_xyz/9,5,2 0.0 P _part/18/_geo_xyz/9,6,0 0.0 P _part/18/_geo_xyz/9,6,1 0.0 P _part/18/_geo_xyz/9,6,2 0.0 P _part/18/_geo_xyz/9,7,0 0.0 P _part/18/_geo_xyz/9,7,1 0.0 P _part/18/_geo_xyz/9,7,2 0.0 P _part/18/_geo_xyz/9,8,0 0.0 P _part/18/_geo_xyz/9,8,1 0.0 P _part/18/_geo_xyz/9,8,2 0.0 P _part/18/_geo_xyz/9,9,0 0.0 P _part/18/_geo_xyz/9,9,1 0.0 P _part/18/_geo_xyz/9,9,2 0.0 P _part/18/_geo_xyz/i_count 20 P _part/18/_geo_xyz/j_count 18 P _part/18/_geo_xyz/k_count 3 P _part/18/_locked/0,0 0 P _part/18/_locked/0,1 0 P _part/18/_locked/0,10 0 P _part/18/_locked/0,11 0 P _part/18/_locked/0,12 0 P _part/18/_locked/0,13 0 P _part/18/_locked/0,14 0 P _part/18/_locked/0,15 0 P _part/18/_locked/0,16 0 P _part/18/_locked/0,17 0 P _part/18/_locked/0,2 0 P _part/18/_locked/0,3 0 P _part/18/_locked/0,4 0 P _part/18/_locked/0,5 0 P _part/18/_locked/0,6 0 P _part/18/_locked/0,7 0 P _part/18/_locked/0,8 0 P _part/18/_locked/0,9 0 P _part/18/_locked/1,0 0 P _part/18/_locked/1,1 0 P _part/18/_locked/1,10 0 P _part/18/_locked/1,11 0 P _part/18/_locked/1,12 0 P _part/18/_locked/1,13 0 P _part/18/_locked/1,14 0 P _part/18/_locked/1,15 0 P _part/18/_locked/1,16 0 P _part/18/_locked/1,17 0 P _part/18/_locked/1,2 0 P _part/18/_locked/1,3 0 P _part/18/_locked/1,4 0 P _part/18/_locked/1,5 0 P _part/18/_locked/1,6 0 P _part/18/_locked/1,7 0 P _part/18/_locked/1,8 0 P _part/18/_locked/1,9 0 P _part/18/_locked/10,0 0 P _part/18/_locked/10,1 0 P _part/18/_locked/10,10 0 P _part/18/_locked/10,11 0 P _part/18/_locked/10,12 0 P _part/18/_locked/10,13 0 P _part/18/_locked/10,14 0 P _part/18/_locked/10,15 0 P _part/18/_locked/10,16 0 P _part/18/_locked/10,17 0 P _part/18/_locked/10,2 0 P _part/18/_locked/10,3 0 P _part/18/_locked/10,4 0 P _part/18/_locked/10,5 0 P _part/18/_locked/10,6 0 P _part/18/_locked/10,7 0 P _part/18/_locked/10,8 0 P _part/18/_locked/10,9 0 P _part/18/_locked/11,0 0 P _part/18/_locked/11,1 0 P _part/18/_locked/11,10 0 P _part/18/_locked/11,11 0 P _part/18/_locked/11,12 0 P _part/18/_locked/11,13 0 P _part/18/_locked/11,14 0 P _part/18/_locked/11,15 0 P _part/18/_locked/11,16 0 P _part/18/_locked/11,17 0 P _part/18/_locked/11,2 0 P _part/18/_locked/11,3 0 P _part/18/_locked/11,4 0 P _part/18/_locked/11,5 0 P _part/18/_locked/11,6 0 P _part/18/_locked/11,7 0 P _part/18/_locked/11,8 0 P _part/18/_locked/11,9 0 P _part/18/_locked/12,0 0 P _part/18/_locked/12,1 0 P _part/18/_locked/12,10 0 P _part/18/_locked/12,11 0 P _part/18/_locked/12,12 0 P _part/18/_locked/12,13 0 P _part/18/_locked/12,14 0 P _part/18/_locked/12,15 0 P _part/18/_locked/12,16 0 P _part/18/_locked/12,17 0 P _part/18/_locked/12,2 0 P _part/18/_locked/12,3 0 P _part/18/_locked/12,4 0 P _part/18/_locked/12,5 0 P _part/18/_locked/12,6 0 P _part/18/_locked/12,7 0 P _part/18/_locked/12,8 0 P _part/18/_locked/12,9 0 P _part/18/_locked/13,0 0 P _part/18/_locked/13,1 0 P _part/18/_locked/13,10 0 P _part/18/_locked/13,11 0 P _part/18/_locked/13,12 0 P _part/18/_locked/13,13 0 P _part/18/_locked/13,14 0 P _part/18/_locked/13,15 0 P _part/18/_locked/13,16 0 P _part/18/_locked/13,17 0 P _part/18/_locked/13,2 0 P _part/18/_locked/13,3 0 P _part/18/_locked/13,4 0 P _part/18/_locked/13,5 0 P _part/18/_locked/13,6 0 P _part/18/_locked/13,7 0 P _part/18/_locked/13,8 0 P _part/18/_locked/13,9 0 P _part/18/_locked/14,0 0 P _part/18/_locked/14,1 0 P _part/18/_locked/14,10 0 P _part/18/_locked/14,11 0 P _part/18/_locked/14,12 0 P _part/18/_locked/14,13 0 P _part/18/_locked/14,14 0 P _part/18/_locked/14,15 0 P _part/18/_locked/14,16 0 P _part/18/_locked/14,17 0 P _part/18/_locked/14,2 0 P _part/18/_locked/14,3 0 P _part/18/_locked/14,4 0 P _part/18/_locked/14,5 0 P _part/18/_locked/14,6 0 P _part/18/_locked/14,7 0 P _part/18/_locked/14,8 0 P _part/18/_locked/14,9 0 P _part/18/_locked/15,0 0 P _part/18/_locked/15,1 0 P _part/18/_locked/15,10 0 P _part/18/_locked/15,11 0 P _part/18/_locked/15,12 0 P _part/18/_locked/15,13 0 P _part/18/_locked/15,14 0 P _part/18/_locked/15,15 0 P _part/18/_locked/15,16 0 P _part/18/_locked/15,17 0 P _part/18/_locked/15,2 0 P _part/18/_locked/15,3 0 P _part/18/_locked/15,4 0 P _part/18/_locked/15,5 0 P _part/18/_locked/15,6 0 P _part/18/_locked/15,7 0 P _part/18/_locked/15,8 0 P _part/18/_locked/15,9 0 P _part/18/_locked/16,0 0 P _part/18/_locked/16,1 0 P _part/18/_locked/16,10 0 P _part/18/_locked/16,11 0 P _part/18/_locked/16,12 0 P _part/18/_locked/16,13 0 P _part/18/_locked/16,14 0 P _part/18/_locked/16,15 0 P _part/18/_locked/16,16 0 P _part/18/_locked/16,17 0 P _part/18/_locked/16,2 0 P _part/18/_locked/16,3 0 P _part/18/_locked/16,4 0 P _part/18/_locked/16,5 0 P _part/18/_locked/16,6 0 P _part/18/_locked/16,7 0 P _part/18/_locked/16,8 0 P _part/18/_locked/16,9 0 P _part/18/_locked/17,0 0 P _part/18/_locked/17,1 0 P _part/18/_locked/17,10 0 P _part/18/_locked/17,11 0 P _part/18/_locked/17,12 0 P _part/18/_locked/17,13 0 P _part/18/_locked/17,14 0 P _part/18/_locked/17,15 0 P _part/18/_locked/17,16 0 P _part/18/_locked/17,17 0 P _part/18/_locked/17,2 0 P _part/18/_locked/17,3 0 P _part/18/_locked/17,4 0 P _part/18/_locked/17,5 0 P _part/18/_locked/17,6 0 P _part/18/_locked/17,7 0 P _part/18/_locked/17,8 0 P _part/18/_locked/17,9 0 P _part/18/_locked/18,0 0 P _part/18/_locked/18,1 0 P _part/18/_locked/18,10 0 P _part/18/_locked/18,11 0 P _part/18/_locked/18,12 0 P _part/18/_locked/18,13 0 P _part/18/_locked/18,14 0 P _part/18/_locked/18,15 0 P _part/18/_locked/18,16 0 P _part/18/_locked/18,17 0 P _part/18/_locked/18,2 0 P _part/18/_locked/18,3 0 P _part/18/_locked/18,4 0 P _part/18/_locked/18,5 0 P _part/18/_locked/18,6 0 P _part/18/_locked/18,7 0 P _part/18/_locked/18,8 0 P _part/18/_locked/18,9 0 P _part/18/_locked/19,0 0 P _part/18/_locked/19,1 0 P _part/18/_locked/19,10 0 P _part/18/_locked/19,11 0 P _part/18/_locked/19,12 0 P _part/18/_locked/19,13 0 P _part/18/_locked/19,14 0 P _part/18/_locked/19,15 0 P _part/18/_locked/19,16 0 P _part/18/_locked/19,17 0 P _part/18/_locked/19,2 0 P _part/18/_locked/19,3 0 P _part/18/_locked/19,4 0 P _part/18/_locked/19,5 0 P _part/18/_locked/19,6 0 P _part/18/_locked/19,7 0 P _part/18/_locked/19,8 0 P _part/18/_locked/19,9 0 P _part/18/_locked/2,0 0 P _part/18/_locked/2,1 0 P _part/18/_locked/2,10 0 P _part/18/_locked/2,11 0 P _part/18/_locked/2,12 0 P _part/18/_locked/2,13 0 P _part/18/_locked/2,14 0 P _part/18/_locked/2,15 0 P _part/18/_locked/2,16 0 P _part/18/_locked/2,17 0 P _part/18/_locked/2,2 0 P _part/18/_locked/2,3 0 P _part/18/_locked/2,4 0 P _part/18/_locked/2,5 0 P _part/18/_locked/2,6 0 P _part/18/_locked/2,7 0 P _part/18/_locked/2,8 0 P _part/18/_locked/2,9 0 P _part/18/_locked/3,0 0 P _part/18/_locked/3,1 0 P _part/18/_locked/3,10 0 P _part/18/_locked/3,11 0 P _part/18/_locked/3,12 0 P _part/18/_locked/3,13 0 P _part/18/_locked/3,14 0 P _part/18/_locked/3,15 0 P _part/18/_locked/3,16 0 P _part/18/_locked/3,17 0 P _part/18/_locked/3,2 0 P _part/18/_locked/3,3 0 P _part/18/_locked/3,4 0 P _part/18/_locked/3,5 0 P _part/18/_locked/3,6 0 P _part/18/_locked/3,7 0 P _part/18/_locked/3,8 0 P _part/18/_locked/3,9 0 P _part/18/_locked/4,0 0 P _part/18/_locked/4,1 0 P _part/18/_locked/4,10 0 P _part/18/_locked/4,11 0 P _part/18/_locked/4,12 0 P _part/18/_locked/4,13 0 P _part/18/_locked/4,14 0 P _part/18/_locked/4,15 0 P _part/18/_locked/4,16 0 P _part/18/_locked/4,17 0 P _part/18/_locked/4,2 0 P _part/18/_locked/4,3 0 P _part/18/_locked/4,4 0 P _part/18/_locked/4,5 0 P _part/18/_locked/4,6 0 P _part/18/_locked/4,7 0 P _part/18/_locked/4,8 0 P _part/18/_locked/4,9 0 P _part/18/_locked/5,0 0 P _part/18/_locked/5,1 0 P _part/18/_locked/5,10 0 P _part/18/_locked/5,11 0 P _part/18/_locked/5,12 0 P _part/18/_locked/5,13 0 P _part/18/_locked/5,14 0 P _part/18/_locked/5,15 0 P _part/18/_locked/5,16 0 P _part/18/_locked/5,17 0 P _part/18/_locked/5,2 0 P _part/18/_locked/5,3 0 P _part/18/_locked/5,4 0 P _part/18/_locked/5,5 0 P _part/18/_locked/5,6 0 P _part/18/_locked/5,7 0 P _part/18/_locked/5,8 0 P _part/18/_locked/5,9 0 P _part/18/_locked/6,0 0 P _part/18/_locked/6,1 0 P _part/18/_locked/6,10 0 P _part/18/_locked/6,11 0 P _part/18/_locked/6,12 0 P _part/18/_locked/6,13 0 P _part/18/_locked/6,14 0 P _part/18/_locked/6,15 0 P _part/18/_locked/6,16 0 P _part/18/_locked/6,17 0 P _part/18/_locked/6,2 0 P _part/18/_locked/6,3 0 P _part/18/_locked/6,4 0 P _part/18/_locked/6,5 0 P _part/18/_locked/6,6 0 P _part/18/_locked/6,7 0 P _part/18/_locked/6,8 0 P _part/18/_locked/6,9 0 P _part/18/_locked/7,0 0 P _part/18/_locked/7,1 0 P _part/18/_locked/7,10 0 P _part/18/_locked/7,11 0 P _part/18/_locked/7,12 0 P _part/18/_locked/7,13 0 P _part/18/_locked/7,14 0 P _part/18/_locked/7,15 0 P _part/18/_locked/7,16 0 P _part/18/_locked/7,17 0 P _part/18/_locked/7,2 0 P _part/18/_locked/7,3 0 P _part/18/_locked/7,4 0 P _part/18/_locked/7,5 0 P _part/18/_locked/7,6 0 P _part/18/_locked/7,7 0 P _part/18/_locked/7,8 0 P _part/18/_locked/7,9 0 P _part/18/_locked/8,0 0 P _part/18/_locked/8,1 0 P _part/18/_locked/8,10 0 P _part/18/_locked/8,11 0 P _part/18/_locked/8,12 0 P _part/18/_locked/8,13 0 P _part/18/_locked/8,14 0 P _part/18/_locked/8,15 0 P _part/18/_locked/8,16 0 P _part/18/_locked/8,17 0 P _part/18/_locked/8,2 0 P _part/18/_locked/8,3 0 P _part/18/_locked/8,4 0 P _part/18/_locked/8,5 0 P _part/18/_locked/8,6 0 P _part/18/_locked/8,7 0 P _part/18/_locked/8,8 0 P _part/18/_locked/8,9 0 P _part/18/_locked/9,0 0 P _part/18/_locked/9,1 0 P _part/18/_locked/9,10 0 P _part/18/_locked/9,11 0 P _part/18/_locked/9,12 0 P _part/18/_locked/9,13 0 P _part/18/_locked/9,14 0 P _part/18/_locked/9,15 0 P _part/18/_locked/9,16 0 P _part/18/_locked/9,17 0 P _part/18/_locked/9,2 0 P _part/18/_locked/9,3 0 P _part/18/_locked/9,4 0 P _part/18/_locked/9,5 0 P _part/18/_locked/9,6 0 P _part/18/_locked/9,7 0 P _part/18/_locked/9,8 0 P _part/18/_locked/9,9 0 P _part/18/_locked/i_count 20 P _part/18/_locked/j_count 18 P _part/18/_part_cd 0.075000003 P _part/18/_part_phi 0.0 P _part/18/_part_psi 0.0 P _part/18/_part_rad 2.0 P _part/18/_part_specs_eq 1 P _part/18/_part_specs_invis 0 P _part/18/_part_specs_unused1 0 P _part/18/_part_specs_unused2 0 P _part/18/_part_tex 0 P _part/18/_part_the 0.0 P _part/18/_part_x 0.0 P _part/18/_part_y 0.0 P _part/18/_part_z 32.808399200 P _part/18/_patt_con 0 P _part/18/_patt_prt 0 P _part/18/_patt_rat 0.0 P _part/18/_r_dim 14 P _part/18/_s_dim 8 P _part/18/_scon 37.676635742 P _part/18/_top_s1 0.017999999 P _part/18/_top_s2 0.261999995 P _part/18/_top_t1 0.0 P _part/18/_top_t2 0.252999991 P _part/56/_aero_x_os 0.0 P _part/56/_aero_y_os 0.0 P _part/56/_aero_z_os 0.0 P _part/56/_area_frnt 0.0 P _part/56/_area_side 0.0 P _part/56/_area_vert 0.0 P _part/56/_bot_s1 0.0 P _part/56/_bot_s2 0.754000008 P _part/56/_bot_t1 0.509999990 P _part/56/_bot_t2 0.754000008 P _part/56/_damp 1.883831739 P _part/56/_geo_xyz/0,0,0 0.0 P _part/56/_geo_xyz/0,0,1 0.032808393 P _part/56/_geo_xyz/0,0,2 0.0 P _part/56/_geo_xyz/0,1,0 0.012555230 P _part/56/_geo_xyz/0,1,1 0.030311007 P _part/56/_geo_xyz/0,1,2 0.0 P _part/56/_geo_xyz/0,10,0 -0.012555226 P _part/56/_geo_xyz/0,10,1 -0.030311018 P _part/56/_geo_xyz/0,10,2 0.0 P _part/56/_geo_xyz/0,11,0 -0.023199040 P _part/56/_geo_xyz/0,11,1 -0.023199048 P _part/56/_geo_xyz/0,11,2 0.0 P _part/56/_geo_xyz/0,12,0 -0.030311007 P _part/56/_geo_xyz/0,12,1 -0.012555236 P _part/56/_geo_xyz/0,12,2 0.0 P _part/56/_geo_xyz/0,13,0 -0.032808397 P _part/56/_geo_xyz/0,13,1 -0.000000006 P _part/56/_geo_xyz/0,13,2 0.0 P _part/56/_geo_xyz/0,14,0 -0.030311007 P _part/56/_geo_xyz/0,14,1 0.012555228 P _part/56/_geo_xyz/0,14,2 0.0 P _part/56/_geo_xyz/0,15,0 -0.023199039 P _part/56/_geo_xyz/0,15,1 0.023199040 P _part/56/_geo_xyz/0,15,2 0.0 P _part/56/_geo_xyz/0,16,0 -0.012555230 P _part/56/_geo_xyz/0,16,1 0.030311007 P _part/56/_geo_xyz/0,16,2 0.0 P _part/56/_geo_xyz/0,17,0 0.000000001 P _part/56/_geo_xyz/0,17,1 0.032808393 P _part/56/_geo_xyz/0,17,2 0.0 P _part/56/_geo_xyz/0,2,0 0.023199040 P _part/56/_geo_xyz/0,2,1 0.023199039 P _part/56/_geo_xyz/0,2,2 0.0 P _part/56/_geo_xyz/0,3,0 0.030311007 P _part/56/_geo_xyz/0,3,1 0.012555227 P _part/56/_geo_xyz/0,3,2 0.0 P _part/56/_geo_xyz/0,4,0 0.032808397 P _part/56/_geo_xyz/0,4,1 -0.000000006 P _part/56/_geo_xyz/0,4,2 0.0 P _part/56/_geo_xyz/0,5,0 0.030311005 P _part/56/_geo_xyz/0,5,1 -0.012555238 P _part/56/_geo_xyz/0,5,2 0.0 P _part/56/_geo_xyz/0,6,0 0.023199040 P _part/56/_geo_xyz/0,6,1 -0.023199048 P _part/56/_geo_xyz/0,6,2 0.0 P _part/56/_geo_xyz/0,7,0 0.012555225 P _part/56/_geo_xyz/0,7,1 -0.030311018 P _part/56/_geo_xyz/0,7,2 0.0 P _part/56/_geo_xyz/0,8,0 -0.000000001 P _part/56/_geo_xyz/0,8,1 -0.032808408 P _part/56/_geo_xyz/0,8,2 0.0 P _part/56/_geo_xyz/0,9,0 -0.000000001 P _part/56/_geo_xyz/0,9,1 -0.032808408 P _part/56/_geo_xyz/0,9,2 0.0 P _part/56/_geo_xyz/1,0,0 0.0 P _part/56/_geo_xyz/1,0,1 0.032808397 P _part/56/_geo_xyz/1,0,2 3.280839920 P _part/56/_geo_xyz/1,1,0 0.012555230 P _part/56/_geo_xyz/1,1,1 0.030311001 P _part/56/_geo_xyz/1,1,2 3.293788195 P _part/56/_geo_xyz/1,10,0 -0.012555226 P _part/56/_geo_xyz/1,10,1 -0.030311013 P _part/56/_geo_xyz/1,10,2 3.296237946 P _part/56/_geo_xyz/1,11,0 -0.023199040 P _part/56/_geo_xyz/1,11,1 -0.023199044 P _part/56/_geo_xyz/1,11,2 3.295887947 P _part/56/_geo_xyz/1,12,0 -0.030311007 P _part/56/_geo_xyz/1,12,1 -0.012555235 P _part/56/_geo_xyz/1,12,2 3.295538187 P _part/56/_geo_xyz/1,13,0 -0.032808397 P _part/56/_geo_xyz/1,13,1 -0.000000006 P _part/56/_geo_xyz/1,13,2 3.295188189 P _part/56/_geo_xyz/1,14,0 -0.030311007 P _part/56/_geo_xyz/1,14,1 0.012555227 P _part/56/_geo_xyz/1,14,2 3.294838190 P _part/56/_geo_xyz/1,15,0 -0.023199039 P _part/56/_geo_xyz/1,15,1 0.023199037 P _part/56/_geo_xyz/1,15,2 3.294488192 P _part/56/_geo_xyz/1,16,0 -0.012555230 P _part/56/_geo_xyz/1,16,1 0.030311001 P _part/56/_geo_xyz/1,16,2 3.294138193 P _part/56/_geo_xyz/1,17,0 0.000000001 P _part/56/_geo_xyz/1,17,1 0.032808397 P _part/56/_geo_xyz/1,17,2 3.281189919 P _part/56/_geo_xyz/1,2,0 0.023199040 P _part/56/_geo_xyz/1,2,1 0.023199035 P _part/56/_geo_xyz/1,2,2 3.294138193 P _part/56/_geo_xyz/1,3,0 0.030311007 P _part/56/_geo_xyz/1,3,1 0.012555225 P _part/56/_geo_xyz/1,3,2 3.294488192 P _part/56/_geo_xyz/1,4,0 0.032808397 P _part/56/_geo_xyz/1,4,1 -0.000000006 P _part/56/_geo_xyz/1,4,2 3.294838190 P _part/56/_geo_xyz/1,5,0 0.030311005 P _part/56/_geo_xyz/1,5,1 -0.012555238 P _part/56/_geo_xyz/1,5,2 3.295188189 P _part/56/_geo_xyz/1,6,0 0.023199040 P _part/56/_geo_xyz/1,6,1 -0.023199044 P _part/56/_geo_xyz/1,6,2 3.295538187 P _part/56/_geo_xyz/1,7,0 0.012555225 P _part/56/_geo_xyz/1,7,1 -0.030311016 P _part/56/_geo_xyz/1,7,2 3.295887947 P _part/56/_geo_xyz/1,8,0 -0.000000001 P _part/56/_geo_xyz/1,8,1 -0.032808401 P _part/56/_geo_xyz/1,8,2 3.296237946 P _part/56/_geo_xyz/1,9,0 -0.000000001 P _part/56/_geo_xyz/1,9,1 -0.032808401 P _part/56/_geo_xyz/1,9,2 3.296587944 P _part/56/_geo_xyz/10,0,0 0.0 P _part/56/_geo_xyz/10,0,1 0.032808397 P _part/56/_geo_xyz/10,0,2 32.808399200 P _part/56/_geo_xyz/10,1,0 0.012555230 P _part/56/_geo_xyz/10,1,1 0.030311001 P _part/56/_geo_xyz/10,1,2 32.818111420 P _part/56/_geo_xyz/10,10,0 -0.012555226 P _part/56/_geo_xyz/10,10,1 -0.030311013 P _part/56/_geo_xyz/10,10,2 32.819946289 P _part/56/_geo_xyz/10,11,0 -0.023199040 P _part/56/_geo_xyz/10,11,1 -0.023199044 P _part/56/_geo_xyz/10,11,2 32.819683075 P _part/56/_geo_xyz/10,12,0 -0.030311007 P _part/56/_geo_xyz/10,12,1 -0.012555235 P _part/56/_geo_xyz/10,12,2 32.819423676 P _part/56/_geo_xyz/10,13,0 -0.032808397 P _part/56/_geo_xyz/10,13,1 -0.000000006 P _part/56/_geo_xyz/10,13,2 32.819160461 P _part/56/_geo_xyz/10,14,0 -0.030311007 P _part/56/_geo_xyz/10,14,1 0.012555227 P _part/56/_geo_xyz/10,14,2 32.818897247 P _part/56/_geo_xyz/10,15,0 -0.023199039 P _part/56/_geo_xyz/10,15,1 0.023199037 P _part/56/_geo_xyz/10,15,2 32.818634033 P _part/56/_geo_xyz/10,16,0 -0.012555230 P _part/56/_geo_xyz/10,16,1 0.030311001 P _part/56/_geo_xyz/10,16,2 32.818370819 P _part/56/_geo_xyz/10,17,0 0.000000001 P _part/56/_geo_xyz/10,17,1 0.032808397 P _part/56/_geo_xyz/10,17,2 32.808662415 P _part/56/_geo_xyz/10,2,0 0.023199040 P _part/56/_geo_xyz/10,2,1 0.023199035 P _part/56/_geo_xyz/10,2,2 32.818370819 P _part/56/_geo_xyz/10,3,0 0.030311007 P _part/56/_geo_xyz/10,3,1 0.012555225 P _part/56/_geo_xyz/10,3,2 32.818634033 P _part/56/_geo_xyz/10,4,0 0.032808397 P _part/56/_geo_xyz/10,4,1 -0.000000006 P _part/56/_geo_xyz/10,4,2 32.818897247 P _part/56/_geo_xyz/10,5,0 0.030311005 P _part/56/_geo_xyz/10,5,1 -0.012555238 P _part/56/_geo_xyz/10,5,2 32.819160461 P _part/56/_geo_xyz/10,6,0 0.023199040 P _part/56/_geo_xyz/10,6,1 -0.023199044 P _part/56/_geo_xyz/10,6,2 32.819423676 P _part/56/_geo_xyz/10,7,0 0.012555225 P _part/56/_geo_xyz/10,7,1 -0.030311016 P _part/56/_geo_xyz/10,7,2 32.819683075 P _part/56/_geo_xyz/10,8,0 -0.000000001 P _part/56/_geo_xyz/10,8,1 -0.032808401 P _part/56/_geo_xyz/10,8,2 32.819946289 P _part/56/_geo_xyz/10,9,0 -0.000000001 P _part/56/_geo_xyz/10,9,1 -0.032808401 P _part/56/_geo_xyz/10,9,2 32.820209503 P _part/56/_geo_xyz/11,0,0 0.0 P _part/56/_geo_xyz/11,0,1 2.001312256 P _part/56/_geo_xyz/11,0,2 6.988189220 P _part/56/_geo_xyz/11,1,0 0.765366912 P _part/56/_geo_xyz/11,1,1 1.847759008 P _part/56/_geo_xyz/11,1,2 7.0 P _part/56/_geo_xyz/11,10,0 -0.0 P _part/56/_geo_xyz/11,10,1 -1.847759247 P _part/56/_geo_xyz/11,10,2 7.0 P _part/56/_geo_xyz/11,11,0 -1.414213538 P _part/56/_geo_xyz/11,11,1 -1.414213538 P _part/56/_geo_xyz/11,11,2 7.0 P _part/56/_geo_xyz/11,12,0 -1.847759008 P _part/56/_geo_xyz/11,12,1 -0.765367031 P _part/56/_geo_xyz/11,12,2 7.0 P _part/56/_geo_xyz/11,13,0 -2.0 P _part/56/_geo_xyz/11,13,1 -0.000000087 P _part/56/_geo_xyz/11,13,2 7.0 P _part/56/_geo_xyz/11,14,0 -1.847759008 P _part/56/_geo_xyz/11,14,1 0.765366852 P _part/56/_geo_xyz/11,14,2 7.0 P _part/56/_geo_xyz/11,15,0 -1.414213538 P _part/56/_geo_xyz/11,15,1 1.414213538 P _part/56/_geo_xyz/11,15,2 7.0 P _part/56/_geo_xyz/11,16,0 -0.765366912 P _part/56/_geo_xyz/11,16,1 1.847759008 P _part/56/_geo_xyz/11,16,2 7.0 P _part/56/_geo_xyz/11,17,0 -0.0 P _part/56/_geo_xyz/11,17,1 2.001312256 P _part/56/_geo_xyz/11,17,2 6.988189220 P _part/56/_geo_xyz/11,2,0 1.414213538 P _part/56/_geo_xyz/11,2,1 1.414213538 P _part/56/_geo_xyz/11,2,2 7.0 P _part/56/_geo_xyz/11,3,0 1.847759008 P _part/56/_geo_xyz/11,3,1 0.765366852 P _part/56/_geo_xyz/11,3,2 7.0 P _part/56/_geo_xyz/11,4,0 2.0 P _part/56/_geo_xyz/11,4,1 -0.000000087 P _part/56/_geo_xyz/11,4,2 7.0 P _part/56/_geo_xyz/11,5,0 1.847759008 P _part/56/_geo_xyz/11,5,1 -0.765367031 P _part/56/_geo_xyz/11,5,2 7.0 P _part/56/_geo_xyz/11,6,0 1.414213538 P _part/56/_geo_xyz/11,6,1 -1.414213538 P _part/56/_geo_xyz/11,6,2 7.0 P _part/56/_geo_xyz/11,7,0 0.0 P _part/56/_geo_xyz/11,7,1 -1.847759247 P _part/56/_geo_xyz/11,7,2 7.0 P _part/56/_geo_xyz/11,8,0 0.0 P _part/56/_geo_xyz/11,8,1 -1.847759247 P _part/56/_geo_xyz/11,8,2 7.0 P _part/56/_geo_xyz/11,9,0 -0.0 P _part/56/_geo_xyz/11,9,1 -1.847759247 P _part/56/_geo_xyz/11,9,2 7.0 P _part/56/_geo_xyz/12,0,0 0.0 P _part/56/_geo_xyz/12,0,1 2.001312256 P _part/56/_geo_xyz/12,0,2 8.005249023 P _part/56/_geo_xyz/12,1,0 0.765366912 P _part/56/_geo_xyz/12,1,1 1.847759008 P _part/56/_geo_xyz/12,1,2 8.0 P _part/56/_geo_xyz/12,10,0 -0.0 P _part/56/_geo_xyz/12,10,1 -1.847759247 P _part/56/_geo_xyz/12,10,2 8.0 P _part/56/_geo_xyz/12,11,0 -1.414213538 P _part/56/_geo_xyz/12,11,1 -1.414213538 P _part/56/_geo_xyz/12,11,2 8.0 P _part/56/_geo_xyz/12,12,0 -1.847759008 P _part/56/_geo_xyz/12,12,1 -0.765367031 P _part/56/_geo_xyz/12,12,2 8.0 P _part/56/_geo_xyz/12,13,0 -2.0 P _part/56/_geo_xyz/12,13,1 -0.000000087 P _part/56/_geo_xyz/12,13,2 8.0 P _part/56/_geo_xyz/12,14,0 -1.847759008 P _part/56/_geo_xyz/12,14,1 0.765366852 P _part/56/_geo_xyz/12,14,2 8.0 P _part/56/_geo_xyz/12,15,0 -1.414213538 P _part/56/_geo_xyz/12,15,1 1.414213538 P _part/56/_geo_xyz/12,15,2 8.0 P _part/56/_geo_xyz/12,16,0 -0.765366912 P _part/56/_geo_xyz/12,16,1 1.847759008 P _part/56/_geo_xyz/12,16,2 8.0 P _part/56/_geo_xyz/12,17,0 -0.0 P _part/56/_geo_xyz/12,17,1 2.001312256 P _part/56/_geo_xyz/12,17,2 8.005249023 P _part/56/_geo_xyz/12,2,0 1.414213538 P _part/56/_geo_xyz/12,2,1 1.414213538 P _part/56/_geo_xyz/12,2,2 8.0 P _part/56/_geo_xyz/12,3,0 1.847759008 P _part/56/_geo_xyz/12,3,1 0.765366852 P _part/56/_geo_xyz/12,3,2 8.0 P _part/56/_geo_xyz/12,4,0 2.0 P _part/56/_geo_xyz/12,4,1 -0.000000087 P _part/56/_geo_xyz/12,4,2 8.0 P _part/56/_geo_xyz/12,5,0 1.847759008 P _part/56/_geo_xyz/12,5,1 -0.765367031 P _part/56/_geo_xyz/12,5,2 8.0 P _part/56/_geo_xyz/12,6,0 1.414213538 P _part/56/_geo_xyz/12,6,1 -1.414213538 P _part/56/_geo_xyz/12,6,2 8.0 P _part/56/_geo_xyz/12,7,0 0.0 P _part/56/_geo_xyz/12,7,1 -1.847759247 P _part/56/_geo_xyz/12,7,2 8.0 P _part/56/_geo_xyz/12,8,0 0.0 P _part/56/_geo_xyz/12,8,1 -1.847759247 P _part/56/_geo_xyz/12,8,2 8.0 P _part/56/_geo_xyz/12,9,0 -0.0 P _part/56/_geo_xyz/12,9,1 -1.847759247 P _part/56/_geo_xyz/12,9,2 8.0 P _part/56/_geo_xyz/13,0,0 0.0 P _part/56/_geo_xyz/13,0,1 0.0 P _part/56/_geo_xyz/13,0,2 32.808399200 P _part/56/_geo_xyz/13,1,0 0.0 P _part/56/_geo_xyz/13,1,1 0.0 P _part/56/_geo_xyz/13,1,2 32.808399200 P _part/56/_geo_xyz/13,10,0 -0.0 P _part/56/_geo_xyz/13,10,1 0.0 P _part/56/_geo_xyz/13,10,2 32.808399200 P _part/56/_geo_xyz/13,11,0 -0.0 P _part/56/_geo_xyz/13,11,1 0.0 P _part/56/_geo_xyz/13,11,2 32.808399200 P _part/56/_geo_xyz/13,12,0 -0.0 P _part/56/_geo_xyz/13,12,1 0.0 P _part/56/_geo_xyz/13,12,2 32.808399200 P _part/56/_geo_xyz/13,13,0 -0.0 P _part/56/_geo_xyz/13,13,1 0.0 P _part/56/_geo_xyz/13,13,2 32.808399200 P _part/56/_geo_xyz/13,14,0 -0.0 P _part/56/_geo_xyz/13,14,1 0.0 P _part/56/_geo_xyz/13,14,2 32.808399200 P _part/56/_geo_xyz/13,15,0 -0.0 P _part/56/_geo_xyz/13,15,1 0.0 P _part/56/_geo_xyz/13,15,2 32.808399200 P _part/56/_geo_xyz/13,16,0 -0.0 P _part/56/_geo_xyz/13,16,1 0.0 P _part/56/_geo_xyz/13,16,2 32.808399200 P _part/56/_geo_xyz/13,17,0 -0.0 P _part/56/_geo_xyz/13,17,1 0.0 P _part/56/_geo_xyz/13,17,2 32.808399200 P _part/56/_geo_xyz/13,2,0 0.0 P _part/56/_geo_xyz/13,2,1 0.0 P _part/56/_geo_xyz/13,2,2 32.808399200 P _part/56/_geo_xyz/13,3,0 0.0 P _part/56/_geo_xyz/13,3,1 0.0 P _part/56/_geo_xyz/13,3,2 32.808399200 P _part/56/_geo_xyz/13,4,0 0.0 P _part/56/_geo_xyz/13,4,1 0.0 P _part/56/_geo_xyz/13,4,2 32.808399200 P _part/56/_geo_xyz/13,5,0 0.0 P _part/56/_geo_xyz/13,5,1 0.0 P _part/56/_geo_xyz/13,5,2 32.808399200 P _part/56/_geo_xyz/13,6,0 0.0 P _part/56/_geo_xyz/13,6,1 0.0 P _part/56/_geo_xyz/13,6,2 32.808399200 P _part/56/_geo_xyz/13,7,0 0.0 P _part/56/_geo_xyz/13,7,1 0.0 P _part/56/_geo_xyz/13,7,2 32.808399200 P _part/56/_geo_xyz/13,8,0 0.0 P _part/56/_geo_xyz/13,8,1 0.0 P _part/56/_geo_xyz/13,8,2 32.808399200 P _part/56/_geo_xyz/13,9,0 -0.0 P _part/56/_geo_xyz/13,9,1 0.0 P _part/56/_geo_xyz/13,9,2 32.808399200 P _part/56/_geo_xyz/14,0,0 0.0 P _part/56/_geo_xyz/14,0,1 0.0 P _part/56/_geo_xyz/14,0,2 0.0 P _part/56/_geo_xyz/14,1,0 0.0 P _part/56/_geo_xyz/14,1,1 0.0 P _part/56/_geo_xyz/14,1,2 0.0 P _part/56/_geo_xyz/14,10,0 0.0 P _part/56/_geo_xyz/14,10,1 0.0 P _part/56/_geo_xyz/14,10,2 0.0 P _part/56/_geo_xyz/14,11,0 0.0 P _part/56/_geo_xyz/14,11,1 0.0 P _part/56/_geo_xyz/14,11,2 0.0 P _part/56/_geo_xyz/14,12,0 0.0 P _part/56/_geo_xyz/14,12,1 0.0 P _part/56/_geo_xyz/14,12,2 0.0 P _part/56/_geo_xyz/14,13,0 0.0 P _part/56/_geo_xyz/14,13,1 0.0 P _part/56/_geo_xyz/14,13,2 0.0 P _part/56/_geo_xyz/14,14,0 0.0 P _part/56/_geo_xyz/14,14,1 0.0 P _part/56/_geo_xyz/14,14,2 0.0 P _part/56/_geo_xyz/14,15,0 0.0 P _part/56/_geo_xyz/14,15,1 0.0 P _part/56/_geo_xyz/14,15,2 0.0 P _part/56/_geo_xyz/14,16,0 0.0 P _part/56/_geo_xyz/14,16,1 0.0 P _part/56/_geo_xyz/14,16,2 0.0 P _part/56/_geo_xyz/14,17,0 0.0 P _part/56/_geo_xyz/14,17,1 0.0 P _part/56/_geo_xyz/14,17,2 0.0 P _part/56/_geo_xyz/14,2,0 0.0 P _part/56/_geo_xyz/14,2,1 0.0 P _part/56/_geo_xyz/14,2,2 0.0 P _part/56/_geo_xyz/14,3,0 0.0 P _part/56/_geo_xyz/14,3,1 0.0 P _part/56/_geo_xyz/14,3,2 0.0 P _part/56/_geo_xyz/14,4,0 0.0 P _part/56/_geo_xyz/14,4,1 0.0 P _part/56/_geo_xyz/14,4,2 0.0 P _part/56/_geo_xyz/14,5,0 0.0 P _part/56/_geo_xyz/14,5,1 0.0 P _part/56/_geo_xyz/14,5,2 0.0 P _part/56/_geo_xyz/14,6,0 0.0 P _part/56/_geo_xyz/14,6,1 0.0 P _part/56/_geo_xyz/14,6,2 0.0 P _part/56/_geo_xyz/14,7,0 0.0 P _part/56/_geo_xyz/14,7,1 0.0 P _part/56/_geo_xyz/14,7,2 0.0 P _part/56/_geo_xyz/14,8,0 0.0 P _part/56/_geo_xyz/14,8,1 0.0 P _part/56/_geo_xyz/14,8,2 0.0 P _part/56/_geo_xyz/14,9,0 0.0 P _part/56/_geo_xyz/14,9,1 0.0 P _part/56/_geo_xyz/14,9,2 0.0 P _part/56/_geo_xyz/15,0,0 0.0 P _part/56/_geo_xyz/15,0,1 0.0 P _part/56/_geo_xyz/15,0,2 0.0 P _part/56/_geo_xyz/15,1,0 0.0 P _part/56/_geo_xyz/15,1,1 0.0 P _part/56/_geo_xyz/15,1,2 0.0 P _part/56/_geo_xyz/15,10,0 0.0 P _part/56/_geo_xyz/15,10,1 0.0 P _part/56/_geo_xyz/15,10,2 0.0 P _part/56/_geo_xyz/15,11,0 0.0 P _part/56/_geo_xyz/15,11,1 0.0 P _part/56/_geo_xyz/15,11,2 0.0 P _part/56/_geo_xyz/15,12,0 0.0 P _part/56/_geo_xyz/15,12,1 0.0 P _part/56/_geo_xyz/15,12,2 0.0 P _part/56/_geo_xyz/15,13,0 0.0 P _part/56/_geo_xyz/15,13,1 0.0 P _part/56/_geo_xyz/15,13,2 0.0 P _part/56/_geo_xyz/15,14,0 0.0 P _part/56/_geo_xyz/15,14,1 0.0 P _part/56/_geo_xyz/15,14,2 0.0 P _part/56/_geo_xyz/15,15,0 0.0 P _part/56/_geo_xyz/15,15,1 0.0 P _part/56/_geo_xyz/15,15,2 0.0 P _part/56/_geo_xyz/15,16,0 0.0 P _part/56/_geo_xyz/15,16,1 0.0 P _part/56/_geo_xyz/15,16,2 0.0 P _part/56/_geo_xyz/15,17,0 0.0 P _part/56/_geo_xyz/15,17,1 0.0 P _part/56/_geo_xyz/15,17,2 0.0 P _part/56/_geo_xyz/15,2,0 0.0 P _part/56/_geo_xyz/15,2,1 0.0 P _part/56/_geo_xyz/15,2,2 0.0 P _part/56/_geo_xyz/15,3,0 0.0 P _part/56/_geo_xyz/15,3,1 0.0 P _part/56/_geo_xyz/15,3,2 0.0 P _part/56/_geo_xyz/15,4,0 0.0 P _part/56/_geo_xyz/15,4,1 0.0 P _part/56/_geo_xyz/15,4,2 0.0 P _part/56/_geo_xyz/15,5,0 0.0 P _part/56/_geo_xyz/15,5,1 0.0 P _part/56/_geo_xyz/15,5,2 0.0 P _part/56/_geo_xyz/15,6,0 0.0 P _part/56/_geo_xyz/15,6,1 0.0 P _part/56/_geo_xyz/15,6,2 0.0 P _part/56/_geo_xyz/15,7,0 0.0 P _part/56/_geo_xyz/15,7,1 0.0 P _part/56/_geo_xyz/15,7,2 0.0 P _part/56/_geo_xyz/15,8,0 0.0 P _part/56/_geo_xyz/15,8,1 0.0 P _part/56/_geo_xyz/15,8,2 0.0 P _part/56/_geo_xyz/15,9,0 0.0 P _part/56/_geo_xyz/15,9,1 0.0 P _part/56/_geo_xyz/15,9,2 0.0 P _part/56/_geo_xyz/16,0,0 0.0 P _part/56/_geo_xyz/16,0,1 0.0 P _part/56/_geo_xyz/16,0,2 0.0 P _part/56/_geo_xyz/16,1,0 0.0 P _part/56/_geo_xyz/16,1,1 0.0 P _part/56/_geo_xyz/16,1,2 0.0 P _part/56/_geo_xyz/16,10,0 0.0 P _part/56/_geo_xyz/16,10,1 0.0 P _part/56/_geo_xyz/16,10,2 0.0 P _part/56/_geo_xyz/16,11,0 0.0 P _part/56/_geo_xyz/16,11,1 0.0 P _part/56/_geo_xyz/16,11,2 0.0 P _part/56/_geo_xyz/16,12,0 0.0 P _part/56/_geo_xyz/16,12,1 0.0 P _part/56/_geo_xyz/16,12,2 0.0 P _part/56/_geo_xyz/16,13,0 0.0 P _part/56/_geo_xyz/16,13,1 0.0 P _part/56/_geo_xyz/16,13,2 0.0 P _part/56/_geo_xyz/16,14,0 0.0 P _part/56/_geo_xyz/16,14,1 0.0 P _part/56/_geo_xyz/16,14,2 0.0 P _part/56/_geo_xyz/16,15,0 0.0 P _part/56/_geo_xyz/16,15,1 0.0 P _part/56/_geo_xyz/16,15,2 0.0 P _part/56/_geo_xyz/16,16,0 0.0 P _part/56/_geo_xyz/16,16,1 0.0 P _part/56/_geo_xyz/16,16,2 0.0 P _part/56/_geo_xyz/16,17,0 0.0 P _part/56/_geo_xyz/16,17,1 0.0 P _part/56/_geo_xyz/16,17,2 0.0 P _part/56/_geo_xyz/16,2,0 0.0 P _part/56/_geo_xyz/16,2,1 0.0 P _part/56/_geo_xyz/16,2,2 0.0 P _part/56/_geo_xyz/16,3,0 0.0 P _part/56/_geo_xyz/16,3,1 0.0 P _part/56/_geo_xyz/16,3,2 0.0 P _part/56/_geo_xyz/16,4,0 0.0 P _part/56/_geo_xyz/16,4,1 0.0 P _part/56/_geo_xyz/16,4,2 0.0 P _part/56/_geo_xyz/16,5,0 0.0 P _part/56/_geo_xyz/16,5,1 0.0 P _part/56/_geo_xyz/16,5,2 0.0 P _part/56/_geo_xyz/16,6,0 0.0 P _part/56/_geo_xyz/16,6,1 0.0 P _part/56/_geo_xyz/16,6,2 0.0 P _part/56/_geo_xyz/16,7,0 0.0 P _part/56/_geo_xyz/16,7,1 0.0 P _part/56/_geo_xyz/16,7,2 0.0 P _part/56/_geo_xyz/16,8,0 0.0 P _part/56/_geo_xyz/16,8,1 0.0 P _part/56/_geo_xyz/16,8,2 0.0 P _part/56/_geo_xyz/16,9,0 0.0 P _part/56/_geo_xyz/16,9,1 0.0 P _part/56/_geo_xyz/16,9,2 0.0 P _part/56/_geo_xyz/17,0,0 0.0 P _part/56/_geo_xyz/17,0,1 0.0 P _part/56/_geo_xyz/17,0,2 0.0 P _part/56/_geo_xyz/17,1,0 0.0 P _part/56/_geo_xyz/17,1,1 0.0 P _part/56/_geo_xyz/17,1,2 0.0 P _part/56/_geo_xyz/17,10,0 0.0 P _part/56/_geo_xyz/17,10,1 0.0 P _part/56/_geo_xyz/17,10,2 0.0 P _part/56/_geo_xyz/17,11,0 0.0 P _part/56/_geo_xyz/17,11,1 0.0 P _part/56/_geo_xyz/17,11,2 0.0 P _part/56/_geo_xyz/17,12,0 0.0 P _part/56/_geo_xyz/17,12,1 0.0 P _part/56/_geo_xyz/17,12,2 0.0 P _part/56/_geo_xyz/17,13,0 0.0 P _part/56/_geo_xyz/17,13,1 0.0 P _part/56/_geo_xyz/17,13,2 0.0 P _part/56/_geo_xyz/17,14,0 0.0 P _part/56/_geo_xyz/17,14,1 0.0 P _part/56/_geo_xyz/17,14,2 0.0 P _part/56/_geo_xyz/17,15,0 0.0 P _part/56/_geo_xyz/17,15,1 0.0 P _part/56/_geo_xyz/17,15,2 0.0 P _part/56/_geo_xyz/17,16,0 0.0 P _part/56/_geo_xyz/17,16,1 0.0 P _part/56/_geo_xyz/17,16,2 0.0 P _part/56/_geo_xyz/17,17,0 0.0 P _part/56/_geo_xyz/17,17,1 0.0 P _part/56/_geo_xyz/17,17,2 0.0 P _part/56/_geo_xyz/17,2,0 0.0 P _part/56/_geo_xyz/17,2,1 0.0 P _part/56/_geo_xyz/17,2,2 0.0 P _part/56/_geo_xyz/17,3,0 0.0 P _part/56/_geo_xyz/17,3,1 0.0 P _part/56/_geo_xyz/17,3,2 0.0 P _part/56/_geo_xyz/17,4,0 0.0 P _part/56/_geo_xyz/17,4,1 0.0 P _part/56/_geo_xyz/17,4,2 0.0 P _part/56/_geo_xyz/17,5,0 0.0 P _part/56/_geo_xyz/17,5,1 0.0 P _part/56/_geo_xyz/17,5,2 0.0 P _part/56/_geo_xyz/17,6,0 0.0 P _part/56/_geo_xyz/17,6,1 0.0 P _part/56/_geo_xyz/17,6,2 0.0 P _part/56/_geo_xyz/17,7,0 0.0 P _part/56/_geo_xyz/17,7,1 0.0 P _part/56/_geo_xyz/17,7,2 0.0 P _part/56/_geo_xyz/17,8,0 0.0 P _part/56/_geo_xyz/17,8,1 0.0 P _part/56/_geo_xyz/17,8,2 0.0 P _part/56/_geo_xyz/17,9,0 0.0 P _part/56/_geo_xyz/17,9,1 0.0 P _part/56/_geo_xyz/17,9,2 0.0 P _part/56/_geo_xyz/18,0,0 0.0 P _part/56/_geo_xyz/18,0,1 0.0 P _part/56/_geo_xyz/18,0,2 0.0 P _part/56/_geo_xyz/18,1,0 0.0 P _part/56/_geo_xyz/18,1,1 0.0 P _part/56/_geo_xyz/18,1,2 0.0 P _part/56/_geo_xyz/18,10,0 0.0 P _part/56/_geo_xyz/18,10,1 0.0 P _part/56/_geo_xyz/18,10,2 0.0 P _part/56/_geo_xyz/18,11,0 0.0 P _part/56/_geo_xyz/18,11,1 0.0 P _part/56/_geo_xyz/18,11,2 0.0 P _part/56/_geo_xyz/18,12,0 0.0 P _part/56/_geo_xyz/18,12,1 0.0 P _part/56/_geo_xyz/18,12,2 0.0 P _part/56/_geo_xyz/18,13,0 0.0 P _part/56/_geo_xyz/18,13,1 0.0 P _part/56/_geo_xyz/18,13,2 0.0 P _part/56/_geo_xyz/18,14,0 0.0 P _part/56/_geo_xyz/18,14,1 0.0 P _part/56/_geo_xyz/18,14,2 0.0 P _part/56/_geo_xyz/18,15,0 0.0 P _part/56/_geo_xyz/18,15,1 0.0 P _part/56/_geo_xyz/18,15,2 0.0 P _part/56/_geo_xyz/18,16,0 0.0 P _part/56/_geo_xyz/18,16,1 0.0 P _part/56/_geo_xyz/18,16,2 0.0 P _part/56/_geo_xyz/18,17,0 0.0 P _part/56/_geo_xyz/18,17,1 0.0 P _part/56/_geo_xyz/18,17,2 0.0 P _part/56/_geo_xyz/18,2,0 0.0 P _part/56/_geo_xyz/18,2,1 0.0 P _part/56/_geo_xyz/18,2,2 0.0 P _part/56/_geo_xyz/18,3,0 0.0 P _part/56/_geo_xyz/18,3,1 0.0 P _part/56/_geo_xyz/18,3,2 0.0 P _part/56/_geo_xyz/18,4,0 0.0 P _part/56/_geo_xyz/18,4,1 0.0 P _part/56/_geo_xyz/18,4,2 0.0 P _part/56/_geo_xyz/18,5,0 0.0 P _part/56/_geo_xyz/18,5,1 0.0 P _part/56/_geo_xyz/18,5,2 0.0 P _part/56/_geo_xyz/18,6,0 0.0 P _part/56/_geo_xyz/18,6,1 0.0 P _part/56/_geo_xyz/18,6,2 0.0 P _part/56/_geo_xyz/18,7,0 0.0 P _part/56/_geo_xyz/18,7,1 0.0 P _part/56/_geo_xyz/18,7,2 0.0 P _part/56/_geo_xyz/18,8,0 0.0 P _part/56/_geo_xyz/18,8,1 0.0 P _part/56/_geo_xyz/18,8,2 0.0 P _part/56/_geo_xyz/18,9,0 0.0 P _part/56/_geo_xyz/18,9,1 0.0 P _part/56/_geo_xyz/18,9,2 0.0 P _part/56/_geo_xyz/19,0,0 0.0 P _part/56/_geo_xyz/19,0,1 0.0 P _part/56/_geo_xyz/19,0,2 0.0 P _part/56/_geo_xyz/19,1,0 0.0 P _part/56/_geo_xyz/19,1,1 0.0 P _part/56/_geo_xyz/19,1,2 0.0 P _part/56/_geo_xyz/19,10,0 0.0 P _part/56/_geo_xyz/19,10,1 0.0 P _part/56/_geo_xyz/19,10,2 0.0 P _part/56/_geo_xyz/19,11,0 0.0 P _part/56/_geo_xyz/19,11,1 0.0 P _part/56/_geo_xyz/19,11,2 0.0 P _part/56/_geo_xyz/19,12,0 0.0 P _part/56/_geo_xyz/19,12,1 0.0 P _part/56/_geo_xyz/19,12,2 0.0 P _part/56/_geo_xyz/19,13,0 0.0 P _part/56/_geo_xyz/19,13,1 0.0 P _part/56/_geo_xyz/19,13,2 0.0 P _part/56/_geo_xyz/19,14,0 0.0 P _part/56/_geo_xyz/19,14,1 0.0 P _part/56/_geo_xyz/19,14,2 0.0 P _part/56/_geo_xyz/19,15,0 0.0 P _part/56/_geo_xyz/19,15,1 0.0 P _part/56/_geo_xyz/19,15,2 0.0 P _part/56/_geo_xyz/19,16,0 0.0 P _part/56/_geo_xyz/19,16,1 0.0 P _part/56/_geo_xyz/19,16,2 0.0 P _part/56/_geo_xyz/19,17,0 0.0 P _part/56/_geo_xyz/19,17,1 0.0 P _part/56/_geo_xyz/19,17,2 0.0 P _part/56/_geo_xyz/19,2,0 0.0 P _part/56/_geo_xyz/19,2,1 0.0 P _part/56/_geo_xyz/19,2,2 0.0 P _part/56/_geo_xyz/19,3,0 0.0 P _part/56/_geo_xyz/19,3,1 0.0 P _part/56/_geo_xyz/19,3,2 0.0 P _part/56/_geo_xyz/19,4,0 0.0 P _part/56/_geo_xyz/19,4,1 0.0 P _part/56/_geo_xyz/19,4,2 0.0 P _part/56/_geo_xyz/19,5,0 0.0 P _part/56/_geo_xyz/19,5,1 0.0 P _part/56/_geo_xyz/19,5,2 0.0 P _part/56/_geo_xyz/19,6,0 0.0 P _part/56/_geo_xyz/19,6,1 0.0 P _part/56/_geo_xyz/19,6,2 0.0 P _part/56/_geo_xyz/19,7,0 0.0 P _part/56/_geo_xyz/19,7,1 0.0 P _part/56/_geo_xyz/19,7,2 0.0 P _part/56/_geo_xyz/19,8,0 0.0 P _part/56/_geo_xyz/19,8,1 0.0 P _part/56/_geo_xyz/19,8,2 0.0 P _part/56/_geo_xyz/19,9,0 0.0 P _part/56/_geo_xyz/19,9,1 0.0 P _part/56/_geo_xyz/19,9,2 0.0 P _part/56/_geo_xyz/2,0,0 0.0 P _part/56/_geo_xyz/2,0,1 0.032808397 P _part/56/_geo_xyz/2,0,2 6.561679840 P _part/56/_geo_xyz/2,1,0 0.012555230 P _part/56/_geo_xyz/2,1,1 0.030311001 P _part/56/_geo_xyz/2,1,2 6.560600758 P _part/56/_geo_xyz/2,10,0 -0.012555226 P _part/56/_geo_xyz/2,10,1 -0.030311013 P _part/56/_geo_xyz/2,10,2 6.560396671 P _part/56/_geo_xyz/2,11,0 -0.023199040 P _part/56/_geo_xyz/2,11,1 -0.023199044 P _part/56/_geo_xyz/2,11,2 6.560425758 P _part/56/_geo_xyz/2,12,0 -0.030311007 P _part/56/_geo_xyz/2,12,1 -0.012555235 P _part/56/_geo_xyz/2,12,2 6.560454845 P _part/56/_geo_xyz/2,13,0 -0.032808397 P _part/56/_geo_xyz/2,13,1 -0.000000006 P _part/56/_geo_xyz/2,13,2 6.560484409 P _part/56/_geo_xyz/2,14,0 -0.030311007 P _part/56/_geo_xyz/2,14,1 0.012555227 P _part/56/_geo_xyz/2,14,2 6.560513496 P _part/56/_geo_xyz/2,15,0 -0.023199039 P _part/56/_geo_xyz/2,15,1 0.023199037 P _part/56/_geo_xyz/2,15,2 6.560542583 P _part/56/_geo_xyz/2,16,0 -0.012555230 P _part/56/_geo_xyz/2,16,1 0.030311001 P _part/56/_geo_xyz/2,16,2 6.560571671 P _part/56/_geo_xyz/2,17,0 0.000000001 P _part/56/_geo_xyz/2,17,1 0.032808397 P _part/56/_geo_xyz/2,17,2 6.561650753 P _part/56/_geo_xyz/2,2,0 0.023199040 P _part/56/_geo_xyz/2,2,1 0.023199035 P _part/56/_geo_xyz/2,2,2 6.560571671 P _part/56/_geo_xyz/2,3,0 0.030311007 P _part/56/_geo_xyz/2,3,1 0.012555225 P _part/56/_geo_xyz/2,3,2 6.560542583 P _part/56/_geo_xyz/2,4,0 0.032808397 P _part/56/_geo_xyz/2,4,1 -0.000000006 P _part/56/_geo_xyz/2,4,2 6.560513496 P _part/56/_geo_xyz/2,5,0 0.030311005 P _part/56/_geo_xyz/2,5,1 -0.012555238 P _part/56/_geo_xyz/2,5,2 6.560484409 P _part/56/_geo_xyz/2,6,0 0.023199040 P _part/56/_geo_xyz/2,6,1 -0.023199044 P _part/56/_geo_xyz/2,6,2 6.560454845 P _part/56/_geo_xyz/2,7,0 0.012555225 P _part/56/_geo_xyz/2,7,1 -0.030311016 P _part/56/_geo_xyz/2,7,2 6.560425758 P _part/56/_geo_xyz/2,8,0 -0.000000001 P _part/56/_geo_xyz/2,8,1 -0.032808401 P _part/56/_geo_xyz/2,8,2 6.560396671 P _part/56/_geo_xyz/2,9,0 -0.000000001 P _part/56/_geo_xyz/2,9,1 -0.032808401 P _part/56/_geo_xyz/2,9,2 6.560367584 P _part/56/_geo_xyz/3,0,0 0.0 P _part/56/_geo_xyz/3,0,1 0.032808397 P _part/56/_geo_xyz/3,0,2 9.842519760 P _part/56/_geo_xyz/3,1,0 0.012555230 P _part/56/_geo_xyz/3,1,1 0.030311001 P _part/56/_geo_xyz/3,1,2 9.854390144 P _part/56/_geo_xyz/3,10,0 -0.012555226 P _part/56/_geo_xyz/3,10,1 -0.030311013 P _part/56/_geo_xyz/3,10,2 9.856636047 P _part/56/_geo_xyz/3,11,0 -0.023199040 P _part/56/_geo_xyz/3,11,1 -0.023199044 P _part/56/_geo_xyz/3,11,2 9.856314659 P _part/56/_geo_xyz/3,12,0 -0.030311007 P _part/56/_geo_xyz/3,12,1 -0.012555235 P _part/56/_geo_xyz/3,12,2 9.855994225 P _part/56/_geo_xyz/3,13,0 -0.032808397 P _part/56/_geo_xyz/3,13,1 -0.000000006 P _part/56/_geo_xyz/3,13,2 9.855672836 P _part/56/_geo_xyz/3,14,0 -0.030311007 P _part/56/_geo_xyz/3,14,1 0.012555227 P _part/56/_geo_xyz/3,14,2 9.855352402 P _part/56/_geo_xyz/3,15,0 -0.023199039 P _part/56/_geo_xyz/3,15,1 0.023199037 P _part/56/_geo_xyz/3,15,2 9.855031967 P _part/56/_geo_xyz/3,16,0 -0.012555230 P _part/56/_geo_xyz/3,16,1 0.030311001 P _part/56/_geo_xyz/3,16,2 9.854710579 P _part/56/_geo_xyz/3,17,0 0.000000001 P _part/56/_geo_xyz/3,17,1 0.032808397 P _part/56/_geo_xyz/3,17,2 9.842840195 P _part/56/_geo_xyz/3,2,0 0.023199040 P _part/56/_geo_xyz/3,2,1 0.023199035 P _part/56/_geo_xyz/3,2,2 9.854710579 P _part/56/_geo_xyz/3,3,0 0.030311007 P _part/56/_geo_xyz/3,3,1 0.012555225 P _part/56/_geo_xyz/3,3,2 9.855031967 P _part/56/_geo_xyz/3,4,0 0.032808397 P _part/56/_geo_xyz/3,4,1 -0.000000006 P _part/56/_geo_xyz/3,4,2 9.855352402 P _part/56/_geo_xyz/3,5,0 0.030311005 P _part/56/_geo_xyz/3,5,1 -0.012555238 P _part/56/_geo_xyz/3,5,2 9.855672836 P _part/56/_geo_xyz/3,6,0 0.023199040 P _part/56/_geo_xyz/3,6,1 -0.023199044 P _part/56/_geo_xyz/3,6,2 9.855994225 P _part/56/_geo_xyz/3,7,0 0.012555225 P _part/56/_geo_xyz/3,7,1 -0.030311016 P _part/56/_geo_xyz/3,7,2 9.856314659 P _part/56/_geo_xyz/3,8,0 -0.000000001 P _part/56/_geo_xyz/3,8,1 -0.032808401 P _part/56/_geo_xyz/3,8,2 9.856636047 P _part/56/_geo_xyz/3,9,0 -0.000000001 P _part/56/_geo_xyz/3,9,1 -0.032808401 P _part/56/_geo_xyz/3,9,2 9.856956482 P _part/56/_geo_xyz/4,0,0 0.0 P _part/56/_geo_xyz/4,0,1 0.032808397 P _part/56/_geo_xyz/4,0,2 13.123359680 P _part/56/_geo_xyz/4,1,0 0.012555230 P _part/56/_geo_xyz/4,1,1 0.030311001 P _part/56/_geo_xyz/4,1,2 13.121201515 P _part/56/_geo_xyz/4,10,0 -0.012555226 P _part/56/_geo_xyz/4,10,1 -0.030311013 P _part/56/_geo_xyz/4,10,2 13.120793343 P _part/56/_geo_xyz/4,11,0 -0.023199040 P _part/56/_geo_xyz/4,11,1 -0.023199044 P _part/56/_geo_xyz/4,11,2 13.120851517 P _part/56/_geo_xyz/4,12,0 -0.030311007 P _part/56/_geo_xyz/4,12,1 -0.012555235 P _part/56/_geo_xyz/4,12,2 13.120909691 P _part/56/_geo_xyz/4,13,0 -0.032808397 P _part/56/_geo_xyz/4,13,1 -0.000000006 P _part/56/_geo_xyz/4,13,2 13.120968819 P _part/56/_geo_xyz/4,14,0 -0.030311007 P _part/56/_geo_xyz/4,14,1 0.012555227 P _part/56/_geo_xyz/4,14,2 13.121026993 P _part/56/_geo_xyz/4,15,0 -0.023199039 P _part/56/_geo_xyz/4,15,1 0.023199037 P _part/56/_geo_xyz/4,15,2 13.121085167 P _part/56/_geo_xyz/4,16,0 -0.012555230 P _part/56/_geo_xyz/4,16,1 0.030311001 P _part/56/_geo_xyz/4,16,2 13.121143341 P _part/56/_geo_xyz/4,17,0 0.000000001 P _part/56/_geo_xyz/4,17,1 0.032808397 P _part/56/_geo_xyz/4,17,2 13.123301506 P _part/56/_geo_xyz/4,2,0 0.023199040 P _part/56/_geo_xyz/4,2,1 0.023199035 P _part/56/_geo_xyz/4,2,2 13.121143341 P _part/56/_geo_xyz/4,3,0 0.030311007 P _part/56/_geo_xyz/4,3,1 0.012555225 P _part/56/_geo_xyz/4,3,2 13.121085167 P _part/56/_geo_xyz/4,4,0 0.032808397 P _part/56/_geo_xyz/4,4,1 -0.000000006 P _part/56/_geo_xyz/4,4,2 13.121026993 P _part/56/_geo_xyz/4,5,0 0.030311005 P _part/56/_geo_xyz/4,5,1 -0.012555238 P _part/56/_geo_xyz/4,5,2 13.120968819 P _part/56/_geo_xyz/4,6,0 0.023199040 P _part/56/_geo_xyz/4,6,1 -0.023199044 P _part/56/_geo_xyz/4,6,2 13.120909691 P _part/56/_geo_xyz/4,7,0 0.012555225 P _part/56/_geo_xyz/4,7,1 -0.030311016 P _part/56/_geo_xyz/4,7,2 13.120851517 P _part/56/_geo_xyz/4,8,0 -0.000000001 P _part/56/_geo_xyz/4,8,1 -0.032808401 P _part/56/_geo_xyz/4,8,2 13.120793343 P _part/56/_geo_xyz/4,9,0 -0.000000001 P _part/56/_geo_xyz/4,9,1 -0.032808401 P _part/56/_geo_xyz/4,9,2 13.120735168 P _part/56/_geo_xyz/5,0,0 0.0 P _part/56/_geo_xyz/5,0,1 0.032808397 P _part/56/_geo_xyz/5,0,2 16.404199600 P _part/56/_geo_xyz/5,1,0 0.012555230 P _part/56/_geo_xyz/5,1,1 0.030311001 P _part/56/_geo_xyz/5,1,2 16.414991379 P _part/56/_geo_xyz/5,10,0 -0.012555226 P _part/56/_geo_xyz/5,10,1 -0.030311013 P _part/56/_geo_xyz/5,10,2 16.417032242 P _part/56/_geo_xyz/5,11,0 -0.023199040 P _part/56/_geo_xyz/5,11,1 -0.023199044 P _part/56/_geo_xyz/5,11,2 16.416740417 P _part/56/_geo_xyz/5,12,0 -0.030311007 P _part/56/_geo_xyz/5,12,1 -0.012555235 P _part/56/_geo_xyz/5,12,2 16.416448593 P _part/56/_geo_xyz/5,13,0 -0.032808397 P _part/56/_geo_xyz/5,13,1 -0.000000006 P _part/56/_geo_xyz/5,13,2 16.416156769 P _part/56/_geo_xyz/5,14,0 -0.030311007 P _part/56/_geo_xyz/5,14,1 0.012555227 P _part/56/_geo_xyz/5,14,2 16.415864944 P _part/56/_geo_xyz/5,15,0 -0.023199039 P _part/56/_geo_xyz/5,15,1 0.023199037 P _part/56/_geo_xyz/5,15,2 16.415575027 P _part/56/_geo_xyz/5,16,0 -0.012555230 P _part/56/_geo_xyz/5,16,1 0.030311001 P _part/56/_geo_xyz/5,16,2 16.415283203 P _part/56/_geo_xyz/5,17,0 0.000000001 P _part/56/_geo_xyz/5,17,1 0.032808397 P _part/56/_geo_xyz/5,17,2 16.404491425 P _part/56/_geo_xyz/5,2,0 0.023199040 P _part/56/_geo_xyz/5,2,1 0.023199035 P _part/56/_geo_xyz/5,2,2 16.415283203 P _part/56/_geo_xyz/5,3,0 0.030311007 P _part/56/_geo_xyz/5,3,1 0.012555225 P _part/56/_geo_xyz/5,3,2 16.415575027 P _part/56/_geo_xyz/5,4,0 0.032808397 P _part/56/_geo_xyz/5,4,1 -0.000000006 P _part/56/_geo_xyz/5,4,2 16.415864944 P _part/56/_geo_xyz/5,5,0 0.030311005 P _part/56/_geo_xyz/5,5,1 -0.012555238 P _part/56/_geo_xyz/5,5,2 16.416156769 P _part/56/_geo_xyz/5,6,0 0.023199040 P _part/56/_geo_xyz/5,6,1 -0.023199044 P _part/56/_geo_xyz/5,6,2 16.416448593 P _part/56/_geo_xyz/5,7,0 0.012555225 P _part/56/_geo_xyz/5,7,1 -0.030311016 P _part/56/_geo_xyz/5,7,2 16.416740417 P _part/56/_geo_xyz/5,8,0 -0.000000001 P _part/56/_geo_xyz/5,8,1 -0.032808401 P _part/56/_geo_xyz/5,8,2 16.417032242 P _part/56/_geo_xyz/5,9,0 -0.000000001 P _part/56/_geo_xyz/5,9,1 -0.032808401 P _part/56/_geo_xyz/5,9,2 16.417324066 P _part/56/_geo_xyz/6,0,0 0.0 P _part/56/_geo_xyz/6,0,1 0.032808397 P _part/56/_geo_xyz/6,0,2 19.685039520 P _part/56/_geo_xyz/6,1,0 0.012555230 P _part/56/_geo_xyz/6,1,1 0.030311001 P _part/56/_geo_xyz/6,1,2 19.695831299 P _part/56/_geo_xyz/6,10,0 -0.012555226 P _part/56/_geo_xyz/6,10,1 -0.030311013 P _part/56/_geo_xyz/6,10,2 19.697872162 P _part/56/_geo_xyz/6,11,0 -0.023199040 P _part/56/_geo_xyz/6,11,1 -0.023199044 P _part/56/_geo_xyz/6,11,2 19.697580338 P _part/56/_geo_xyz/6,12,0 -0.030311007 P _part/56/_geo_xyz/6,12,1 -0.012555235 P _part/56/_geo_xyz/6,12,2 19.697288513 P _part/56/_geo_xyz/6,13,0 -0.032808397 P _part/56/_geo_xyz/6,13,1 -0.000000006 P _part/56/_geo_xyz/6,13,2 19.696996689 P _part/56/_geo_xyz/6,14,0 -0.030311007 P _part/56/_geo_xyz/6,14,1 0.012555227 P _part/56/_geo_xyz/6,14,2 19.696704865 P _part/56/_geo_xyz/6,15,0 -0.023199039 P _part/56/_geo_xyz/6,15,1 0.023199037 P _part/56/_geo_xyz/6,15,2 19.696414948 P _part/56/_geo_xyz/6,16,0 -0.012555230 P _part/56/_geo_xyz/6,16,1 0.030311001 P _part/56/_geo_xyz/6,16,2 19.696123123 P _part/56/_geo_xyz/6,17,0 0.000000001 P _part/56/_geo_xyz/6,17,1 0.032808397 P _part/56/_geo_xyz/6,17,2 19.685331345 P _part/56/_geo_xyz/6,2,0 0.023199040 P _part/56/_geo_xyz/6,2,1 0.023199035 P _part/56/_geo_xyz/6,2,2 19.696123123 P _part/56/_geo_xyz/6,3,0 0.030311007 P _part/56/_geo_xyz/6,3,1 0.012555225 P _part/56/_geo_xyz/6,3,2 19.696414948 P _part/56/_geo_xyz/6,4,0 0.032808397 P _part/56/_geo_xyz/6,4,1 -0.000000006 P _part/56/_geo_xyz/6,4,2 19.696704865 P _part/56/_geo_xyz/6,5,0 0.030311005 P _part/56/_geo_xyz/6,5,1 -0.012555238 P _part/56/_geo_xyz/6,5,2 19.696996689 P _part/56/_geo_xyz/6,6,0 0.023199040 P _part/56/_geo_xyz/6,6,1 -0.023199044 P _part/56/_geo_xyz/6,6,2 19.697288513 P _part/56/_geo_xyz/6,7,0 0.012555225 P _part/56/_geo_xyz/6,7,1 -0.030311016 P _part/56/_geo_xyz/6,7,2 19.697580338 P _part/56/_geo_xyz/6,8,0 -0.000000001 P _part/56/_geo_xyz/6,8,1 -0.032808401 P _part/56/_geo_xyz/6,8,2 19.697872162 P _part/56/_geo_xyz/6,9,0 -0.000000001 P _part/56/_geo_xyz/6,9,1 -0.032808401 P _part/56/_geo_xyz/6,9,2 19.698163986 P _part/56/_geo_xyz/7,0,0 0.0 P _part/56/_geo_xyz/7,0,1 0.032808397 P _part/56/_geo_xyz/7,0,2 22.965879440 P _part/56/_geo_xyz/7,1,0 0.012555230 P _part/56/_geo_xyz/7,1,1 0.030311001 P _part/56/_geo_xyz/7,1,2 22.962642670 P _part/56/_geo_xyz/7,10,0 -0.012555226 P _part/56/_geo_xyz/7,10,1 -0.030311013 P _part/56/_geo_xyz/7,10,2 22.962030411 P _part/56/_geo_xyz/7,11,0 -0.023199040 P _part/56/_geo_xyz/7,11,1 -0.023199044 P _part/56/_geo_xyz/7,11,2 22.962118149 P _part/56/_geo_xyz/7,12,0 -0.030311007 P _part/56/_geo_xyz/7,12,1 -0.012555235 P _part/56/_geo_xyz/7,12,2 22.962205887 P _part/56/_geo_xyz/7,13,0 -0.032808397 P _part/56/_geo_xyz/7,13,1 -0.000000006 P _part/56/_geo_xyz/7,13,2 22.962293625 P _part/56/_geo_xyz/7,14,0 -0.030311007 P _part/56/_geo_xyz/7,14,1 0.012555227 P _part/56/_geo_xyz/7,14,2 22.962381363 P _part/56/_geo_xyz/7,15,0 -0.023199039 P _part/56/_geo_xyz/7,15,1 0.023199037 P _part/56/_geo_xyz/7,15,2 22.962467194 P _part/56/_geo_xyz/7,16,0 -0.012555230 P _part/56/_geo_xyz/7,16,1 0.030311001 P _part/56/_geo_xyz/7,16,2 22.962554932 P _part/56/_geo_xyz/7,17,0 0.000000001 P _part/56/_geo_xyz/7,17,1 0.032808397 P _part/56/_geo_xyz/7,17,2 22.965791702 P _part/56/_geo_xyz/7,2,0 0.023199040 P _part/56/_geo_xyz/7,2,1 0.023199035 P _part/56/_geo_xyz/7,2,2 22.962554932 P _part/56/_geo_xyz/7,3,0 0.030311007 P _part/56/_geo_xyz/7,3,1 0.012555225 P _part/56/_geo_xyz/7,3,2 22.962467194 P _part/56/_geo_xyz/7,4,0 0.032808397 P _part/56/_geo_xyz/7,4,1 -0.000000006 P _part/56/_geo_xyz/7,4,2 22.962381363 P _part/56/_geo_xyz/7,5,0 0.030311005 P _part/56/_geo_xyz/7,5,1 -0.012555238 P _part/56/_geo_xyz/7,5,2 22.962293625 P _part/56/_geo_xyz/7,6,0 0.023199040 P _part/56/_geo_xyz/7,6,1 -0.023199044 P _part/56/_geo_xyz/7,6,2 22.962205887 P _part/56/_geo_xyz/7,7,0 0.012555225 P _part/56/_geo_xyz/7,7,1 -0.030311016 P _part/56/_geo_xyz/7,7,2 22.962118149 P _part/56/_geo_xyz/7,8,0 -0.000000001 P _part/56/_geo_xyz/7,8,1 -0.032808401 P _part/56/_geo_xyz/7,8,2 22.962030411 P _part/56/_geo_xyz/7,9,0 -0.000000001 P _part/56/_geo_xyz/7,9,1 -0.032808401 P _part/56/_geo_xyz/7,9,2 22.961942673 P _part/56/_geo_xyz/8,0,0 0.0 P _part/56/_geo_xyz/8,0,1 0.032808397 P _part/56/_geo_xyz/8,0,2 26.246719360 P _part/56/_geo_xyz/8,1,0 0.012555230 P _part/56/_geo_xyz/8,1,1 0.030311001 P _part/56/_geo_xyz/8,1,2 26.256431580 P _part/56/_geo_xyz/8,10,0 -0.012555226 P _part/56/_geo_xyz/8,10,1 -0.030311013 P _part/56/_geo_xyz/8,10,2 26.258268356 P _part/56/_geo_xyz/8,11,0 -0.023199040 P _part/56/_geo_xyz/8,11,1 -0.023199044 P _part/56/_geo_xyz/8,11,2 26.258007050 P _part/56/_geo_xyz/8,12,0 -0.030311007 P _part/56/_geo_xyz/8,12,1 -0.012555235 P _part/56/_geo_xyz/8,12,2 26.257743835 P _part/56/_geo_xyz/8,13,0 -0.032808397 P _part/56/_geo_xyz/8,13,1 -0.000000006 P _part/56/_geo_xyz/8,13,2 26.257482529 P _part/56/_geo_xyz/8,14,0 -0.030311007 P _part/56/_geo_xyz/8,14,1 0.012555227 P _part/56/_geo_xyz/8,14,2 26.257219315 P _part/56/_geo_xyz/8,15,0 -0.023199039 P _part/56/_geo_xyz/8,15,1 0.023199037 P _part/56/_geo_xyz/8,15,2 26.256956100 P _part/56/_geo_xyz/8,16,0 -0.012555230 P _part/56/_geo_xyz/8,16,1 0.030311001 P _part/56/_geo_xyz/8,16,2 26.256694794 P _part/56/_geo_xyz/8,17,0 0.000000001 P _part/56/_geo_xyz/8,17,1 0.032808397 P _part/56/_geo_xyz/8,17,2 26.246982574 P _part/56/_geo_xyz/8,2,0 0.023199040 P _part/56/_geo_xyz/8,2,1 0.023199035 P _part/56/_geo_xyz/8,2,2 26.256694794 P _part/56/_geo_xyz/8,3,0 0.030311007 P _part/56/_geo_xyz/8,3,1 0.012555225 P _part/56/_geo_xyz/8,3,2 26.256956100 P _part/56/_geo_xyz/8,4,0 0.032808397 P _part/56/_geo_xyz/8,4,1 -0.000000006 P _part/56/_geo_xyz/8,4,2 26.257219315 P _part/56/_geo_xyz/8,5,0 0.030311005 P _part/56/_geo_xyz/8,5,1 -0.012555238 P _part/56/_geo_xyz/8,5,2 26.257482529 P _part/56/_geo_xyz/8,6,0 0.023199040 P _part/56/_geo_xyz/8,6,1 -0.023199044 P _part/56/_geo_xyz/8,6,2 26.257743835 P _part/56/_geo_xyz/8,7,0 0.012555225 P _part/56/_geo_xyz/8,7,1 -0.030311016 P _part/56/_geo_xyz/8,7,2 26.258007050 P _part/56/_geo_xyz/8,8,0 -0.000000001 P _part/56/_geo_xyz/8,8,1 -0.032808401 P _part/56/_geo_xyz/8,8,2 26.258268356 P _part/56/_geo_xyz/8,9,0 -0.000000001 P _part/56/_geo_xyz/8,9,1 -0.032808401 P _part/56/_geo_xyz/8,9,2 26.258531570 P _part/56/_geo_xyz/9,0,0 0.0 P _part/56/_geo_xyz/9,0,1 0.032808397 P _part/56/_geo_xyz/9,0,2 29.527559280 P _part/56/_geo_xyz/9,1,0 0.012555230 P _part/56/_geo_xyz/9,1,1 0.030311001 P _part/56/_geo_xyz/9,1,2 29.537271500 P _part/56/_geo_xyz/9,10,0 -0.012555226 P _part/56/_geo_xyz/9,10,1 -0.030311013 P _part/56/_geo_xyz/9,10,2 29.539108276 P _part/56/_geo_xyz/9,11,0 -0.023199040 P _part/56/_geo_xyz/9,11,1 -0.023199044 P _part/56/_geo_xyz/9,11,2 29.538846970 P _part/56/_geo_xyz/9,12,0 -0.030311007 P _part/56/_geo_xyz/9,12,1 -0.012555235 P _part/56/_geo_xyz/9,12,2 29.538583755 P _part/56/_geo_xyz/9,13,0 -0.032808397 P _part/56/_geo_xyz/9,13,1 -0.000000006 P _part/56/_geo_xyz/9,13,2 29.538322449 P _part/56/_geo_xyz/9,14,0 -0.030311007 P _part/56/_geo_xyz/9,14,1 0.012555227 P _part/56/_geo_xyz/9,14,2 29.538059235 P _part/56/_geo_xyz/9,15,0 -0.023199039 P _part/56/_geo_xyz/9,15,1 0.023199037 P _part/56/_geo_xyz/9,15,2 29.537796021 P _part/56/_geo_xyz/9,16,0 -0.012555230 P _part/56/_geo_xyz/9,16,1 0.030311001 P _part/56/_geo_xyz/9,16,2 29.537534714 P _part/56/_geo_xyz/9,17,0 0.000000001 P _part/56/_geo_xyz/9,17,1 0.032808397 P _part/56/_geo_xyz/9,17,2 29.527822495 P _part/56/_geo_xyz/9,2,0 0.023199040 P _part/56/_geo_xyz/9,2,1 0.023199035 P _part/56/_geo_xyz/9,2,2 29.537534714 P _part/56/_geo_xyz/9,3,0 0.030311007 P _part/56/_geo_xyz/9,3,1 0.012555225 P _part/56/_geo_xyz/9,3,2 29.537796021 P _part/56/_geo_xyz/9,4,0 0.032808397 P _part/56/_geo_xyz/9,4,1 -0.000000006 P _part/56/_geo_xyz/9,4,2 29.538059235 P _part/56/_geo_xyz/9,5,0 0.030311005 P _part/56/_geo_xyz/9,5,1 -0.012555238 P _part/56/_geo_xyz/9,5,2 29.538322449 P _part/56/_geo_xyz/9,6,0 0.023199040 P _part/56/_geo_xyz/9,6,1 -0.023199044 P _part/56/_geo_xyz/9,6,2 29.538583755 P _part/56/_geo_xyz/9,7,0 0.012555225 P _part/56/_geo_xyz/9,7,1 -0.030311016 P _part/56/_geo_xyz/9,7,2 29.538846970 P _part/56/_geo_xyz/9,8,0 -0.000000001 P _part/56/_geo_xyz/9,8,1 -0.032808401 P _part/56/_geo_xyz/9,8,2 29.539108276 P _part/56/_geo_xyz/9,9,0 -0.000000001 P _part/56/_geo_xyz/9,9,1 -0.032808401 P _part/56/_geo_xyz/9,9,2 29.539371490 P _part/56/_geo_xyz/i_count 20 P _part/56/_geo_xyz/j_count 18 P _part/56/_geo_xyz/k_count 3 P _part/56/_locked/0,0 0 P _part/56/_locked/0,1 0 P _part/56/_locked/0,10 0 P _part/56/_locked/0,11 0 P _part/56/_locked/0,12 0 P _part/56/_locked/0,13 0 P _part/56/_locked/0,14 0 P _part/56/_locked/0,15 0 P _part/56/_locked/0,16 0 P _part/56/_locked/0,17 0 P _part/56/_locked/0,2 0 P _part/56/_locked/0,3 0 P _part/56/_locked/0,4 0 P _part/56/_locked/0,5 0 P _part/56/_locked/0,6 0 P _part/56/_locked/0,7 0 P _part/56/_locked/0,8 0 P _part/56/_locked/0,9 0 P _part/56/_locked/1,0 0 P _part/56/_locked/1,1 0 P _part/56/_locked/1,10 0 P _part/56/_locked/1,11 0 P _part/56/_locked/1,12 0 P _part/56/_locked/1,13 0 P _part/56/_locked/1,14 0 P _part/56/_locked/1,15 0 P _part/56/_locked/1,16 0 P _part/56/_locked/1,17 0 P _part/56/_locked/1,2 0 P _part/56/_locked/1,3 0 P _part/56/_locked/1,4 0 P _part/56/_locked/1,5 0 P _part/56/_locked/1,6 0 P _part/56/_locked/1,7 0 P _part/56/_locked/1,8 0 P _part/56/_locked/1,9 0 P _part/56/_locked/10,0 0 P _part/56/_locked/10,1 0 P _part/56/_locked/10,10 0 P _part/56/_locked/10,11 0 P _part/56/_locked/10,12 0 P _part/56/_locked/10,13 0 P _part/56/_locked/10,14 0 P _part/56/_locked/10,15 0 P _part/56/_locked/10,16 0 P _part/56/_locked/10,17 0 P _part/56/_locked/10,2 0 P _part/56/_locked/10,3 0 P _part/56/_locked/10,4 0 P _part/56/_locked/10,5 0 P _part/56/_locked/10,6 0 P _part/56/_locked/10,7 0 P _part/56/_locked/10,8 0 P _part/56/_locked/10,9 0 P _part/56/_locked/11,0 0 P _part/56/_locked/11,1 0 P _part/56/_locked/11,10 0 P _part/56/_locked/11,11 0 P _part/56/_locked/11,12 0 P _part/56/_locked/11,13 0 P _part/56/_locked/11,14 0 P _part/56/_locked/11,15 0 P _part/56/_locked/11,16 0 P _part/56/_locked/11,17 0 P _part/56/_locked/11,2 0 P _part/56/_locked/11,3 0 P _part/56/_locked/11,4 0 P _part/56/_locked/11,5 0 P _part/56/_locked/11,6 0 P _part/56/_locked/11,7 0 P _part/56/_locked/11,8 0 P _part/56/_locked/11,9 0 P _part/56/_locked/12,0 0 P _part/56/_locked/12,1 0 P _part/56/_locked/12,10 0 P _part/56/_locked/12,11 0 P _part/56/_locked/12,12 0 P _part/56/_locked/12,13 0 P _part/56/_locked/12,14 0 P _part/56/_locked/12,15 0 P _part/56/_locked/12,16 0 P _part/56/_locked/12,17 0 P _part/56/_locked/12,2 0 P _part/56/_locked/12,3 0 P _part/56/_locked/12,4 0 P _part/56/_locked/12,5 0 P _part/56/_locked/12,6 0 P _part/56/_locked/12,7 0 P _part/56/_locked/12,8 0 P _part/56/_locked/12,9 0 P _part/56/_locked/13,0 0 P _part/56/_locked/13,1 0 P _part/56/_locked/13,10 0 P _part/56/_locked/13,11 0 P _part/56/_locked/13,12 0 P _part/56/_locked/13,13 0 P _part/56/_locked/13,14 0 P _part/56/_locked/13,15 0 P _part/56/_locked/13,16 0 P _part/56/_locked/13,17 0 P _part/56/_locked/13,2 0 P _part/56/_locked/13,3 0 P _part/56/_locked/13,4 0 P _part/56/_locked/13,5 0 P _part/56/_locked/13,6 0 P _part/56/_locked/13,7 0 P _part/56/_locked/13,8 0 P _part/56/_locked/13,9 0 P _part/56/_locked/14,0 0 P _part/56/_locked/14,1 0 P _part/56/_locked/14,10 0 P _part/56/_locked/14,11 0 P _part/56/_locked/14,12 0 P _part/56/_locked/14,13 0 P _part/56/_locked/14,14 0 P _part/56/_locked/14,15 0 P _part/56/_locked/14,16 0 P _part/56/_locked/14,17 0 P _part/56/_locked/14,2 0 P _part/56/_locked/14,3 0 P _part/56/_locked/14,4 0 P _part/56/_locked/14,5 0 P _part/56/_locked/14,6 0 P _part/56/_locked/14,7 0 P _part/56/_locked/14,8 0 P _part/56/_locked/14,9 0 P _part/56/_locked/15,0 0 P _part/56/_locked/15,1 0 P _part/56/_locked/15,10 0 P _part/56/_locked/15,11 0 P _part/56/_locked/15,12 0 P _part/56/_locked/15,13 0 P _part/56/_locked/15,14 0 P _part/56/_locked/15,15 0 P _part/56/_locked/15,16 0 P _part/56/_locked/15,17 0 P _part/56/_locked/15,2 0 P _part/56/_locked/15,3 0 P _part/56/_locked/15,4 0 P _part/56/_locked/15,5 0 P _part/56/_locked/15,6 0 P _part/56/_locked/15,7 0 P _part/56/_locked/15,8 0 P _part/56/_locked/15,9 0 P _part/56/_locked/16,0 0 P _part/56/_locked/16,1 0 P _part/56/_locked/16,10 0 P _part/56/_locked/16,11 0 P _part/56/_locked/16,12 0 P _part/56/_locked/16,13 0 P _part/56/_locked/16,14 0 P _part/56/_locked/16,15 0 P _part/56/_locked/16,16 0 P _part/56/_locked/16,17 0 P _part/56/_locked/16,2 0 P _part/56/_locked/16,3 0 P _part/56/_locked/16,4 0 P _part/56/_locked/16,5 0 P _part/56/_locked/16,6 0 P _part/56/_locked/16,7 0 P _part/56/_locked/16,8 0 P _part/56/_locked/16,9 0 P _part/56/_locked/17,0 0 P _part/56/_locked/17,1 0 P _part/56/_locked/17,10 0 P _part/56/_locked/17,11 0 P _part/56/_locked/17,12 0 P _part/56/_locked/17,13 0 P _part/56/_locked/17,14 0 P _part/56/_locked/17,15 0 P _part/56/_locked/17,16 0 P _part/56/_locked/17,17 0 P _part/56/_locked/17,2 0 P _part/56/_locked/17,3 0 P _part/56/_locked/17,4 0 P _part/56/_locked/17,5 0 P _part/56/_locked/17,6 0 P _part/56/_locked/17,7 0 P _part/56/_locked/17,8 0 P _part/56/_locked/17,9 0 P _part/56/_locked/18,0 0 P _part/56/_locked/18,1 0 P _part/56/_locked/18,10 0 P _part/56/_locked/18,11 0 P _part/56/_locked/18,12 0 P _part/56/_locked/18,13 0 P _part/56/_locked/18,14 0 P _part/56/_locked/18,15 0 P _part/56/_locked/18,16 0 P _part/56/_locked/18,17 0 P _part/56/_locked/18,2 0 P _part/56/_locked/18,3 0 P _part/56/_locked/18,4 0 P _part/56/_locked/18,5 0 P _part/56/_locked/18,6 0 P _part/56/_locked/18,7 0 P _part/56/_locked/18,8 0 P _part/56/_locked/18,9 0 P _part/56/_locked/19,0 0 P _part/56/_locked/19,1 0 P _part/56/_locked/19,10 0 P _part/56/_locked/19,11 0 P _part/56/_locked/19,12 0 P _part/56/_locked/19,13 0 P _part/56/_locked/19,14 0 P _part/56/_locked/19,15 0 P _part/56/_locked/19,16 0 P _part/56/_locked/19,17 0 P _part/56/_locked/19,2 0 P _part/56/_locked/19,3 0 P _part/56/_locked/19,4 0 P _part/56/_locked/19,5 0 P _part/56/_locked/19,6 0 P _part/56/_locked/19,7 0 P _part/56/_locked/19,8 0 P _part/56/_locked/19,9 0 P _part/56/_locked/2,0 0 P _part/56/_locked/2,1 0 P _part/56/_locked/2,10 0 P _part/56/_locked/2,11 0 P _part/56/_locked/2,12 0 P _part/56/_locked/2,13 0 P _part/56/_locked/2,14 0 P _part/56/_locked/2,15 0 P _part/56/_locked/2,16 0 P _part/56/_locked/2,17 0 P _part/56/_locked/2,2 0 P _part/56/_locked/2,3 0 P _part/56/_locked/2,4 0 P _part/56/_locked/2,5 0 P _part/56/_locked/2,6 0 P _part/56/_locked/2,7 0 P _part/56/_locked/2,8 0 P _part/56/_locked/2,9 0 P _part/56/_locked/3,0 0 P _part/56/_locked/3,1 0 P _part/56/_locked/3,10 0 P _part/56/_locked/3,11 0 P _part/56/_locked/3,12 0 P _part/56/_locked/3,13 0 P _part/56/_locked/3,14 0 P _part/56/_locked/3,15 0 P _part/56/_locked/3,16 0 P _part/56/_locked/3,17 0 P _part/56/_locked/3,2 0 P _part/56/_locked/3,3 0 P _part/56/_locked/3,4 0 P _part/56/_locked/3,5 0 P _part/56/_locked/3,6 0 P _part/56/_locked/3,7 0 P _part/56/_locked/3,8 0 P _part/56/_locked/3,9 0 P _part/56/_locked/4,0 0 P _part/56/_locked/4,1 0 P _part/56/_locked/4,10 0 P _part/56/_locked/4,11 0 P _part/56/_locked/4,12 0 P _part/56/_locked/4,13 0 P _part/56/_locked/4,14 0 P _part/56/_locked/4,15 0 P _part/56/_locked/4,16 0 P _part/56/_locked/4,17 0 P _part/56/_locked/4,2 0 P _part/56/_locked/4,3 0 P _part/56/_locked/4,4 0 P _part/56/_locked/4,5 0 P _part/56/_locked/4,6 0 P _part/56/_locked/4,7 0 P _part/56/_locked/4,8 0 P _part/56/_locked/4,9 0 P _part/56/_locked/5,0 0 P _part/56/_locked/5,1 0 P _part/56/_locked/5,10 0 P _part/56/_locked/5,11 0 P _part/56/_locked/5,12 0 P _part/56/_locked/5,13 0 P _part/56/_locked/5,14 0 P _part/56/_locked/5,15 0 P _part/56/_locked/5,16 0 P _part/56/_locked/5,17 0 P _part/56/_locked/5,2 0 P _part/56/_locked/5,3 0 P _part/56/_locked/5,4 0 P _part/56/_locked/5,5 0 P _part/56/_locked/5,6 0 P _part/56/_locked/5,7 0 P _part/56/_locked/5,8 0 P _part/56/_locked/5,9 0 P _part/56/_locked/6,0 0 P _part/56/_locked/6,1 0 P _part/56/_locked/6,10 0 P _part/56/_locked/6,11 0 P _part/56/_locked/6,12 0 P _part/56/_locked/6,13 0 P _part/56/_locked/6,14 0 P _part/56/_locked/6,15 0 P _part/56/_locked/6,16 0 P _part/56/_locked/6,17 0 P _part/56/_locked/6,2 0 P _part/56/_locked/6,3 0 P _part/56/_locked/6,4 0 P _part/56/_locked/6,5 0 P _part/56/_locked/6,6 0 P _part/56/_locked/6,7 0 P _part/56/_locked/6,8 0 P _part/56/_locked/6,9 0 P _part/56/_locked/7,0 0 P _part/56/_locked/7,1 0 P _part/56/_locked/7,10 0 P _part/56/_locked/7,11 0 P _part/56/_locked/7,12 0 P _part/56/_locked/7,13 0 P _part/56/_locked/7,14 0 P _part/56/_locked/7,15 0 P _part/56/_locked/7,16 0 P _part/56/_locked/7,17 0 P _part/56/_locked/7,2 0 P _part/56/_locked/7,3 0 P _part/56/_locked/7,4 0 P _part/56/_locked/7,5 0 P _part/56/_locked/7,6 0 P _part/56/_locked/7,7 0 P _part/56/_locked/7,8 0 P _part/56/_locked/7,9 0 P _part/56/_locked/8,0 0 P _part/56/_locked/8,1 0 P _part/56/_locked/8,10 0 P _part/56/_locked/8,11 0 P _part/56/_locked/8,12 0 P _part/56/_locked/8,13 0 P _part/56/_locked/8,14 0 P _part/56/_locked/8,15 0 P _part/56/_locked/8,16 0 P _part/56/_locked/8,17 0 P _part/56/_locked/8,2 0 P _part/56/_locked/8,3 0 P _part/56/_locked/8,4 0 P _part/56/_locked/8,5 0 P _part/56/_locked/8,6 0 P _part/56/_locked/8,7 0 P _part/56/_locked/8,8 0 P _part/56/_locked/8,9 0 P _part/56/_locked/9,0 0 P _part/56/_locked/9,1 0 P _part/56/_locked/9,10 0 P _part/56/_locked/9,11 0 P _part/56/_locked/9,12 0 P _part/56/_locked/9,13 0 P _part/56/_locked/9,14 0 P _part/56/_locked/9,15 0 P _part/56/_locked/9,16 0 P _part/56/_locked/9,17 0 P _part/56/_locked/9,2 0 P _part/56/_locked/9,3 0 P _part/56/_locked/9,4 0 P _part/56/_locked/9,5 0 P _part/56/_locked/9,6 0 P _part/56/_locked/9,7 0 P _part/56/_locked/9,8 0 P _part/56/_locked/9,9 0 P _part/56/_locked/i_count 20 P _part/56/_locked/j_count 18 P _part/56/_part_cd 0.075000003 P _part/56/_part_phi 0.0 P _part/56/_part_psi 0.0 P _part/56/_part_rad 0.360892385 P _part/56/_part_specs_eq 1 P _part/56/_part_specs_invis 0 P _part/56/_part_specs_unused1 0 P _part/56/_part_specs_unused2 0 P _part/56/_part_tex 0 P _part/56/_part_the 0.0 P _part/56/_part_x 0.0 P _part/56/_part_y 0.0 P _part/56/_part_z 0.0 P _part/56/_patt_con 0 P _part/56/_patt_prt 0 P _part/56/_patt_rat 0.0 P _part/56/_r_dim 18 P _part/56/_s_dim 11 P _part/56/_scon 37.676635742 P _part/56/_top_s1 0.0 P _part/56/_top_s2 0.754000008 P _part/56/_top_t1 0.755999982 P _part/56/_top_t2 1.0 P _part/8/_aero_x_os 0.0 P _part/8/_aero_y_os 0.0 P _part/8/_aero_z_os 0.0 P _part/8/_area_frnt 0.0 P _part/8/_area_side 0.0 P _part/8/_area_vert 0.0 P _part/8/_bot_s1 0.755999982 P _part/8/_bot_s2 1.0 P _part/8/_bot_t1 0.0 P _part/8/_bot_t2 0.384000003 P _part/8/_damp 1.883831739 P _part/8/_geo_xyz/0,0,0 0.0 P _part/8/_geo_xyz/0,0,1 0.0 P _part/8/_geo_xyz/0,0,2 -0.820209980 P _part/8/_geo_xyz/0,1,0 0.0 P _part/8/_geo_xyz/0,1,1 0.139720470 P _part/8/_geo_xyz/0,1,2 -0.716699481 P _part/8/_geo_xyz/0,10,0 0.0 P _part/8/_geo_xyz/0,10,1 -0.134547234 P _part/8/_geo_xyz/0,10,2 -0.069849081 P _part/8/_geo_xyz/0,11,0 0.0 P _part/8/_geo_xyz/0,11,1 -0.113846451 P _part/8/_geo_xyz/0,11,2 -0.597703397 P _part/8/_geo_xyz/0,12,0 0.0 P _part/8/_geo_xyz/0,12,1 -0.082795277 P _part/8/_geo_xyz/0,12,2 -0.747768998 P _part/8/_geo_xyz/0,13,0 0.0 P _part/8/_geo_xyz/0,13,1 0.0 P _part/8/_geo_xyz/0,13,2 -0.820209980 P _part/8/_geo_xyz/0,14,0 0.0 P _part/8/_geo_xyz/0,14,1 0.0 P _part/8/_geo_xyz/0,14,2 0.0 P _part/8/_geo_xyz/0,15,0 0.0 P _part/8/_geo_xyz/0,15,1 0.0 P _part/8/_geo_xyz/0,15,2 0.0 P _part/8/_geo_xyz/0,16,0 0.0 P _part/8/_geo_xyz/0,16,1 0.0 P _part/8/_geo_xyz/0,16,2 0.0 P _part/8/_geo_xyz/0,17,0 0.0 P _part/8/_geo_xyz/0,17,1 0.0 P _part/8/_geo_xyz/0,17,2 0.0 P _part/8/_geo_xyz/0,2,0 0.0 P _part/8/_geo_xyz/0,2,1 0.227692902 P _part/8/_geo_xyz/0,2,2 -0.468307078 P _part/8/_geo_xyz/0,3,0 0.0 P _part/8/_geo_xyz/0,3,1 0.263917327 P _part/8/_geo_xyz/0,3,2 -0.018110236 P _part/8/_geo_xyz/0,4,0 0.0 P _part/8/_geo_xyz/0,4,1 0.238043293 P _part/8/_geo_xyz/0,4,2 0.628740191 P _part/8/_geo_xyz/0,5,0 0.0 P _part/8/_geo_xyz/0,5,1 0.213162914 P _part/8/_geo_xyz/0,5,2 0.820209980 P _part/8/_geo_xyz/0,6,0 0.0 P _part/8/_geo_xyz/0,6,1 0.0 P _part/8/_geo_xyz/0,6,2 2.460629940 P _part/8/_geo_xyz/0,7,0 0.0 P _part/8/_geo_xyz/0,7,1 0.0 P _part/8/_geo_xyz/0,7,2 2.460629940 P _part/8/_geo_xyz/0,8,0 0.0 P _part/8/_geo_xyz/0,8,1 -0.092678614 P _part/8/_geo_xyz/0,8,2 0.820209980 P _part/8/_geo_xyz/0,9,0 0.0 P _part/8/_geo_xyz/0,9,1 -0.103496060 P _part/8/_geo_xyz/0,9,2 0.628740191 P _part/8/_geo_xyz/1,0,0 -6.561679840 P _part/8/_geo_xyz/1,0,1 0.0 P _part/8/_geo_xyz/1,0,2 -0.820209980 P _part/8/_geo_xyz/1,1,0 -6.561679840 P _part/8/_geo_xyz/1,1,1 0.139720470 P _part/8/_geo_xyz/1,1,2 -0.716699481 P _part/8/_geo_xyz/1,10,0 -6.561679840 P _part/8/_geo_xyz/1,10,1 -0.134547234 P _part/8/_geo_xyz/1,10,2 -0.069849081 P _part/8/_geo_xyz/1,11,0 -6.561679840 P _part/8/_geo_xyz/1,11,1 -0.113846451 P _part/8/_geo_xyz/1,11,2 -0.597703397 P _part/8/_geo_xyz/1,12,0 -6.561679840 P _part/8/_geo_xyz/1,12,1 -0.082795277 P _part/8/_geo_xyz/1,12,2 -0.747768998 P _part/8/_geo_xyz/1,13,0 -6.561679840 P _part/8/_geo_xyz/1,13,1 0.0 P _part/8/_geo_xyz/1,13,2 -0.820209980 P _part/8/_geo_xyz/1,14,0 0.0 P _part/8/_geo_xyz/1,14,1 0.0 P _part/8/_geo_xyz/1,14,2 0.0 P _part/8/_geo_xyz/1,15,0 0.0 P _part/8/_geo_xyz/1,15,1 0.0 P _part/8/_geo_xyz/1,15,2 0.0 P _part/8/_geo_xyz/1,16,0 0.0 P _part/8/_geo_xyz/1,16,1 0.0 P _part/8/_geo_xyz/1,16,2 0.0 P _part/8/_geo_xyz/1,17,0 0.0 P _part/8/_geo_xyz/1,17,1 0.0 P _part/8/_geo_xyz/1,17,2 0.0 P _part/8/_geo_xyz/1,2,0 -6.561679840 P _part/8/_geo_xyz/1,2,1 0.227692902 P _part/8/_geo_xyz/1,2,2 -0.468307078 P _part/8/_geo_xyz/1,3,0 -6.561679840 P _part/8/_geo_xyz/1,3,1 0.263917327 P _part/8/_geo_xyz/1,3,2 -0.018110236 P _part/8/_geo_xyz/1,4,0 -6.561679840 P _part/8/_geo_xyz/1,4,1 0.238043293 P _part/8/_geo_xyz/1,4,2 0.628740191 P _part/8/_geo_xyz/1,5,0 -6.561679840 P _part/8/_geo_xyz/1,5,1 0.213162914 P _part/8/_geo_xyz/1,5,2 0.820209980 P _part/8/_geo_xyz/1,6,0 -6.561679840 P _part/8/_geo_xyz/1,6,1 0.0 P _part/8/_geo_xyz/1,6,2 2.460629940 P _part/8/_geo_xyz/1,7,0 -6.561679840 P _part/8/_geo_xyz/1,7,1 0.0 P _part/8/_geo_xyz/1,7,2 2.460629940 P _part/8/_geo_xyz/1,8,0 -6.561679840 P _part/8/_geo_xyz/1,8,1 -0.092678614 P _part/8/_geo_xyz/1,8,2 0.820209980 P _part/8/_geo_xyz/1,9,0 -6.561679840 P _part/8/_geo_xyz/1,9,1 -0.103496060 P _part/8/_geo_xyz/1,9,2 0.628740191 P _part/8/_geo_xyz/10,0,0 -32.808399200 P _part/8/_geo_xyz/10,0,1 0.0 P _part/8/_geo_xyz/10,0,2 -0.820209980 P _part/8/_geo_xyz/10,1,0 -32.808399200 P _part/8/_geo_xyz/10,1,1 0.139720470 P _part/8/_geo_xyz/10,1,2 -0.716699481 P _part/8/_geo_xyz/10,10,0 -32.808399200 P _part/8/_geo_xyz/10,10,1 -0.134547234 P _part/8/_geo_xyz/10,10,2 -0.069849081 P _part/8/_geo_xyz/10,11,0 -32.808399200 P _part/8/_geo_xyz/10,11,1 -0.113846451 P _part/8/_geo_xyz/10,11,2 -0.597703397 P _part/8/_geo_xyz/10,12,0 -32.808399200 P _part/8/_geo_xyz/10,12,1 -0.082795277 P _part/8/_geo_xyz/10,12,2 -0.747768998 P _part/8/_geo_xyz/10,13,0 -32.808399200 P _part/8/_geo_xyz/10,13,1 0.0 P _part/8/_geo_xyz/10,13,2 -0.820209980 P _part/8/_geo_xyz/10,14,0 0.0 P _part/8/_geo_xyz/10,14,1 0.0 P _part/8/_geo_xyz/10,14,2 0.0 P _part/8/_geo_xyz/10,15,0 0.0 P _part/8/_geo_xyz/10,15,1 0.0 P _part/8/_geo_xyz/10,15,2 0.0 P _part/8/_geo_xyz/10,16,0 0.0 P _part/8/_geo_xyz/10,16,1 0.0 P _part/8/_geo_xyz/10,16,2 0.0 P _part/8/_geo_xyz/10,17,0 0.0 P _part/8/_geo_xyz/10,17,1 0.0 P _part/8/_geo_xyz/10,17,2 0.0 P _part/8/_geo_xyz/10,2,0 -32.808399200 P _part/8/_geo_xyz/10,2,1 0.227692902 P _part/8/_geo_xyz/10,2,2 -0.468307078 P _part/8/_geo_xyz/10,3,0 -32.808399200 P _part/8/_geo_xyz/10,3,1 0.263917327 P _part/8/_geo_xyz/10,3,2 -0.018110236 P _part/8/_geo_xyz/10,4,0 -32.808399200 P _part/8/_geo_xyz/10,4,1 0.238043293 P _part/8/_geo_xyz/10,4,2 0.628740191 P _part/8/_geo_xyz/10,5,0 -32.808399200 P _part/8/_geo_xyz/10,5,1 0.106581457 P _part/8/_geo_xyz/10,5,2 1.640419960 P _part/8/_geo_xyz/10,6,0 -32.808399200 P _part/8/_geo_xyz/10,6,1 0.0 P _part/8/_geo_xyz/10,6,2 2.460629940 P _part/8/_geo_xyz/10,7,0 -32.808399200 P _part/8/_geo_xyz/10,7,1 0.0 P _part/8/_geo_xyz/10,7,2 2.460629940 P _part/8/_geo_xyz/10,8,0 -32.808399200 P _part/8/_geo_xyz/10,8,1 -0.046339307 P _part/8/_geo_xyz/10,8,2 1.640419960 P _part/8/_geo_xyz/10,9,0 -32.808399200 P _part/8/_geo_xyz/10,9,1 -0.103496060 P _part/8/_geo_xyz/10,9,2 0.628740191 P _part/8/_geo_xyz/11,0,0 -39.370079041 P _part/8/_geo_xyz/11,0,1 0.0 P _part/8/_geo_xyz/11,0,2 -0.820209980 P _part/8/_geo_xyz/11,1,0 -39.370079041 P _part/8/_geo_xyz/11,1,1 0.139720470 P _part/8/_geo_xyz/11,1,2 -0.716699481 P _part/8/_geo_xyz/11,10,0 -39.370079041 P _part/8/_geo_xyz/11,10,1 -0.134547234 P _part/8/_geo_xyz/11,10,2 -0.069849081 P _part/8/_geo_xyz/11,11,0 -39.370079041 P _part/8/_geo_xyz/11,11,1 -0.113846451 P _part/8/_geo_xyz/11,11,2 -0.597703397 P _part/8/_geo_xyz/11,12,0 -39.370079041 P _part/8/_geo_xyz/11,12,1 -0.082795277 P _part/8/_geo_xyz/11,12,2 -0.747768998 P _part/8/_geo_xyz/11,13,0 -39.370079041 P _part/8/_geo_xyz/11,13,1 0.0 P _part/8/_geo_xyz/11,13,2 -0.820209980 P _part/8/_geo_xyz/11,14,0 0.0 P _part/8/_geo_xyz/11,14,1 0.0 P _part/8/_geo_xyz/11,14,2 0.0 P _part/8/_geo_xyz/11,15,0 0.0 P _part/8/_geo_xyz/11,15,1 0.0 P _part/8/_geo_xyz/11,15,2 0.0 P _part/8/_geo_xyz/11,16,0 0.0 P _part/8/_geo_xyz/11,16,1 0.0 P _part/8/_geo_xyz/11,16,2 0.0 P _part/8/_geo_xyz/11,17,0 0.0 P _part/8/_geo_xyz/11,17,1 0.0 P _part/8/_geo_xyz/11,17,2 0.0 P _part/8/_geo_xyz/11,2,0 -39.370079041 P _part/8/_geo_xyz/11,2,1 0.227692902 P _part/8/_geo_xyz/11,2,2 -0.468307078 P _part/8/_geo_xyz/11,3,0 -39.370079041 P _part/8/_geo_xyz/11,3,1 0.263917327 P _part/8/_geo_xyz/11,3,2 -0.018110236 P _part/8/_geo_xyz/11,4,0 -39.370079041 P _part/8/_geo_xyz/11,4,1 0.238043293 P _part/8/_geo_xyz/11,4,2 0.628740191 P _part/8/_geo_xyz/11,5,0 -39.370079041 P _part/8/_geo_xyz/11,5,1 0.106581457 P _part/8/_geo_xyz/11,5,2 1.640419960 P _part/8/_geo_xyz/11,6,0 -39.370079041 P _part/8/_geo_xyz/11,6,1 0.0 P _part/8/_geo_xyz/11,6,2 2.460629940 P _part/8/_geo_xyz/11,7,0 -39.370079041 P _part/8/_geo_xyz/11,7,1 0.0 P _part/8/_geo_xyz/11,7,2 2.460629940 P _part/8/_geo_xyz/11,8,0 -39.370079041 P _part/8/_geo_xyz/11,8,1 -0.046339307 P _part/8/_geo_xyz/11,8,2 1.640419960 P _part/8/_geo_xyz/11,9,0 -39.370079041 P _part/8/_geo_xyz/11,9,1 -0.103496060 P _part/8/_geo_xyz/11,9,2 0.628740191 P _part/8/_geo_xyz/12,0,0 -39.370079041 P _part/8/_geo_xyz/12,0,1 0.0 P _part/8/_geo_xyz/12,0,2 -0.820209980 P _part/8/_geo_xyz/12,1,0 -39.370079041 P _part/8/_geo_xyz/12,1,1 0.139720470 P _part/8/_geo_xyz/12,1,2 -0.716699481 P _part/8/_geo_xyz/12,10,0 -39.370079041 P _part/8/_geo_xyz/12,10,1 -0.134547234 P _part/8/_geo_xyz/12,10,2 -0.069849081 P _part/8/_geo_xyz/12,11,0 -39.370079041 P _part/8/_geo_xyz/12,11,1 -0.113846451 P _part/8/_geo_xyz/12,11,2 -0.597703397 P _part/8/_geo_xyz/12,12,0 -39.370079041 P _part/8/_geo_xyz/12,12,1 -0.082795277 P _part/8/_geo_xyz/12,12,2 -0.747768998 P _part/8/_geo_xyz/12,13,0 -39.370079041 P _part/8/_geo_xyz/12,13,1 0.0 P _part/8/_geo_xyz/12,13,2 -0.820209980 P _part/8/_geo_xyz/12,14,0 0.0 P _part/8/_geo_xyz/12,14,1 0.0 P _part/8/_geo_xyz/12,14,2 0.0 P _part/8/_geo_xyz/12,15,0 0.0 P _part/8/_geo_xyz/12,15,1 0.0 P _part/8/_geo_xyz/12,15,2 0.0 P _part/8/_geo_xyz/12,16,0 0.0 P _part/8/_geo_xyz/12,16,1 0.0 P _part/8/_geo_xyz/12,16,2 0.0 P _part/8/_geo_xyz/12,17,0 0.0 P _part/8/_geo_xyz/12,17,1 0.0 P _part/8/_geo_xyz/12,17,2 0.0 P _part/8/_geo_xyz/12,2,0 -39.370079041 P _part/8/_geo_xyz/12,2,1 0.227692902 P _part/8/_geo_xyz/12,2,2 -0.468307078 P _part/8/_geo_xyz/12,3,0 -39.370079041 P _part/8/_geo_xyz/12,3,1 0.263917327 P _part/8/_geo_xyz/12,3,2 -0.018110236 P _part/8/_geo_xyz/12,4,0 -39.370079041 P _part/8/_geo_xyz/12,4,1 0.238043293 P _part/8/_geo_xyz/12,4,2 0.628740191 P _part/8/_geo_xyz/12,5,0 -39.370079041 P _part/8/_geo_xyz/12,5,1 0.213162914 P _part/8/_geo_xyz/12,5,2 0.820209980 P _part/8/_geo_xyz/12,6,0 -39.370079041 P _part/8/_geo_xyz/12,6,1 0.0 P _part/8/_geo_xyz/12,6,2 2.460629940 P _part/8/_geo_xyz/12,7,0 -39.370079041 P _part/8/_geo_xyz/12,7,1 0.0 P _part/8/_geo_xyz/12,7,2 2.460629940 P _part/8/_geo_xyz/12,8,0 -39.370079041 P _part/8/_geo_xyz/12,8,1 -0.092678614 P _part/8/_geo_xyz/12,8,2 0.820209980 P _part/8/_geo_xyz/12,9,0 -39.370079041 P _part/8/_geo_xyz/12,9,1 -0.103496060 P _part/8/_geo_xyz/12,9,2 0.628740191 P _part/8/_geo_xyz/13,0,0 -45.931758881 P _part/8/_geo_xyz/13,0,1 0.0 P _part/8/_geo_xyz/13,0,2 -0.820209980 P _part/8/_geo_xyz/13,1,0 -45.931758881 P _part/8/_geo_xyz/13,1,1 0.139720470 P _part/8/_geo_xyz/13,1,2 -0.716699481 P _part/8/_geo_xyz/13,10,0 -45.931758881 P _part/8/_geo_xyz/13,10,1 -0.134547234 P _part/8/_geo_xyz/13,10,2 -0.069849081 P _part/8/_geo_xyz/13,11,0 -45.931758881 P _part/8/_geo_xyz/13,11,1 -0.113846451 P _part/8/_geo_xyz/13,11,2 -0.597703397 P _part/8/_geo_xyz/13,12,0 -45.931758881 P _part/8/_geo_xyz/13,12,1 -0.082795277 P _part/8/_geo_xyz/13,12,2 -0.747768998 P _part/8/_geo_xyz/13,13,0 -45.931758881 P _part/8/_geo_xyz/13,13,1 0.0 P _part/8/_geo_xyz/13,13,2 -0.820209980 P _part/8/_geo_xyz/13,14,0 0.0 P _part/8/_geo_xyz/13,14,1 0.0 P _part/8/_geo_xyz/13,14,2 0.0 P _part/8/_geo_xyz/13,15,0 0.0 P _part/8/_geo_xyz/13,15,1 0.0 P _part/8/_geo_xyz/13,15,2 0.0 P _part/8/_geo_xyz/13,16,0 0.0 P _part/8/_geo_xyz/13,16,1 0.0 P _part/8/_geo_xyz/13,16,2 0.0 P _part/8/_geo_xyz/13,17,0 0.0 P _part/8/_geo_xyz/13,17,1 0.0 P _part/8/_geo_xyz/13,17,2 0.0 P _part/8/_geo_xyz/13,2,0 -45.931758881 P _part/8/_geo_xyz/13,2,1 0.227692902 P _part/8/_geo_xyz/13,2,2 -0.468307078 P _part/8/_geo_xyz/13,3,0 -45.931758881 P _part/8/_geo_xyz/13,3,1 0.263917327 P _part/8/_geo_xyz/13,3,2 -0.018110236 P _part/8/_geo_xyz/13,4,0 -45.931758881 P _part/8/_geo_xyz/13,4,1 0.238043293 P _part/8/_geo_xyz/13,4,2 0.628740191 P _part/8/_geo_xyz/13,5,0 -45.931758881 P _part/8/_geo_xyz/13,5,1 0.213162914 P _part/8/_geo_xyz/13,5,2 0.820209980 P _part/8/_geo_xyz/13,6,0 -45.931758881 P _part/8/_geo_xyz/13,6,1 0.0 P _part/8/_geo_xyz/13,6,2 2.460629940 P _part/8/_geo_xyz/13,7,0 -45.931758881 P _part/8/_geo_xyz/13,7,1 0.0 P _part/8/_geo_xyz/13,7,2 2.460629940 P _part/8/_geo_xyz/13,8,0 -45.931758881 P _part/8/_geo_xyz/13,8,1 -0.092678614 P _part/8/_geo_xyz/13,8,2 0.820209980 P _part/8/_geo_xyz/13,9,0 -45.931758881 P _part/8/_geo_xyz/13,9,1 -0.103496060 P _part/8/_geo_xyz/13,9,2 0.628740191 P _part/8/_geo_xyz/14,0,0 -45.931758881 P _part/8/_geo_xyz/14,0,1 0.0 P _part/8/_geo_xyz/14,0,2 -0.820209980 P _part/8/_geo_xyz/14,1,0 -45.931758881 P _part/8/_geo_xyz/14,1,1 0.139720470 P _part/8/_geo_xyz/14,1,2 -0.716699481 P _part/8/_geo_xyz/14,10,0 -45.931758881 P _part/8/_geo_xyz/14,10,1 -0.134547234 P _part/8/_geo_xyz/14,10,2 -0.069849081 P _part/8/_geo_xyz/14,11,0 -45.931758881 P _part/8/_geo_xyz/14,11,1 -0.113846451 P _part/8/_geo_xyz/14,11,2 -0.597703397 P _part/8/_geo_xyz/14,12,0 -45.931758881 P _part/8/_geo_xyz/14,12,1 -0.082795277 P _part/8/_geo_xyz/14,12,2 -0.747768998 P _part/8/_geo_xyz/14,13,0 -45.931758881 P _part/8/_geo_xyz/14,13,1 0.0 P _part/8/_geo_xyz/14,13,2 -0.820209980 P _part/8/_geo_xyz/14,14,0 0.0 P _part/8/_geo_xyz/14,14,1 0.0 P _part/8/_geo_xyz/14,14,2 0.0 P _part/8/_geo_xyz/14,15,0 0.0 P _part/8/_geo_xyz/14,15,1 0.0 P _part/8/_geo_xyz/14,15,2 0.0 P _part/8/_geo_xyz/14,16,0 0.0 P _part/8/_geo_xyz/14,16,1 0.0 P _part/8/_geo_xyz/14,16,2 0.0 P _part/8/_geo_xyz/14,17,0 0.0 P _part/8/_geo_xyz/14,17,1 0.0 P _part/8/_geo_xyz/14,17,2 0.0 P _part/8/_geo_xyz/14,2,0 -45.931758881 P _part/8/_geo_xyz/14,2,1 0.227692902 P _part/8/_geo_xyz/14,2,2 -0.468307078 P _part/8/_geo_xyz/14,3,0 -45.931758881 P _part/8/_geo_xyz/14,3,1 0.263917327 P _part/8/_geo_xyz/14,3,2 -0.018110236 P _part/8/_geo_xyz/14,4,0 -45.931758881 P _part/8/_geo_xyz/14,4,1 0.238043293 P _part/8/_geo_xyz/14,4,2 0.628740191 P _part/8/_geo_xyz/14,5,0 -45.931758881 P _part/8/_geo_xyz/14,5,1 0.213162914 P _part/8/_geo_xyz/14,5,2 0.820209980 P _part/8/_geo_xyz/14,6,0 -45.931758881 P _part/8/_geo_xyz/14,6,1 0.0 P _part/8/_geo_xyz/14,6,2 2.460629940 P _part/8/_geo_xyz/14,7,0 -45.931758881 P _part/8/_geo_xyz/14,7,1 0.0 P _part/8/_geo_xyz/14,7,2 2.460629940 P _part/8/_geo_xyz/14,8,0 -45.931758881 P _part/8/_geo_xyz/14,8,1 -0.092678614 P _part/8/_geo_xyz/14,8,2 0.820209980 P _part/8/_geo_xyz/14,9,0 -45.931758881 P _part/8/_geo_xyz/14,9,1 -0.103496060 P _part/8/_geo_xyz/14,9,2 0.628740191 P _part/8/_geo_xyz/15,0,0 -52.493438721 P _part/8/_geo_xyz/15,0,1 0.0 P _part/8/_geo_xyz/15,0,2 -0.820209980 P _part/8/_geo_xyz/15,1,0 -52.493438721 P _part/8/_geo_xyz/15,1,1 0.139720470 P _part/8/_geo_xyz/15,1,2 -0.716699481 P _part/8/_geo_xyz/15,10,0 -52.493438721 P _part/8/_geo_xyz/15,10,1 -0.134547234 P _part/8/_geo_xyz/15,10,2 -0.069849081 P _part/8/_geo_xyz/15,11,0 -52.493438721 P _part/8/_geo_xyz/15,11,1 -0.113846451 P _part/8/_geo_xyz/15,11,2 -0.597703397 P _part/8/_geo_xyz/15,12,0 -52.493438721 P _part/8/_geo_xyz/15,12,1 -0.082795277 P _part/8/_geo_xyz/15,12,2 -0.747768998 P _part/8/_geo_xyz/15,13,0 -52.493438721 P _part/8/_geo_xyz/15,13,1 0.0 P _part/8/_geo_xyz/15,13,2 -0.820209980 P _part/8/_geo_xyz/15,14,0 0.0 P _part/8/_geo_xyz/15,14,1 0.0 P _part/8/_geo_xyz/15,14,2 0.0 P _part/8/_geo_xyz/15,15,0 0.0 P _part/8/_geo_xyz/15,15,1 0.0 P _part/8/_geo_xyz/15,15,2 0.0 P _part/8/_geo_xyz/15,16,0 0.0 P _part/8/_geo_xyz/15,16,1 0.0 P _part/8/_geo_xyz/15,16,2 0.0 P _part/8/_geo_xyz/15,17,0 0.0 P _part/8/_geo_xyz/15,17,1 0.0 P _part/8/_geo_xyz/15,17,2 0.0 P _part/8/_geo_xyz/15,2,0 -52.493438721 P _part/8/_geo_xyz/15,2,1 0.227692902 P _part/8/_geo_xyz/15,2,2 -0.468307078 P _part/8/_geo_xyz/15,3,0 -52.493438721 P _part/8/_geo_xyz/15,3,1 0.263917327 P _part/8/_geo_xyz/15,3,2 -0.018110236 P _part/8/_geo_xyz/15,4,0 -52.493438721 P _part/8/_geo_xyz/15,4,1 0.238043293 P _part/8/_geo_xyz/15,4,2 0.628740191 P _part/8/_geo_xyz/15,5,0 -52.493438721 P _part/8/_geo_xyz/15,5,1 0.213162914 P _part/8/_geo_xyz/15,5,2 0.820209980 P _part/8/_geo_xyz/15,6,0 -52.493438721 P _part/8/_geo_xyz/15,6,1 0.0 P _part/8/_geo_xyz/15,6,2 2.460629940 P _part/8/_geo_xyz/15,7,0 -52.493438721 P _part/8/_geo_xyz/15,7,1 0.0 P _part/8/_geo_xyz/15,7,2 2.460629940 P _part/8/_geo_xyz/15,8,0 -52.493438721 P _part/8/_geo_xyz/15,8,1 -0.092678614 P _part/8/_geo_xyz/15,8,2 0.820209980 P _part/8/_geo_xyz/15,9,0 -52.493438721 P _part/8/_geo_xyz/15,9,1 -0.103496060 P _part/8/_geo_xyz/15,9,2 0.628740191 P _part/8/_geo_xyz/16,0,0 -52.493438721 P _part/8/_geo_xyz/16,0,1 0.0 P _part/8/_geo_xyz/16,0,2 -0.820209980 P _part/8/_geo_xyz/16,1,0 -52.493438721 P _part/8/_geo_xyz/16,1,1 0.139720470 P _part/8/_geo_xyz/16,1,2 -0.716699481 P _part/8/_geo_xyz/16,10,0 -52.493438721 P _part/8/_geo_xyz/16,10,1 -0.134547234 P _part/8/_geo_xyz/16,10,2 -0.069849081 P _part/8/_geo_xyz/16,11,0 -52.493438721 P _part/8/_geo_xyz/16,11,1 -0.113846451 P _part/8/_geo_xyz/16,11,2 -0.597703397 P _part/8/_geo_xyz/16,12,0 -52.493438721 P _part/8/_geo_xyz/16,12,1 -0.082795277 P _part/8/_geo_xyz/16,12,2 -0.747768998 P _part/8/_geo_xyz/16,13,0 -52.493438721 P _part/8/_geo_xyz/16,13,1 0.0 P _part/8/_geo_xyz/16,13,2 -0.820209980 P _part/8/_geo_xyz/16,14,0 0.0 P _part/8/_geo_xyz/16,14,1 0.0 P _part/8/_geo_xyz/16,14,2 0.0 P _part/8/_geo_xyz/16,15,0 0.0 P _part/8/_geo_xyz/16,15,1 0.0 P _part/8/_geo_xyz/16,15,2 0.0 P _part/8/_geo_xyz/16,16,0 0.0 P _part/8/_geo_xyz/16,16,1 0.0 P _part/8/_geo_xyz/16,16,2 0.0 P _part/8/_geo_xyz/16,17,0 0.0 P _part/8/_geo_xyz/16,17,1 0.0 P _part/8/_geo_xyz/16,17,2 0.0 P _part/8/_geo_xyz/16,2,0 -52.493438721 P _part/8/_geo_xyz/16,2,1 0.227692902 P _part/8/_geo_xyz/16,2,2 -0.468307078 P _part/8/_geo_xyz/16,3,0 -52.493438721 P _part/8/_geo_xyz/16,3,1 0.263917327 P _part/8/_geo_xyz/16,3,2 -0.018110236 P _part/8/_geo_xyz/16,4,0 -52.493438721 P _part/8/_geo_xyz/16,4,1 0.238043293 P _part/8/_geo_xyz/16,4,2 0.628740191 P _part/8/_geo_xyz/16,5,0 -52.493438721 P _part/8/_geo_xyz/16,5,1 0.213162914 P _part/8/_geo_xyz/16,5,2 0.820209980 P _part/8/_geo_xyz/16,6,0 -52.493438721 P _part/8/_geo_xyz/16,6,1 0.0 P _part/8/_geo_xyz/16,6,2 2.460629940 P _part/8/_geo_xyz/16,7,0 -52.493438721 P _part/8/_geo_xyz/16,7,1 0.0 P _part/8/_geo_xyz/16,7,2 2.460629940 P _part/8/_geo_xyz/16,8,0 -52.493438721 P _part/8/_geo_xyz/16,8,1 -0.092678614 P _part/8/_geo_xyz/16,8,2 0.820209980 P _part/8/_geo_xyz/16,9,0 -52.493438721 P _part/8/_geo_xyz/16,9,1 -0.103496060 P _part/8/_geo_xyz/16,9,2 0.628740191 P _part/8/_geo_xyz/17,0,0 0.0 P _part/8/_geo_xyz/17,0,1 0.0 P _part/8/_geo_xyz/17,0,2 0.0 P _part/8/_geo_xyz/17,1,0 0.0 P _part/8/_geo_xyz/17,1,1 0.0 P _part/8/_geo_xyz/17,1,2 0.0 P _part/8/_geo_xyz/17,10,0 0.0 P _part/8/_geo_xyz/17,10,1 0.0 P _part/8/_geo_xyz/17,10,2 0.0 P _part/8/_geo_xyz/17,11,0 0.0 P _part/8/_geo_xyz/17,11,1 0.0 P _part/8/_geo_xyz/17,11,2 0.0 P _part/8/_geo_xyz/17,12,0 0.0 P _part/8/_geo_xyz/17,12,1 0.0 P _part/8/_geo_xyz/17,12,2 0.0 P _part/8/_geo_xyz/17,13,0 0.0 P _part/8/_geo_xyz/17,13,1 0.0 P _part/8/_geo_xyz/17,13,2 0.0 P _part/8/_geo_xyz/17,14,0 0.0 P _part/8/_geo_xyz/17,14,1 0.0 P _part/8/_geo_xyz/17,14,2 0.0 P _part/8/_geo_xyz/17,15,0 0.0 P _part/8/_geo_xyz/17,15,1 0.0 P _part/8/_geo_xyz/17,15,2 0.0 P _part/8/_geo_xyz/17,16,0 0.0 P _part/8/_geo_xyz/17,16,1 0.0 P _part/8/_geo_xyz/17,16,2 0.0 P _part/8/_geo_xyz/17,17,0 0.0 P _part/8/_geo_xyz/17,17,1 0.0 P _part/8/_geo_xyz/17,17,2 0.0 P _part/8/_geo_xyz/17,2,0 0.0 P _part/8/_geo_xyz/17,2,1 0.0 P _part/8/_geo_xyz/17,2,2 0.0 P _part/8/_geo_xyz/17,3,0 0.0 P _part/8/_geo_xyz/17,3,1 0.0 P _part/8/_geo_xyz/17,3,2 0.0 P _part/8/_geo_xyz/17,4,0 0.0 P _part/8/_geo_xyz/17,4,1 0.0 P _part/8/_geo_xyz/17,4,2 0.0 P _part/8/_geo_xyz/17,5,0 0.0 P _part/8/_geo_xyz/17,5,1 0.0 P _part/8/_geo_xyz/17,5,2 0.0 P _part/8/_geo_xyz/17,6,0 0.0 P _part/8/_geo_xyz/17,6,1 0.0 P _part/8/_geo_xyz/17,6,2 0.0 P _part/8/_geo_xyz/17,7,0 0.0 P _part/8/_geo_xyz/17,7,1 0.0 P _part/8/_geo_xyz/17,7,2 0.0 P _part/8/_geo_xyz/17,8,0 0.0 P _part/8/_geo_xyz/17,8,1 0.0 P _part/8/_geo_xyz/17,8,2 0.0 P _part/8/_geo_xyz/17,9,0 0.0 P _part/8/_geo_xyz/17,9,1 0.0 P _part/8/_geo_xyz/17,9,2 0.0 P _part/8/_geo_xyz/18,0,0 0.0 P _part/8/_geo_xyz/18,0,1 0.0 P _part/8/_geo_xyz/18,0,2 0.0 P _part/8/_geo_xyz/18,1,0 0.0 P _part/8/_geo_xyz/18,1,1 0.0 P _part/8/_geo_xyz/18,1,2 0.0 P _part/8/_geo_xyz/18,10,0 0.0 P _part/8/_geo_xyz/18,10,1 0.0 P _part/8/_geo_xyz/18,10,2 0.0 P _part/8/_geo_xyz/18,11,0 0.0 P _part/8/_geo_xyz/18,11,1 0.0 P _part/8/_geo_xyz/18,11,2 0.0 P _part/8/_geo_xyz/18,12,0 0.0 P _part/8/_geo_xyz/18,12,1 0.0 P _part/8/_geo_xyz/18,12,2 0.0 P _part/8/_geo_xyz/18,13,0 0.0 P _part/8/_geo_xyz/18,13,1 0.0 P _part/8/_geo_xyz/18,13,2 0.0 P _part/8/_geo_xyz/18,14,0 0.0 P _part/8/_geo_xyz/18,14,1 0.0 P _part/8/_geo_xyz/18,14,2 0.0 P _part/8/_geo_xyz/18,15,0 0.0 P _part/8/_geo_xyz/18,15,1 0.0 P _part/8/_geo_xyz/18,15,2 0.0 P _part/8/_geo_xyz/18,16,0 0.0 P _part/8/_geo_xyz/18,16,1 0.0 P _part/8/_geo_xyz/18,16,2 0.0 P _part/8/_geo_xyz/18,17,0 0.0 P _part/8/_geo_xyz/18,17,1 0.0 P _part/8/_geo_xyz/18,17,2 0.0 P _part/8/_geo_xyz/18,2,0 0.0 P _part/8/_geo_xyz/18,2,1 0.0 P _part/8/_geo_xyz/18,2,2 0.0 P _part/8/_geo_xyz/18,3,0 0.0 P _part/8/_geo_xyz/18,3,1 0.0 P _part/8/_geo_xyz/18,3,2 0.0 P _part/8/_geo_xyz/18,4,0 0.0 P _part/8/_geo_xyz/18,4,1 0.0 P _part/8/_geo_xyz/18,4,2 0.0 P _part/8/_geo_xyz/18,5,0 0.0 P _part/8/_geo_xyz/18,5,1 0.0 P _part/8/_geo_xyz/18,5,2 0.0 P _part/8/_geo_xyz/18,6,0 0.0 P _part/8/_geo_xyz/18,6,1 0.0 P _part/8/_geo_xyz/18,6,2 0.0 P _part/8/_geo_xyz/18,7,0 0.0 P _part/8/_geo_xyz/18,7,1 0.0 P _part/8/_geo_xyz/18,7,2 0.0 P _part/8/_geo_xyz/18,8,0 0.0 P _part/8/_geo_xyz/18,8,1 0.0 P _part/8/_geo_xyz/18,8,2 0.0 P _part/8/_geo_xyz/18,9,0 0.0 P _part/8/_geo_xyz/18,9,1 0.0 P _part/8/_geo_xyz/18,9,2 0.0 P _part/8/_geo_xyz/19,0,0 0.0 P _part/8/_geo_xyz/19,0,1 0.0 P _part/8/_geo_xyz/19,0,2 0.0 P _part/8/_geo_xyz/19,1,0 0.0 P _part/8/_geo_xyz/19,1,1 0.0 P _part/8/_geo_xyz/19,1,2 0.0 P _part/8/_geo_xyz/19,10,0 0.0 P _part/8/_geo_xyz/19,10,1 0.0 P _part/8/_geo_xyz/19,10,2 0.0 P _part/8/_geo_xyz/19,11,0 0.0 P _part/8/_geo_xyz/19,11,1 0.0 P _part/8/_geo_xyz/19,11,2 0.0 P _part/8/_geo_xyz/19,12,0 0.0 P _part/8/_geo_xyz/19,12,1 0.0 P _part/8/_geo_xyz/19,12,2 0.0 P _part/8/_geo_xyz/19,13,0 0.0 P _part/8/_geo_xyz/19,13,1 0.0 P _part/8/_geo_xyz/19,13,2 0.0 P _part/8/_geo_xyz/19,14,0 0.0 P _part/8/_geo_xyz/19,14,1 0.0 P _part/8/_geo_xyz/19,14,2 0.0 P _part/8/_geo_xyz/19,15,0 0.0 P _part/8/_geo_xyz/19,15,1 0.0 P _part/8/_geo_xyz/19,15,2 0.0 P _part/8/_geo_xyz/19,16,0 0.0 P _part/8/_geo_xyz/19,16,1 0.0 P _part/8/_geo_xyz/19,16,2 0.0 P _part/8/_geo_xyz/19,17,0 0.0 P _part/8/_geo_xyz/19,17,1 0.0 P _part/8/_geo_xyz/19,17,2 0.0 P _part/8/_geo_xyz/19,2,0 0.0 P _part/8/_geo_xyz/19,2,1 0.0 P _part/8/_geo_xyz/19,2,2 0.0 P _part/8/_geo_xyz/19,3,0 0.0 P _part/8/_geo_xyz/19,3,1 0.0 P _part/8/_geo_xyz/19,3,2 0.0 P _part/8/_geo_xyz/19,4,0 0.0 P _part/8/_geo_xyz/19,4,1 0.0 P _part/8/_geo_xyz/19,4,2 0.0 P _part/8/_geo_xyz/19,5,0 0.0 P _part/8/_geo_xyz/19,5,1 0.0 P _part/8/_geo_xyz/19,5,2 0.0 P _part/8/_geo_xyz/19,6,0 0.0 P _part/8/_geo_xyz/19,6,1 0.0 P _part/8/_geo_xyz/19,6,2 0.0 P _part/8/_geo_xyz/19,7,0 0.0 P _part/8/_geo_xyz/19,7,1 0.0 P _part/8/_geo_xyz/19,7,2 0.0 P _part/8/_geo_xyz/19,8,0 0.0 P _part/8/_geo_xyz/19,8,1 0.0 P _part/8/_geo_xyz/19,8,2 0.0 P _part/8/_geo_xyz/19,9,0 0.0 P _part/8/_geo_xyz/19,9,1 0.0 P _part/8/_geo_xyz/19,9,2 0.0 P _part/8/_geo_xyz/2,0,0 -6.561679840 P _part/8/_geo_xyz/2,0,1 0.0 P _part/8/_geo_xyz/2,0,2 -0.820209980 P _part/8/_geo_xyz/2,1,0 -6.561679840 P _part/8/_geo_xyz/2,1,1 0.139720470 P _part/8/_geo_xyz/2,1,2 -0.716699481 P _part/8/_geo_xyz/2,10,0 -6.561679840 P _part/8/_geo_xyz/2,10,1 -0.134547234 P _part/8/_geo_xyz/2,10,2 -0.069849081 P _part/8/_geo_xyz/2,11,0 -6.561679840 P _part/8/_geo_xyz/2,11,1 -0.113846451 P _part/8/_geo_xyz/2,11,2 -0.597703397 P _part/8/_geo_xyz/2,12,0 -6.561679840 P _part/8/_geo_xyz/2,12,1 -0.082795277 P _part/8/_geo_xyz/2,12,2 -0.747768998 P _part/8/_geo_xyz/2,13,0 -6.561679840 P _part/8/_geo_xyz/2,13,1 0.0 P _part/8/_geo_xyz/2,13,2 -0.820209980 P _part/8/_geo_xyz/2,14,0 0.0 P _part/8/_geo_xyz/2,14,1 0.0 P _part/8/_geo_xyz/2,14,2 0.0 P _part/8/_geo_xyz/2,15,0 0.0 P _part/8/_geo_xyz/2,15,1 0.0 P _part/8/_geo_xyz/2,15,2 0.0 P _part/8/_geo_xyz/2,16,0 0.0 P _part/8/_geo_xyz/2,16,1 0.0 P _part/8/_geo_xyz/2,16,2 0.0 P _part/8/_geo_xyz/2,17,0 0.0 P _part/8/_geo_xyz/2,17,1 0.0 P _part/8/_geo_xyz/2,17,2 0.0 P _part/8/_geo_xyz/2,2,0 -6.561679840 P _part/8/_geo_xyz/2,2,1 0.227692902 P _part/8/_geo_xyz/2,2,2 -0.468307078 P _part/8/_geo_xyz/2,3,0 -6.561679840 P _part/8/_geo_xyz/2,3,1 0.263917327 P _part/8/_geo_xyz/2,3,2 -0.018110236 P _part/8/_geo_xyz/2,4,0 -6.561679840 P _part/8/_geo_xyz/2,4,1 0.238043293 P _part/8/_geo_xyz/2,4,2 0.628740191 P _part/8/_geo_xyz/2,5,0 -6.561679840 P _part/8/_geo_xyz/2,5,1 0.106581457 P _part/8/_geo_xyz/2,5,2 1.640419960 P _part/8/_geo_xyz/2,6,0 -6.561679840 P _part/8/_geo_xyz/2,6,1 0.0 P _part/8/_geo_xyz/2,6,2 2.460629940 P _part/8/_geo_xyz/2,7,0 -6.561679840 P _part/8/_geo_xyz/2,7,1 0.0 P _part/8/_geo_xyz/2,7,2 2.460629940 P _part/8/_geo_xyz/2,8,0 -6.561679840 P _part/8/_geo_xyz/2,8,1 -0.046339307 P _part/8/_geo_xyz/2,8,2 1.640419960 P _part/8/_geo_xyz/2,9,0 -6.561679840 P _part/8/_geo_xyz/2,9,1 -0.103496060 P _part/8/_geo_xyz/2,9,2 0.628740191 P _part/8/_geo_xyz/3,0,0 -13.123359680 P _part/8/_geo_xyz/3,0,1 0.0 P _part/8/_geo_xyz/3,0,2 -0.820209980 P _part/8/_geo_xyz/3,1,0 -13.123359680 P _part/8/_geo_xyz/3,1,1 0.139720470 P _part/8/_geo_xyz/3,1,2 -0.716699481 P _part/8/_geo_xyz/3,10,0 -13.123359680 P _part/8/_geo_xyz/3,10,1 -0.134547234 P _part/8/_geo_xyz/3,10,2 -0.069849081 P _part/8/_geo_xyz/3,11,0 -13.123359680 P _part/8/_geo_xyz/3,11,1 -0.113846451 P _part/8/_geo_xyz/3,11,2 -0.597703397 P _part/8/_geo_xyz/3,12,0 -13.123359680 P _part/8/_geo_xyz/3,12,1 -0.082795277 P _part/8/_geo_xyz/3,12,2 -0.747768998 P _part/8/_geo_xyz/3,13,0 -13.123359680 P _part/8/_geo_xyz/3,13,1 0.0 P _part/8/_geo_xyz/3,13,2 -0.820209980 P _part/8/_geo_xyz/3,14,0 0.0 P _part/8/_geo_xyz/3,14,1 0.0 P _part/8/_geo_xyz/3,14,2 0.0 P _part/8/_geo_xyz/3,15,0 0.0 P _part/8/_geo_xyz/3,15,1 0.0 P _part/8/_geo_xyz/3,15,2 0.0 P _part/8/_geo_xyz/3,16,0 0.0 P _part/8/_geo_xyz/3,16,1 0.0 P _part/8/_geo_xyz/3,16,2 0.0 P _part/8/_geo_xyz/3,17,0 0.0 P _part/8/_geo_xyz/3,17,1 0.0 P _part/8/_geo_xyz/3,17,2 0.0 P _part/8/_geo_xyz/3,2,0 -13.123359680 P _part/8/_geo_xyz/3,2,1 0.227692902 P _part/8/_geo_xyz/3,2,2 -0.468307078 P _part/8/_geo_xyz/3,3,0 -13.123359680 P _part/8/_geo_xyz/3,3,1 0.263917327 P _part/8/_geo_xyz/3,3,2 -0.018110236 P _part/8/_geo_xyz/3,4,0 -13.123359680 P _part/8/_geo_xyz/3,4,1 0.238043293 P _part/8/_geo_xyz/3,4,2 0.628740191 P _part/8/_geo_xyz/3,5,0 -13.123359680 P _part/8/_geo_xyz/3,5,1 0.106581457 P _part/8/_geo_xyz/3,5,2 1.640419960 P _part/8/_geo_xyz/3,6,0 -13.123359680 P _part/8/_geo_xyz/3,6,1 0.0 P _part/8/_geo_xyz/3,6,2 2.460629940 P _part/8/_geo_xyz/3,7,0 -13.123359680 P _part/8/_geo_xyz/3,7,1 0.0 P _part/8/_geo_xyz/3,7,2 2.460629940 P _part/8/_geo_xyz/3,8,0 -13.123359680 P _part/8/_geo_xyz/3,8,1 -0.046339307 P _part/8/_geo_xyz/3,8,2 1.640419960 P _part/8/_geo_xyz/3,9,0 -13.123359680 P _part/8/_geo_xyz/3,9,1 -0.103496060 P _part/8/_geo_xyz/3,9,2 0.628740191 P _part/8/_geo_xyz/4,0,0 -13.123359680 P _part/8/_geo_xyz/4,0,1 0.0 P _part/8/_geo_xyz/4,0,2 -0.820209980 P _part/8/_geo_xyz/4,1,0 -13.123359680 P _part/8/_geo_xyz/4,1,1 0.139720470 P _part/8/_geo_xyz/4,1,2 -0.716699481 P _part/8/_geo_xyz/4,10,0 -13.123359680 P _part/8/_geo_xyz/4,10,1 -0.134547234 P _part/8/_geo_xyz/4,10,2 -0.069849081 P _part/8/_geo_xyz/4,11,0 -13.123359680 P _part/8/_geo_xyz/4,11,1 -0.113846451 P _part/8/_geo_xyz/4,11,2 -0.597703397 P _part/8/_geo_xyz/4,12,0 -13.123359680 P _part/8/_geo_xyz/4,12,1 -0.082795277 P _part/8/_geo_xyz/4,12,2 -0.747768998 P _part/8/_geo_xyz/4,13,0 -13.123359680 P _part/8/_geo_xyz/4,13,1 0.0 P _part/8/_geo_xyz/4,13,2 -0.820209980 P _part/8/_geo_xyz/4,14,0 0.0 P _part/8/_geo_xyz/4,14,1 0.0 P _part/8/_geo_xyz/4,14,2 0.0 P _part/8/_geo_xyz/4,15,0 0.0 P _part/8/_geo_xyz/4,15,1 0.0 P _part/8/_geo_xyz/4,15,2 0.0 P _part/8/_geo_xyz/4,16,0 0.0 P _part/8/_geo_xyz/4,16,1 0.0 P _part/8/_geo_xyz/4,16,2 0.0 P _part/8/_geo_xyz/4,17,0 0.0 P _part/8/_geo_xyz/4,17,1 0.0 P _part/8/_geo_xyz/4,17,2 0.0 P _part/8/_geo_xyz/4,2,0 -13.123359680 P _part/8/_geo_xyz/4,2,1 0.227692902 P _part/8/_geo_xyz/4,2,2 -0.468307078 P _part/8/_geo_xyz/4,3,0 -13.123359680 P _part/8/_geo_xyz/4,3,1 0.263917327 P _part/8/_geo_xyz/4,3,2 -0.018110236 P _part/8/_geo_xyz/4,4,0 -13.123359680 P _part/8/_geo_xyz/4,4,1 0.238043293 P _part/8/_geo_xyz/4,4,2 0.628740191 P _part/8/_geo_xyz/4,5,0 -13.123359680 P _part/8/_geo_xyz/4,5,1 0.106581457 P _part/8/_geo_xyz/4,5,2 1.640419960 P _part/8/_geo_xyz/4,6,0 -13.123359680 P _part/8/_geo_xyz/4,6,1 0.0 P _part/8/_geo_xyz/4,6,2 2.460629940 P _part/8/_geo_xyz/4,7,0 -13.123359680 P _part/8/_geo_xyz/4,7,1 0.0 P _part/8/_geo_xyz/4,7,2 2.460629940 P _part/8/_geo_xyz/4,8,0 -13.123359680 P _part/8/_geo_xyz/4,8,1 -0.046339307 P _part/8/_geo_xyz/4,8,2 1.640419960 P _part/8/_geo_xyz/4,9,0 -13.123359680 P _part/8/_geo_xyz/4,9,1 -0.103496060 P _part/8/_geo_xyz/4,9,2 0.628740191 P _part/8/_geo_xyz/5,0,0 -19.685039520 P _part/8/_geo_xyz/5,0,1 0.0 P _part/8/_geo_xyz/5,0,2 -0.820209980 P _part/8/_geo_xyz/5,1,0 -19.685039520 P _part/8/_geo_xyz/5,1,1 0.139720470 P _part/8/_geo_xyz/5,1,2 -0.716699481 P _part/8/_geo_xyz/5,10,0 -19.685039520 P _part/8/_geo_xyz/5,10,1 -0.134547234 P _part/8/_geo_xyz/5,10,2 -0.069849081 P _part/8/_geo_xyz/5,11,0 -19.685039520 P _part/8/_geo_xyz/5,11,1 -0.113846451 P _part/8/_geo_xyz/5,11,2 -0.597703397 P _part/8/_geo_xyz/5,12,0 -19.685039520 P _part/8/_geo_xyz/5,12,1 -0.082795277 P _part/8/_geo_xyz/5,12,2 -0.747768998 P _part/8/_geo_xyz/5,13,0 -19.685039520 P _part/8/_geo_xyz/5,13,1 0.0 P _part/8/_geo_xyz/5,13,2 -0.820209980 P _part/8/_geo_xyz/5,14,0 0.0 P _part/8/_geo_xyz/5,14,1 0.0 P _part/8/_geo_xyz/5,14,2 0.0 P _part/8/_geo_xyz/5,15,0 0.0 P _part/8/_geo_xyz/5,15,1 0.0 P _part/8/_geo_xyz/5,15,2 0.0 P _part/8/_geo_xyz/5,16,0 0.0 P _part/8/_geo_xyz/5,16,1 0.0 P _part/8/_geo_xyz/5,16,2 0.0 P _part/8/_geo_xyz/5,17,0 0.0 P _part/8/_geo_xyz/5,17,1 0.0 P _part/8/_geo_xyz/5,17,2 0.0 P _part/8/_geo_xyz/5,2,0 -19.685039520 P _part/8/_geo_xyz/5,2,1 0.227692902 P _part/8/_geo_xyz/5,2,2 -0.468307078 P _part/8/_geo_xyz/5,3,0 -19.685039520 P _part/8/_geo_xyz/5,3,1 0.263917327 P _part/8/_geo_xyz/5,3,2 -0.018110236 P _part/8/_geo_xyz/5,4,0 -19.685039520 P _part/8/_geo_xyz/5,4,1 0.238043293 P _part/8/_geo_xyz/5,4,2 0.628740191 P _part/8/_geo_xyz/5,5,0 -19.685039520 P _part/8/_geo_xyz/5,5,1 0.106581457 P _part/8/_geo_xyz/5,5,2 1.640419960 P _part/8/_geo_xyz/5,6,0 -19.685039520 P _part/8/_geo_xyz/5,6,1 0.0 P _part/8/_geo_xyz/5,6,2 2.460629940 P _part/8/_geo_xyz/5,7,0 -19.685039520 P _part/8/_geo_xyz/5,7,1 0.0 P _part/8/_geo_xyz/5,7,2 2.460629940 P _part/8/_geo_xyz/5,8,0 -19.685039520 P _part/8/_geo_xyz/5,8,1 -0.046339307 P _part/8/_geo_xyz/5,8,2 1.640419960 P _part/8/_geo_xyz/5,9,0 -19.685039520 P _part/8/_geo_xyz/5,9,1 -0.103496060 P _part/8/_geo_xyz/5,9,2 0.628740191 P _part/8/_geo_xyz/6,0,0 -19.685039520 P _part/8/_geo_xyz/6,0,1 0.0 P _part/8/_geo_xyz/6,0,2 -0.820209980 P _part/8/_geo_xyz/6,1,0 -19.685039520 P _part/8/_geo_xyz/6,1,1 0.139720470 P _part/8/_geo_xyz/6,1,2 -0.716699481 P _part/8/_geo_xyz/6,10,0 -19.685039520 P _part/8/_geo_xyz/6,10,1 -0.134547234 P _part/8/_geo_xyz/6,10,2 -0.069849081 P _part/8/_geo_xyz/6,11,0 -19.685039520 P _part/8/_geo_xyz/6,11,1 -0.113846451 P _part/8/_geo_xyz/6,11,2 -0.597703397 P _part/8/_geo_xyz/6,12,0 -19.685039520 P _part/8/_geo_xyz/6,12,1 -0.082795277 P _part/8/_geo_xyz/6,12,2 -0.747768998 P _part/8/_geo_xyz/6,13,0 -19.685039520 P _part/8/_geo_xyz/6,13,1 0.0 P _part/8/_geo_xyz/6,13,2 -0.820209980 P _part/8/_geo_xyz/6,14,0 0.0 P _part/8/_geo_xyz/6,14,1 0.0 P _part/8/_geo_xyz/6,14,2 0.0 P _part/8/_geo_xyz/6,15,0 0.0 P _part/8/_geo_xyz/6,15,1 0.0 P _part/8/_geo_xyz/6,15,2 0.0 P _part/8/_geo_xyz/6,16,0 0.0 P _part/8/_geo_xyz/6,16,1 0.0 P _part/8/_geo_xyz/6,16,2 0.0 P _part/8/_geo_xyz/6,17,0 0.0 P _part/8/_geo_xyz/6,17,1 0.0 P _part/8/_geo_xyz/6,17,2 0.0 P _part/8/_geo_xyz/6,2,0 -19.685039520 P _part/8/_geo_xyz/6,2,1 0.227692902 P _part/8/_geo_xyz/6,2,2 -0.468307078 P _part/8/_geo_xyz/6,3,0 -19.685039520 P _part/8/_geo_xyz/6,3,1 0.263917327 P _part/8/_geo_xyz/6,3,2 -0.018110236 P _part/8/_geo_xyz/6,4,0 -19.685039520 P _part/8/_geo_xyz/6,4,1 0.238043293 P _part/8/_geo_xyz/6,4,2 0.628740191 P _part/8/_geo_xyz/6,5,0 -19.685039520 P _part/8/_geo_xyz/6,5,1 0.106581457 P _part/8/_geo_xyz/6,5,2 1.640419960 P _part/8/_geo_xyz/6,6,0 -19.685039520 P _part/8/_geo_xyz/6,6,1 0.0 P _part/8/_geo_xyz/6,6,2 2.460629940 P _part/8/_geo_xyz/6,7,0 -19.685039520 P _part/8/_geo_xyz/6,7,1 0.0 P _part/8/_geo_xyz/6,7,2 2.460629940 P _part/8/_geo_xyz/6,8,0 -19.685039520 P _part/8/_geo_xyz/6,8,1 -0.046339307 P _part/8/_geo_xyz/6,8,2 1.640419960 P _part/8/_geo_xyz/6,9,0 -19.685039520 P _part/8/_geo_xyz/6,9,1 -0.103496060 P _part/8/_geo_xyz/6,9,2 0.628740191 P _part/8/_geo_xyz/7,0,0 -26.246719360 P _part/8/_geo_xyz/7,0,1 0.0 P _part/8/_geo_xyz/7,0,2 -0.820209980 P _part/8/_geo_xyz/7,1,0 -26.246719360 P _part/8/_geo_xyz/7,1,1 0.139720470 P _part/8/_geo_xyz/7,1,2 -0.716699481 P _part/8/_geo_xyz/7,10,0 -26.246719360 P _part/8/_geo_xyz/7,10,1 -0.134547234 P _part/8/_geo_xyz/7,10,2 -0.069849081 P _part/8/_geo_xyz/7,11,0 -26.246719360 P _part/8/_geo_xyz/7,11,1 -0.113846451 P _part/8/_geo_xyz/7,11,2 -0.597703397 P _part/8/_geo_xyz/7,12,0 -26.246719360 P _part/8/_geo_xyz/7,12,1 -0.082795277 P _part/8/_geo_xyz/7,12,2 -0.747768998 P _part/8/_geo_xyz/7,13,0 -26.246719360 P _part/8/_geo_xyz/7,13,1 0.0 P _part/8/_geo_xyz/7,13,2 -0.820209980 P _part/8/_geo_xyz/7,14,0 0.0 P _part/8/_geo_xyz/7,14,1 0.0 P _part/8/_geo_xyz/7,14,2 0.0 P _part/8/_geo_xyz/7,15,0 0.0 P _part/8/_geo_xyz/7,15,1 0.0 P _part/8/_geo_xyz/7,15,2 0.0 P _part/8/_geo_xyz/7,16,0 0.0 P _part/8/_geo_xyz/7,16,1 0.0 P _part/8/_geo_xyz/7,16,2 0.0 P _part/8/_geo_xyz/7,17,0 0.0 P _part/8/_geo_xyz/7,17,1 0.0 P _part/8/_geo_xyz/7,17,2 0.0 P _part/8/_geo_xyz/7,2,0 -26.246719360 P _part/8/_geo_xyz/7,2,1 0.227692902 P _part/8/_geo_xyz/7,2,2 -0.468307078 P _part/8/_geo_xyz/7,3,0 -26.246719360 P _part/8/_geo_xyz/7,3,1 0.263917327 P _part/8/_geo_xyz/7,3,2 -0.018110236 P _part/8/_geo_xyz/7,4,0 -26.246719360 P _part/8/_geo_xyz/7,4,1 0.238043293 P _part/8/_geo_xyz/7,4,2 0.628740191 P _part/8/_geo_xyz/7,5,0 -26.246719360 P _part/8/_geo_xyz/7,5,1 0.106581457 P _part/8/_geo_xyz/7,5,2 1.640419960 P _part/8/_geo_xyz/7,6,0 -26.246719360 P _part/8/_geo_xyz/7,6,1 0.0 P _part/8/_geo_xyz/7,6,2 2.460629940 P _part/8/_geo_xyz/7,7,0 -26.246719360 P _part/8/_geo_xyz/7,7,1 0.0 P _part/8/_geo_xyz/7,7,2 2.460629940 P _part/8/_geo_xyz/7,8,0 -26.246719360 P _part/8/_geo_xyz/7,8,1 -0.046339307 P _part/8/_geo_xyz/7,8,2 1.640419960 P _part/8/_geo_xyz/7,9,0 -26.246719360 P _part/8/_geo_xyz/7,9,1 -0.103496060 P _part/8/_geo_xyz/7,9,2 0.628740191 P _part/8/_geo_xyz/8,0,0 -26.246719360 P _part/8/_geo_xyz/8,0,1 0.0 P _part/8/_geo_xyz/8,0,2 -0.820209980 P _part/8/_geo_xyz/8,1,0 -26.246719360 P _part/8/_geo_xyz/8,1,1 0.139720470 P _part/8/_geo_xyz/8,1,2 -0.716699481 P _part/8/_geo_xyz/8,10,0 -26.246719360 P _part/8/_geo_xyz/8,10,1 -0.134547234 P _part/8/_geo_xyz/8,10,2 -0.069849081 P _part/8/_geo_xyz/8,11,0 -26.246719360 P _part/8/_geo_xyz/8,11,1 -0.113846451 P _part/8/_geo_xyz/8,11,2 -0.597703397 P _part/8/_geo_xyz/8,12,0 -26.246719360 P _part/8/_geo_xyz/8,12,1 -0.082795277 P _part/8/_geo_xyz/8,12,2 -0.747768998 P _part/8/_geo_xyz/8,13,0 -26.246719360 P _part/8/_geo_xyz/8,13,1 0.0 P _part/8/_geo_xyz/8,13,2 -0.820209980 P _part/8/_geo_xyz/8,14,0 0.0 P _part/8/_geo_xyz/8,14,1 0.0 P _part/8/_geo_xyz/8,14,2 0.0 P _part/8/_geo_xyz/8,15,0 0.0 P _part/8/_geo_xyz/8,15,1 0.0 P _part/8/_geo_xyz/8,15,2 0.0 P _part/8/_geo_xyz/8,16,0 0.0 P _part/8/_geo_xyz/8,16,1 0.0 P _part/8/_geo_xyz/8,16,2 0.0 P _part/8/_geo_xyz/8,17,0 0.0 P _part/8/_geo_xyz/8,17,1 0.0 P _part/8/_geo_xyz/8,17,2 0.0 P _part/8/_geo_xyz/8,2,0 -26.246719360 P _part/8/_geo_xyz/8,2,1 0.227692902 P _part/8/_geo_xyz/8,2,2 -0.468307078 P _part/8/_geo_xyz/8,3,0 -26.246719360 P _part/8/_geo_xyz/8,3,1 0.263917327 P _part/8/_geo_xyz/8,3,2 -0.018110236 P _part/8/_geo_xyz/8,4,0 -26.246719360 P _part/8/_geo_xyz/8,4,1 0.238043293 P _part/8/_geo_xyz/8,4,2 0.628740191 P _part/8/_geo_xyz/8,5,0 -26.246719360 P _part/8/_geo_xyz/8,5,1 0.106581457 P _part/8/_geo_xyz/8,5,2 1.640419960 P _part/8/_geo_xyz/8,6,0 -26.246719360 P _part/8/_geo_xyz/8,6,1 0.0 P _part/8/_geo_xyz/8,6,2 2.460629940 P _part/8/_geo_xyz/8,7,0 -26.246719360 P _part/8/_geo_xyz/8,7,1 0.0 P _part/8/_geo_xyz/8,7,2 2.460629940 P _part/8/_geo_xyz/8,8,0 -26.246719360 P _part/8/_geo_xyz/8,8,1 -0.046339307 P _part/8/_geo_xyz/8,8,2 1.640419960 P _part/8/_geo_xyz/8,9,0 -26.246719360 P _part/8/_geo_xyz/8,9,1 -0.103496060 P _part/8/_geo_xyz/8,9,2 0.628740191 P _part/8/_geo_xyz/9,0,0 -32.808399200 P _part/8/_geo_xyz/9,0,1 0.0 P _part/8/_geo_xyz/9,0,2 -0.820209980 P _part/8/_geo_xyz/9,1,0 -32.808399200 P _part/8/_geo_xyz/9,1,1 0.139720470 P _part/8/_geo_xyz/9,1,2 -0.716699481 P _part/8/_geo_xyz/9,10,0 -32.808399200 P _part/8/_geo_xyz/9,10,1 -0.134547234 P _part/8/_geo_xyz/9,10,2 -0.069849081 P _part/8/_geo_xyz/9,11,0 -32.808399200 P _part/8/_geo_xyz/9,11,1 -0.113846451 P _part/8/_geo_xyz/9,11,2 -0.597703397 P _part/8/_geo_xyz/9,12,0 -32.808399200 P _part/8/_geo_xyz/9,12,1 -0.082795277 P _part/8/_geo_xyz/9,12,2 -0.747768998 P _part/8/_geo_xyz/9,13,0 -32.808399200 P _part/8/_geo_xyz/9,13,1 0.0 P _part/8/_geo_xyz/9,13,2 -0.820209980 P _part/8/_geo_xyz/9,14,0 0.0 P _part/8/_geo_xyz/9,14,1 0.0 P _part/8/_geo_xyz/9,14,2 0.0 P _part/8/_geo_xyz/9,15,0 0.0 P _part/8/_geo_xyz/9,15,1 0.0 P _part/8/_geo_xyz/9,15,2 0.0 P _part/8/_geo_xyz/9,16,0 0.0 P _part/8/_geo_xyz/9,16,1 0.0 P _part/8/_geo_xyz/9,16,2 0.0 P _part/8/_geo_xyz/9,17,0 0.0 P _part/8/_geo_xyz/9,17,1 0.0 P _part/8/_geo_xyz/9,17,2 0.0 P _part/8/_geo_xyz/9,2,0 -32.808399200 P _part/8/_geo_xyz/9,2,1 0.227692902 P _part/8/_geo_xyz/9,2,2 -0.468307078 P _part/8/_geo_xyz/9,3,0 -32.808399200 P _part/8/_geo_xyz/9,3,1 0.263917327 P _part/8/_geo_xyz/9,3,2 -0.018110236 P _part/8/_geo_xyz/9,4,0 -32.808399200 P _part/8/_geo_xyz/9,4,1 0.238043293 P _part/8/_geo_xyz/9,4,2 0.628740191 P _part/8/_geo_xyz/9,5,0 -32.808399200 P _part/8/_geo_xyz/9,5,1 0.106581457 P _part/8/_geo_xyz/9,5,2 1.640419960 P _part/8/_geo_xyz/9,6,0 -32.808399200 P _part/8/_geo_xyz/9,6,1 0.0 P _part/8/_geo_xyz/9,6,2 2.460629940 P _part/8/_geo_xyz/9,7,0 -32.808399200 P _part/8/_geo_xyz/9,7,1 0.0 P _part/8/_geo_xyz/9,7,2 2.460629940 P _part/8/_geo_xyz/9,8,0 -32.808399200 P _part/8/_geo_xyz/9,8,1 -0.046339307 P _part/8/_geo_xyz/9,8,2 1.640419960 P _part/8/_geo_xyz/9,9,0 -32.808399200 P _part/8/_geo_xyz/9,9,1 -0.103496060 P _part/8/_geo_xyz/9,9,2 0.628740191 P _part/8/_geo_xyz/i_count 20 P _part/8/_geo_xyz/j_count 18 P _part/8/_geo_xyz/k_count 3 P _part/8/_locked/0,0 0 P _part/8/_locked/0,1 0 P _part/8/_locked/0,10 0 P _part/8/_locked/0,11 0 P _part/8/_locked/0,12 0 P _part/8/_locked/0,13 0 P _part/8/_locked/0,14 0 P _part/8/_locked/0,15 0 P _part/8/_locked/0,16 0 P _part/8/_locked/0,17 0 P _part/8/_locked/0,2 0 P _part/8/_locked/0,3 0 P _part/8/_locked/0,4 0 P _part/8/_locked/0,5 0 P _part/8/_locked/0,6 0 P _part/8/_locked/0,7 0 P _part/8/_locked/0,8 0 P _part/8/_locked/0,9 0 P _part/8/_locked/1,0 0 P _part/8/_locked/1,1 0 P _part/8/_locked/1,10 0 P _part/8/_locked/1,11 0 P _part/8/_locked/1,12 0 P _part/8/_locked/1,13 0 P _part/8/_locked/1,14 0 P _part/8/_locked/1,15 0 P _part/8/_locked/1,16 0 P _part/8/_locked/1,17 0 P _part/8/_locked/1,2 0 P _part/8/_locked/1,3 0 P _part/8/_locked/1,4 0 P _part/8/_locked/1,5 0 P _part/8/_locked/1,6 0 P _part/8/_locked/1,7 0 P _part/8/_locked/1,8 0 P _part/8/_locked/1,9 0 P _part/8/_locked/10,0 0 P _part/8/_locked/10,1 0 P _part/8/_locked/10,10 0 P _part/8/_locked/10,11 0 P _part/8/_locked/10,12 0 P _part/8/_locked/10,13 0 P _part/8/_locked/10,14 0 P _part/8/_locked/10,15 0 P _part/8/_locked/10,16 0 P _part/8/_locked/10,17 0 P _part/8/_locked/10,2 0 P _part/8/_locked/10,3 0 P _part/8/_locked/10,4 0 P _part/8/_locked/10,5 0 P _part/8/_locked/10,6 0 P _part/8/_locked/10,7 0 P _part/8/_locked/10,8 0 P _part/8/_locked/10,9 0 P _part/8/_locked/11,0 0 P _part/8/_locked/11,1 0 P _part/8/_locked/11,10 0 P _part/8/_locked/11,11 0 P _part/8/_locked/11,12 0 P _part/8/_locked/11,13 0 P _part/8/_locked/11,14 0 P _part/8/_locked/11,15 0 P _part/8/_locked/11,16 0 P _part/8/_locked/11,17 0 P _part/8/_locked/11,2 0 P _part/8/_locked/11,3 0 P _part/8/_locked/11,4 0 P _part/8/_locked/11,5 0 P _part/8/_locked/11,6 0 P _part/8/_locked/11,7 0 P _part/8/_locked/11,8 0 P _part/8/_locked/11,9 0 P _part/8/_locked/12,0 0 P _part/8/_locked/12,1 0 P _part/8/_locked/12,10 0 P _part/8/_locked/12,11 0 P _part/8/_locked/12,12 0 P _part/8/_locked/12,13 0 P _part/8/_locked/12,14 0 P _part/8/_locked/12,15 0 P _part/8/_locked/12,16 0 P _part/8/_locked/12,17 0 P _part/8/_locked/12,2 0 P _part/8/_locked/12,3 0 P _part/8/_locked/12,4 0 P _part/8/_locked/12,5 0 P _part/8/_locked/12,6 0 P _part/8/_locked/12,7 0 P _part/8/_locked/12,8 0 P _part/8/_locked/12,9 0 P _part/8/_locked/13,0 0 P _part/8/_locked/13,1 0 P _part/8/_locked/13,10 0 P _part/8/_locked/13,11 0 P _part/8/_locked/13,12 0 P _part/8/_locked/13,13 0 P _part/8/_locked/13,14 0 P _part/8/_locked/13,15 0 P _part/8/_locked/13,16 0 P _part/8/_locked/13,17 0 P _part/8/_locked/13,2 0 P _part/8/_locked/13,3 0 P _part/8/_locked/13,4 0 P _part/8/_locked/13,5 0 P _part/8/_locked/13,6 0 P _part/8/_locked/13,7 0 P _part/8/_locked/13,8 0 P _part/8/_locked/13,9 0 P _part/8/_locked/14,0 0 P _part/8/_locked/14,1 0 P _part/8/_locked/14,10 0 P _part/8/_locked/14,11 0 P _part/8/_locked/14,12 0 P _part/8/_locked/14,13 0 P _part/8/_locked/14,14 0 P _part/8/_locked/14,15 0 P _part/8/_locked/14,16 0 P _part/8/_locked/14,17 0 P _part/8/_locked/14,2 0 P _part/8/_locked/14,3 0 P _part/8/_locked/14,4 0 P _part/8/_locked/14,5 0 P _part/8/_locked/14,6 0 P _part/8/_locked/14,7 0 P _part/8/_locked/14,8 0 P _part/8/_locked/14,9 0 P _part/8/_locked/15,0 0 P _part/8/_locked/15,1 0 P _part/8/_locked/15,10 0 P _part/8/_locked/15,11 0 P _part/8/_locked/15,12 0 P _part/8/_locked/15,13 0 P _part/8/_locked/15,14 0 P _part/8/_locked/15,15 0 P _part/8/_locked/15,16 0 P _part/8/_locked/15,17 0 P _part/8/_locked/15,2 0 P _part/8/_locked/15,3 0 P _part/8/_locked/15,4 0 P _part/8/_locked/15,5 0 P _part/8/_locked/15,6 0 P _part/8/_locked/15,7 0 P _part/8/_locked/15,8 0 P _part/8/_locked/15,9 0 P _part/8/_locked/16,0 0 P _part/8/_locked/16,1 0 P _part/8/_locked/16,10 0 P _part/8/_locked/16,11 0 P _part/8/_locked/16,12 0 P _part/8/_locked/16,13 0 P _part/8/_locked/16,14 0 P _part/8/_locked/16,15 0 P _part/8/_locked/16,16 0 P _part/8/_locked/16,17 0 P _part/8/_locked/16,2 0 P _part/8/_locked/16,3 0 P _part/8/_locked/16,4 0 P _part/8/_locked/16,5 0 P _part/8/_locked/16,6 0 P _part/8/_locked/16,7 0 P _part/8/_locked/16,8 0 P _part/8/_locked/16,9 0 P _part/8/_locked/17,0 0 P _part/8/_locked/17,1 0 P _part/8/_locked/17,10 0 P _part/8/_locked/17,11 0 P _part/8/_locked/17,12 0 P _part/8/_locked/17,13 0 P _part/8/_locked/17,14 0 P _part/8/_locked/17,15 0 P _part/8/_locked/17,16 0 P _part/8/_locked/17,17 0 P _part/8/_locked/17,2 0 P _part/8/_locked/17,3 0 P _part/8/_locked/17,4 0 P _part/8/_locked/17,5 0 P _part/8/_locked/17,6 0 P _part/8/_locked/17,7 0 P _part/8/_locked/17,8 0 P _part/8/_locked/17,9 0 P _part/8/_locked/18,0 0 P _part/8/_locked/18,1 0 P _part/8/_locked/18,10 0 P _part/8/_locked/18,11 0 P _part/8/_locked/18,12 0 P _part/8/_locked/18,13 0 P _part/8/_locked/18,14 0 P _part/8/_locked/18,15 0 P _part/8/_locked/18,16 0 P _part/8/_locked/18,17 0 P _part/8/_locked/18,2 0 P _part/8/_locked/18,3 0 P _part/8/_locked/18,4 0 P _part/8/_locked/18,5 0 P _part/8/_locked/18,6 0 P _part/8/_locked/18,7 0 P _part/8/_locked/18,8 0 P _part/8/_locked/18,9 0 P _part/8/_locked/19,0 0 P _part/8/_locked/19,1 0 P _part/8/_locked/19,10 0 P _part/8/_locked/19,11 0 P _part/8/_locked/19,12 0 P _part/8/_locked/19,13 0 P _part/8/_locked/19,14 0 P _part/8/_locked/19,15 0 P _part/8/_locked/19,16 0 P _part/8/_locked/19,17 0 P _part/8/_locked/19,2 0 P _part/8/_locked/19,3 0 P _part/8/_locked/19,4 0 P _part/8/_locked/19,5 0 P _part/8/_locked/19,6 0 P _part/8/_locked/19,7 0 P _part/8/_locked/19,8 0 P _part/8/_locked/19,9 0 P _part/8/_locked/2,0 0 P _part/8/_locked/2,1 0 P _part/8/_locked/2,10 0 P _part/8/_locked/2,11 0 P _part/8/_locked/2,12 0 P _part/8/_locked/2,13 0 P _part/8/_locked/2,14 0 P _part/8/_locked/2,15 0 P _part/8/_locked/2,16 0 P _part/8/_locked/2,17 0 P _part/8/_locked/2,2 0 P _part/8/_locked/2,3 0 P _part/8/_locked/2,4 0 P _part/8/_locked/2,5 0 P _part/8/_locked/2,6 0 P _part/8/_locked/2,7 0 P _part/8/_locked/2,8 0 P _part/8/_locked/2,9 0 P _part/8/_locked/3,0 0 P _part/8/_locked/3,1 0 P _part/8/_locked/3,10 0 P _part/8/_locked/3,11 0 P _part/8/_locked/3,12 0 P _part/8/_locked/3,13 0 P _part/8/_locked/3,14 0 P _part/8/_locked/3,15 0 P _part/8/_locked/3,16 0 P _part/8/_locked/3,17 0 P _part/8/_locked/3,2 0 P _part/8/_locked/3,3 0 P _part/8/_locked/3,4 0 P _part/8/_locked/3,5 0 P _part/8/_locked/3,6 0 P _part/8/_locked/3,7 0 P _part/8/_locked/3,8 0 P _part/8/_locked/3,9 0 P _part/8/_locked/4,0 0 P _part/8/_locked/4,1 0 P _part/8/_locked/4,10 0 P _part/8/_locked/4,11 0 P _part/8/_locked/4,12 0 P _part/8/_locked/4,13 0 P _part/8/_locked/4,14 0 P _part/8/_locked/4,15 0 P _part/8/_locked/4,16 0 P _part/8/_locked/4,17 0 P _part/8/_locked/4,2 0 P _part/8/_locked/4,3 0 P _part/8/_locked/4,4 0 P _part/8/_locked/4,5 0 P _part/8/_locked/4,6 0 P _part/8/_locked/4,7 0 P _part/8/_locked/4,8 0 P _part/8/_locked/4,9 0 P _part/8/_locked/5,0 0 P _part/8/_locked/5,1 0 P _part/8/_locked/5,10 0 P _part/8/_locked/5,11 0 P _part/8/_locked/5,12 0 P _part/8/_locked/5,13 0 P _part/8/_locked/5,14 0 P _part/8/_locked/5,15 0 P _part/8/_locked/5,16 0 P _part/8/_locked/5,17 0 P _part/8/_locked/5,2 0 P _part/8/_locked/5,3 0 P _part/8/_locked/5,4 0 P _part/8/_locked/5,5 0 P _part/8/_locked/5,6 0 P _part/8/_locked/5,7 0 P _part/8/_locked/5,8 0 P _part/8/_locked/5,9 0 P _part/8/_locked/6,0 0 P _part/8/_locked/6,1 0 P _part/8/_locked/6,10 0 P _part/8/_locked/6,11 0 P _part/8/_locked/6,12 0 P _part/8/_locked/6,13 0 P _part/8/_locked/6,14 0 P _part/8/_locked/6,15 0 P _part/8/_locked/6,16 0 P _part/8/_locked/6,17 0 P _part/8/_locked/6,2 0 P _part/8/_locked/6,3 0 P _part/8/_locked/6,4 0 P _part/8/_locked/6,5 0 P _part/8/_locked/6,6 0 P _part/8/_locked/6,7 0 P _part/8/_locked/6,8 0 P _part/8/_locked/6,9 0 P _part/8/_locked/7,0 0 P _part/8/_locked/7,1 0 P _part/8/_locked/7,10 0 P _part/8/_locked/7,11 0 P _part/8/_locked/7,12 0 P _part/8/_locked/7,13 0 P _part/8/_locked/7,14 0 P _part/8/_locked/7,15 0 P _part/8/_locked/7,16 0 P _part/8/_locked/7,17 0 P _part/8/_locked/7,2 0 P _part/8/_locked/7,3 0 P _part/8/_locked/7,4 0 P _part/8/_locked/7,5 0 P _part/8/_locked/7,6 0 P _part/8/_locked/7,7 0 P _part/8/_locked/7,8 0 P _part/8/_locked/7,9 0 P _part/8/_locked/8,0 0 P _part/8/_locked/8,1 0 P _part/8/_locked/8,10 0 P _part/8/_locked/8,11 0 P _part/8/_locked/8,12 0 P _part/8/_locked/8,13 0 P _part/8/_locked/8,14 0 P _part/8/_locked/8,15 0 P _part/8/_locked/8,16 0 P _part/8/_locked/8,17 0 P _part/8/_locked/8,2 0 P _part/8/_locked/8,3 0 P _part/8/_locked/8,4 0 P _part/8/_locked/8,5 0 P _part/8/_locked/8,6 0 P _part/8/_locked/8,7 0 P _part/8/_locked/8,8 0 P _part/8/_locked/8,9 0 P _part/8/_locked/9,0 0 P _part/8/_locked/9,1 0 P _part/8/_locked/9,10 0 P _part/8/_locked/9,11 0 P _part/8/_locked/9,12 0 P _part/8/_locked/9,13 0 P _part/8/_locked/9,14 0 P _part/8/_locked/9,15 0 P _part/8/_locked/9,16 0 P _part/8/_locked/9,17 0 P _part/8/_locked/9,2 0 P _part/8/_locked/9,3 0 P _part/8/_locked/9,4 0 P _part/8/_locked/9,5 0 P _part/8/_locked/9,6 0 P _part/8/_locked/9,7 0 P _part/8/_locked/9,8 0 P _part/8/_locked/9,9 0 P _part/8/_locked/i_count 20 P _part/8/_locked/j_count 18 P _part/8/_part_cd 0.075000003 P _part/8/_part_phi 0.0 P _part/8/_part_psi 0.0 P _part/8/_part_rad 2.0 P _part/8/_part_specs_eq 1 P _part/8/_part_specs_invis 0 P _part/8/_part_specs_unused1 0 P _part/8/_part_specs_unused2 0 P _part/8/_part_tex 1 P _part/8/_part_the 0.0 P _part/8/_part_x -0.0 P _part/8/_part_y 0.0 P _part/8/_part_z 0.0 P _part/8/_patt_con 0 P _part/8/_patt_prt 0 P _part/8/_patt_rat 0.0 P _part/8/_r_dim 14 P _part/8/_s_dim 16 P _part/8/_scon 37.676635742 P _part/8/_top_s1 0.755999982 P _part/8/_top_s2 1.0 P _part/8/_top_t1 0.0 P _part/8/_top_t2 0.384000003 P _part/85/_aero_x_os 0.0 P _part/85/_aero_y_os 0.0 P _part/85/_aero_z_os 0.0 P _part/85/_area_frnt 0.0 P _part/85/_area_side 0.0 P _part/85/_area_vert 0.0 P _part/85/_bot_s1 0.632812500 P _part/85/_bot_s2 0.753906250 P _part/85/_bot_t1 0.254882812 P _part/85/_bot_t2 0.295898438 P _part/85/_damp 1.883831739 P _part/85/_geo_xyz/0,0,0 0.0 P _part/85/_geo_xyz/0,0,1 2.0 P _part/85/_geo_xyz/0,0,2 0.0 P _part/85/_geo_xyz/0,1,0 1.414213538 P _part/85/_geo_xyz/0,1,1 1.414213538 P _part/85/_geo_xyz/0,1,2 0.0 P _part/85/_geo_xyz/0,10,0 0.0 P _part/85/_geo_xyz/0,10,1 0.0 P _part/85/_geo_xyz/0,10,2 0.0 P _part/85/_geo_xyz/0,11,0 0.0 P _part/85/_geo_xyz/0,11,1 0.0 P _part/85/_geo_xyz/0,11,2 0.0 P _part/85/_geo_xyz/0,12,0 0.0 P _part/85/_geo_xyz/0,12,1 0.0 P _part/85/_geo_xyz/0,12,2 0.0 P _part/85/_geo_xyz/0,13,0 0.0 P _part/85/_geo_xyz/0,13,1 0.0 P _part/85/_geo_xyz/0,13,2 0.0 P _part/85/_geo_xyz/0,14,0 0.0 P _part/85/_geo_xyz/0,14,1 0.0 P _part/85/_geo_xyz/0,14,2 0.0 P _part/85/_geo_xyz/0,15,0 0.0 P _part/85/_geo_xyz/0,15,1 0.0 P _part/85/_geo_xyz/0,15,2 0.0 P _part/85/_geo_xyz/0,16,0 0.0 P _part/85/_geo_xyz/0,16,1 0.0 P _part/85/_geo_xyz/0,16,2 0.0 P _part/85/_geo_xyz/0,17,0 0.0 P _part/85/_geo_xyz/0,17,1 0.0 P _part/85/_geo_xyz/0,17,2 0.0 P _part/85/_geo_xyz/0,2,0 2.0 P _part/85/_geo_xyz/0,2,1 -0.000000087 P _part/85/_geo_xyz/0,2,2 0.0 P _part/85/_geo_xyz/0,3,0 1.414213538 P _part/85/_geo_xyz/0,3,1 -1.414213538 P _part/85/_geo_xyz/0,3,2 0.0 P _part/85/_geo_xyz/0,4,0 -0.000000175 P _part/85/_geo_xyz/0,4,1 -2.0 P _part/85/_geo_xyz/0,4,2 0.0 P _part/85/_geo_xyz/0,5,0 -0.000000175 P _part/85/_geo_xyz/0,5,1 -2.0 P _part/85/_geo_xyz/0,5,2 0.0 P _part/85/_geo_xyz/0,6,0 -1.414213777 P _part/85/_geo_xyz/0,6,1 -1.414213300 P _part/85/_geo_xyz/0,6,2 0.0 P _part/85/_geo_xyz/0,7,0 -2.0 P _part/85/_geo_xyz/0,7,1 0.000000024 P _part/85/_geo_xyz/0,7,2 0.0 P _part/85/_geo_xyz/0,8,0 -1.414213061 P _part/85/_geo_xyz/0,8,1 1.414214015 P _part/85/_geo_xyz/0,8,2 0.0 P _part/85/_geo_xyz/0,9,0 0.000000350 P _part/85/_geo_xyz/0,9,1 2.0 P _part/85/_geo_xyz/0,9,2 0.0 P _part/85/_geo_xyz/1,0,0 0.0 P _part/85/_geo_xyz/1,0,1 2.0 P _part/85/_geo_xyz/1,0,2 1.0 P _part/85/_geo_xyz/1,1,0 1.414213538 P _part/85/_geo_xyz/1,1,1 1.414213538 P _part/85/_geo_xyz/1,1,2 1.0 P _part/85/_geo_xyz/1,10,0 0.0 P _part/85/_geo_xyz/1,10,1 0.0 P _part/85/_geo_xyz/1,10,2 0.0 P _part/85/_geo_xyz/1,11,0 0.0 P _part/85/_geo_xyz/1,11,1 0.0 P _part/85/_geo_xyz/1,11,2 0.0 P _part/85/_geo_xyz/1,12,0 0.0 P _part/85/_geo_xyz/1,12,1 0.0 P _part/85/_geo_xyz/1,12,2 0.0 P _part/85/_geo_xyz/1,13,0 0.0 P _part/85/_geo_xyz/1,13,1 0.0 P _part/85/_geo_xyz/1,13,2 0.0 P _part/85/_geo_xyz/1,14,0 0.0 P _part/85/_geo_xyz/1,14,1 0.0 P _part/85/_geo_xyz/1,14,2 0.0 P _part/85/_geo_xyz/1,15,0 0.0 P _part/85/_geo_xyz/1,15,1 0.0 P _part/85/_geo_xyz/1,15,2 0.0 P _part/85/_geo_xyz/1,16,0 0.0 P _part/85/_geo_xyz/1,16,1 0.0 P _part/85/_geo_xyz/1,16,2 0.0 P _part/85/_geo_xyz/1,17,0 0.0 P _part/85/_geo_xyz/1,17,1 0.0 P _part/85/_geo_xyz/1,17,2 0.0 P _part/85/_geo_xyz/1,2,0 2.0 P _part/85/_geo_xyz/1,2,1 -0.000000087 P _part/85/_geo_xyz/1,2,2 1.0 P _part/85/_geo_xyz/1,3,0 1.414213538 P _part/85/_geo_xyz/1,3,1 -1.414213538 P _part/85/_geo_xyz/1,3,2 1.0 P _part/85/_geo_xyz/1,4,0 -0.000000175 P _part/85/_geo_xyz/1,4,1 -2.0 P _part/85/_geo_xyz/1,4,2 1.0 P _part/85/_geo_xyz/1,5,0 -0.000000175 P _part/85/_geo_xyz/1,5,1 -2.0 P _part/85/_geo_xyz/1,5,2 1.0 P _part/85/_geo_xyz/1,6,0 -1.414213777 P _part/85/_geo_xyz/1,6,1 -1.414213300 P _part/85/_geo_xyz/1,6,2 1.0 P _part/85/_geo_xyz/1,7,0 -2.0 P _part/85/_geo_xyz/1,7,1 0.000000024 P _part/85/_geo_xyz/1,7,2 1.0 P _part/85/_geo_xyz/1,8,0 -1.414213061 P _part/85/_geo_xyz/1,8,1 1.414214015 P _part/85/_geo_xyz/1,8,2 1.0 P _part/85/_geo_xyz/1,9,0 0.000000350 P _part/85/_geo_xyz/1,9,1 2.0 P _part/85/_geo_xyz/1,9,2 1.0 P _part/85/_geo_xyz/10,0,0 0.0 P _part/85/_geo_xyz/10,0,1 0.0 P _part/85/_geo_xyz/10,0,2 0.0 P _part/85/_geo_xyz/10,1,0 0.0 P _part/85/_geo_xyz/10,1,1 0.0 P _part/85/_geo_xyz/10,1,2 0.0 P _part/85/_geo_xyz/10,10,0 0.0 P _part/85/_geo_xyz/10,10,1 0.0 P _part/85/_geo_xyz/10,10,2 0.0 P _part/85/_geo_xyz/10,11,0 0.0 P _part/85/_geo_xyz/10,11,1 0.0 P _part/85/_geo_xyz/10,11,2 0.0 P _part/85/_geo_xyz/10,12,0 0.0 P _part/85/_geo_xyz/10,12,1 0.0 P _part/85/_geo_xyz/10,12,2 0.0 P _part/85/_geo_xyz/10,13,0 0.0 P _part/85/_geo_xyz/10,13,1 0.0 P _part/85/_geo_xyz/10,13,2 0.0 P _part/85/_geo_xyz/10,14,0 0.0 P _part/85/_geo_xyz/10,14,1 0.0 P _part/85/_geo_xyz/10,14,2 0.0 P _part/85/_geo_xyz/10,15,0 0.0 P _part/85/_geo_xyz/10,15,1 0.0 P _part/85/_geo_xyz/10,15,2 0.0 P _part/85/_geo_xyz/10,16,0 0.0 P _part/85/_geo_xyz/10,16,1 0.0 P _part/85/_geo_xyz/10,16,2 0.0 P _part/85/_geo_xyz/10,17,0 0.0 P _part/85/_geo_xyz/10,17,1 0.0 P _part/85/_geo_xyz/10,17,2 0.0 P _part/85/_geo_xyz/10,2,0 0.0 P _part/85/_geo_xyz/10,2,1 0.0 P _part/85/_geo_xyz/10,2,2 0.0 P _part/85/_geo_xyz/10,3,0 0.0 P _part/85/_geo_xyz/10,3,1 0.0 P _part/85/_geo_xyz/10,3,2 0.0 P _part/85/_geo_xyz/10,4,0 0.0 P _part/85/_geo_xyz/10,4,1 0.0 P _part/85/_geo_xyz/10,4,2 0.0 P _part/85/_geo_xyz/10,5,0 0.0 P _part/85/_geo_xyz/10,5,1 0.0 P _part/85/_geo_xyz/10,5,2 0.0 P _part/85/_geo_xyz/10,6,0 0.0 P _part/85/_geo_xyz/10,6,1 0.0 P _part/85/_geo_xyz/10,6,2 0.0 P _part/85/_geo_xyz/10,7,0 0.0 P _part/85/_geo_xyz/10,7,1 0.0 P _part/85/_geo_xyz/10,7,2 0.0 P _part/85/_geo_xyz/10,8,0 0.0 P _part/85/_geo_xyz/10,8,1 0.0 P _part/85/_geo_xyz/10,8,2 0.0 P _part/85/_geo_xyz/10,9,0 0.0 P _part/85/_geo_xyz/10,9,1 0.0 P _part/85/_geo_xyz/10,9,2 0.0 P _part/85/_geo_xyz/11,0,0 0.0 P _part/85/_geo_xyz/11,0,1 0.0 P _part/85/_geo_xyz/11,0,2 0.0 P _part/85/_geo_xyz/11,1,0 0.0 P _part/85/_geo_xyz/11,1,1 0.0 P _part/85/_geo_xyz/11,1,2 0.0 P _part/85/_geo_xyz/11,10,0 0.0 P _part/85/_geo_xyz/11,10,1 0.0 P _part/85/_geo_xyz/11,10,2 0.0 P _part/85/_geo_xyz/11,11,0 0.0 P _part/85/_geo_xyz/11,11,1 0.0 P _part/85/_geo_xyz/11,11,2 0.0 P _part/85/_geo_xyz/11,12,0 0.0 P _part/85/_geo_xyz/11,12,1 0.0 P _part/85/_geo_xyz/11,12,2 0.0 P _part/85/_geo_xyz/11,13,0 0.0 P _part/85/_geo_xyz/11,13,1 0.0 P _part/85/_geo_xyz/11,13,2 0.0 P _part/85/_geo_xyz/11,14,0 0.0 P _part/85/_geo_xyz/11,14,1 0.0 P _part/85/_geo_xyz/11,14,2 0.0 P _part/85/_geo_xyz/11,15,0 0.0 P _part/85/_geo_xyz/11,15,1 0.0 P _part/85/_geo_xyz/11,15,2 0.0 P _part/85/_geo_xyz/11,16,0 0.0 P _part/85/_geo_xyz/11,16,1 0.0 P _part/85/_geo_xyz/11,16,2 0.0 P _part/85/_geo_xyz/11,17,0 0.0 P _part/85/_geo_xyz/11,17,1 0.0 P _part/85/_geo_xyz/11,17,2 0.0 P _part/85/_geo_xyz/11,2,0 0.0 P _part/85/_geo_xyz/11,2,1 0.0 P _part/85/_geo_xyz/11,2,2 0.0 P _part/85/_geo_xyz/11,3,0 0.0 P _part/85/_geo_xyz/11,3,1 0.0 P _part/85/_geo_xyz/11,3,2 0.0 P _part/85/_geo_xyz/11,4,0 0.0 P _part/85/_geo_xyz/11,4,1 0.0 P _part/85/_geo_xyz/11,4,2 0.0 P _part/85/_geo_xyz/11,5,0 0.0 P _part/85/_geo_xyz/11,5,1 0.0 P _part/85/_geo_xyz/11,5,2 0.0 P _part/85/_geo_xyz/11,6,0 0.0 P _part/85/_geo_xyz/11,6,1 0.0 P _part/85/_geo_xyz/11,6,2 0.0 P _part/85/_geo_xyz/11,7,0 0.0 P _part/85/_geo_xyz/11,7,1 0.0 P _part/85/_geo_xyz/11,7,2 0.0 P _part/85/_geo_xyz/11,8,0 0.0 P _part/85/_geo_xyz/11,8,1 0.0 P _part/85/_geo_xyz/11,8,2 0.0 P _part/85/_geo_xyz/11,9,0 0.0 P _part/85/_geo_xyz/11,9,1 0.0 P _part/85/_geo_xyz/11,9,2 0.0 P _part/85/_geo_xyz/12,0,0 0.0 P _part/85/_geo_xyz/12,0,1 0.0 P _part/85/_geo_xyz/12,0,2 0.0 P _part/85/_geo_xyz/12,1,0 0.0 P _part/85/_geo_xyz/12,1,1 0.0 P _part/85/_geo_xyz/12,1,2 0.0 P _part/85/_geo_xyz/12,10,0 0.0 P _part/85/_geo_xyz/12,10,1 0.0 P _part/85/_geo_xyz/12,10,2 0.0 P _part/85/_geo_xyz/12,11,0 0.0 P _part/85/_geo_xyz/12,11,1 0.0 P _part/85/_geo_xyz/12,11,2 0.0 P _part/85/_geo_xyz/12,12,0 0.0 P _part/85/_geo_xyz/12,12,1 0.0 P _part/85/_geo_xyz/12,12,2 0.0 P _part/85/_geo_xyz/12,13,0 0.0 P _part/85/_geo_xyz/12,13,1 0.0 P _part/85/_geo_xyz/12,13,2 0.0 P _part/85/_geo_xyz/12,14,0 0.0 P _part/85/_geo_xyz/12,14,1 0.0 P _part/85/_geo_xyz/12,14,2 0.0 P _part/85/_geo_xyz/12,15,0 0.0 P _part/85/_geo_xyz/12,15,1 0.0 P _part/85/_geo_xyz/12,15,2 0.0 P _part/85/_geo_xyz/12,16,0 0.0 P _part/85/_geo_xyz/12,16,1 0.0 P _part/85/_geo_xyz/12,16,2 0.0 P _part/85/_geo_xyz/12,17,0 0.0 P _part/85/_geo_xyz/12,17,1 0.0 P _part/85/_geo_xyz/12,17,2 0.0 P _part/85/_geo_xyz/12,2,0 0.0 P _part/85/_geo_xyz/12,2,1 0.0 P _part/85/_geo_xyz/12,2,2 0.0 P _part/85/_geo_xyz/12,3,0 0.0 P _part/85/_geo_xyz/12,3,1 0.0 P _part/85/_geo_xyz/12,3,2 0.0 P _part/85/_geo_xyz/12,4,0 0.0 P _part/85/_geo_xyz/12,4,1 0.0 P _part/85/_geo_xyz/12,4,2 0.0 P _part/85/_geo_xyz/12,5,0 0.0 P _part/85/_geo_xyz/12,5,1 0.0 P _part/85/_geo_xyz/12,5,2 0.0 P _part/85/_geo_xyz/12,6,0 0.0 P _part/85/_geo_xyz/12,6,1 0.0 P _part/85/_geo_xyz/12,6,2 0.0 P _part/85/_geo_xyz/12,7,0 0.0 P _part/85/_geo_xyz/12,7,1 0.0 P _part/85/_geo_xyz/12,7,2 0.0 P _part/85/_geo_xyz/12,8,0 0.0 P _part/85/_geo_xyz/12,8,1 0.0 P _part/85/_geo_xyz/12,8,2 0.0 P _part/85/_geo_xyz/12,9,0 0.0 P _part/85/_geo_xyz/12,9,1 0.0 P _part/85/_geo_xyz/12,9,2 0.0 P _part/85/_geo_xyz/13,0,0 0.0 P _part/85/_geo_xyz/13,0,1 0.0 P _part/85/_geo_xyz/13,0,2 0.0 P _part/85/_geo_xyz/13,1,0 0.0 P _part/85/_geo_xyz/13,1,1 0.0 P _part/85/_geo_xyz/13,1,2 0.0 P _part/85/_geo_xyz/13,10,0 0.0 P _part/85/_geo_xyz/13,10,1 0.0 P _part/85/_geo_xyz/13,10,2 0.0 P _part/85/_geo_xyz/13,11,0 0.0 P _part/85/_geo_xyz/13,11,1 0.0 P _part/85/_geo_xyz/13,11,2 0.0 P _part/85/_geo_xyz/13,12,0 0.0 P _part/85/_geo_xyz/13,12,1 0.0 P _part/85/_geo_xyz/13,12,2 0.0 P _part/85/_geo_xyz/13,13,0 0.0 P _part/85/_geo_xyz/13,13,1 0.0 P _part/85/_geo_xyz/13,13,2 0.0 P _part/85/_geo_xyz/13,14,0 0.0 P _part/85/_geo_xyz/13,14,1 0.0 P _part/85/_geo_xyz/13,14,2 0.0 P _part/85/_geo_xyz/13,15,0 0.0 P _part/85/_geo_xyz/13,15,1 0.0 P _part/85/_geo_xyz/13,15,2 0.0 P _part/85/_geo_xyz/13,16,0 0.0 P _part/85/_geo_xyz/13,16,1 0.0 P _part/85/_geo_xyz/13,16,2 0.0 P _part/85/_geo_xyz/13,17,0 0.0 P _part/85/_geo_xyz/13,17,1 0.0 P _part/85/_geo_xyz/13,17,2 0.0 P _part/85/_geo_xyz/13,2,0 0.0 P _part/85/_geo_xyz/13,2,1 0.0 P _part/85/_geo_xyz/13,2,2 0.0 P _part/85/_geo_xyz/13,3,0 0.0 P _part/85/_geo_xyz/13,3,1 0.0 P _part/85/_geo_xyz/13,3,2 0.0 P _part/85/_geo_xyz/13,4,0 0.0 P _part/85/_geo_xyz/13,4,1 0.0 P _part/85/_geo_xyz/13,4,2 0.0 P _part/85/_geo_xyz/13,5,0 0.0 P _part/85/_geo_xyz/13,5,1 0.0 P _part/85/_geo_xyz/13,5,2 0.0 P _part/85/_geo_xyz/13,6,0 0.0 P _part/85/_geo_xyz/13,6,1 0.0 P _part/85/_geo_xyz/13,6,2 0.0 P _part/85/_geo_xyz/13,7,0 0.0 P _part/85/_geo_xyz/13,7,1 0.0 P _part/85/_geo_xyz/13,7,2 0.0 P _part/85/_geo_xyz/13,8,0 0.0 P _part/85/_geo_xyz/13,8,1 0.0 P _part/85/_geo_xyz/13,8,2 0.0 P _part/85/_geo_xyz/13,9,0 0.0 P _part/85/_geo_xyz/13,9,1 0.0 P _part/85/_geo_xyz/13,9,2 0.0 P _part/85/_geo_xyz/14,0,0 0.0 P _part/85/_geo_xyz/14,0,1 0.0 P _part/85/_geo_xyz/14,0,2 0.0 P _part/85/_geo_xyz/14,1,0 0.0 P _part/85/_geo_xyz/14,1,1 0.0 P _part/85/_geo_xyz/14,1,2 0.0 P _part/85/_geo_xyz/14,10,0 0.0 P _part/85/_geo_xyz/14,10,1 0.0 P _part/85/_geo_xyz/14,10,2 0.0 P _part/85/_geo_xyz/14,11,0 0.0 P _part/85/_geo_xyz/14,11,1 0.0 P _part/85/_geo_xyz/14,11,2 0.0 P _part/85/_geo_xyz/14,12,0 0.0 P _part/85/_geo_xyz/14,12,1 0.0 P _part/85/_geo_xyz/14,12,2 0.0 P _part/85/_geo_xyz/14,13,0 0.0 P _part/85/_geo_xyz/14,13,1 0.0 P _part/85/_geo_xyz/14,13,2 0.0 P _part/85/_geo_xyz/14,14,0 0.0 P _part/85/_geo_xyz/14,14,1 0.0 P _part/85/_geo_xyz/14,14,2 0.0 P _part/85/_geo_xyz/14,15,0 0.0 P _part/85/_geo_xyz/14,15,1 0.0 P _part/85/_geo_xyz/14,15,2 0.0 P _part/85/_geo_xyz/14,16,0 0.0 P _part/85/_geo_xyz/14,16,1 0.0 P _part/85/_geo_xyz/14,16,2 0.0 P _part/85/_geo_xyz/14,17,0 0.0 P _part/85/_geo_xyz/14,17,1 0.0 P _part/85/_geo_xyz/14,17,2 0.0 P _part/85/_geo_xyz/14,2,0 0.0 P _part/85/_geo_xyz/14,2,1 0.0 P _part/85/_geo_xyz/14,2,2 0.0 P _part/85/_geo_xyz/14,3,0 0.0 P _part/85/_geo_xyz/14,3,1 0.0 P _part/85/_geo_xyz/14,3,2 0.0 P _part/85/_geo_xyz/14,4,0 0.0 P _part/85/_geo_xyz/14,4,1 0.0 P _part/85/_geo_xyz/14,4,2 0.0 P _part/85/_geo_xyz/14,5,0 0.0 P _part/85/_geo_xyz/14,5,1 0.0 P _part/85/_geo_xyz/14,5,2 0.0 P _part/85/_geo_xyz/14,6,0 0.0 P _part/85/_geo_xyz/14,6,1 0.0 P _part/85/_geo_xyz/14,6,2 0.0 P _part/85/_geo_xyz/14,7,0 0.0 P _part/85/_geo_xyz/14,7,1 0.0 P _part/85/_geo_xyz/14,7,2 0.0 P _part/85/_geo_xyz/14,8,0 0.0 P _part/85/_geo_xyz/14,8,1 0.0 P _part/85/_geo_xyz/14,8,2 0.0 P _part/85/_geo_xyz/14,9,0 0.0 P _part/85/_geo_xyz/14,9,1 0.0 P _part/85/_geo_xyz/14,9,2 0.0 P _part/85/_geo_xyz/15,0,0 0.0 P _part/85/_geo_xyz/15,0,1 0.0 P _part/85/_geo_xyz/15,0,2 0.0 P _part/85/_geo_xyz/15,1,0 0.0 P _part/85/_geo_xyz/15,1,1 0.0 P _part/85/_geo_xyz/15,1,2 0.0 P _part/85/_geo_xyz/15,10,0 0.0 P _part/85/_geo_xyz/15,10,1 0.0 P _part/85/_geo_xyz/15,10,2 0.0 P _part/85/_geo_xyz/15,11,0 0.0 P _part/85/_geo_xyz/15,11,1 0.0 P _part/85/_geo_xyz/15,11,2 0.0 P _part/85/_geo_xyz/15,12,0 0.0 P _part/85/_geo_xyz/15,12,1 0.0 P _part/85/_geo_xyz/15,12,2 0.0 P _part/85/_geo_xyz/15,13,0 0.0 P _part/85/_geo_xyz/15,13,1 0.0 P _part/85/_geo_xyz/15,13,2 0.0 P _part/85/_geo_xyz/15,14,0 0.0 P _part/85/_geo_xyz/15,14,1 0.0 P _part/85/_geo_xyz/15,14,2 0.0 P _part/85/_geo_xyz/15,15,0 0.0 P _part/85/_geo_xyz/15,15,1 0.0 P _part/85/_geo_xyz/15,15,2 0.0 P _part/85/_geo_xyz/15,16,0 0.0 P _part/85/_geo_xyz/15,16,1 0.0 P _part/85/_geo_xyz/15,16,2 0.0 P _part/85/_geo_xyz/15,17,0 0.0 P _part/85/_geo_xyz/15,17,1 0.0 P _part/85/_geo_xyz/15,17,2 0.0 P _part/85/_geo_xyz/15,2,0 0.0 P _part/85/_geo_xyz/15,2,1 0.0 P _part/85/_geo_xyz/15,2,2 0.0 P _part/85/_geo_xyz/15,3,0 0.0 P _part/85/_geo_xyz/15,3,1 0.0 P _part/85/_geo_xyz/15,3,2 0.0 P _part/85/_geo_xyz/15,4,0 0.0 P _part/85/_geo_xyz/15,4,1 0.0 P _part/85/_geo_xyz/15,4,2 0.0 P _part/85/_geo_xyz/15,5,0 0.0 P _part/85/_geo_xyz/15,5,1 0.0 P _part/85/_geo_xyz/15,5,2 0.0 P _part/85/_geo_xyz/15,6,0 0.0 P _part/85/_geo_xyz/15,6,1 0.0 P _part/85/_geo_xyz/15,6,2 0.0 P _part/85/_geo_xyz/15,7,0 0.0 P _part/85/_geo_xyz/15,7,1 0.0 P _part/85/_geo_xyz/15,7,2 0.0 P _part/85/_geo_xyz/15,8,0 0.0 P _part/85/_geo_xyz/15,8,1 0.0 P _part/85/_geo_xyz/15,8,2 0.0 P _part/85/_geo_xyz/15,9,0 0.0 P _part/85/_geo_xyz/15,9,1 0.0 P _part/85/_geo_xyz/15,9,2 0.0 P _part/85/_geo_xyz/16,0,0 0.0 P _part/85/_geo_xyz/16,0,1 0.0 P _part/85/_geo_xyz/16,0,2 0.0 P _part/85/_geo_xyz/16,1,0 0.0 P _part/85/_geo_xyz/16,1,1 0.0 P _part/85/_geo_xyz/16,1,2 0.0 P _part/85/_geo_xyz/16,10,0 0.0 P _part/85/_geo_xyz/16,10,1 0.0 P _part/85/_geo_xyz/16,10,2 0.0 P _part/85/_geo_xyz/16,11,0 0.0 P _part/85/_geo_xyz/16,11,1 0.0 P _part/85/_geo_xyz/16,11,2 0.0 P _part/85/_geo_xyz/16,12,0 0.0 P _part/85/_geo_xyz/16,12,1 0.0 P _part/85/_geo_xyz/16,12,2 0.0 P _part/85/_geo_xyz/16,13,0 0.0 P _part/85/_geo_xyz/16,13,1 0.0 P _part/85/_geo_xyz/16,13,2 0.0 P _part/85/_geo_xyz/16,14,0 0.0 P _part/85/_geo_xyz/16,14,1 0.0 P _part/85/_geo_xyz/16,14,2 0.0 P _part/85/_geo_xyz/16,15,0 0.0 P _part/85/_geo_xyz/16,15,1 0.0 P _part/85/_geo_xyz/16,15,2 0.0 P _part/85/_geo_xyz/16,16,0 0.0 P _part/85/_geo_xyz/16,16,1 0.0 P _part/85/_geo_xyz/16,16,2 0.0 P _part/85/_geo_xyz/16,17,0 0.0 P _part/85/_geo_xyz/16,17,1 0.0 P _part/85/_geo_xyz/16,17,2 0.0 P _part/85/_geo_xyz/16,2,0 0.0 P _part/85/_geo_xyz/16,2,1 0.0 P _part/85/_geo_xyz/16,2,2 0.0 P _part/85/_geo_xyz/16,3,0 0.0 P _part/85/_geo_xyz/16,3,1 0.0 P _part/85/_geo_xyz/16,3,2 0.0 P _part/85/_geo_xyz/16,4,0 0.0 P _part/85/_geo_xyz/16,4,1 0.0 P _part/85/_geo_xyz/16,4,2 0.0 P _part/85/_geo_xyz/16,5,0 0.0 P _part/85/_geo_xyz/16,5,1 0.0 P _part/85/_geo_xyz/16,5,2 0.0 P _part/85/_geo_xyz/16,6,0 0.0 P _part/85/_geo_xyz/16,6,1 0.0 P _part/85/_geo_xyz/16,6,2 0.0 P _part/85/_geo_xyz/16,7,0 0.0 P _part/85/_geo_xyz/16,7,1 0.0 P _part/85/_geo_xyz/16,7,2 0.0 P _part/85/_geo_xyz/16,8,0 0.0 P _part/85/_geo_xyz/16,8,1 0.0 P _part/85/_geo_xyz/16,8,2 0.0 P _part/85/_geo_xyz/16,9,0 0.0 P _part/85/_geo_xyz/16,9,1 0.0 P _part/85/_geo_xyz/16,9,2 0.0 P _part/85/_geo_xyz/17,0,0 0.0 P _part/85/_geo_xyz/17,0,1 0.0 P _part/85/_geo_xyz/17,0,2 0.0 P _part/85/_geo_xyz/17,1,0 0.0 P _part/85/_geo_xyz/17,1,1 0.0 P _part/85/_geo_xyz/17,1,2 0.0 P _part/85/_geo_xyz/17,10,0 0.0 P _part/85/_geo_xyz/17,10,1 0.0 P _part/85/_geo_xyz/17,10,2 0.0 P _part/85/_geo_xyz/17,11,0 0.0 P _part/85/_geo_xyz/17,11,1 0.0 P _part/85/_geo_xyz/17,11,2 0.0 P _part/85/_geo_xyz/17,12,0 0.0 P _part/85/_geo_xyz/17,12,1 0.0 P _part/85/_geo_xyz/17,12,2 0.0 P _part/85/_geo_xyz/17,13,0 0.0 P _part/85/_geo_xyz/17,13,1 0.0 P _part/85/_geo_xyz/17,13,2 0.0 P _part/85/_geo_xyz/17,14,0 0.0 P _part/85/_geo_xyz/17,14,1 0.0 P _part/85/_geo_xyz/17,14,2 0.0 P _part/85/_geo_xyz/17,15,0 0.0 P _part/85/_geo_xyz/17,15,1 0.0 P _part/85/_geo_xyz/17,15,2 0.0 P _part/85/_geo_xyz/17,16,0 0.0 P _part/85/_geo_xyz/17,16,1 0.0 P _part/85/_geo_xyz/17,16,2 0.0 P _part/85/_geo_xyz/17,17,0 0.0 P _part/85/_geo_xyz/17,17,1 0.0 P _part/85/_geo_xyz/17,17,2 0.0 P _part/85/_geo_xyz/17,2,0 0.0 P _part/85/_geo_xyz/17,2,1 0.0 P _part/85/_geo_xyz/17,2,2 0.0 P _part/85/_geo_xyz/17,3,0 0.0 P _part/85/_geo_xyz/17,3,1 0.0 P _part/85/_geo_xyz/17,3,2 0.0 P _part/85/_geo_xyz/17,4,0 0.0 P _part/85/_geo_xyz/17,4,1 0.0 P _part/85/_geo_xyz/17,4,2 0.0 P _part/85/_geo_xyz/17,5,0 0.0 P _part/85/_geo_xyz/17,5,1 0.0 P _part/85/_geo_xyz/17,5,2 0.0 P _part/85/_geo_xyz/17,6,0 0.0 P _part/85/_geo_xyz/17,6,1 0.0 P _part/85/_geo_xyz/17,6,2 0.0 P _part/85/_geo_xyz/17,7,0 0.0 P _part/85/_geo_xyz/17,7,1 0.0 P _part/85/_geo_xyz/17,7,2 0.0 P _part/85/_geo_xyz/17,8,0 0.0 P _part/85/_geo_xyz/17,8,1 0.0 P _part/85/_geo_xyz/17,8,2 0.0 P _part/85/_geo_xyz/17,9,0 0.0 P _part/85/_geo_xyz/17,9,1 0.0 P _part/85/_geo_xyz/17,9,2 0.0 P _part/85/_geo_xyz/18,0,0 0.0 P _part/85/_geo_xyz/18,0,1 0.0 P _part/85/_geo_xyz/18,0,2 0.0 P _part/85/_geo_xyz/18,1,0 0.0 P _part/85/_geo_xyz/18,1,1 0.0 P _part/85/_geo_xyz/18,1,2 0.0 P _part/85/_geo_xyz/18,10,0 0.0 P _part/85/_geo_xyz/18,10,1 0.0 P _part/85/_geo_xyz/18,10,2 0.0 P _part/85/_geo_xyz/18,11,0 0.0 P _part/85/_geo_xyz/18,11,1 0.0 P _part/85/_geo_xyz/18,11,2 0.0 P _part/85/_geo_xyz/18,12,0 0.0 P _part/85/_geo_xyz/18,12,1 0.0 P _part/85/_geo_xyz/18,12,2 0.0 P _part/85/_geo_xyz/18,13,0 0.0 P _part/85/_geo_xyz/18,13,1 0.0 P _part/85/_geo_xyz/18,13,2 0.0 P _part/85/_geo_xyz/18,14,0 0.0 P _part/85/_geo_xyz/18,14,1 0.0 P _part/85/_geo_xyz/18,14,2 0.0 P _part/85/_geo_xyz/18,15,0 0.0 P _part/85/_geo_xyz/18,15,1 0.0 P _part/85/_geo_xyz/18,15,2 0.0 P _part/85/_geo_xyz/18,16,0 0.0 P _part/85/_geo_xyz/18,16,1 0.0 P _part/85/_geo_xyz/18,16,2 0.0 P _part/85/_geo_xyz/18,17,0 0.0 P _part/85/_geo_xyz/18,17,1 0.0 P _part/85/_geo_xyz/18,17,2 0.0 P _part/85/_geo_xyz/18,2,0 0.0 P _part/85/_geo_xyz/18,2,1 0.0 P _part/85/_geo_xyz/18,2,2 0.0 P _part/85/_geo_xyz/18,3,0 0.0 P _part/85/_geo_xyz/18,3,1 0.0 P _part/85/_geo_xyz/18,3,2 0.0 P _part/85/_geo_xyz/18,4,0 0.0 P _part/85/_geo_xyz/18,4,1 0.0 P _part/85/_geo_xyz/18,4,2 0.0 P _part/85/_geo_xyz/18,5,0 0.0 P _part/85/_geo_xyz/18,5,1 0.0 P _part/85/_geo_xyz/18,5,2 0.0 P _part/85/_geo_xyz/18,6,0 0.0 P _part/85/_geo_xyz/18,6,1 0.0 P _part/85/_geo_xyz/18,6,2 0.0 P _part/85/_geo_xyz/18,7,0 0.0 P _part/85/_geo_xyz/18,7,1 0.0 P _part/85/_geo_xyz/18,7,2 0.0 P _part/85/_geo_xyz/18,8,0 0.0 P _part/85/_geo_xyz/18,8,1 0.0 P _part/85/_geo_xyz/18,8,2 0.0 P _part/85/_geo_xyz/18,9,0 0.0 P _part/85/_geo_xyz/18,9,1 0.0 P _part/85/_geo_xyz/18,9,2 0.0 P _part/85/_geo_xyz/19,0,0 0.0 P _part/85/_geo_xyz/19,0,1 0.0 P _part/85/_geo_xyz/19,0,2 0.0 P _part/85/_geo_xyz/19,1,0 0.0 P _part/85/_geo_xyz/19,1,1 0.0 P _part/85/_geo_xyz/19,1,2 0.0 P _part/85/_geo_xyz/19,10,0 0.0 P _part/85/_geo_xyz/19,10,1 0.0 P _part/85/_geo_xyz/19,10,2 0.0 P _part/85/_geo_xyz/19,11,0 0.0 P _part/85/_geo_xyz/19,11,1 0.0 P _part/85/_geo_xyz/19,11,2 0.0 P _part/85/_geo_xyz/19,12,0 0.0 P _part/85/_geo_xyz/19,12,1 0.0 P _part/85/_geo_xyz/19,12,2 0.0 P _part/85/_geo_xyz/19,13,0 0.0 P _part/85/_geo_xyz/19,13,1 0.0 P _part/85/_geo_xyz/19,13,2 0.0 P _part/85/_geo_xyz/19,14,0 0.0 P _part/85/_geo_xyz/19,14,1 0.0 P _part/85/_geo_xyz/19,14,2 0.0 P _part/85/_geo_xyz/19,15,0 0.0 P _part/85/_geo_xyz/19,15,1 0.0 P _part/85/_geo_xyz/19,15,2 0.0 P _part/85/_geo_xyz/19,16,0 0.0 P _part/85/_geo_xyz/19,16,1 0.0 P _part/85/_geo_xyz/19,16,2 0.0 P _part/85/_geo_xyz/19,17,0 0.0 P _part/85/_geo_xyz/19,17,1 0.0 P _part/85/_geo_xyz/19,17,2 0.0 P _part/85/_geo_xyz/19,2,0 0.0 P _part/85/_geo_xyz/19,2,1 0.0 P _part/85/_geo_xyz/19,2,2 0.0 P _part/85/_geo_xyz/19,3,0 0.0 P _part/85/_geo_xyz/19,3,1 0.0 P _part/85/_geo_xyz/19,3,2 0.0 P _part/85/_geo_xyz/19,4,0 0.0 P _part/85/_geo_xyz/19,4,1 0.0 P _part/85/_geo_xyz/19,4,2 0.0 P _part/85/_geo_xyz/19,5,0 0.0 P _part/85/_geo_xyz/19,5,1 0.0 P _part/85/_geo_xyz/19,5,2 0.0 P _part/85/_geo_xyz/19,6,0 0.0 P _part/85/_geo_xyz/19,6,1 0.0 P _part/85/_geo_xyz/19,6,2 0.0 P _part/85/_geo_xyz/19,7,0 0.0 P _part/85/_geo_xyz/19,7,1 0.0 P _part/85/_geo_xyz/19,7,2 0.0 P _part/85/_geo_xyz/19,8,0 0.0 P _part/85/_geo_xyz/19,8,1 0.0 P _part/85/_geo_xyz/19,8,2 0.0 P _part/85/_geo_xyz/19,9,0 0.0 P _part/85/_geo_xyz/19,9,1 0.0 P _part/85/_geo_xyz/19,9,2 0.0 P _part/85/_geo_xyz/2,0,0 0.0 P _part/85/_geo_xyz/2,0,1 2.0 P _part/85/_geo_xyz/2,0,2 2.0 P _part/85/_geo_xyz/2,1,0 1.414213538 P _part/85/_geo_xyz/2,1,1 1.414213538 P _part/85/_geo_xyz/2,1,2 2.0 P _part/85/_geo_xyz/2,10,0 0.0 P _part/85/_geo_xyz/2,10,1 0.0 P _part/85/_geo_xyz/2,10,2 0.0 P _part/85/_geo_xyz/2,11,0 0.0 P _part/85/_geo_xyz/2,11,1 0.0 P _part/85/_geo_xyz/2,11,2 0.0 P _part/85/_geo_xyz/2,12,0 0.0 P _part/85/_geo_xyz/2,12,1 0.0 P _part/85/_geo_xyz/2,12,2 0.0 P _part/85/_geo_xyz/2,13,0 0.0 P _part/85/_geo_xyz/2,13,1 0.0 P _part/85/_geo_xyz/2,13,2 0.0 P _part/85/_geo_xyz/2,14,0 0.0 P _part/85/_geo_xyz/2,14,1 0.0 P _part/85/_geo_xyz/2,14,2 0.0 P _part/85/_geo_xyz/2,15,0 0.0 P _part/85/_geo_xyz/2,15,1 0.0 P _part/85/_geo_xyz/2,15,2 0.0 P _part/85/_geo_xyz/2,16,0 0.0 P _part/85/_geo_xyz/2,16,1 0.0 P _part/85/_geo_xyz/2,16,2 0.0 P _part/85/_geo_xyz/2,17,0 0.0 P _part/85/_geo_xyz/2,17,1 0.0 P _part/85/_geo_xyz/2,17,2 0.0 P _part/85/_geo_xyz/2,2,0 2.0 P _part/85/_geo_xyz/2,2,1 -0.000000087 P _part/85/_geo_xyz/2,2,2 2.0 P _part/85/_geo_xyz/2,3,0 1.414213538 P _part/85/_geo_xyz/2,3,1 -1.414213538 P _part/85/_geo_xyz/2,3,2 2.0 P _part/85/_geo_xyz/2,4,0 -0.000000175 P _part/85/_geo_xyz/2,4,1 -2.0 P _part/85/_geo_xyz/2,4,2 2.0 P _part/85/_geo_xyz/2,5,0 -0.000000175 P _part/85/_geo_xyz/2,5,1 -2.0 P _part/85/_geo_xyz/2,5,2 2.0 P _part/85/_geo_xyz/2,6,0 -1.414213777 P _part/85/_geo_xyz/2,6,1 -1.414213300 P _part/85/_geo_xyz/2,6,2 2.0 P _part/85/_geo_xyz/2,7,0 -2.0 P _part/85/_geo_xyz/2,7,1 0.000000024 P _part/85/_geo_xyz/2,7,2 2.0 P _part/85/_geo_xyz/2,8,0 -1.414213061 P _part/85/_geo_xyz/2,8,1 1.414214015 P _part/85/_geo_xyz/2,8,2 2.0 P _part/85/_geo_xyz/2,9,0 0.000000350 P _part/85/_geo_xyz/2,9,1 2.0 P _part/85/_geo_xyz/2,9,2 2.0 P _part/85/_geo_xyz/3,0,0 0.0 P _part/85/_geo_xyz/3,0,1 2.0 P _part/85/_geo_xyz/3,0,2 3.0 P _part/85/_geo_xyz/3,1,0 1.414213538 P _part/85/_geo_xyz/3,1,1 1.414213538 P _part/85/_geo_xyz/3,1,2 3.0 P _part/85/_geo_xyz/3,10,0 0.0 P _part/85/_geo_xyz/3,10,1 0.0 P _part/85/_geo_xyz/3,10,2 0.0 P _part/85/_geo_xyz/3,11,0 0.0 P _part/85/_geo_xyz/3,11,1 0.0 P _part/85/_geo_xyz/3,11,2 0.0 P _part/85/_geo_xyz/3,12,0 0.0 P _part/85/_geo_xyz/3,12,1 0.0 P _part/85/_geo_xyz/3,12,2 0.0 P _part/85/_geo_xyz/3,13,0 0.0 P _part/85/_geo_xyz/3,13,1 0.0 P _part/85/_geo_xyz/3,13,2 0.0 P _part/85/_geo_xyz/3,14,0 0.0 P _part/85/_geo_xyz/3,14,1 0.0 P _part/85/_geo_xyz/3,14,2 0.0 P _part/85/_geo_xyz/3,15,0 0.0 P _part/85/_geo_xyz/3,15,1 0.0 P _part/85/_geo_xyz/3,15,2 0.0 P _part/85/_geo_xyz/3,16,0 0.0 P _part/85/_geo_xyz/3,16,1 0.0 P _part/85/_geo_xyz/3,16,2 0.0 P _part/85/_geo_xyz/3,17,0 0.0 P _part/85/_geo_xyz/3,17,1 0.0 P _part/85/_geo_xyz/3,17,2 0.0 P _part/85/_geo_xyz/3,2,0 2.0 P _part/85/_geo_xyz/3,2,1 -0.000000087 P _part/85/_geo_xyz/3,2,2 3.0 P _part/85/_geo_xyz/3,3,0 1.414213538 P _part/85/_geo_xyz/3,3,1 -1.414213538 P _part/85/_geo_xyz/3,3,2 3.0 P _part/85/_geo_xyz/3,4,0 -0.000000175 P _part/85/_geo_xyz/3,4,1 -2.0 P _part/85/_geo_xyz/3,4,2 3.0 P _part/85/_geo_xyz/3,5,0 -0.000000175 P _part/85/_geo_xyz/3,5,1 -2.0 P _part/85/_geo_xyz/3,5,2 3.0 P _part/85/_geo_xyz/3,6,0 -1.414213777 P _part/85/_geo_xyz/3,6,1 -1.414213300 P _part/85/_geo_xyz/3,6,2 3.0 P _part/85/_geo_xyz/3,7,0 -2.0 P _part/85/_geo_xyz/3,7,1 0.000000024 P _part/85/_geo_xyz/3,7,2 3.0 P _part/85/_geo_xyz/3,8,0 -1.414213061 P _part/85/_geo_xyz/3,8,1 1.414214015 P _part/85/_geo_xyz/3,8,2 3.0 P _part/85/_geo_xyz/3,9,0 0.000000350 P _part/85/_geo_xyz/3,9,1 2.0 P _part/85/_geo_xyz/3,9,2 3.0 P _part/85/_geo_xyz/4,0,0 0.0 P _part/85/_geo_xyz/4,0,1 2.0 P _part/85/_geo_xyz/4,0,2 4.0 P _part/85/_geo_xyz/4,1,0 1.414213538 P _part/85/_geo_xyz/4,1,1 1.414213538 P _part/85/_geo_xyz/4,1,2 4.0 P _part/85/_geo_xyz/4,10,0 0.0 P _part/85/_geo_xyz/4,10,1 0.0 P _part/85/_geo_xyz/4,10,2 0.0 P _part/85/_geo_xyz/4,11,0 0.0 P _part/85/_geo_xyz/4,11,1 0.0 P _part/85/_geo_xyz/4,11,2 0.0 P _part/85/_geo_xyz/4,12,0 0.0 P _part/85/_geo_xyz/4,12,1 0.0 P _part/85/_geo_xyz/4,12,2 0.0 P _part/85/_geo_xyz/4,13,0 0.0 P _part/85/_geo_xyz/4,13,1 0.0 P _part/85/_geo_xyz/4,13,2 0.0 P _part/85/_geo_xyz/4,14,0 0.0 P _part/85/_geo_xyz/4,14,1 0.0 P _part/85/_geo_xyz/4,14,2 0.0 P _part/85/_geo_xyz/4,15,0 0.0 P _part/85/_geo_xyz/4,15,1 0.0 P _part/85/_geo_xyz/4,15,2 0.0 P _part/85/_geo_xyz/4,16,0 0.0 P _part/85/_geo_xyz/4,16,1 0.0 P _part/85/_geo_xyz/4,16,2 0.0 P _part/85/_geo_xyz/4,17,0 0.0 P _part/85/_geo_xyz/4,17,1 0.0 P _part/85/_geo_xyz/4,17,2 0.0 P _part/85/_geo_xyz/4,2,0 2.0 P _part/85/_geo_xyz/4,2,1 -0.000000087 P _part/85/_geo_xyz/4,2,2 4.0 P _part/85/_geo_xyz/4,3,0 1.414213538 P _part/85/_geo_xyz/4,3,1 -1.414213538 P _part/85/_geo_xyz/4,3,2 4.0 P _part/85/_geo_xyz/4,4,0 -0.000000175 P _part/85/_geo_xyz/4,4,1 -2.0 P _part/85/_geo_xyz/4,4,2 4.0 P _part/85/_geo_xyz/4,5,0 -0.000000175 P _part/85/_geo_xyz/4,5,1 -2.0 P _part/85/_geo_xyz/4,5,2 4.0 P _part/85/_geo_xyz/4,6,0 -1.414213777 P _part/85/_geo_xyz/4,6,1 -1.414213300 P _part/85/_geo_xyz/4,6,2 4.0 P _part/85/_geo_xyz/4,7,0 -2.0 P _part/85/_geo_xyz/4,7,1 0.000000024 P _part/85/_geo_xyz/4,7,2 4.0 P _part/85/_geo_xyz/4,8,0 -1.414213061 P _part/85/_geo_xyz/4,8,1 1.414214015 P _part/85/_geo_xyz/4,8,2 4.0 P _part/85/_geo_xyz/4,9,0 0.000000350 P _part/85/_geo_xyz/4,9,1 2.0 P _part/85/_geo_xyz/4,9,2 4.0 P _part/85/_geo_xyz/5,0,0 0.0 P _part/85/_geo_xyz/5,0,1 2.0 P _part/85/_geo_xyz/5,0,2 5.0 P _part/85/_geo_xyz/5,1,0 1.414213538 P _part/85/_geo_xyz/5,1,1 1.414213538 P _part/85/_geo_xyz/5,1,2 5.0 P _part/85/_geo_xyz/5,10,0 0.0 P _part/85/_geo_xyz/5,10,1 0.0 P _part/85/_geo_xyz/5,10,2 0.0 P _part/85/_geo_xyz/5,11,0 0.0 P _part/85/_geo_xyz/5,11,1 0.0 P _part/85/_geo_xyz/5,11,2 0.0 P _part/85/_geo_xyz/5,12,0 0.0 P _part/85/_geo_xyz/5,12,1 0.0 P _part/85/_geo_xyz/5,12,2 0.0 P _part/85/_geo_xyz/5,13,0 0.0 P _part/85/_geo_xyz/5,13,1 0.0 P _part/85/_geo_xyz/5,13,2 0.0 P _part/85/_geo_xyz/5,14,0 0.0 P _part/85/_geo_xyz/5,14,1 0.0 P _part/85/_geo_xyz/5,14,2 0.0 P _part/85/_geo_xyz/5,15,0 0.0 P _part/85/_geo_xyz/5,15,1 0.0 P _part/85/_geo_xyz/5,15,2 0.0 P _part/85/_geo_xyz/5,16,0 0.0 P _part/85/_geo_xyz/5,16,1 0.0 P _part/85/_geo_xyz/5,16,2 0.0 P _part/85/_geo_xyz/5,17,0 0.0 P _part/85/_geo_xyz/5,17,1 0.0 P _part/85/_geo_xyz/5,17,2 0.0 P _part/85/_geo_xyz/5,2,0 2.0 P _part/85/_geo_xyz/5,2,1 -0.000000087 P _part/85/_geo_xyz/5,2,2 5.0 P _part/85/_geo_xyz/5,3,0 1.414213538 P _part/85/_geo_xyz/5,3,1 -1.414213538 P _part/85/_geo_xyz/5,3,2 5.0 P _part/85/_geo_xyz/5,4,0 -0.000000175 P _part/85/_geo_xyz/5,4,1 -2.0 P _part/85/_geo_xyz/5,4,2 5.0 P _part/85/_geo_xyz/5,5,0 -0.000000175 P _part/85/_geo_xyz/5,5,1 -2.0 P _part/85/_geo_xyz/5,5,2 5.0 P _part/85/_geo_xyz/5,6,0 -1.414213777 P _part/85/_geo_xyz/5,6,1 -1.414213300 P _part/85/_geo_xyz/5,6,2 5.0 P _part/85/_geo_xyz/5,7,0 -2.0 P _part/85/_geo_xyz/5,7,1 0.000000024 P _part/85/_geo_xyz/5,7,2 5.0 P _part/85/_geo_xyz/5,8,0 -1.414213061 P _part/85/_geo_xyz/5,8,1 1.414214015 P _part/85/_geo_xyz/5,8,2 5.0 P _part/85/_geo_xyz/5,9,0 0.000000350 P _part/85/_geo_xyz/5,9,1 2.0 P _part/85/_geo_xyz/5,9,2 5.0 P _part/85/_geo_xyz/6,0,0 0.0 P _part/85/_geo_xyz/6,0,1 2.0 P _part/85/_geo_xyz/6,0,2 6.0 P _part/85/_geo_xyz/6,1,0 1.414213538 P _part/85/_geo_xyz/6,1,1 1.414213538 P _part/85/_geo_xyz/6,1,2 6.0 P _part/85/_geo_xyz/6,10,0 0.0 P _part/85/_geo_xyz/6,10,1 0.0 P _part/85/_geo_xyz/6,10,2 0.0 P _part/85/_geo_xyz/6,11,0 0.0 P _part/85/_geo_xyz/6,11,1 0.0 P _part/85/_geo_xyz/6,11,2 0.0 P _part/85/_geo_xyz/6,12,0 0.0 P _part/85/_geo_xyz/6,12,1 0.0 P _part/85/_geo_xyz/6,12,2 0.0 P _part/85/_geo_xyz/6,13,0 0.0 P _part/85/_geo_xyz/6,13,1 0.0 P _part/85/_geo_xyz/6,13,2 0.0 P _part/85/_geo_xyz/6,14,0 0.0 P _part/85/_geo_xyz/6,14,1 0.0 P _part/85/_geo_xyz/6,14,2 0.0 P _part/85/_geo_xyz/6,15,0 0.0 P _part/85/_geo_xyz/6,15,1 0.0 P _part/85/_geo_xyz/6,15,2 0.0 P _part/85/_geo_xyz/6,16,0 0.0 P _part/85/_geo_xyz/6,16,1 0.0 P _part/85/_geo_xyz/6,16,2 0.0 P _part/85/_geo_xyz/6,17,0 0.0 P _part/85/_geo_xyz/6,17,1 0.0 P _part/85/_geo_xyz/6,17,2 0.0 P _part/85/_geo_xyz/6,2,0 2.0 P _part/85/_geo_xyz/6,2,1 -0.000000087 P _part/85/_geo_xyz/6,2,2 6.0 P _part/85/_geo_xyz/6,3,0 1.414213538 P _part/85/_geo_xyz/6,3,1 -1.414213538 P _part/85/_geo_xyz/6,3,2 6.0 P _part/85/_geo_xyz/6,4,0 -0.000000175 P _part/85/_geo_xyz/6,4,1 -2.0 P _part/85/_geo_xyz/6,4,2 6.0 P _part/85/_geo_xyz/6,5,0 -0.000000175 P _part/85/_geo_xyz/6,5,1 -2.0 P _part/85/_geo_xyz/6,5,2 6.0 P _part/85/_geo_xyz/6,6,0 -1.414213777 P _part/85/_geo_xyz/6,6,1 -1.414213300 P _part/85/_geo_xyz/6,6,2 6.0 P _part/85/_geo_xyz/6,7,0 -2.0 P _part/85/_geo_xyz/6,7,1 0.000000024 P _part/85/_geo_xyz/6,7,2 6.0 P _part/85/_geo_xyz/6,8,0 -1.414213061 P _part/85/_geo_xyz/6,8,1 1.414214015 P _part/85/_geo_xyz/6,8,2 6.0 P _part/85/_geo_xyz/6,9,0 0.000000350 P _part/85/_geo_xyz/6,9,1 2.0 P _part/85/_geo_xyz/6,9,2 6.0 P _part/85/_geo_xyz/7,0,0 0.0 P _part/85/_geo_xyz/7,0,1 2.0 P _part/85/_geo_xyz/7,0,2 7.0 P _part/85/_geo_xyz/7,1,0 1.414213538 P _part/85/_geo_xyz/7,1,1 1.414213538 P _part/85/_geo_xyz/7,1,2 7.0 P _part/85/_geo_xyz/7,10,0 0.0 P _part/85/_geo_xyz/7,10,1 0.0 P _part/85/_geo_xyz/7,10,2 0.0 P _part/85/_geo_xyz/7,11,0 0.0 P _part/85/_geo_xyz/7,11,1 0.0 P _part/85/_geo_xyz/7,11,2 0.0 P _part/85/_geo_xyz/7,12,0 0.0 P _part/85/_geo_xyz/7,12,1 0.0 P _part/85/_geo_xyz/7,12,2 0.0 P _part/85/_geo_xyz/7,13,0 0.0 P _part/85/_geo_xyz/7,13,1 0.0 P _part/85/_geo_xyz/7,13,2 0.0 P _part/85/_geo_xyz/7,14,0 0.0 P _part/85/_geo_xyz/7,14,1 0.0 P _part/85/_geo_xyz/7,14,2 0.0 P _part/85/_geo_xyz/7,15,0 0.0 P _part/85/_geo_xyz/7,15,1 0.0 P _part/85/_geo_xyz/7,15,2 0.0 P _part/85/_geo_xyz/7,16,0 0.0 P _part/85/_geo_xyz/7,16,1 0.0 P _part/85/_geo_xyz/7,16,2 0.0 P _part/85/_geo_xyz/7,17,0 0.0 P _part/85/_geo_xyz/7,17,1 0.0 P _part/85/_geo_xyz/7,17,2 0.0 P _part/85/_geo_xyz/7,2,0 2.0 P _part/85/_geo_xyz/7,2,1 -0.000000087 P _part/85/_geo_xyz/7,2,2 7.0 P _part/85/_geo_xyz/7,3,0 1.414213538 P _part/85/_geo_xyz/7,3,1 -1.414213538 P _part/85/_geo_xyz/7,3,2 7.0 P _part/85/_geo_xyz/7,4,0 -0.000000175 P _part/85/_geo_xyz/7,4,1 -2.0 P _part/85/_geo_xyz/7,4,2 7.0 P _part/85/_geo_xyz/7,5,0 -0.000000175 P _part/85/_geo_xyz/7,5,1 -2.0 P _part/85/_geo_xyz/7,5,2 7.0 P _part/85/_geo_xyz/7,6,0 -1.414213777 P _part/85/_geo_xyz/7,6,1 -1.414213300 P _part/85/_geo_xyz/7,6,2 7.0 P _part/85/_geo_xyz/7,7,0 -2.0 P _part/85/_geo_xyz/7,7,1 0.000000024 P _part/85/_geo_xyz/7,7,2 7.0 P _part/85/_geo_xyz/7,8,0 -1.414213061 P _part/85/_geo_xyz/7,8,1 1.414214015 P _part/85/_geo_xyz/7,8,2 7.0 P _part/85/_geo_xyz/7,9,0 0.000000350 P _part/85/_geo_xyz/7,9,1 2.0 P _part/85/_geo_xyz/7,9,2 7.0 P _part/85/_geo_xyz/8,0,0 0.0 P _part/85/_geo_xyz/8,0,1 0.0 P _part/85/_geo_xyz/8,0,2 0.0 P _part/85/_geo_xyz/8,1,0 0.0 P _part/85/_geo_xyz/8,1,1 0.0 P _part/85/_geo_xyz/8,1,2 0.0 P _part/85/_geo_xyz/8,10,0 0.0 P _part/85/_geo_xyz/8,10,1 0.0 P _part/85/_geo_xyz/8,10,2 0.0 P _part/85/_geo_xyz/8,11,0 0.0 P _part/85/_geo_xyz/8,11,1 0.0 P _part/85/_geo_xyz/8,11,2 0.0 P _part/85/_geo_xyz/8,12,0 0.0 P _part/85/_geo_xyz/8,12,1 0.0 P _part/85/_geo_xyz/8,12,2 0.0 P _part/85/_geo_xyz/8,13,0 0.0 P _part/85/_geo_xyz/8,13,1 0.0 P _part/85/_geo_xyz/8,13,2 0.0 P _part/85/_geo_xyz/8,14,0 0.0 P _part/85/_geo_xyz/8,14,1 0.0 P _part/85/_geo_xyz/8,14,2 0.0 P _part/85/_geo_xyz/8,15,0 0.0 P _part/85/_geo_xyz/8,15,1 0.0 P _part/85/_geo_xyz/8,15,2 0.0 P _part/85/_geo_xyz/8,16,0 0.0 P _part/85/_geo_xyz/8,16,1 0.0 P _part/85/_geo_xyz/8,16,2 0.0 P _part/85/_geo_xyz/8,17,0 0.0 P _part/85/_geo_xyz/8,17,1 0.0 P _part/85/_geo_xyz/8,17,2 0.0 P _part/85/_geo_xyz/8,2,0 0.0 P _part/85/_geo_xyz/8,2,1 0.0 P _part/85/_geo_xyz/8,2,2 0.0 P _part/85/_geo_xyz/8,3,0 0.0 P _part/85/_geo_xyz/8,3,1 0.0 P _part/85/_geo_xyz/8,3,2 0.0 P _part/85/_geo_xyz/8,4,0 0.0 P _part/85/_geo_xyz/8,4,1 0.0 P _part/85/_geo_xyz/8,4,2 0.0 P _part/85/_geo_xyz/8,5,0 0.0 P _part/85/_geo_xyz/8,5,1 0.0 P _part/85/_geo_xyz/8,5,2 0.0 P _part/85/_geo_xyz/8,6,0 0.0 P _part/85/_geo_xyz/8,6,1 0.0 P _part/85/_geo_xyz/8,6,2 0.0 P _part/85/_geo_xyz/8,7,0 0.0 P _part/85/_geo_xyz/8,7,1 0.0 P _part/85/_geo_xyz/8,7,2 0.0 P _part/85/_geo_xyz/8,8,0 0.0 P _part/85/_geo_xyz/8,8,1 0.0 P _part/85/_geo_xyz/8,8,2 0.0 P _part/85/_geo_xyz/8,9,0 0.0 P _part/85/_geo_xyz/8,9,1 0.0 P _part/85/_geo_xyz/8,9,2 0.0 P _part/85/_geo_xyz/9,0,0 0.0 P _part/85/_geo_xyz/9,0,1 0.0 P _part/85/_geo_xyz/9,0,2 0.0 P _part/85/_geo_xyz/9,1,0 0.0 P _part/85/_geo_xyz/9,1,1 0.0 P _part/85/_geo_xyz/9,1,2 0.0 P _part/85/_geo_xyz/9,10,0 0.0 P _part/85/_geo_xyz/9,10,1 0.0 P _part/85/_geo_xyz/9,10,2 0.0 P _part/85/_geo_xyz/9,11,0 0.0 P _part/85/_geo_xyz/9,11,1 0.0 P _part/85/_geo_xyz/9,11,2 0.0 P _part/85/_geo_xyz/9,12,0 0.0 P _part/85/_geo_xyz/9,12,1 0.0 P _part/85/_geo_xyz/9,12,2 0.0 P _part/85/_geo_xyz/9,13,0 0.0 P _part/85/_geo_xyz/9,13,1 0.0 P _part/85/_geo_xyz/9,13,2 0.0 P _part/85/_geo_xyz/9,14,0 0.0 P _part/85/_geo_xyz/9,14,1 0.0 P _part/85/_geo_xyz/9,14,2 0.0 P _part/85/_geo_xyz/9,15,0 0.0 P _part/85/_geo_xyz/9,15,1 0.0 P _part/85/_geo_xyz/9,15,2 0.0 P _part/85/_geo_xyz/9,16,0 0.0 P _part/85/_geo_xyz/9,16,1 0.0 P _part/85/_geo_xyz/9,16,2 0.0 P _part/85/_geo_xyz/9,17,0 0.0 P _part/85/_geo_xyz/9,17,1 0.0 P _part/85/_geo_xyz/9,17,2 0.0 P _part/85/_geo_xyz/9,2,0 0.0 P _part/85/_geo_xyz/9,2,1 0.0 P _part/85/_geo_xyz/9,2,2 0.0 P _part/85/_geo_xyz/9,3,0 0.0 P _part/85/_geo_xyz/9,3,1 0.0 P _part/85/_geo_xyz/9,3,2 0.0 P _part/85/_geo_xyz/9,4,0 0.0 P _part/85/_geo_xyz/9,4,1 0.0 P _part/85/_geo_xyz/9,4,2 0.0 P _part/85/_geo_xyz/9,5,0 0.0 P _part/85/_geo_xyz/9,5,1 0.0 P _part/85/_geo_xyz/9,5,2 0.0 P _part/85/_geo_xyz/9,6,0 0.0 P _part/85/_geo_xyz/9,6,1 0.0 P _part/85/_geo_xyz/9,6,2 0.0 P _part/85/_geo_xyz/9,7,0 0.0 P _part/85/_geo_xyz/9,7,1 0.0 P _part/85/_geo_xyz/9,7,2 0.0 P _part/85/_geo_xyz/9,8,0 0.0 P _part/85/_geo_xyz/9,8,1 0.0 P _part/85/_geo_xyz/9,8,2 0.0 P _part/85/_geo_xyz/9,9,0 0.0 P _part/85/_geo_xyz/9,9,1 0.0 P _part/85/_geo_xyz/9,9,2 0.0 P _part/85/_geo_xyz/i_count 20 P _part/85/_geo_xyz/j_count 18 P _part/85/_geo_xyz/k_count 3 P _part/85/_locked/0,0 0 P _part/85/_locked/0,1 0 P _part/85/_locked/0,10 0 P _part/85/_locked/0,11 0 P _part/85/_locked/0,12 0 P _part/85/_locked/0,13 0 P _part/85/_locked/0,14 0 P _part/85/_locked/0,15 0 P _part/85/_locked/0,16 0 P _part/85/_locked/0,17 0 P _part/85/_locked/0,2 0 P _part/85/_locked/0,3 0 P _part/85/_locked/0,4 0 P _part/85/_locked/0,5 0 P _part/85/_locked/0,6 0 P _part/85/_locked/0,7 0 P _part/85/_locked/0,8 0 P _part/85/_locked/0,9 0 P _part/85/_locked/1,0 0 P _part/85/_locked/1,1 0 P _part/85/_locked/1,10 0 P _part/85/_locked/1,11 0 P _part/85/_locked/1,12 0 P _part/85/_locked/1,13 0 P _part/85/_locked/1,14 0 P _part/85/_locked/1,15 0 P _part/85/_locked/1,16 0 P _part/85/_locked/1,17 0 P _part/85/_locked/1,2 0 P _part/85/_locked/1,3 0 P _part/85/_locked/1,4 0 P _part/85/_locked/1,5 0 P _part/85/_locked/1,6 0 P _part/85/_locked/1,7 0 P _part/85/_locked/1,8 0 P _part/85/_locked/1,9 0 P _part/85/_locked/10,0 0 P _part/85/_locked/10,1 0 P _part/85/_locked/10,10 0 P _part/85/_locked/10,11 0 P _part/85/_locked/10,12 0 P _part/85/_locked/10,13 0 P _part/85/_locked/10,14 0 P _part/85/_locked/10,15 0 P _part/85/_locked/10,16 0 P _part/85/_locked/10,17 0 P _part/85/_locked/10,2 0 P _part/85/_locked/10,3 0 P _part/85/_locked/10,4 0 P _part/85/_locked/10,5 0 P _part/85/_locked/10,6 0 P _part/85/_locked/10,7 0 P _part/85/_locked/10,8 0 P _part/85/_locked/10,9 0 P _part/85/_locked/11,0 0 P _part/85/_locked/11,1 0 P _part/85/_locked/11,10 0 P _part/85/_locked/11,11 0 P _part/85/_locked/11,12 0 P _part/85/_locked/11,13 0 P _part/85/_locked/11,14 0 P _part/85/_locked/11,15 0 P _part/85/_locked/11,16 0 P _part/85/_locked/11,17 0 P _part/85/_locked/11,2 0 P _part/85/_locked/11,3 0 P _part/85/_locked/11,4 0 P _part/85/_locked/11,5 0 P _part/85/_locked/11,6 0 P _part/85/_locked/11,7 0 P _part/85/_locked/11,8 0 P _part/85/_locked/11,9 0 P _part/85/_locked/12,0 0 P _part/85/_locked/12,1 0 P _part/85/_locked/12,10 0 P _part/85/_locked/12,11 0 P _part/85/_locked/12,12 0 P _part/85/_locked/12,13 0 P _part/85/_locked/12,14 0 P _part/85/_locked/12,15 0 P _part/85/_locked/12,16 0 P _part/85/_locked/12,17 0 P _part/85/_locked/12,2 0 P _part/85/_locked/12,3 0 P _part/85/_locked/12,4 0 P _part/85/_locked/12,5 0 P _part/85/_locked/12,6 0 P _part/85/_locked/12,7 0 P _part/85/_locked/12,8 0 P _part/85/_locked/12,9 0 P _part/85/_locked/13,0 0 P _part/85/_locked/13,1 0 P _part/85/_locked/13,10 0 P _part/85/_locked/13,11 0 P _part/85/_locked/13,12 0 P _part/85/_locked/13,13 0 P _part/85/_locked/13,14 0 P _part/85/_locked/13,15 0 P _part/85/_locked/13,16 0 P _part/85/_locked/13,17 0 P _part/85/_locked/13,2 0 P _part/85/_locked/13,3 0 P _part/85/_locked/13,4 0 P _part/85/_locked/13,5 0 P _part/85/_locked/13,6 0 P _part/85/_locked/13,7 0 P _part/85/_locked/13,8 0 P _part/85/_locked/13,9 0 P _part/85/_locked/14,0 0 P _part/85/_locked/14,1 0 P _part/85/_locked/14,10 0 P _part/85/_locked/14,11 0 P _part/85/_locked/14,12 0 P _part/85/_locked/14,13 0 P _part/85/_locked/14,14 0 P _part/85/_locked/14,15 0 P _part/85/_locked/14,16 0 P _part/85/_locked/14,17 0 P _part/85/_locked/14,2 0 P _part/85/_locked/14,3 0 P _part/85/_locked/14,4 0 P _part/85/_locked/14,5 0 P _part/85/_locked/14,6 0 P _part/85/_locked/14,7 0 P _part/85/_locked/14,8 0 P _part/85/_locked/14,9 0 P _part/85/_locked/15,0 0 P _part/85/_locked/15,1 0 P _part/85/_locked/15,10 0 P _part/85/_locked/15,11 0 P _part/85/_locked/15,12 0 P _part/85/_locked/15,13 0 P _part/85/_locked/15,14 0 P _part/85/_locked/15,15 0 P _part/85/_locked/15,16 0 P _part/85/_locked/15,17 0 P _part/85/_locked/15,2 0 P _part/85/_locked/15,3 0 P _part/85/_locked/15,4 0 P _part/85/_locked/15,5 0 P _part/85/_locked/15,6 0 P _part/85/_locked/15,7 0 P _part/85/_locked/15,8 0 P _part/85/_locked/15,9 0 P _part/85/_locked/16,0 0 P _part/85/_locked/16,1 0 P _part/85/_locked/16,10 0 P _part/85/_locked/16,11 0 P _part/85/_locked/16,12 0 P _part/85/_locked/16,13 0 P _part/85/_locked/16,14 0 P _part/85/_locked/16,15 0 P _part/85/_locked/16,16 0 P _part/85/_locked/16,17 0 P _part/85/_locked/16,2 0 P _part/85/_locked/16,3 0 P _part/85/_locked/16,4 0 P _part/85/_locked/16,5 0 P _part/85/_locked/16,6 0 P _part/85/_locked/16,7 0 P _part/85/_locked/16,8 0 P _part/85/_locked/16,9 0 P _part/85/_locked/17,0 0 P _part/85/_locked/17,1 0 P _part/85/_locked/17,10 0 P _part/85/_locked/17,11 0 P _part/85/_locked/17,12 0 P _part/85/_locked/17,13 0 P _part/85/_locked/17,14 0 P _part/85/_locked/17,15 0 P _part/85/_locked/17,16 0 P _part/85/_locked/17,17 0 P _part/85/_locked/17,2 0 P _part/85/_locked/17,3 0 P _part/85/_locked/17,4 0 P _part/85/_locked/17,5 0 P _part/85/_locked/17,6 0 P _part/85/_locked/17,7 0 P _part/85/_locked/17,8 0 P _part/85/_locked/17,9 0 P _part/85/_locked/18,0 0 P _part/85/_locked/18,1 0 P _part/85/_locked/18,10 0 P _part/85/_locked/18,11 0 P _part/85/_locked/18,12 0 P _part/85/_locked/18,13 0 P _part/85/_locked/18,14 0 P _part/85/_locked/18,15 0 P _part/85/_locked/18,16 0 P _part/85/_locked/18,17 0 P _part/85/_locked/18,2 0 P _part/85/_locked/18,3 0 P _part/85/_locked/18,4 0 P _part/85/_locked/18,5 0 P _part/85/_locked/18,6 0 P _part/85/_locked/18,7 0 P _part/85/_locked/18,8 0 P _part/85/_locked/18,9 0 P _part/85/_locked/19,0 0 P _part/85/_locked/19,1 0 P _part/85/_locked/19,10 0 P _part/85/_locked/19,11 0 P _part/85/_locked/19,12 0 P _part/85/_locked/19,13 0 P _part/85/_locked/19,14 0 P _part/85/_locked/19,15 0 P _part/85/_locked/19,16 0 P _part/85/_locked/19,17 0 P _part/85/_locked/19,2 0 P _part/85/_locked/19,3 0 P _part/85/_locked/19,4 0 P _part/85/_locked/19,5 0 P _part/85/_locked/19,6 0 P _part/85/_locked/19,7 0 P _part/85/_locked/19,8 0 P _part/85/_locked/19,9 0 P _part/85/_locked/2,0 0 P _part/85/_locked/2,1 0 P _part/85/_locked/2,10 0 P _part/85/_locked/2,11 0 P _part/85/_locked/2,12 0 P _part/85/_locked/2,13 0 P _part/85/_locked/2,14 0 P _part/85/_locked/2,15 0 P _part/85/_locked/2,16 0 P _part/85/_locked/2,17 0 P _part/85/_locked/2,2 0 P _part/85/_locked/2,3 0 P _part/85/_locked/2,4 0 P _part/85/_locked/2,5 0 P _part/85/_locked/2,6 0 P _part/85/_locked/2,7 0 P _part/85/_locked/2,8 0 P _part/85/_locked/2,9 0 P _part/85/_locked/3,0 0 P _part/85/_locked/3,1 0 P _part/85/_locked/3,10 0 P _part/85/_locked/3,11 0 P _part/85/_locked/3,12 0 P _part/85/_locked/3,13 0 P _part/85/_locked/3,14 0 P _part/85/_locked/3,15 0 P _part/85/_locked/3,16 0 P _part/85/_locked/3,17 0 P _part/85/_locked/3,2 0 P _part/85/_locked/3,3 0 P _part/85/_locked/3,4 0 P _part/85/_locked/3,5 0 P _part/85/_locked/3,6 0 P _part/85/_locked/3,7 0 P _part/85/_locked/3,8 0 P _part/85/_locked/3,9 0 P _part/85/_locked/4,0 0 P _part/85/_locked/4,1 0 P _part/85/_locked/4,10 0 P _part/85/_locked/4,11 0 P _part/85/_locked/4,12 0 P _part/85/_locked/4,13 0 P _part/85/_locked/4,14 0 P _part/85/_locked/4,15 0 P _part/85/_locked/4,16 0 P _part/85/_locked/4,17 0 P _part/85/_locked/4,2 0 P _part/85/_locked/4,3 0 P _part/85/_locked/4,4 0 P _part/85/_locked/4,5 0 P _part/85/_locked/4,6 0 P _part/85/_locked/4,7 0 P _part/85/_locked/4,8 0 P _part/85/_locked/4,9 0 P _part/85/_locked/5,0 0 P _part/85/_locked/5,1 0 P _part/85/_locked/5,10 0 P _part/85/_locked/5,11 0 P _part/85/_locked/5,12 0 P _part/85/_locked/5,13 0 P _part/85/_locked/5,14 0 P _part/85/_locked/5,15 0 P _part/85/_locked/5,16 0 P _part/85/_locked/5,17 0 P _part/85/_locked/5,2 0 P _part/85/_locked/5,3 0 P _part/85/_locked/5,4 0 P _part/85/_locked/5,5 0 P _part/85/_locked/5,6 0 P _part/85/_locked/5,7 0 P _part/85/_locked/5,8 0 P _part/85/_locked/5,9 0 P _part/85/_locked/6,0 0 P _part/85/_locked/6,1 0 P _part/85/_locked/6,10 0 P _part/85/_locked/6,11 0 P _part/85/_locked/6,12 0 P _part/85/_locked/6,13 0 P _part/85/_locked/6,14 0 P _part/85/_locked/6,15 0 P _part/85/_locked/6,16 0 P _part/85/_locked/6,17 0 P _part/85/_locked/6,2 0 P _part/85/_locked/6,3 0 P _part/85/_locked/6,4 0 P _part/85/_locked/6,5 0 P _part/85/_locked/6,6 0 P _part/85/_locked/6,7 0 P _part/85/_locked/6,8 0 P _part/85/_locked/6,9 0 P _part/85/_locked/7,0 0 P _part/85/_locked/7,1 0 P _part/85/_locked/7,10 0 P _part/85/_locked/7,11 0 P _part/85/_locked/7,12 0 P _part/85/_locked/7,13 0 P _part/85/_locked/7,14 0 P _part/85/_locked/7,15 0 P _part/85/_locked/7,16 0 P _part/85/_locked/7,17 0 P _part/85/_locked/7,2 0 P _part/85/_locked/7,3 0 P _part/85/_locked/7,4 0 P _part/85/_locked/7,5 0 P _part/85/_locked/7,6 0 P _part/85/_locked/7,7 0 P _part/85/_locked/7,8 0 P _part/85/_locked/7,9 0 P _part/85/_locked/8,0 0 P _part/85/_locked/8,1 0 P _part/85/_locked/8,10 0 P _part/85/_locked/8,11 0 P _part/85/_locked/8,12 0 P _part/85/_locked/8,13 0 P _part/85/_locked/8,14 0 P _part/85/_locked/8,15 0 P _part/85/_locked/8,16 0 P _part/85/_locked/8,17 0 P _part/85/_locked/8,2 0 P _part/85/_locked/8,3 0 P _part/85/_locked/8,4 0 P _part/85/_locked/8,5 0 P _part/85/_locked/8,6 0 P _part/85/_locked/8,7 0 P _part/85/_locked/8,8 0 P _part/85/_locked/8,9 0 P _part/85/_locked/9,0 0 P _part/85/_locked/9,1 0 P _part/85/_locked/9,10 0 P _part/85/_locked/9,11 0 P _part/85/_locked/9,12 0 P _part/85/_locked/9,13 0 P _part/85/_locked/9,14 0 P _part/85/_locked/9,15 0 P _part/85/_locked/9,16 0 P _part/85/_locked/9,17 0 P _part/85/_locked/9,2 0 P _part/85/_locked/9,3 0 P _part/85/_locked/9,4 0 P _part/85/_locked/9,5 0 P _part/85/_locked/9,6 0 P _part/85/_locked/9,7 0 P _part/85/_locked/9,8 0 P _part/85/_locked/9,9 0 P _part/85/_locked/i_count 20 P _part/85/_locked/j_count 18 P _part/85/_part_cd 0.075000003 P _part/85/_part_phi 0.0 P _part/85/_part_psi 0.0 P _part/85/_part_rad 2.0 P _part/85/_part_specs_eq 0 P _part/85/_part_specs_invis 0 P _part/85/_part_specs_unused1 0 P _part/85/_part_specs_unused2 0 P _part/85/_part_tex 0 P _part/85/_part_the 0.0 P _part/85/_part_x 0.0 P _part/85/_part_y 0.0 P _part/85/_part_z 0.0 P _part/85/_patt_con 0 P _part/85/_patt_prt 0 P _part/85/_patt_rat 0.0 P _part/85/_r_dim 10 P _part/85/_s_dim 8 P _part/85/_scon 37.676635742 P _part/85/_top_s1 0.632812500 P _part/85/_top_s2 0.753906250 P _part/85/_top_t1 0.254882812 P _part/85/_top_t2 0.295898438 P _part/86/_aero_x_os 0.0 P _part/86/_aero_y_os 0.0 P _part/86/_aero_z_os 0.0 P _part/86/_area_frnt 0.0 P _part/86/_area_side 0.0 P _part/86/_area_vert 0.0 P _part/86/_bot_s1 0.632812500 P _part/86/_bot_s2 0.753906250 P _part/86/_bot_t1 0.254882812 P _part/86/_bot_t2 0.295898438 P _part/86/_damp 1.883831739 P _part/86/_geo_xyz/0,0,0 0.0 P _part/86/_geo_xyz/0,0,1 2.0 P _part/86/_geo_xyz/0,0,2 0.0 P _part/86/_geo_xyz/0,1,0 1.414213538 P _part/86/_geo_xyz/0,1,1 1.414213538 P _part/86/_geo_xyz/0,1,2 0.0 P _part/86/_geo_xyz/0,10,0 0.0 P _part/86/_geo_xyz/0,10,1 0.0 P _part/86/_geo_xyz/0,10,2 0.0 P _part/86/_geo_xyz/0,11,0 0.0 P _part/86/_geo_xyz/0,11,1 0.0 P _part/86/_geo_xyz/0,11,2 0.0 P _part/86/_geo_xyz/0,12,0 0.0 P _part/86/_geo_xyz/0,12,1 0.0 P _part/86/_geo_xyz/0,12,2 0.0 P _part/86/_geo_xyz/0,13,0 0.0 P _part/86/_geo_xyz/0,13,1 0.0 P _part/86/_geo_xyz/0,13,2 0.0 P _part/86/_geo_xyz/0,14,0 0.0 P _part/86/_geo_xyz/0,14,1 0.0 P _part/86/_geo_xyz/0,14,2 0.0 P _part/86/_geo_xyz/0,15,0 0.0 P _part/86/_geo_xyz/0,15,1 0.0 P _part/86/_geo_xyz/0,15,2 0.0 P _part/86/_geo_xyz/0,16,0 0.0 P _part/86/_geo_xyz/0,16,1 0.0 P _part/86/_geo_xyz/0,16,2 0.0 P _part/86/_geo_xyz/0,17,0 0.0 P _part/86/_geo_xyz/0,17,1 0.0 P _part/86/_geo_xyz/0,17,2 0.0 P _part/86/_geo_xyz/0,2,0 2.0 P _part/86/_geo_xyz/0,2,1 -0.000000087 P _part/86/_geo_xyz/0,2,2 0.0 P _part/86/_geo_xyz/0,3,0 1.414213538 P _part/86/_geo_xyz/0,3,1 -1.414213538 P _part/86/_geo_xyz/0,3,2 0.0 P _part/86/_geo_xyz/0,4,0 -0.000000175 P _part/86/_geo_xyz/0,4,1 -2.0 P _part/86/_geo_xyz/0,4,2 0.0 P _part/86/_geo_xyz/0,5,0 -0.000000175 P _part/86/_geo_xyz/0,5,1 -2.0 P _part/86/_geo_xyz/0,5,2 0.0 P _part/86/_geo_xyz/0,6,0 -1.414213777 P _part/86/_geo_xyz/0,6,1 -1.414213300 P _part/86/_geo_xyz/0,6,2 0.0 P _part/86/_geo_xyz/0,7,0 -2.0 P _part/86/_geo_xyz/0,7,1 0.000000024 P _part/86/_geo_xyz/0,7,2 0.0 P _part/86/_geo_xyz/0,8,0 -1.414213061 P _part/86/_geo_xyz/0,8,1 1.414214015 P _part/86/_geo_xyz/0,8,2 0.0 P _part/86/_geo_xyz/0,9,0 0.000000350 P _part/86/_geo_xyz/0,9,1 2.0 P _part/86/_geo_xyz/0,9,2 0.0 P _part/86/_geo_xyz/1,0,0 0.0 P _part/86/_geo_xyz/1,0,1 2.0 P _part/86/_geo_xyz/1,0,2 1.0 P _part/86/_geo_xyz/1,1,0 1.414213538 P _part/86/_geo_xyz/1,1,1 1.414213538 P _part/86/_geo_xyz/1,1,2 1.0 P _part/86/_geo_xyz/1,10,0 0.0 P _part/86/_geo_xyz/1,10,1 0.0 P _part/86/_geo_xyz/1,10,2 0.0 P _part/86/_geo_xyz/1,11,0 0.0 P _part/86/_geo_xyz/1,11,1 0.0 P _part/86/_geo_xyz/1,11,2 0.0 P _part/86/_geo_xyz/1,12,0 0.0 P _part/86/_geo_xyz/1,12,1 0.0 P _part/86/_geo_xyz/1,12,2 0.0 P _part/86/_geo_xyz/1,13,0 0.0 P _part/86/_geo_xyz/1,13,1 0.0 P _part/86/_geo_xyz/1,13,2 0.0 P _part/86/_geo_xyz/1,14,0 0.0 P _part/86/_geo_xyz/1,14,1 0.0 P _part/86/_geo_xyz/1,14,2 0.0 P _part/86/_geo_xyz/1,15,0 0.0 P _part/86/_geo_xyz/1,15,1 0.0 P _part/86/_geo_xyz/1,15,2 0.0 P _part/86/_geo_xyz/1,16,0 0.0 P _part/86/_geo_xyz/1,16,1 0.0 P _part/86/_geo_xyz/1,16,2 0.0 P _part/86/_geo_xyz/1,17,0 0.0 P _part/86/_geo_xyz/1,17,1 0.0 P _part/86/_geo_xyz/1,17,2 0.0 P _part/86/_geo_xyz/1,2,0 2.0 P _part/86/_geo_xyz/1,2,1 -0.000000087 P _part/86/_geo_xyz/1,2,2 1.0 P _part/86/_geo_xyz/1,3,0 1.414213538 P _part/86/_geo_xyz/1,3,1 -1.414213538 P _part/86/_geo_xyz/1,3,2 1.0 P _part/86/_geo_xyz/1,4,0 -0.000000175 P _part/86/_geo_xyz/1,4,1 -2.0 P _part/86/_geo_xyz/1,4,2 1.0 P _part/86/_geo_xyz/1,5,0 -0.000000175 P _part/86/_geo_xyz/1,5,1 -2.0 P _part/86/_geo_xyz/1,5,2 1.0 P _part/86/_geo_xyz/1,6,0 -1.414213777 P _part/86/_geo_xyz/1,6,1 -1.414213300 P _part/86/_geo_xyz/1,6,2 1.0 P _part/86/_geo_xyz/1,7,0 -2.0 P _part/86/_geo_xyz/1,7,1 0.000000024 P _part/86/_geo_xyz/1,7,2 1.0 P _part/86/_geo_xyz/1,8,0 -1.414213061 P _part/86/_geo_xyz/1,8,1 1.414214015 P _part/86/_geo_xyz/1,8,2 1.0 P _part/86/_geo_xyz/1,9,0 0.000000350 P _part/86/_geo_xyz/1,9,1 2.0 P _part/86/_geo_xyz/1,9,2 1.0 P _part/86/_geo_xyz/10,0,0 0.0 P _part/86/_geo_xyz/10,0,1 0.0 P _part/86/_geo_xyz/10,0,2 0.0 P _part/86/_geo_xyz/10,1,0 0.0 P _part/86/_geo_xyz/10,1,1 0.0 P _part/86/_geo_xyz/10,1,2 0.0 P _part/86/_geo_xyz/10,10,0 0.0 P _part/86/_geo_xyz/10,10,1 0.0 P _part/86/_geo_xyz/10,10,2 0.0 P _part/86/_geo_xyz/10,11,0 0.0 P _part/86/_geo_xyz/10,11,1 0.0 P _part/86/_geo_xyz/10,11,2 0.0 P _part/86/_geo_xyz/10,12,0 0.0 P _part/86/_geo_xyz/10,12,1 0.0 P _part/86/_geo_xyz/10,12,2 0.0 P _part/86/_geo_xyz/10,13,0 0.0 P _part/86/_geo_xyz/10,13,1 0.0 P _part/86/_geo_xyz/10,13,2 0.0 P _part/86/_geo_xyz/10,14,0 0.0 P _part/86/_geo_xyz/10,14,1 0.0 P _part/86/_geo_xyz/10,14,2 0.0 P _part/86/_geo_xyz/10,15,0 0.0 P _part/86/_geo_xyz/10,15,1 0.0 P _part/86/_geo_xyz/10,15,2 0.0 P _part/86/_geo_xyz/10,16,0 0.0 P _part/86/_geo_xyz/10,16,1 0.0 P _part/86/_geo_xyz/10,16,2 0.0 P _part/86/_geo_xyz/10,17,0 0.0 P _part/86/_geo_xyz/10,17,1 0.0 P _part/86/_geo_xyz/10,17,2 0.0 P _part/86/_geo_xyz/10,2,0 0.0 P _part/86/_geo_xyz/10,2,1 0.0 P _part/86/_geo_xyz/10,2,2 0.0 P _part/86/_geo_xyz/10,3,0 0.0 P _part/86/_geo_xyz/10,3,1 0.0 P _part/86/_geo_xyz/10,3,2 0.0 P _part/86/_geo_xyz/10,4,0 0.0 P _part/86/_geo_xyz/10,4,1 0.0 P _part/86/_geo_xyz/10,4,2 0.0 P _part/86/_geo_xyz/10,5,0 0.0 P _part/86/_geo_xyz/10,5,1 0.0 P _part/86/_geo_xyz/10,5,2 0.0 P _part/86/_geo_xyz/10,6,0 0.0 P _part/86/_geo_xyz/10,6,1 0.0 P _part/86/_geo_xyz/10,6,2 0.0 P _part/86/_geo_xyz/10,7,0 0.0 P _part/86/_geo_xyz/10,7,1 0.0 P _part/86/_geo_xyz/10,7,2 0.0 P _part/86/_geo_xyz/10,8,0 0.0 P _part/86/_geo_xyz/10,8,1 0.0 P _part/86/_geo_xyz/10,8,2 0.0 P _part/86/_geo_xyz/10,9,0 0.0 P _part/86/_geo_xyz/10,9,1 0.0 P _part/86/_geo_xyz/10,9,2 0.0 P _part/86/_geo_xyz/11,0,0 0.0 P _part/86/_geo_xyz/11,0,1 0.0 P _part/86/_geo_xyz/11,0,2 0.0 P _part/86/_geo_xyz/11,1,0 0.0 P _part/86/_geo_xyz/11,1,1 0.0 P _part/86/_geo_xyz/11,1,2 0.0 P _part/86/_geo_xyz/11,10,0 0.0 P _part/86/_geo_xyz/11,10,1 0.0 P _part/86/_geo_xyz/11,10,2 0.0 P _part/86/_geo_xyz/11,11,0 0.0 P _part/86/_geo_xyz/11,11,1 0.0 P _part/86/_geo_xyz/11,11,2 0.0 P _part/86/_geo_xyz/11,12,0 0.0 P _part/86/_geo_xyz/11,12,1 0.0 P _part/86/_geo_xyz/11,12,2 0.0 P _part/86/_geo_xyz/11,13,0 0.0 P _part/86/_geo_xyz/11,13,1 0.0 P _part/86/_geo_xyz/11,13,2 0.0 P _part/86/_geo_xyz/11,14,0 0.0 P _part/86/_geo_xyz/11,14,1 0.0 P _part/86/_geo_xyz/11,14,2 0.0 P _part/86/_geo_xyz/11,15,0 0.0 P _part/86/_geo_xyz/11,15,1 0.0 P _part/86/_geo_xyz/11,15,2 0.0 P _part/86/_geo_xyz/11,16,0 0.0 P _part/86/_geo_xyz/11,16,1 0.0 P _part/86/_geo_xyz/11,16,2 0.0 P _part/86/_geo_xyz/11,17,0 0.0 P _part/86/_geo_xyz/11,17,1 0.0 P _part/86/_geo_xyz/11,17,2 0.0 P _part/86/_geo_xyz/11,2,0 0.0 P _part/86/_geo_xyz/11,2,1 0.0 P _part/86/_geo_xyz/11,2,2 0.0 P _part/86/_geo_xyz/11,3,0 0.0 P _part/86/_geo_xyz/11,3,1 0.0 P _part/86/_geo_xyz/11,3,2 0.0 P _part/86/_geo_xyz/11,4,0 0.0 P _part/86/_geo_xyz/11,4,1 0.0 P _part/86/_geo_xyz/11,4,2 0.0 P _part/86/_geo_xyz/11,5,0 0.0 P _part/86/_geo_xyz/11,5,1 0.0 P _part/86/_geo_xyz/11,5,2 0.0 P _part/86/_geo_xyz/11,6,0 0.0 P _part/86/_geo_xyz/11,6,1 0.0 P _part/86/_geo_xyz/11,6,2 0.0 P _part/86/_geo_xyz/11,7,0 0.0 P _part/86/_geo_xyz/11,7,1 0.0 P _part/86/_geo_xyz/11,7,2 0.0 P _part/86/_geo_xyz/11,8,0 0.0 P _part/86/_geo_xyz/11,8,1 0.0 P _part/86/_geo_xyz/11,8,2 0.0 P _part/86/_geo_xyz/11,9,0 0.0 P _part/86/_geo_xyz/11,9,1 0.0 P _part/86/_geo_xyz/11,9,2 0.0 P _part/86/_geo_xyz/12,0,0 0.0 P _part/86/_geo_xyz/12,0,1 0.0 P _part/86/_geo_xyz/12,0,2 0.0 P _part/86/_geo_xyz/12,1,0 0.0 P _part/86/_geo_xyz/12,1,1 0.0 P _part/86/_geo_xyz/12,1,2 0.0 P _part/86/_geo_xyz/12,10,0 0.0 P _part/86/_geo_xyz/12,10,1 0.0 P _part/86/_geo_xyz/12,10,2 0.0 P _part/86/_geo_xyz/12,11,0 0.0 P _part/86/_geo_xyz/12,11,1 0.0 P _part/86/_geo_xyz/12,11,2 0.0 P _part/86/_geo_xyz/12,12,0 0.0 P _part/86/_geo_xyz/12,12,1 0.0 P _part/86/_geo_xyz/12,12,2 0.0 P _part/86/_geo_xyz/12,13,0 0.0 P _part/86/_geo_xyz/12,13,1 0.0 P _part/86/_geo_xyz/12,13,2 0.0 P _part/86/_geo_xyz/12,14,0 0.0 P _part/86/_geo_xyz/12,14,1 0.0 P _part/86/_geo_xyz/12,14,2 0.0 P _part/86/_geo_xyz/12,15,0 0.0 P _part/86/_geo_xyz/12,15,1 0.0 P _part/86/_geo_xyz/12,15,2 0.0 P _part/86/_geo_xyz/12,16,0 0.0 P _part/86/_geo_xyz/12,16,1 0.0 P _part/86/_geo_xyz/12,16,2 0.0 P _part/86/_geo_xyz/12,17,0 0.0 P _part/86/_geo_xyz/12,17,1 0.0 P _part/86/_geo_xyz/12,17,2 0.0 P _part/86/_geo_xyz/12,2,0 0.0 P _part/86/_geo_xyz/12,2,1 0.0 P _part/86/_geo_xyz/12,2,2 0.0 P _part/86/_geo_xyz/12,3,0 0.0 P _part/86/_geo_xyz/12,3,1 0.0 P _part/86/_geo_xyz/12,3,2 0.0 P _part/86/_geo_xyz/12,4,0 0.0 P _part/86/_geo_xyz/12,4,1 0.0 P _part/86/_geo_xyz/12,4,2 0.0 P _part/86/_geo_xyz/12,5,0 0.0 P _part/86/_geo_xyz/12,5,1 0.0 P _part/86/_geo_xyz/12,5,2 0.0 P _part/86/_geo_xyz/12,6,0 0.0 P _part/86/_geo_xyz/12,6,1 0.0 P _part/86/_geo_xyz/12,6,2 0.0 P _part/86/_geo_xyz/12,7,0 0.0 P _part/86/_geo_xyz/12,7,1 0.0 P _part/86/_geo_xyz/12,7,2 0.0 P _part/86/_geo_xyz/12,8,0 0.0 P _part/86/_geo_xyz/12,8,1 0.0 P _part/86/_geo_xyz/12,8,2 0.0 P _part/86/_geo_xyz/12,9,0 0.0 P _part/86/_geo_xyz/12,9,1 0.0 P _part/86/_geo_xyz/12,9,2 0.0 P _part/86/_geo_xyz/13,0,0 0.0 P _part/86/_geo_xyz/13,0,1 0.0 P _part/86/_geo_xyz/13,0,2 0.0 P _part/86/_geo_xyz/13,1,0 0.0 P _part/86/_geo_xyz/13,1,1 0.0 P _part/86/_geo_xyz/13,1,2 0.0 P _part/86/_geo_xyz/13,10,0 0.0 P _part/86/_geo_xyz/13,10,1 0.0 P _part/86/_geo_xyz/13,10,2 0.0 P _part/86/_geo_xyz/13,11,0 0.0 P _part/86/_geo_xyz/13,11,1 0.0 P _part/86/_geo_xyz/13,11,2 0.0 P _part/86/_geo_xyz/13,12,0 0.0 P _part/86/_geo_xyz/13,12,1 0.0 P _part/86/_geo_xyz/13,12,2 0.0 P _part/86/_geo_xyz/13,13,0 0.0 P _part/86/_geo_xyz/13,13,1 0.0 P _part/86/_geo_xyz/13,13,2 0.0 P _part/86/_geo_xyz/13,14,0 0.0 P _part/86/_geo_xyz/13,14,1 0.0 P _part/86/_geo_xyz/13,14,2 0.0 P _part/86/_geo_xyz/13,15,0 0.0 P _part/86/_geo_xyz/13,15,1 0.0 P _part/86/_geo_xyz/13,15,2 0.0 P _part/86/_geo_xyz/13,16,0 0.0 P _part/86/_geo_xyz/13,16,1 0.0 P _part/86/_geo_xyz/13,16,2 0.0 P _part/86/_geo_xyz/13,17,0 0.0 P _part/86/_geo_xyz/13,17,1 0.0 P _part/86/_geo_xyz/13,17,2 0.0 P _part/86/_geo_xyz/13,2,0 0.0 P _part/86/_geo_xyz/13,2,1 0.0 P _part/86/_geo_xyz/13,2,2 0.0 P _part/86/_geo_xyz/13,3,0 0.0 P _part/86/_geo_xyz/13,3,1 0.0 P _part/86/_geo_xyz/13,3,2 0.0 P _part/86/_geo_xyz/13,4,0 0.0 P _part/86/_geo_xyz/13,4,1 0.0 P _part/86/_geo_xyz/13,4,2 0.0 P _part/86/_geo_xyz/13,5,0 0.0 P _part/86/_geo_xyz/13,5,1 0.0 P _part/86/_geo_xyz/13,5,2 0.0 P _part/86/_geo_xyz/13,6,0 0.0 P _part/86/_geo_xyz/13,6,1 0.0 P _part/86/_geo_xyz/13,6,2 0.0 P _part/86/_geo_xyz/13,7,0 0.0 P _part/86/_geo_xyz/13,7,1 0.0 P _part/86/_geo_xyz/13,7,2 0.0 P _part/86/_geo_xyz/13,8,0 0.0 P _part/86/_geo_xyz/13,8,1 0.0 P _part/86/_geo_xyz/13,8,2 0.0 P _part/86/_geo_xyz/13,9,0 0.0 P _part/86/_geo_xyz/13,9,1 0.0 P _part/86/_geo_xyz/13,9,2 0.0 P _part/86/_geo_xyz/14,0,0 0.0 P _part/86/_geo_xyz/14,0,1 0.0 P _part/86/_geo_xyz/14,0,2 0.0 P _part/86/_geo_xyz/14,1,0 0.0 P _part/86/_geo_xyz/14,1,1 0.0 P _part/86/_geo_xyz/14,1,2 0.0 P _part/86/_geo_xyz/14,10,0 0.0 P _part/86/_geo_xyz/14,10,1 0.0 P _part/86/_geo_xyz/14,10,2 0.0 P _part/86/_geo_xyz/14,11,0 0.0 P _part/86/_geo_xyz/14,11,1 0.0 P _part/86/_geo_xyz/14,11,2 0.0 P _part/86/_geo_xyz/14,12,0 0.0 P _part/86/_geo_xyz/14,12,1 0.0 P _part/86/_geo_xyz/14,12,2 0.0 P _part/86/_geo_xyz/14,13,0 0.0 P _part/86/_geo_xyz/14,13,1 0.0 P _part/86/_geo_xyz/14,13,2 0.0 P _part/86/_geo_xyz/14,14,0 0.0 P _part/86/_geo_xyz/14,14,1 0.0 P _part/86/_geo_xyz/14,14,2 0.0 P _part/86/_geo_xyz/14,15,0 0.0 P _part/86/_geo_xyz/14,15,1 0.0 P _part/86/_geo_xyz/14,15,2 0.0 P _part/86/_geo_xyz/14,16,0 0.0 P _part/86/_geo_xyz/14,16,1 0.0 P _part/86/_geo_xyz/14,16,2 0.0 P _part/86/_geo_xyz/14,17,0 0.0 P _part/86/_geo_xyz/14,17,1 0.0 P _part/86/_geo_xyz/14,17,2 0.0 P _part/86/_geo_xyz/14,2,0 0.0 P _part/86/_geo_xyz/14,2,1 0.0 P _part/86/_geo_xyz/14,2,2 0.0 P _part/86/_geo_xyz/14,3,0 0.0 P _part/86/_geo_xyz/14,3,1 0.0 P _part/86/_geo_xyz/14,3,2 0.0 P _part/86/_geo_xyz/14,4,0 0.0 P _part/86/_geo_xyz/14,4,1 0.0 P _part/86/_geo_xyz/14,4,2 0.0 P _part/86/_geo_xyz/14,5,0 0.0 P _part/86/_geo_xyz/14,5,1 0.0 P _part/86/_geo_xyz/14,5,2 0.0 P _part/86/_geo_xyz/14,6,0 0.0 P _part/86/_geo_xyz/14,6,1 0.0 P _part/86/_geo_xyz/14,6,2 0.0 P _part/86/_geo_xyz/14,7,0 0.0 P _part/86/_geo_xyz/14,7,1 0.0 P _part/86/_geo_xyz/14,7,2 0.0 P _part/86/_geo_xyz/14,8,0 0.0 P _part/86/_geo_xyz/14,8,1 0.0 P _part/86/_geo_xyz/14,8,2 0.0 P _part/86/_geo_xyz/14,9,0 0.0 P _part/86/_geo_xyz/14,9,1 0.0 P _part/86/_geo_xyz/14,9,2 0.0 P _part/86/_geo_xyz/15,0,0 0.0 P _part/86/_geo_xyz/15,0,1 0.0 P _part/86/_geo_xyz/15,0,2 0.0 P _part/86/_geo_xyz/15,1,0 0.0 P _part/86/_geo_xyz/15,1,1 0.0 P _part/86/_geo_xyz/15,1,2 0.0 P _part/86/_geo_xyz/15,10,0 0.0 P _part/86/_geo_xyz/15,10,1 0.0 P _part/86/_geo_xyz/15,10,2 0.0 P _part/86/_geo_xyz/15,11,0 0.0 P _part/86/_geo_xyz/15,11,1 0.0 P _part/86/_geo_xyz/15,11,2 0.0 P _part/86/_geo_xyz/15,12,0 0.0 P _part/86/_geo_xyz/15,12,1 0.0 P _part/86/_geo_xyz/15,12,2 0.0 P _part/86/_geo_xyz/15,13,0 0.0 P _part/86/_geo_xyz/15,13,1 0.0 P _part/86/_geo_xyz/15,13,2 0.0 P _part/86/_geo_xyz/15,14,0 0.0 P _part/86/_geo_xyz/15,14,1 0.0 P _part/86/_geo_xyz/15,14,2 0.0 P _part/86/_geo_xyz/15,15,0 0.0 P _part/86/_geo_xyz/15,15,1 0.0 P _part/86/_geo_xyz/15,15,2 0.0 P _part/86/_geo_xyz/15,16,0 0.0 P _part/86/_geo_xyz/15,16,1 0.0 P _part/86/_geo_xyz/15,16,2 0.0 P _part/86/_geo_xyz/15,17,0 0.0 P _part/86/_geo_xyz/15,17,1 0.0 P _part/86/_geo_xyz/15,17,2 0.0 P _part/86/_geo_xyz/15,2,0 0.0 P _part/86/_geo_xyz/15,2,1 0.0 P _part/86/_geo_xyz/15,2,2 0.0 P _part/86/_geo_xyz/15,3,0 0.0 P _part/86/_geo_xyz/15,3,1 0.0 P _part/86/_geo_xyz/15,3,2 0.0 P _part/86/_geo_xyz/15,4,0 0.0 P _part/86/_geo_xyz/15,4,1 0.0 P _part/86/_geo_xyz/15,4,2 0.0 P _part/86/_geo_xyz/15,5,0 0.0 P _part/86/_geo_xyz/15,5,1 0.0 P _part/86/_geo_xyz/15,5,2 0.0 P _part/86/_geo_xyz/15,6,0 0.0 P _part/86/_geo_xyz/15,6,1 0.0 P _part/86/_geo_xyz/15,6,2 0.0 P _part/86/_geo_xyz/15,7,0 0.0 P _part/86/_geo_xyz/15,7,1 0.0 P _part/86/_geo_xyz/15,7,2 0.0 P _part/86/_geo_xyz/15,8,0 0.0 P _part/86/_geo_xyz/15,8,1 0.0 P _part/86/_geo_xyz/15,8,2 0.0 P _part/86/_geo_xyz/15,9,0 0.0 P _part/86/_geo_xyz/15,9,1 0.0 P _part/86/_geo_xyz/15,9,2 0.0 P _part/86/_geo_xyz/16,0,0 0.0 P _part/86/_geo_xyz/16,0,1 0.0 P _part/86/_geo_xyz/16,0,2 0.0 P _part/86/_geo_xyz/16,1,0 0.0 P _part/86/_geo_xyz/16,1,1 0.0 P _part/86/_geo_xyz/16,1,2 0.0 P _part/86/_geo_xyz/16,10,0 0.0 P _part/86/_geo_xyz/16,10,1 0.0 P _part/86/_geo_xyz/16,10,2 0.0 P _part/86/_geo_xyz/16,11,0 0.0 P _part/86/_geo_xyz/16,11,1 0.0 P _part/86/_geo_xyz/16,11,2 0.0 P _part/86/_geo_xyz/16,12,0 0.0 P _part/86/_geo_xyz/16,12,1 0.0 P _part/86/_geo_xyz/16,12,2 0.0 P _part/86/_geo_xyz/16,13,0 0.0 P _part/86/_geo_xyz/16,13,1 0.0 P _part/86/_geo_xyz/16,13,2 0.0 P _part/86/_geo_xyz/16,14,0 0.0 P _part/86/_geo_xyz/16,14,1 0.0 P _part/86/_geo_xyz/16,14,2 0.0 P _part/86/_geo_xyz/16,15,0 0.0 P _part/86/_geo_xyz/16,15,1 0.0 P _part/86/_geo_xyz/16,15,2 0.0 P _part/86/_geo_xyz/16,16,0 0.0 P _part/86/_geo_xyz/16,16,1 0.0 P _part/86/_geo_xyz/16,16,2 0.0 P _part/86/_geo_xyz/16,17,0 0.0 P _part/86/_geo_xyz/16,17,1 0.0 P _part/86/_geo_xyz/16,17,2 0.0 P _part/86/_geo_xyz/16,2,0 0.0 P _part/86/_geo_xyz/16,2,1 0.0 P _part/86/_geo_xyz/16,2,2 0.0 P _part/86/_geo_xyz/16,3,0 0.0 P _part/86/_geo_xyz/16,3,1 0.0 P _part/86/_geo_xyz/16,3,2 0.0 P _part/86/_geo_xyz/16,4,0 0.0 P _part/86/_geo_xyz/16,4,1 0.0 P _part/86/_geo_xyz/16,4,2 0.0 P _part/86/_geo_xyz/16,5,0 0.0 P _part/86/_geo_xyz/16,5,1 0.0 P _part/86/_geo_xyz/16,5,2 0.0 P _part/86/_geo_xyz/16,6,0 0.0 P _part/86/_geo_xyz/16,6,1 0.0 P _part/86/_geo_xyz/16,6,2 0.0 P _part/86/_geo_xyz/16,7,0 0.0 P _part/86/_geo_xyz/16,7,1 0.0 P _part/86/_geo_xyz/16,7,2 0.0 P _part/86/_geo_xyz/16,8,0 0.0 P _part/86/_geo_xyz/16,8,1 0.0 P _part/86/_geo_xyz/16,8,2 0.0 P _part/86/_geo_xyz/16,9,0 0.0 P _part/86/_geo_xyz/16,9,1 0.0 P _part/86/_geo_xyz/16,9,2 0.0 P _part/86/_geo_xyz/17,0,0 0.0 P _part/86/_geo_xyz/17,0,1 0.0 P _part/86/_geo_xyz/17,0,2 0.0 P _part/86/_geo_xyz/17,1,0 0.0 P _part/86/_geo_xyz/17,1,1 0.0 P _part/86/_geo_xyz/17,1,2 0.0 P _part/86/_geo_xyz/17,10,0 0.0 P _part/86/_geo_xyz/17,10,1 0.0 P _part/86/_geo_xyz/17,10,2 0.0 P _part/86/_geo_xyz/17,11,0 0.0 P _part/86/_geo_xyz/17,11,1 0.0 P _part/86/_geo_xyz/17,11,2 0.0 P _part/86/_geo_xyz/17,12,0 0.0 P _part/86/_geo_xyz/17,12,1 0.0 P _part/86/_geo_xyz/17,12,2 0.0 P _part/86/_geo_xyz/17,13,0 0.0 P _part/86/_geo_xyz/17,13,1 0.0 P _part/86/_geo_xyz/17,13,2 0.0 P _part/86/_geo_xyz/17,14,0 0.0 P _part/86/_geo_xyz/17,14,1 0.0 P _part/86/_geo_xyz/17,14,2 0.0 P _part/86/_geo_xyz/17,15,0 0.0 P _part/86/_geo_xyz/17,15,1 0.0 P _part/86/_geo_xyz/17,15,2 0.0 P _part/86/_geo_xyz/17,16,0 0.0 P _part/86/_geo_xyz/17,16,1 0.0 P _part/86/_geo_xyz/17,16,2 0.0 P _part/86/_geo_xyz/17,17,0 0.0 P _part/86/_geo_xyz/17,17,1 0.0 P _part/86/_geo_xyz/17,17,2 0.0 P _part/86/_geo_xyz/17,2,0 0.0 P _part/86/_geo_xyz/17,2,1 0.0 P _part/86/_geo_xyz/17,2,2 0.0 P _part/86/_geo_xyz/17,3,0 0.0 P _part/86/_geo_xyz/17,3,1 0.0 P _part/86/_geo_xyz/17,3,2 0.0 P _part/86/_geo_xyz/17,4,0 0.0 P _part/86/_geo_xyz/17,4,1 0.0 P _part/86/_geo_xyz/17,4,2 0.0 P _part/86/_geo_xyz/17,5,0 0.0 P _part/86/_geo_xyz/17,5,1 0.0 P _part/86/_geo_xyz/17,5,2 0.0 P _part/86/_geo_xyz/17,6,0 0.0 P _part/86/_geo_xyz/17,6,1 0.0 P _part/86/_geo_xyz/17,6,2 0.0 P _part/86/_geo_xyz/17,7,0 0.0 P _part/86/_geo_xyz/17,7,1 0.0 P _part/86/_geo_xyz/17,7,2 0.0 P _part/86/_geo_xyz/17,8,0 0.0 P _part/86/_geo_xyz/17,8,1 0.0 P _part/86/_geo_xyz/17,8,2 0.0 P _part/86/_geo_xyz/17,9,0 0.0 P _part/86/_geo_xyz/17,9,1 0.0 P _part/86/_geo_xyz/17,9,2 0.0 P _part/86/_geo_xyz/18,0,0 0.0 P _part/86/_geo_xyz/18,0,1 0.0 P _part/86/_geo_xyz/18,0,2 0.0 P _part/86/_geo_xyz/18,1,0 0.0 P _part/86/_geo_xyz/18,1,1 0.0 P _part/86/_geo_xyz/18,1,2 0.0 P _part/86/_geo_xyz/18,10,0 0.0 P _part/86/_geo_xyz/18,10,1 0.0 P _part/86/_geo_xyz/18,10,2 0.0 P _part/86/_geo_xyz/18,11,0 0.0 P _part/86/_geo_xyz/18,11,1 0.0 P _part/86/_geo_xyz/18,11,2 0.0 P _part/86/_geo_xyz/18,12,0 0.0 P _part/86/_geo_xyz/18,12,1 0.0 P _part/86/_geo_xyz/18,12,2 0.0 P _part/86/_geo_xyz/18,13,0 0.0 P _part/86/_geo_xyz/18,13,1 0.0 P _part/86/_geo_xyz/18,13,2 0.0 P _part/86/_geo_xyz/18,14,0 0.0 P _part/86/_geo_xyz/18,14,1 0.0 P _part/86/_geo_xyz/18,14,2 0.0 P _part/86/_geo_xyz/18,15,0 0.0 P _part/86/_geo_xyz/18,15,1 0.0 P _part/86/_geo_xyz/18,15,2 0.0 P _part/86/_geo_xyz/18,16,0 0.0 P _part/86/_geo_xyz/18,16,1 0.0 P _part/86/_geo_xyz/18,16,2 0.0 P _part/86/_geo_xyz/18,17,0 0.0 P _part/86/_geo_xyz/18,17,1 0.0 P _part/86/_geo_xyz/18,17,2 0.0 P _part/86/_geo_xyz/18,2,0 0.0 P _part/86/_geo_xyz/18,2,1 0.0 P _part/86/_geo_xyz/18,2,2 0.0 P _part/86/_geo_xyz/18,3,0 0.0 P _part/86/_geo_xyz/18,3,1 0.0 P _part/86/_geo_xyz/18,3,2 0.0 P _part/86/_geo_xyz/18,4,0 0.0 P _part/86/_geo_xyz/18,4,1 0.0 P _part/86/_geo_xyz/18,4,2 0.0 P _part/86/_geo_xyz/18,5,0 0.0 P _part/86/_geo_xyz/18,5,1 0.0 P _part/86/_geo_xyz/18,5,2 0.0 P _part/86/_geo_xyz/18,6,0 0.0 P _part/86/_geo_xyz/18,6,1 0.0 P _part/86/_geo_xyz/18,6,2 0.0 P _part/86/_geo_xyz/18,7,0 0.0 P _part/86/_geo_xyz/18,7,1 0.0 P _part/86/_geo_xyz/18,7,2 0.0 P _part/86/_geo_xyz/18,8,0 0.0 P _part/86/_geo_xyz/18,8,1 0.0 P _part/86/_geo_xyz/18,8,2 0.0 P _part/86/_geo_xyz/18,9,0 0.0 P _part/86/_geo_xyz/18,9,1 0.0 P _part/86/_geo_xyz/18,9,2 0.0 P _part/86/_geo_xyz/19,0,0 0.0 P _part/86/_geo_xyz/19,0,1 0.0 P _part/86/_geo_xyz/19,0,2 0.0 P _part/86/_geo_xyz/19,1,0 0.0 P _part/86/_geo_xyz/19,1,1 0.0 P _part/86/_geo_xyz/19,1,2 0.0 P _part/86/_geo_xyz/19,10,0 0.0 P _part/86/_geo_xyz/19,10,1 0.0 P _part/86/_geo_xyz/19,10,2 0.0 P _part/86/_geo_xyz/19,11,0 0.0 P _part/86/_geo_xyz/19,11,1 0.0 P _part/86/_geo_xyz/19,11,2 0.0 P _part/86/_geo_xyz/19,12,0 0.0 P _part/86/_geo_xyz/19,12,1 0.0 P _part/86/_geo_xyz/19,12,2 0.0 P _part/86/_geo_xyz/19,13,0 0.0 P _part/86/_geo_xyz/19,13,1 0.0 P _part/86/_geo_xyz/19,13,2 0.0 P _part/86/_geo_xyz/19,14,0 0.0 P _part/86/_geo_xyz/19,14,1 0.0 P _part/86/_geo_xyz/19,14,2 0.0 P _part/86/_geo_xyz/19,15,0 0.0 P _part/86/_geo_xyz/19,15,1 0.0 P _part/86/_geo_xyz/19,15,2 0.0 P _part/86/_geo_xyz/19,16,0 0.0 P _part/86/_geo_xyz/19,16,1 0.0 P _part/86/_geo_xyz/19,16,2 0.0 P _part/86/_geo_xyz/19,17,0 0.0 P _part/86/_geo_xyz/19,17,1 0.0 P _part/86/_geo_xyz/19,17,2 0.0 P _part/86/_geo_xyz/19,2,0 0.0 P _part/86/_geo_xyz/19,2,1 0.0 P _part/86/_geo_xyz/19,2,2 0.0 P _part/86/_geo_xyz/19,3,0 0.0 P _part/86/_geo_xyz/19,3,1 0.0 P _part/86/_geo_xyz/19,3,2 0.0 P _part/86/_geo_xyz/19,4,0 0.0 P _part/86/_geo_xyz/19,4,1 0.0 P _part/86/_geo_xyz/19,4,2 0.0 P _part/86/_geo_xyz/19,5,0 0.0 P _part/86/_geo_xyz/19,5,1 0.0 P _part/86/_geo_xyz/19,5,2 0.0 P _part/86/_geo_xyz/19,6,0 0.0 P _part/86/_geo_xyz/19,6,1 0.0 P _part/86/_geo_xyz/19,6,2 0.0 P _part/86/_geo_xyz/19,7,0 0.0 P _part/86/_geo_xyz/19,7,1 0.0 P _part/86/_geo_xyz/19,7,2 0.0 P _part/86/_geo_xyz/19,8,0 0.0 P _part/86/_geo_xyz/19,8,1 0.0 P _part/86/_geo_xyz/19,8,2 0.0 P _part/86/_geo_xyz/19,9,0 0.0 P _part/86/_geo_xyz/19,9,1 0.0 P _part/86/_geo_xyz/19,9,2 0.0 P _part/86/_geo_xyz/2,0,0 0.0 P _part/86/_geo_xyz/2,0,1 2.0 P _part/86/_geo_xyz/2,0,2 2.0 P _part/86/_geo_xyz/2,1,0 1.414213538 P _part/86/_geo_xyz/2,1,1 1.414213538 P _part/86/_geo_xyz/2,1,2 2.0 P _part/86/_geo_xyz/2,10,0 0.0 P _part/86/_geo_xyz/2,10,1 0.0 P _part/86/_geo_xyz/2,10,2 0.0 P _part/86/_geo_xyz/2,11,0 0.0 P _part/86/_geo_xyz/2,11,1 0.0 P _part/86/_geo_xyz/2,11,2 0.0 P _part/86/_geo_xyz/2,12,0 0.0 P _part/86/_geo_xyz/2,12,1 0.0 P _part/86/_geo_xyz/2,12,2 0.0 P _part/86/_geo_xyz/2,13,0 0.0 P _part/86/_geo_xyz/2,13,1 0.0 P _part/86/_geo_xyz/2,13,2 0.0 P _part/86/_geo_xyz/2,14,0 0.0 P _part/86/_geo_xyz/2,14,1 0.0 P _part/86/_geo_xyz/2,14,2 0.0 P _part/86/_geo_xyz/2,15,0 0.0 P _part/86/_geo_xyz/2,15,1 0.0 P _part/86/_geo_xyz/2,15,2 0.0 P _part/86/_geo_xyz/2,16,0 0.0 P _part/86/_geo_xyz/2,16,1 0.0 P _part/86/_geo_xyz/2,16,2 0.0 P _part/86/_geo_xyz/2,17,0 0.0 P _part/86/_geo_xyz/2,17,1 0.0 P _part/86/_geo_xyz/2,17,2 0.0 P _part/86/_geo_xyz/2,2,0 2.0 P _part/86/_geo_xyz/2,2,1 -0.000000087 P _part/86/_geo_xyz/2,2,2 2.0 P _part/86/_geo_xyz/2,3,0 1.414213538 P _part/86/_geo_xyz/2,3,1 -1.414213538 P _part/86/_geo_xyz/2,3,2 2.0 P _part/86/_geo_xyz/2,4,0 -0.000000175 P _part/86/_geo_xyz/2,4,1 -2.0 P _part/86/_geo_xyz/2,4,2 2.0 P _part/86/_geo_xyz/2,5,0 -0.000000175 P _part/86/_geo_xyz/2,5,1 -2.0 P _part/86/_geo_xyz/2,5,2 2.0 P _part/86/_geo_xyz/2,6,0 -1.414213777 P _part/86/_geo_xyz/2,6,1 -1.414213300 P _part/86/_geo_xyz/2,6,2 2.0 P _part/86/_geo_xyz/2,7,0 -2.0 P _part/86/_geo_xyz/2,7,1 0.000000024 P _part/86/_geo_xyz/2,7,2 2.0 P _part/86/_geo_xyz/2,8,0 -1.414213061 P _part/86/_geo_xyz/2,8,1 1.414214015 P _part/86/_geo_xyz/2,8,2 2.0 P _part/86/_geo_xyz/2,9,0 0.000000350 P _part/86/_geo_xyz/2,9,1 2.0 P _part/86/_geo_xyz/2,9,2 2.0 P _part/86/_geo_xyz/3,0,0 0.0 P _part/86/_geo_xyz/3,0,1 2.0 P _part/86/_geo_xyz/3,0,2 3.0 P _part/86/_geo_xyz/3,1,0 1.414213538 P _part/86/_geo_xyz/3,1,1 1.414213538 P _part/86/_geo_xyz/3,1,2 3.0 P _part/86/_geo_xyz/3,10,0 0.0 P _part/86/_geo_xyz/3,10,1 0.0 P _part/86/_geo_xyz/3,10,2 0.0 P _part/86/_geo_xyz/3,11,0 0.0 P _part/86/_geo_xyz/3,11,1 0.0 P _part/86/_geo_xyz/3,11,2 0.0 P _part/86/_geo_xyz/3,12,0 0.0 P _part/86/_geo_xyz/3,12,1 0.0 P _part/86/_geo_xyz/3,12,2 0.0 P _part/86/_geo_xyz/3,13,0 0.0 P _part/86/_geo_xyz/3,13,1 0.0 P _part/86/_geo_xyz/3,13,2 0.0 P _part/86/_geo_xyz/3,14,0 0.0 P _part/86/_geo_xyz/3,14,1 0.0 P _part/86/_geo_xyz/3,14,2 0.0 P _part/86/_geo_xyz/3,15,0 0.0 P _part/86/_geo_xyz/3,15,1 0.0 P _part/86/_geo_xyz/3,15,2 0.0 P _part/86/_geo_xyz/3,16,0 0.0 P _part/86/_geo_xyz/3,16,1 0.0 P _part/86/_geo_xyz/3,16,2 0.0 P _part/86/_geo_xyz/3,17,0 0.0 P _part/86/_geo_xyz/3,17,1 0.0 P _part/86/_geo_xyz/3,17,2 0.0 P _part/86/_geo_xyz/3,2,0 2.0 P _part/86/_geo_xyz/3,2,1 -0.000000087 P _part/86/_geo_xyz/3,2,2 3.0 P _part/86/_geo_xyz/3,3,0 1.414213538 P _part/86/_geo_xyz/3,3,1 -1.414213538 P _part/86/_geo_xyz/3,3,2 3.0 P _part/86/_geo_xyz/3,4,0 -0.000000175 P _part/86/_geo_xyz/3,4,1 -2.0 P _part/86/_geo_xyz/3,4,2 3.0 P _part/86/_geo_xyz/3,5,0 -0.000000175 P _part/86/_geo_xyz/3,5,1 -2.0 P _part/86/_geo_xyz/3,5,2 3.0 P _part/86/_geo_xyz/3,6,0 -1.414213777 P _part/86/_geo_xyz/3,6,1 -1.414213300 P _part/86/_geo_xyz/3,6,2 3.0 P _part/86/_geo_xyz/3,7,0 -2.0 P _part/86/_geo_xyz/3,7,1 0.000000024 P _part/86/_geo_xyz/3,7,2 3.0 P _part/86/_geo_xyz/3,8,0 -1.414213061 P _part/86/_geo_xyz/3,8,1 1.414214015 P _part/86/_geo_xyz/3,8,2 3.0 P _part/86/_geo_xyz/3,9,0 0.000000350 P _part/86/_geo_xyz/3,9,1 2.0 P _part/86/_geo_xyz/3,9,2 3.0 P _part/86/_geo_xyz/4,0,0 0.0 P _part/86/_geo_xyz/4,0,1 2.0 P _part/86/_geo_xyz/4,0,2 4.0 P _part/86/_geo_xyz/4,1,0 1.414213538 P _part/86/_geo_xyz/4,1,1 1.414213538 P _part/86/_geo_xyz/4,1,2 4.0 P _part/86/_geo_xyz/4,10,0 0.0 P _part/86/_geo_xyz/4,10,1 0.0 P _part/86/_geo_xyz/4,10,2 0.0 P _part/86/_geo_xyz/4,11,0 0.0 P _part/86/_geo_xyz/4,11,1 0.0 P _part/86/_geo_xyz/4,11,2 0.0 P _part/86/_geo_xyz/4,12,0 0.0 P _part/86/_geo_xyz/4,12,1 0.0 P _part/86/_geo_xyz/4,12,2 0.0 P _part/86/_geo_xyz/4,13,0 0.0 P _part/86/_geo_xyz/4,13,1 0.0 P _part/86/_geo_xyz/4,13,2 0.0 P _part/86/_geo_xyz/4,14,0 0.0 P _part/86/_geo_xyz/4,14,1 0.0 P _part/86/_geo_xyz/4,14,2 0.0 P _part/86/_geo_xyz/4,15,0 0.0 P _part/86/_geo_xyz/4,15,1 0.0 P _part/86/_geo_xyz/4,15,2 0.0 P _part/86/_geo_xyz/4,16,0 0.0 P _part/86/_geo_xyz/4,16,1 0.0 P _part/86/_geo_xyz/4,16,2 0.0 P _part/86/_geo_xyz/4,17,0 0.0 P _part/86/_geo_xyz/4,17,1 0.0 P _part/86/_geo_xyz/4,17,2 0.0 P _part/86/_geo_xyz/4,2,0 2.0 P _part/86/_geo_xyz/4,2,1 -0.000000087 P _part/86/_geo_xyz/4,2,2 4.0 P _part/86/_geo_xyz/4,3,0 1.414213538 P _part/86/_geo_xyz/4,3,1 -1.414213538 P _part/86/_geo_xyz/4,3,2 4.0 P _part/86/_geo_xyz/4,4,0 -0.000000175 P _part/86/_geo_xyz/4,4,1 -2.0 P _part/86/_geo_xyz/4,4,2 4.0 P _part/86/_geo_xyz/4,5,0 -0.000000175 P _part/86/_geo_xyz/4,5,1 -2.0 P _part/86/_geo_xyz/4,5,2 4.0 P _part/86/_geo_xyz/4,6,0 -1.414213777 P _part/86/_geo_xyz/4,6,1 -1.414213300 P _part/86/_geo_xyz/4,6,2 4.0 P _part/86/_geo_xyz/4,7,0 -2.0 P _part/86/_geo_xyz/4,7,1 0.000000024 P _part/86/_geo_xyz/4,7,2 4.0 P _part/86/_geo_xyz/4,8,0 -1.414213061 P _part/86/_geo_xyz/4,8,1 1.414214015 P _part/86/_geo_xyz/4,8,2 4.0 P _part/86/_geo_xyz/4,9,0 0.000000350 P _part/86/_geo_xyz/4,9,1 2.0 P _part/86/_geo_xyz/4,9,2 4.0 P _part/86/_geo_xyz/5,0,0 0.0 P _part/86/_geo_xyz/5,0,1 2.0 P _part/86/_geo_xyz/5,0,2 5.0 P _part/86/_geo_xyz/5,1,0 1.414213538 P _part/86/_geo_xyz/5,1,1 1.414213538 P _part/86/_geo_xyz/5,1,2 5.0 P _part/86/_geo_xyz/5,10,0 0.0 P _part/86/_geo_xyz/5,10,1 0.0 P _part/86/_geo_xyz/5,10,2 0.0 P _part/86/_geo_xyz/5,11,0 0.0 P _part/86/_geo_xyz/5,11,1 0.0 P _part/86/_geo_xyz/5,11,2 0.0 P _part/86/_geo_xyz/5,12,0 0.0 P _part/86/_geo_xyz/5,12,1 0.0 P _part/86/_geo_xyz/5,12,2 0.0 P _part/86/_geo_xyz/5,13,0 0.0 P _part/86/_geo_xyz/5,13,1 0.0 P _part/86/_geo_xyz/5,13,2 0.0 P _part/86/_geo_xyz/5,14,0 0.0 P _part/86/_geo_xyz/5,14,1 0.0 P _part/86/_geo_xyz/5,14,2 0.0 P _part/86/_geo_xyz/5,15,0 0.0 P _part/86/_geo_xyz/5,15,1 0.0 P _part/86/_geo_xyz/5,15,2 0.0 P _part/86/_geo_xyz/5,16,0 0.0 P _part/86/_geo_xyz/5,16,1 0.0 P _part/86/_geo_xyz/5,16,2 0.0 P _part/86/_geo_xyz/5,17,0 0.0 P _part/86/_geo_xyz/5,17,1 0.0 P _part/86/_geo_xyz/5,17,2 0.0 P _part/86/_geo_xyz/5,2,0 2.0 P _part/86/_geo_xyz/5,2,1 -0.000000087 P _part/86/_geo_xyz/5,2,2 5.0 P _part/86/_geo_xyz/5,3,0 1.414213538 P _part/86/_geo_xyz/5,3,1 -1.414213538 P _part/86/_geo_xyz/5,3,2 5.0 P _part/86/_geo_xyz/5,4,0 -0.000000175 P _part/86/_geo_xyz/5,4,1 -2.0 P _part/86/_geo_xyz/5,4,2 5.0 P _part/86/_geo_xyz/5,5,0 -0.000000175 P _part/86/_geo_xyz/5,5,1 -2.0 P _part/86/_geo_xyz/5,5,2 5.0 P _part/86/_geo_xyz/5,6,0 -1.414213777 P _part/86/_geo_xyz/5,6,1 -1.414213300 P _part/86/_geo_xyz/5,6,2 5.0 P _part/86/_geo_xyz/5,7,0 -2.0 P _part/86/_geo_xyz/5,7,1 0.000000024 P _part/86/_geo_xyz/5,7,2 5.0 P _part/86/_geo_xyz/5,8,0 -1.414213061 P _part/86/_geo_xyz/5,8,1 1.414214015 P _part/86/_geo_xyz/5,8,2 5.0 P _part/86/_geo_xyz/5,9,0 0.000000350 P _part/86/_geo_xyz/5,9,1 2.0 P _part/86/_geo_xyz/5,9,2 5.0 P _part/86/_geo_xyz/6,0,0 0.0 P _part/86/_geo_xyz/6,0,1 2.0 P _part/86/_geo_xyz/6,0,2 6.0 P _part/86/_geo_xyz/6,1,0 1.414213538 P _part/86/_geo_xyz/6,1,1 1.414213538 P _part/86/_geo_xyz/6,1,2 6.0 P _part/86/_geo_xyz/6,10,0 0.0 P _part/86/_geo_xyz/6,10,1 0.0 P _part/86/_geo_xyz/6,10,2 0.0 P _part/86/_geo_xyz/6,11,0 0.0 P _part/86/_geo_xyz/6,11,1 0.0 P _part/86/_geo_xyz/6,11,2 0.0 P _part/86/_geo_xyz/6,12,0 0.0 P _part/86/_geo_xyz/6,12,1 0.0 P _part/86/_geo_xyz/6,12,2 0.0 P _part/86/_geo_xyz/6,13,0 0.0 P _part/86/_geo_xyz/6,13,1 0.0 P _part/86/_geo_xyz/6,13,2 0.0 P _part/86/_geo_xyz/6,14,0 0.0 P _part/86/_geo_xyz/6,14,1 0.0 P _part/86/_geo_xyz/6,14,2 0.0 P _part/86/_geo_xyz/6,15,0 0.0 P _part/86/_geo_xyz/6,15,1 0.0 P _part/86/_geo_xyz/6,15,2 0.0 P _part/86/_geo_xyz/6,16,0 0.0 P _part/86/_geo_xyz/6,16,1 0.0 P _part/86/_geo_xyz/6,16,2 0.0 P _part/86/_geo_xyz/6,17,0 0.0 P _part/86/_geo_xyz/6,17,1 0.0 P _part/86/_geo_xyz/6,17,2 0.0 P _part/86/_geo_xyz/6,2,0 2.0 P _part/86/_geo_xyz/6,2,1 -0.000000087 P _part/86/_geo_xyz/6,2,2 6.0 P _part/86/_geo_xyz/6,3,0 1.414213538 P _part/86/_geo_xyz/6,3,1 -1.414213538 P _part/86/_geo_xyz/6,3,2 6.0 P _part/86/_geo_xyz/6,4,0 -0.000000175 P _part/86/_geo_xyz/6,4,1 -2.0 P _part/86/_geo_xyz/6,4,2 6.0 P _part/86/_geo_xyz/6,5,0 -0.000000175 P _part/86/_geo_xyz/6,5,1 -2.0 P _part/86/_geo_xyz/6,5,2 6.0 P _part/86/_geo_xyz/6,6,0 -1.414213777 P _part/86/_geo_xyz/6,6,1 -1.414213300 P _part/86/_geo_xyz/6,6,2 6.0 P _part/86/_geo_xyz/6,7,0 -2.0 P _part/86/_geo_xyz/6,7,1 0.000000024 P _part/86/_geo_xyz/6,7,2 6.0 P _part/86/_geo_xyz/6,8,0 -1.414213061 P _part/86/_geo_xyz/6,8,1 1.414214015 P _part/86/_geo_xyz/6,8,2 6.0 P _part/86/_geo_xyz/6,9,0 0.000000350 P _part/86/_geo_xyz/6,9,1 2.0 P _part/86/_geo_xyz/6,9,2 6.0 P _part/86/_geo_xyz/7,0,0 0.0 P _part/86/_geo_xyz/7,0,1 2.0 P _part/86/_geo_xyz/7,0,2 7.0 P _part/86/_geo_xyz/7,1,0 1.414213538 P _part/86/_geo_xyz/7,1,1 1.414213538 P _part/86/_geo_xyz/7,1,2 7.0 P _part/86/_geo_xyz/7,10,0 0.0 P _part/86/_geo_xyz/7,10,1 0.0 P _part/86/_geo_xyz/7,10,2 0.0 P _part/86/_geo_xyz/7,11,0 0.0 P _part/86/_geo_xyz/7,11,1 0.0 P _part/86/_geo_xyz/7,11,2 0.0 P _part/86/_geo_xyz/7,12,0 0.0 P _part/86/_geo_xyz/7,12,1 0.0 P _part/86/_geo_xyz/7,12,2 0.0 P _part/86/_geo_xyz/7,13,0 0.0 P _part/86/_geo_xyz/7,13,1 0.0 P _part/86/_geo_xyz/7,13,2 0.0 P _part/86/_geo_xyz/7,14,0 0.0 P _part/86/_geo_xyz/7,14,1 0.0 P _part/86/_geo_xyz/7,14,2 0.0 P _part/86/_geo_xyz/7,15,0 0.0 P _part/86/_geo_xyz/7,15,1 0.0 P _part/86/_geo_xyz/7,15,2 0.0 P _part/86/_geo_xyz/7,16,0 0.0 P _part/86/_geo_xyz/7,16,1 0.0 P _part/86/_geo_xyz/7,16,2 0.0 P _part/86/_geo_xyz/7,17,0 0.0 P _part/86/_geo_xyz/7,17,1 0.0 P _part/86/_geo_xyz/7,17,2 0.0 P _part/86/_geo_xyz/7,2,0 2.0 P _part/86/_geo_xyz/7,2,1 -0.000000087 P _part/86/_geo_xyz/7,2,2 7.0 P _part/86/_geo_xyz/7,3,0 1.414213538 P _part/86/_geo_xyz/7,3,1 -1.414213538 P _part/86/_geo_xyz/7,3,2 7.0 P _part/86/_geo_xyz/7,4,0 -0.000000175 P _part/86/_geo_xyz/7,4,1 -2.0 P _part/86/_geo_xyz/7,4,2 7.0 P _part/86/_geo_xyz/7,5,0 -0.000000175 P _part/86/_geo_xyz/7,5,1 -2.0 P _part/86/_geo_xyz/7,5,2 7.0 P _part/86/_geo_xyz/7,6,0 -1.414213777 P _part/86/_geo_xyz/7,6,1 -1.414213300 P _part/86/_geo_xyz/7,6,2 7.0 P _part/86/_geo_xyz/7,7,0 -2.0 P _part/86/_geo_xyz/7,7,1 0.000000024 P _part/86/_geo_xyz/7,7,2 7.0 P _part/86/_geo_xyz/7,8,0 -1.414213061 P _part/86/_geo_xyz/7,8,1 1.414214015 P _part/86/_geo_xyz/7,8,2 7.0 P _part/86/_geo_xyz/7,9,0 0.000000350 P _part/86/_geo_xyz/7,9,1 2.0 P _part/86/_geo_xyz/7,9,2 7.0 P _part/86/_geo_xyz/8,0,0 0.0 P _part/86/_geo_xyz/8,0,1 0.0 P _part/86/_geo_xyz/8,0,2 0.0 P _part/86/_geo_xyz/8,1,0 0.0 P _part/86/_geo_xyz/8,1,1 0.0 P _part/86/_geo_xyz/8,1,2 0.0 P _part/86/_geo_xyz/8,10,0 0.0 P _part/86/_geo_xyz/8,10,1 0.0 P _part/86/_geo_xyz/8,10,2 0.0 P _part/86/_geo_xyz/8,11,0 0.0 P _part/86/_geo_xyz/8,11,1 0.0 P _part/86/_geo_xyz/8,11,2 0.0 P _part/86/_geo_xyz/8,12,0 0.0 P _part/86/_geo_xyz/8,12,1 0.0 P _part/86/_geo_xyz/8,12,2 0.0 P _part/86/_geo_xyz/8,13,0 0.0 P _part/86/_geo_xyz/8,13,1 0.0 P _part/86/_geo_xyz/8,13,2 0.0 P _part/86/_geo_xyz/8,14,0 0.0 P _part/86/_geo_xyz/8,14,1 0.0 P _part/86/_geo_xyz/8,14,2 0.0 P _part/86/_geo_xyz/8,15,0 0.0 P _part/86/_geo_xyz/8,15,1 0.0 P _part/86/_geo_xyz/8,15,2 0.0 P _part/86/_geo_xyz/8,16,0 0.0 P _part/86/_geo_xyz/8,16,1 0.0 P _part/86/_geo_xyz/8,16,2 0.0 P _part/86/_geo_xyz/8,17,0 0.0 P _part/86/_geo_xyz/8,17,1 0.0 P _part/86/_geo_xyz/8,17,2 0.0 P _part/86/_geo_xyz/8,2,0 0.0 P _part/86/_geo_xyz/8,2,1 0.0 P _part/86/_geo_xyz/8,2,2 0.0 P _part/86/_geo_xyz/8,3,0 0.0 P _part/86/_geo_xyz/8,3,1 0.0 P _part/86/_geo_xyz/8,3,2 0.0 P _part/86/_geo_xyz/8,4,0 0.0 P _part/86/_geo_xyz/8,4,1 0.0 P _part/86/_geo_xyz/8,4,2 0.0 P _part/86/_geo_xyz/8,5,0 0.0 P _part/86/_geo_xyz/8,5,1 0.0 P _part/86/_geo_xyz/8,5,2 0.0 P _part/86/_geo_xyz/8,6,0 0.0 P _part/86/_geo_xyz/8,6,1 0.0 P _part/86/_geo_xyz/8,6,2 0.0 P _part/86/_geo_xyz/8,7,0 0.0 P _part/86/_geo_xyz/8,7,1 0.0 P _part/86/_geo_xyz/8,7,2 0.0 P _part/86/_geo_xyz/8,8,0 0.0 P _part/86/_geo_xyz/8,8,1 0.0 P _part/86/_geo_xyz/8,8,2 0.0 P _part/86/_geo_xyz/8,9,0 0.0 P _part/86/_geo_xyz/8,9,1 0.0 P _part/86/_geo_xyz/8,9,2 0.0 P _part/86/_geo_xyz/9,0,0 0.0 P _part/86/_geo_xyz/9,0,1 0.0 P _part/86/_geo_xyz/9,0,2 0.0 P _part/86/_geo_xyz/9,1,0 0.0 P _part/86/_geo_xyz/9,1,1 0.0 P _part/86/_geo_xyz/9,1,2 0.0 P _part/86/_geo_xyz/9,10,0 0.0 P _part/86/_geo_xyz/9,10,1 0.0 P _part/86/_geo_xyz/9,10,2 0.0 P _part/86/_geo_xyz/9,11,0 0.0 P _part/86/_geo_xyz/9,11,1 0.0 P _part/86/_geo_xyz/9,11,2 0.0 P _part/86/_geo_xyz/9,12,0 0.0 P _part/86/_geo_xyz/9,12,1 0.0 P _part/86/_geo_xyz/9,12,2 0.0 P _part/86/_geo_xyz/9,13,0 0.0 P _part/86/_geo_xyz/9,13,1 0.0 P _part/86/_geo_xyz/9,13,2 0.0 P _part/86/_geo_xyz/9,14,0 0.0 P _part/86/_geo_xyz/9,14,1 0.0 P _part/86/_geo_xyz/9,14,2 0.0 P _part/86/_geo_xyz/9,15,0 0.0 P _part/86/_geo_xyz/9,15,1 0.0 P _part/86/_geo_xyz/9,15,2 0.0 P _part/86/_geo_xyz/9,16,0 0.0 P _part/86/_geo_xyz/9,16,1 0.0 P _part/86/_geo_xyz/9,16,2 0.0 P _part/86/_geo_xyz/9,17,0 0.0 P _part/86/_geo_xyz/9,17,1 0.0 P _part/86/_geo_xyz/9,17,2 0.0 P _part/86/_geo_xyz/9,2,0 0.0 P _part/86/_geo_xyz/9,2,1 0.0 P _part/86/_geo_xyz/9,2,2 0.0 P _part/86/_geo_xyz/9,3,0 0.0 P _part/86/_geo_xyz/9,3,1 0.0 P _part/86/_geo_xyz/9,3,2 0.0 P _part/86/_geo_xyz/9,4,0 0.0 P _part/86/_geo_xyz/9,4,1 0.0 P _part/86/_geo_xyz/9,4,2 0.0 P _part/86/_geo_xyz/9,5,0 0.0 P _part/86/_geo_xyz/9,5,1 0.0 P _part/86/_geo_xyz/9,5,2 0.0 P _part/86/_geo_xyz/9,6,0 0.0 P _part/86/_geo_xyz/9,6,1 0.0 P _part/86/_geo_xyz/9,6,2 0.0 P _part/86/_geo_xyz/9,7,0 0.0 P _part/86/_geo_xyz/9,7,1 0.0 P _part/86/_geo_xyz/9,7,2 0.0 P _part/86/_geo_xyz/9,8,0 0.0 P _part/86/_geo_xyz/9,8,1 0.0 P _part/86/_geo_xyz/9,8,2 0.0 P _part/86/_geo_xyz/9,9,0 0.0 P _part/86/_geo_xyz/9,9,1 0.0 P _part/86/_geo_xyz/9,9,2 0.0 P _part/86/_geo_xyz/i_count 20 P _part/86/_geo_xyz/j_count 18 P _part/86/_geo_xyz/k_count 3 P _part/86/_locked/0,0 0 P _part/86/_locked/0,1 0 P _part/86/_locked/0,10 0 P _part/86/_locked/0,11 0 P _part/86/_locked/0,12 0 P _part/86/_locked/0,13 0 P _part/86/_locked/0,14 0 P _part/86/_locked/0,15 0 P _part/86/_locked/0,16 0 P _part/86/_locked/0,17 0 P _part/86/_locked/0,2 0 P _part/86/_locked/0,3 0 P _part/86/_locked/0,4 0 P _part/86/_locked/0,5 0 P _part/86/_locked/0,6 0 P _part/86/_locked/0,7 0 P _part/86/_locked/0,8 0 P _part/86/_locked/0,9 0 P _part/86/_locked/1,0 0 P _part/86/_locked/1,1 0 P _part/86/_locked/1,10 0 P _part/86/_locked/1,11 0 P _part/86/_locked/1,12 0 P _part/86/_locked/1,13 0 P _part/86/_locked/1,14 0 P _part/86/_locked/1,15 0 P _part/86/_locked/1,16 0 P _part/86/_locked/1,17 0 P _part/86/_locked/1,2 0 P _part/86/_locked/1,3 0 P _part/86/_locked/1,4 0 P _part/86/_locked/1,5 0 P _part/86/_locked/1,6 0 P _part/86/_locked/1,7 0 P _part/86/_locked/1,8 0 P _part/86/_locked/1,9 0 P _part/86/_locked/10,0 0 P _part/86/_locked/10,1 0 P _part/86/_locked/10,10 0 P _part/86/_locked/10,11 0 P _part/86/_locked/10,12 0 P _part/86/_locked/10,13 0 P _part/86/_locked/10,14 0 P _part/86/_locked/10,15 0 P _part/86/_locked/10,16 0 P _part/86/_locked/10,17 0 P _part/86/_locked/10,2 0 P _part/86/_locked/10,3 0 P _part/86/_locked/10,4 0 P _part/86/_locked/10,5 0 P _part/86/_locked/10,6 0 P _part/86/_locked/10,7 0 P _part/86/_locked/10,8 0 P _part/86/_locked/10,9 0 P _part/86/_locked/11,0 0 P _part/86/_locked/11,1 0 P _part/86/_locked/11,10 0 P _part/86/_locked/11,11 0 P _part/86/_locked/11,12 0 P _part/86/_locked/11,13 0 P _part/86/_locked/11,14 0 P _part/86/_locked/11,15 0 P _part/86/_locked/11,16 0 P _part/86/_locked/11,17 0 P _part/86/_locked/11,2 0 P _part/86/_locked/11,3 0 P _part/86/_locked/11,4 0 P _part/86/_locked/11,5 0 P _part/86/_locked/11,6 0 P _part/86/_locked/11,7 0 P _part/86/_locked/11,8 0 P _part/86/_locked/11,9 0 P _part/86/_locked/12,0 0 P _part/86/_locked/12,1 0 P _part/86/_locked/12,10 0 P _part/86/_locked/12,11 0 P _part/86/_locked/12,12 0 P _part/86/_locked/12,13 0 P _part/86/_locked/12,14 0 P _part/86/_locked/12,15 0 P _part/86/_locked/12,16 0 P _part/86/_locked/12,17 0 P _part/86/_locked/12,2 0 P _part/86/_locked/12,3 0 P _part/86/_locked/12,4 0 P _part/86/_locked/12,5 0 P _part/86/_locked/12,6 0 P _part/86/_locked/12,7 0 P _part/86/_locked/12,8 0 P _part/86/_locked/12,9 0 P _part/86/_locked/13,0 0 P _part/86/_locked/13,1 0 P _part/86/_locked/13,10 0 P _part/86/_locked/13,11 0 P _part/86/_locked/13,12 0 P _part/86/_locked/13,13 0 P _part/86/_locked/13,14 0 P _part/86/_locked/13,15 0 P _part/86/_locked/13,16 0 P _part/86/_locked/13,17 0 P _part/86/_locked/13,2 0 P _part/86/_locked/13,3 0 P _part/86/_locked/13,4 0 P _part/86/_locked/13,5 0 P _part/86/_locked/13,6 0 P _part/86/_locked/13,7 0 P _part/86/_locked/13,8 0 P _part/86/_locked/13,9 0 P _part/86/_locked/14,0 0 P _part/86/_locked/14,1 0 P _part/86/_locked/14,10 0 P _part/86/_locked/14,11 0 P _part/86/_locked/14,12 0 P _part/86/_locked/14,13 0 P _part/86/_locked/14,14 0 P _part/86/_locked/14,15 0 P _part/86/_locked/14,16 0 P _part/86/_locked/14,17 0 P _part/86/_locked/14,2 0 P _part/86/_locked/14,3 0 P _part/86/_locked/14,4 0 P _part/86/_locked/14,5 0 P _part/86/_locked/14,6 0 P _part/86/_locked/14,7 0 P _part/86/_locked/14,8 0 P _part/86/_locked/14,9 0 P _part/86/_locked/15,0 0 P _part/86/_locked/15,1 0 P _part/86/_locked/15,10 0 P _part/86/_locked/15,11 0 P _part/86/_locked/15,12 0 P _part/86/_locked/15,13 0 P _part/86/_locked/15,14 0 P _part/86/_locked/15,15 0 P _part/86/_locked/15,16 0 P _part/86/_locked/15,17 0 P _part/86/_locked/15,2 0 P _part/86/_locked/15,3 0 P _part/86/_locked/15,4 0 P _part/86/_locked/15,5 0 P _part/86/_locked/15,6 0 P _part/86/_locked/15,7 0 P _part/86/_locked/15,8 0 P _part/86/_locked/15,9 0 P _part/86/_locked/16,0 0 P _part/86/_locked/16,1 0 P _part/86/_locked/16,10 0 P _part/86/_locked/16,11 0 P _part/86/_locked/16,12 0 P _part/86/_locked/16,13 0 P _part/86/_locked/16,14 0 P _part/86/_locked/16,15 0 P _part/86/_locked/16,16 0 P _part/86/_locked/16,17 0 P _part/86/_locked/16,2 0 P _part/86/_locked/16,3 0 P _part/86/_locked/16,4 0 P _part/86/_locked/16,5 0 P _part/86/_locked/16,6 0 P _part/86/_locked/16,7 0 P _part/86/_locked/16,8 0 P _part/86/_locked/16,9 0 P _part/86/_locked/17,0 0 P _part/86/_locked/17,1 0 P _part/86/_locked/17,10 0 P _part/86/_locked/17,11 0 P _part/86/_locked/17,12 0 P _part/86/_locked/17,13 0 P _part/86/_locked/17,14 0 P _part/86/_locked/17,15 0 P _part/86/_locked/17,16 0 P _part/86/_locked/17,17 0 P _part/86/_locked/17,2 0 P _part/86/_locked/17,3 0 P _part/86/_locked/17,4 0 P _part/86/_locked/17,5 0 P _part/86/_locked/17,6 0 P _part/86/_locked/17,7 0 P _part/86/_locked/17,8 0 P _part/86/_locked/17,9 0 P _part/86/_locked/18,0 0 P _part/86/_locked/18,1 0 P _part/86/_locked/18,10 0 P _part/86/_locked/18,11 0 P _part/86/_locked/18,12 0 P _part/86/_locked/18,13 0 P _part/86/_locked/18,14 0 P _part/86/_locked/18,15 0 P _part/86/_locked/18,16 0 P _part/86/_locked/18,17 0 P _part/86/_locked/18,2 0 P _part/86/_locked/18,3 0 P _part/86/_locked/18,4 0 P _part/86/_locked/18,5 0 P _part/86/_locked/18,6 0 P _part/86/_locked/18,7 0 P _part/86/_locked/18,8 0 P _part/86/_locked/18,9 0 P _part/86/_locked/19,0 0 P _part/86/_locked/19,1 0 P _part/86/_locked/19,10 0 P _part/86/_locked/19,11 0 P _part/86/_locked/19,12 0 P _part/86/_locked/19,13 0 P _part/86/_locked/19,14 0 P _part/86/_locked/19,15 0 P _part/86/_locked/19,16 0 P _part/86/_locked/19,17 0 P _part/86/_locked/19,2 0 P _part/86/_locked/19,3 0 P _part/86/_locked/19,4 0 P _part/86/_locked/19,5 0 P _part/86/_locked/19,6 0 P _part/86/_locked/19,7 0 P _part/86/_locked/19,8 0 P _part/86/_locked/19,9 0 P _part/86/_locked/2,0 0 P _part/86/_locked/2,1 0 P _part/86/_locked/2,10 0 P _part/86/_locked/2,11 0 P _part/86/_locked/2,12 0 P _part/86/_locked/2,13 0 P _part/86/_locked/2,14 0 P _part/86/_locked/2,15 0 P _part/86/_locked/2,16 0 P _part/86/_locked/2,17 0 P _part/86/_locked/2,2 0 P _part/86/_locked/2,3 0 P _part/86/_locked/2,4 0 P _part/86/_locked/2,5 0 P _part/86/_locked/2,6 0 P _part/86/_locked/2,7 0 P _part/86/_locked/2,8 0 P _part/86/_locked/2,9 0 P _part/86/_locked/3,0 0 P _part/86/_locked/3,1 0 P _part/86/_locked/3,10 0 P _part/86/_locked/3,11 0 P _part/86/_locked/3,12 0 P _part/86/_locked/3,13 0 P _part/86/_locked/3,14 0 P _part/86/_locked/3,15 0 P _part/86/_locked/3,16 0 P _part/86/_locked/3,17 0 P _part/86/_locked/3,2 0 P _part/86/_locked/3,3 0 P _part/86/_locked/3,4 0 P _part/86/_locked/3,5 0 P _part/86/_locked/3,6 0 P _part/86/_locked/3,7 0 P _part/86/_locked/3,8 0 P _part/86/_locked/3,9 0 P _part/86/_locked/4,0 0 P _part/86/_locked/4,1 0 P _part/86/_locked/4,10 0 P _part/86/_locked/4,11 0 P _part/86/_locked/4,12 0 P _part/86/_locked/4,13 0 P _part/86/_locked/4,14 0 P _part/86/_locked/4,15 0 P _part/86/_locked/4,16 0 P _part/86/_locked/4,17 0 P _part/86/_locked/4,2 0 P _part/86/_locked/4,3 0 P _part/86/_locked/4,4 0 P _part/86/_locked/4,5 0 P _part/86/_locked/4,6 0 P _part/86/_locked/4,7 0 P _part/86/_locked/4,8 0 P _part/86/_locked/4,9 0 P _part/86/_locked/5,0 0 P _part/86/_locked/5,1 0 P _part/86/_locked/5,10 0 P _part/86/_locked/5,11 0 P _part/86/_locked/5,12 0 P _part/86/_locked/5,13 0 P _part/86/_locked/5,14 0 P _part/86/_locked/5,15 0 P _part/86/_locked/5,16 0 P _part/86/_locked/5,17 0 P _part/86/_locked/5,2 0 P _part/86/_locked/5,3 0 P _part/86/_locked/5,4 0 P _part/86/_locked/5,5 0 P _part/86/_locked/5,6 0 P _part/86/_locked/5,7 0 P _part/86/_locked/5,8 0 P _part/86/_locked/5,9 0 P _part/86/_locked/6,0 0 P _part/86/_locked/6,1 0 P _part/86/_locked/6,10 0 P _part/86/_locked/6,11 0 P _part/86/_locked/6,12 0 P _part/86/_locked/6,13 0 P _part/86/_locked/6,14 0 P _part/86/_locked/6,15 0 P _part/86/_locked/6,16 0 P _part/86/_locked/6,17 0 P _part/86/_locked/6,2 0 P _part/86/_locked/6,3 0 P _part/86/_locked/6,4 0 P _part/86/_locked/6,5 0 P _part/86/_locked/6,6 0 P _part/86/_locked/6,7 0 P _part/86/_locked/6,8 0 P _part/86/_locked/6,9 0 P _part/86/_locked/7,0 0 P _part/86/_locked/7,1 0 P _part/86/_locked/7,10 0 P _part/86/_locked/7,11 0 P _part/86/_locked/7,12 0 P _part/86/_locked/7,13 0 P _part/86/_locked/7,14 0 P _part/86/_locked/7,15 0 P _part/86/_locked/7,16 0 P _part/86/_locked/7,17 0 P _part/86/_locked/7,2 0 P _part/86/_locked/7,3 0 P _part/86/_locked/7,4 0 P _part/86/_locked/7,5 0 P _part/86/_locked/7,6 0 P _part/86/_locked/7,7 0 P _part/86/_locked/7,8 0 P _part/86/_locked/7,9 0 P _part/86/_locked/8,0 0 P _part/86/_locked/8,1 0 P _part/86/_locked/8,10 0 P _part/86/_locked/8,11 0 P _part/86/_locked/8,12 0 P _part/86/_locked/8,13 0 P _part/86/_locked/8,14 0 P _part/86/_locked/8,15 0 P _part/86/_locked/8,16 0 P _part/86/_locked/8,17 0 P _part/86/_locked/8,2 0 P _part/86/_locked/8,3 0 P _part/86/_locked/8,4 0 P _part/86/_locked/8,5 0 P _part/86/_locked/8,6 0 P _part/86/_locked/8,7 0 P _part/86/_locked/8,8 0 P _part/86/_locked/8,9 0 P _part/86/_locked/9,0 0 P _part/86/_locked/9,1 0 P _part/86/_locked/9,10 0 P _part/86/_locked/9,11 0 P _part/86/_locked/9,12 0 P _part/86/_locked/9,13 0 P _part/86/_locked/9,14 0 P _part/86/_locked/9,15 0 P _part/86/_locked/9,16 0 P _part/86/_locked/9,17 0 P _part/86/_locked/9,2 0 P _part/86/_locked/9,3 0 P _part/86/_locked/9,4 0 P _part/86/_locked/9,5 0 P _part/86/_locked/9,6 0 P _part/86/_locked/9,7 0 P _part/86/_locked/9,8 0 P _part/86/_locked/9,9 0 P _part/86/_locked/i_count 20 P _part/86/_locked/j_count 18 P _part/86/_part_cd 0.075000003 P _part/86/_part_phi 0.0 P _part/86/_part_psi 0.0 P _part/86/_part_rad 2.0 P _part/86/_part_specs_eq 0 P _part/86/_part_specs_invis 0 P _part/86/_part_specs_unused1 0 P _part/86/_part_specs_unused2 0 P _part/86/_part_tex 0 P _part/86/_part_the 0.0 P _part/86/_part_x 0.0 P _part/86/_part_y 0.0 P _part/86/_part_z 0.0 P _part/86/_patt_con 0 P _part/86/_patt_prt 0 P _part/86/_patt_rat 0.0 P _part/86/_r_dim 10 P _part/86/_s_dim 8 P _part/86/_scon 37.676635742 P _part/86/_top_s1 0.632812500 P _part/86/_top_s2 0.753906250 P _part/86/_top_t1 0.254882812 P _part/86/_top_t2 0.295898438 P _part/87/_aero_x_os 0.0 P _part/87/_aero_y_os 0.0 P _part/87/_aero_z_os 0.0 P _part/87/_area_frnt 0.0 P _part/87/_area_side 0.0 P _part/87/_area_vert 0.0 P _part/87/_bot_s1 0.632812500 P _part/87/_bot_s2 0.753906250 P _part/87/_bot_t1 0.254882812 P _part/87/_bot_t2 0.295898438 P _part/87/_damp 1.883831739 P _part/87/_geo_xyz/0,0,0 0.0 P _part/87/_geo_xyz/0,0,1 2.0 P _part/87/_geo_xyz/0,0,2 0.0 P _part/87/_geo_xyz/0,1,0 1.414213538 P _part/87/_geo_xyz/0,1,1 1.414213538 P _part/87/_geo_xyz/0,1,2 0.0 P _part/87/_geo_xyz/0,10,0 0.0 P _part/87/_geo_xyz/0,10,1 0.0 P _part/87/_geo_xyz/0,10,2 0.0 P _part/87/_geo_xyz/0,11,0 0.0 P _part/87/_geo_xyz/0,11,1 0.0 P _part/87/_geo_xyz/0,11,2 0.0 P _part/87/_geo_xyz/0,12,0 0.0 P _part/87/_geo_xyz/0,12,1 0.0 P _part/87/_geo_xyz/0,12,2 0.0 P _part/87/_geo_xyz/0,13,0 0.0 P _part/87/_geo_xyz/0,13,1 0.0 P _part/87/_geo_xyz/0,13,2 0.0 P _part/87/_geo_xyz/0,14,0 0.0 P _part/87/_geo_xyz/0,14,1 0.0 P _part/87/_geo_xyz/0,14,2 0.0 P _part/87/_geo_xyz/0,15,0 0.0 P _part/87/_geo_xyz/0,15,1 0.0 P _part/87/_geo_xyz/0,15,2 0.0 P _part/87/_geo_xyz/0,16,0 0.0 P _part/87/_geo_xyz/0,16,1 0.0 P _part/87/_geo_xyz/0,16,2 0.0 P _part/87/_geo_xyz/0,17,0 0.0 P _part/87/_geo_xyz/0,17,1 0.0 P _part/87/_geo_xyz/0,17,2 0.0 P _part/87/_geo_xyz/0,2,0 2.0 P _part/87/_geo_xyz/0,2,1 -0.000000087 P _part/87/_geo_xyz/0,2,2 0.0 P _part/87/_geo_xyz/0,3,0 1.414213538 P _part/87/_geo_xyz/0,3,1 -1.414213538 P _part/87/_geo_xyz/0,3,2 0.0 P _part/87/_geo_xyz/0,4,0 -0.000000175 P _part/87/_geo_xyz/0,4,1 -2.0 P _part/87/_geo_xyz/0,4,2 0.0 P _part/87/_geo_xyz/0,5,0 -0.000000175 P _part/87/_geo_xyz/0,5,1 -2.0 P _part/87/_geo_xyz/0,5,2 0.0 P _part/87/_geo_xyz/0,6,0 -1.414213777 P _part/87/_geo_xyz/0,6,1 -1.414213300 P _part/87/_geo_xyz/0,6,2 0.0 P _part/87/_geo_xyz/0,7,0 -2.0 P _part/87/_geo_xyz/0,7,1 0.000000024 P _part/87/_geo_xyz/0,7,2 0.0 P _part/87/_geo_xyz/0,8,0 -1.414213061 P _part/87/_geo_xyz/0,8,1 1.414214015 P _part/87/_geo_xyz/0,8,2 0.0 P _part/87/_geo_xyz/0,9,0 0.000000350 P _part/87/_geo_xyz/0,9,1 2.0 P _part/87/_geo_xyz/0,9,2 0.0 P _part/87/_geo_xyz/1,0,0 0.0 P _part/87/_geo_xyz/1,0,1 2.0 P _part/87/_geo_xyz/1,0,2 1.0 P _part/87/_geo_xyz/1,1,0 1.414213538 P _part/87/_geo_xyz/1,1,1 1.414213538 P _part/87/_geo_xyz/1,1,2 1.0 P _part/87/_geo_xyz/1,10,0 0.0 P _part/87/_geo_xyz/1,10,1 0.0 P _part/87/_geo_xyz/1,10,2 0.0 P _part/87/_geo_xyz/1,11,0 0.0 P _part/87/_geo_xyz/1,11,1 0.0 P _part/87/_geo_xyz/1,11,2 0.0 P _part/87/_geo_xyz/1,12,0 0.0 P _part/87/_geo_xyz/1,12,1 0.0 P _part/87/_geo_xyz/1,12,2 0.0 P _part/87/_geo_xyz/1,13,0 0.0 P _part/87/_geo_xyz/1,13,1 0.0 P _part/87/_geo_xyz/1,13,2 0.0 P _part/87/_geo_xyz/1,14,0 0.0 P _part/87/_geo_xyz/1,14,1 0.0 P _part/87/_geo_xyz/1,14,2 0.0 P _part/87/_geo_xyz/1,15,0 0.0 P _part/87/_geo_xyz/1,15,1 0.0 P _part/87/_geo_xyz/1,15,2 0.0 P _part/87/_geo_xyz/1,16,0 0.0 P _part/87/_geo_xyz/1,16,1 0.0 P _part/87/_geo_xyz/1,16,2 0.0 P _part/87/_geo_xyz/1,17,0 0.0 P _part/87/_geo_xyz/1,17,1 0.0 P _part/87/_geo_xyz/1,17,2 0.0 P _part/87/_geo_xyz/1,2,0 2.0 P _part/87/_geo_xyz/1,2,1 -0.000000087 P _part/87/_geo_xyz/1,2,2 1.0 P _part/87/_geo_xyz/1,3,0 1.414213538 P _part/87/_geo_xyz/1,3,1 -1.414213538 P _part/87/_geo_xyz/1,3,2 1.0 P _part/87/_geo_xyz/1,4,0 -0.000000175 P _part/87/_geo_xyz/1,4,1 -2.0 P _part/87/_geo_xyz/1,4,2 1.0 P _part/87/_geo_xyz/1,5,0 -0.000000175 P _part/87/_geo_xyz/1,5,1 -2.0 P _part/87/_geo_xyz/1,5,2 1.0 P _part/87/_geo_xyz/1,6,0 -1.414213777 P _part/87/_geo_xyz/1,6,1 -1.414213300 P _part/87/_geo_xyz/1,6,2 1.0 P _part/87/_geo_xyz/1,7,0 -2.0 P _part/87/_geo_xyz/1,7,1 0.000000024 P _part/87/_geo_xyz/1,7,2 1.0 P _part/87/_geo_xyz/1,8,0 -1.414213061 P _part/87/_geo_xyz/1,8,1 1.414214015 P _part/87/_geo_xyz/1,8,2 1.0 P _part/87/_geo_xyz/1,9,0 0.000000350 P _part/87/_geo_xyz/1,9,1 2.0 P _part/87/_geo_xyz/1,9,2 1.0 P _part/87/_geo_xyz/10,0,0 0.0 P _part/87/_geo_xyz/10,0,1 0.0 P _part/87/_geo_xyz/10,0,2 0.0 P _part/87/_geo_xyz/10,1,0 0.0 P _part/87/_geo_xyz/10,1,1 0.0 P _part/87/_geo_xyz/10,1,2 0.0 P _part/87/_geo_xyz/10,10,0 0.0 P _part/87/_geo_xyz/10,10,1 0.0 P _part/87/_geo_xyz/10,10,2 0.0 P _part/87/_geo_xyz/10,11,0 0.0 P _part/87/_geo_xyz/10,11,1 0.0 P _part/87/_geo_xyz/10,11,2 0.0 P _part/87/_geo_xyz/10,12,0 0.0 P _part/87/_geo_xyz/10,12,1 0.0 P _part/87/_geo_xyz/10,12,2 0.0 P _part/87/_geo_xyz/10,13,0 0.0 P _part/87/_geo_xyz/10,13,1 0.0 P _part/87/_geo_xyz/10,13,2 0.0 P _part/87/_geo_xyz/10,14,0 0.0 P _part/87/_geo_xyz/10,14,1 0.0 P _part/87/_geo_xyz/10,14,2 0.0 P _part/87/_geo_xyz/10,15,0 0.0 P _part/87/_geo_xyz/10,15,1 0.0 P _part/87/_geo_xyz/10,15,2 0.0 P _part/87/_geo_xyz/10,16,0 0.0 P _part/87/_geo_xyz/10,16,1 0.0 P _part/87/_geo_xyz/10,16,2 0.0 P _part/87/_geo_xyz/10,17,0 0.0 P _part/87/_geo_xyz/10,17,1 0.0 P _part/87/_geo_xyz/10,17,2 0.0 P _part/87/_geo_xyz/10,2,0 0.0 P _part/87/_geo_xyz/10,2,1 0.0 P _part/87/_geo_xyz/10,2,2 0.0 P _part/87/_geo_xyz/10,3,0 0.0 P _part/87/_geo_xyz/10,3,1 0.0 P _part/87/_geo_xyz/10,3,2 0.0 P _part/87/_geo_xyz/10,4,0 0.0 P _part/87/_geo_xyz/10,4,1 0.0 P _part/87/_geo_xyz/10,4,2 0.0 P _part/87/_geo_xyz/10,5,0 0.0 P _part/87/_geo_xyz/10,5,1 0.0 P _part/87/_geo_xyz/10,5,2 0.0 P _part/87/_geo_xyz/10,6,0 0.0 P _part/87/_geo_xyz/10,6,1 0.0 P _part/87/_geo_xyz/10,6,2 0.0 P _part/87/_geo_xyz/10,7,0 0.0 P _part/87/_geo_xyz/10,7,1 0.0 P _part/87/_geo_xyz/10,7,2 0.0 P _part/87/_geo_xyz/10,8,0 0.0 P _part/87/_geo_xyz/10,8,1 0.0 P _part/87/_geo_xyz/10,8,2 0.0 P _part/87/_geo_xyz/10,9,0 0.0 P _part/87/_geo_xyz/10,9,1 0.0 P _part/87/_geo_xyz/10,9,2 0.0 P _part/87/_geo_xyz/11,0,0 0.0 P _part/87/_geo_xyz/11,0,1 0.0 P _part/87/_geo_xyz/11,0,2 0.0 P _part/87/_geo_xyz/11,1,0 0.0 P _part/87/_geo_xyz/11,1,1 0.0 P _part/87/_geo_xyz/11,1,2 0.0 P _part/87/_geo_xyz/11,10,0 0.0 P _part/87/_geo_xyz/11,10,1 0.0 P _part/87/_geo_xyz/11,10,2 0.0 P _part/87/_geo_xyz/11,11,0 0.0 P _part/87/_geo_xyz/11,11,1 0.0 P _part/87/_geo_xyz/11,11,2 0.0 P _part/87/_geo_xyz/11,12,0 0.0 P _part/87/_geo_xyz/11,12,1 0.0 P _part/87/_geo_xyz/11,12,2 0.0 P _part/87/_geo_xyz/11,13,0 0.0 P _part/87/_geo_xyz/11,13,1 0.0 P _part/87/_geo_xyz/11,13,2 0.0 P _part/87/_geo_xyz/11,14,0 0.0 P _part/87/_geo_xyz/11,14,1 0.0 P _part/87/_geo_xyz/11,14,2 0.0 P _part/87/_geo_xyz/11,15,0 0.0 P _part/87/_geo_xyz/11,15,1 0.0 P _part/87/_geo_xyz/11,15,2 0.0 P _part/87/_geo_xyz/11,16,0 0.0 P _part/87/_geo_xyz/11,16,1 0.0 P _part/87/_geo_xyz/11,16,2 0.0 P _part/87/_geo_xyz/11,17,0 0.0 P _part/87/_geo_xyz/11,17,1 0.0 P _part/87/_geo_xyz/11,17,2 0.0 P _part/87/_geo_xyz/11,2,0 0.0 P _part/87/_geo_xyz/11,2,1 0.0 P _part/87/_geo_xyz/11,2,2 0.0 P _part/87/_geo_xyz/11,3,0 0.0 P _part/87/_geo_xyz/11,3,1 0.0 P _part/87/_geo_xyz/11,3,2 0.0 P _part/87/_geo_xyz/11,4,0 0.0 P _part/87/_geo_xyz/11,4,1 0.0 P _part/87/_geo_xyz/11,4,2 0.0 P _part/87/_geo_xyz/11,5,0 0.0 P _part/87/_geo_xyz/11,5,1 0.0 P _part/87/_geo_xyz/11,5,2 0.0 P _part/87/_geo_xyz/11,6,0 0.0 P _part/87/_geo_xyz/11,6,1 0.0 P _part/87/_geo_xyz/11,6,2 0.0 P _part/87/_geo_xyz/11,7,0 0.0 P _part/87/_geo_xyz/11,7,1 0.0 P _part/87/_geo_xyz/11,7,2 0.0 P _part/87/_geo_xyz/11,8,0 0.0 P _part/87/_geo_xyz/11,8,1 0.0 P _part/87/_geo_xyz/11,8,2 0.0 P _part/87/_geo_xyz/11,9,0 0.0 P _part/87/_geo_xyz/11,9,1 0.0 P _part/87/_geo_xyz/11,9,2 0.0 P _part/87/_geo_xyz/12,0,0 0.0 P _part/87/_geo_xyz/12,0,1 0.0 P _part/87/_geo_xyz/12,0,2 0.0 P _part/87/_geo_xyz/12,1,0 0.0 P _part/87/_geo_xyz/12,1,1 0.0 P _part/87/_geo_xyz/12,1,2 0.0 P _part/87/_geo_xyz/12,10,0 0.0 P _part/87/_geo_xyz/12,10,1 0.0 P _part/87/_geo_xyz/12,10,2 0.0 P _part/87/_geo_xyz/12,11,0 0.0 P _part/87/_geo_xyz/12,11,1 0.0 P _part/87/_geo_xyz/12,11,2 0.0 P _part/87/_geo_xyz/12,12,0 0.0 P _part/87/_geo_xyz/12,12,1 0.0 P _part/87/_geo_xyz/12,12,2 0.0 P _part/87/_geo_xyz/12,13,0 0.0 P _part/87/_geo_xyz/12,13,1 0.0 P _part/87/_geo_xyz/12,13,2 0.0 P _part/87/_geo_xyz/12,14,0 0.0 P _part/87/_geo_xyz/12,14,1 0.0 P _part/87/_geo_xyz/12,14,2 0.0 P _part/87/_geo_xyz/12,15,0 0.0 P _part/87/_geo_xyz/12,15,1 0.0 P _part/87/_geo_xyz/12,15,2 0.0 P _part/87/_geo_xyz/12,16,0 0.0 P _part/87/_geo_xyz/12,16,1 0.0 P _part/87/_geo_xyz/12,16,2 0.0 P _part/87/_geo_xyz/12,17,0 0.0 P _part/87/_geo_xyz/12,17,1 0.0 P _part/87/_geo_xyz/12,17,2 0.0 P _part/87/_geo_xyz/12,2,0 0.0 P _part/87/_geo_xyz/12,2,1 0.0 P _part/87/_geo_xyz/12,2,2 0.0 P _part/87/_geo_xyz/12,3,0 0.0 P _part/87/_geo_xyz/12,3,1 0.0 P _part/87/_geo_xyz/12,3,2 0.0 P _part/87/_geo_xyz/12,4,0 0.0 P _part/87/_geo_xyz/12,4,1 0.0 P _part/87/_geo_xyz/12,4,2 0.0 P _part/87/_geo_xyz/12,5,0 0.0 P _part/87/_geo_xyz/12,5,1 0.0 P _part/87/_geo_xyz/12,5,2 0.0 P _part/87/_geo_xyz/12,6,0 0.0 P _part/87/_geo_xyz/12,6,1 0.0 P _part/87/_geo_xyz/12,6,2 0.0 P _part/87/_geo_xyz/12,7,0 0.0 P _part/87/_geo_xyz/12,7,1 0.0 P _part/87/_geo_xyz/12,7,2 0.0 P _part/87/_geo_xyz/12,8,0 0.0 P _part/87/_geo_xyz/12,8,1 0.0 P _part/87/_geo_xyz/12,8,2 0.0 P _part/87/_geo_xyz/12,9,0 0.0 P _part/87/_geo_xyz/12,9,1 0.0 P _part/87/_geo_xyz/12,9,2 0.0 P _part/87/_geo_xyz/13,0,0 0.0 P _part/87/_geo_xyz/13,0,1 0.0 P _part/87/_geo_xyz/13,0,2 0.0 P _part/87/_geo_xyz/13,1,0 0.0 P _part/87/_geo_xyz/13,1,1 0.0 P _part/87/_geo_xyz/13,1,2 0.0 P _part/87/_geo_xyz/13,10,0 0.0 P _part/87/_geo_xyz/13,10,1 0.0 P _part/87/_geo_xyz/13,10,2 0.0 P _part/87/_geo_xyz/13,11,0 0.0 P _part/87/_geo_xyz/13,11,1 0.0 P _part/87/_geo_xyz/13,11,2 0.0 P _part/87/_geo_xyz/13,12,0 0.0 P _part/87/_geo_xyz/13,12,1 0.0 P _part/87/_geo_xyz/13,12,2 0.0 P _part/87/_geo_xyz/13,13,0 0.0 P _part/87/_geo_xyz/13,13,1 0.0 P _part/87/_geo_xyz/13,13,2 0.0 P _part/87/_geo_xyz/13,14,0 0.0 P _part/87/_geo_xyz/13,14,1 0.0 P _part/87/_geo_xyz/13,14,2 0.0 P _part/87/_geo_xyz/13,15,0 0.0 P _part/87/_geo_xyz/13,15,1 0.0 P _part/87/_geo_xyz/13,15,2 0.0 P _part/87/_geo_xyz/13,16,0 0.0 P _part/87/_geo_xyz/13,16,1 0.0 P _part/87/_geo_xyz/13,16,2 0.0 P _part/87/_geo_xyz/13,17,0 0.0 P _part/87/_geo_xyz/13,17,1 0.0 P _part/87/_geo_xyz/13,17,2 0.0 P _part/87/_geo_xyz/13,2,0 0.0 P _part/87/_geo_xyz/13,2,1 0.0 P _part/87/_geo_xyz/13,2,2 0.0 P _part/87/_geo_xyz/13,3,0 0.0 P _part/87/_geo_xyz/13,3,1 0.0 P _part/87/_geo_xyz/13,3,2 0.0 P _part/87/_geo_xyz/13,4,0 0.0 P _part/87/_geo_xyz/13,4,1 0.0 P _part/87/_geo_xyz/13,4,2 0.0 P _part/87/_geo_xyz/13,5,0 0.0 P _part/87/_geo_xyz/13,5,1 0.0 P _part/87/_geo_xyz/13,5,2 0.0 P _part/87/_geo_xyz/13,6,0 0.0 P _part/87/_geo_xyz/13,6,1 0.0 P _part/87/_geo_xyz/13,6,2 0.0 P _part/87/_geo_xyz/13,7,0 0.0 P _part/87/_geo_xyz/13,7,1 0.0 P _part/87/_geo_xyz/13,7,2 0.0 P _part/87/_geo_xyz/13,8,0 0.0 P _part/87/_geo_xyz/13,8,1 0.0 P _part/87/_geo_xyz/13,8,2 0.0 P _part/87/_geo_xyz/13,9,0 0.0 P _part/87/_geo_xyz/13,9,1 0.0 P _part/87/_geo_xyz/13,9,2 0.0 P _part/87/_geo_xyz/14,0,0 0.0 P _part/87/_geo_xyz/14,0,1 0.0 P _part/87/_geo_xyz/14,0,2 0.0 P _part/87/_geo_xyz/14,1,0 0.0 P _part/87/_geo_xyz/14,1,1 0.0 P _part/87/_geo_xyz/14,1,2 0.0 P _part/87/_geo_xyz/14,10,0 0.0 P _part/87/_geo_xyz/14,10,1 0.0 P _part/87/_geo_xyz/14,10,2 0.0 P _part/87/_geo_xyz/14,11,0 0.0 P _part/87/_geo_xyz/14,11,1 0.0 P _part/87/_geo_xyz/14,11,2 0.0 P _part/87/_geo_xyz/14,12,0 0.0 P _part/87/_geo_xyz/14,12,1 0.0 P _part/87/_geo_xyz/14,12,2 0.0 P _part/87/_geo_xyz/14,13,0 0.0 P _part/87/_geo_xyz/14,13,1 0.0 P _part/87/_geo_xyz/14,13,2 0.0 P _part/87/_geo_xyz/14,14,0 0.0 P _part/87/_geo_xyz/14,14,1 0.0 P _part/87/_geo_xyz/14,14,2 0.0 P _part/87/_geo_xyz/14,15,0 0.0 P _part/87/_geo_xyz/14,15,1 0.0 P _part/87/_geo_xyz/14,15,2 0.0 P _part/87/_geo_xyz/14,16,0 0.0 P _part/87/_geo_xyz/14,16,1 0.0 P _part/87/_geo_xyz/14,16,2 0.0 P _part/87/_geo_xyz/14,17,0 0.0 P _part/87/_geo_xyz/14,17,1 0.0 P _part/87/_geo_xyz/14,17,2 0.0 P _part/87/_geo_xyz/14,2,0 0.0 P _part/87/_geo_xyz/14,2,1 0.0 P _part/87/_geo_xyz/14,2,2 0.0 P _part/87/_geo_xyz/14,3,0 0.0 P _part/87/_geo_xyz/14,3,1 0.0 P _part/87/_geo_xyz/14,3,2 0.0 P _part/87/_geo_xyz/14,4,0 0.0 P _part/87/_geo_xyz/14,4,1 0.0 P _part/87/_geo_xyz/14,4,2 0.0 P _part/87/_geo_xyz/14,5,0 0.0 P _part/87/_geo_xyz/14,5,1 0.0 P _part/87/_geo_xyz/14,5,2 0.0 P _part/87/_geo_xyz/14,6,0 0.0 P _part/87/_geo_xyz/14,6,1 0.0 P _part/87/_geo_xyz/14,6,2 0.0 P _part/87/_geo_xyz/14,7,0 0.0 P _part/87/_geo_xyz/14,7,1 0.0 P _part/87/_geo_xyz/14,7,2 0.0 P _part/87/_geo_xyz/14,8,0 0.0 P _part/87/_geo_xyz/14,8,1 0.0 P _part/87/_geo_xyz/14,8,2 0.0 P _part/87/_geo_xyz/14,9,0 0.0 P _part/87/_geo_xyz/14,9,1 0.0 P _part/87/_geo_xyz/14,9,2 0.0 P _part/87/_geo_xyz/15,0,0 0.0 P _part/87/_geo_xyz/15,0,1 0.0 P _part/87/_geo_xyz/15,0,2 0.0 P _part/87/_geo_xyz/15,1,0 0.0 P _part/87/_geo_xyz/15,1,1 0.0 P _part/87/_geo_xyz/15,1,2 0.0 P _part/87/_geo_xyz/15,10,0 0.0 P _part/87/_geo_xyz/15,10,1 0.0 P _part/87/_geo_xyz/15,10,2 0.0 P _part/87/_geo_xyz/15,11,0 0.0 P _part/87/_geo_xyz/15,11,1 0.0 P _part/87/_geo_xyz/15,11,2 0.0 P _part/87/_geo_xyz/15,12,0 0.0 P _part/87/_geo_xyz/15,12,1 0.0 P _part/87/_geo_xyz/15,12,2 0.0 P _part/87/_geo_xyz/15,13,0 0.0 P _part/87/_geo_xyz/15,13,1 0.0 P _part/87/_geo_xyz/15,13,2 0.0 P _part/87/_geo_xyz/15,14,0 0.0 P _part/87/_geo_xyz/15,14,1 0.0 P _part/87/_geo_xyz/15,14,2 0.0 P _part/87/_geo_xyz/15,15,0 0.0 P _part/87/_geo_xyz/15,15,1 0.0 P _part/87/_geo_xyz/15,15,2 0.0 P _part/87/_geo_xyz/15,16,0 0.0 P _part/87/_geo_xyz/15,16,1 0.0 P _part/87/_geo_xyz/15,16,2 0.0 P _part/87/_geo_xyz/15,17,0 0.0 P _part/87/_geo_xyz/15,17,1 0.0 P _part/87/_geo_xyz/15,17,2 0.0 P _part/87/_geo_xyz/15,2,0 0.0 P _part/87/_geo_xyz/15,2,1 0.0 P _part/87/_geo_xyz/15,2,2 0.0 P _part/87/_geo_xyz/15,3,0 0.0 P _part/87/_geo_xyz/15,3,1 0.0 P _part/87/_geo_xyz/15,3,2 0.0 P _part/87/_geo_xyz/15,4,0 0.0 P _part/87/_geo_xyz/15,4,1 0.0 P _part/87/_geo_xyz/15,4,2 0.0 P _part/87/_geo_xyz/15,5,0 0.0 P _part/87/_geo_xyz/15,5,1 0.0 P _part/87/_geo_xyz/15,5,2 0.0 P _part/87/_geo_xyz/15,6,0 0.0 P _part/87/_geo_xyz/15,6,1 0.0 P _part/87/_geo_xyz/15,6,2 0.0 P _part/87/_geo_xyz/15,7,0 0.0 P _part/87/_geo_xyz/15,7,1 0.0 P _part/87/_geo_xyz/15,7,2 0.0 P _part/87/_geo_xyz/15,8,0 0.0 P _part/87/_geo_xyz/15,8,1 0.0 P _part/87/_geo_xyz/15,8,2 0.0 P _part/87/_geo_xyz/15,9,0 0.0 P _part/87/_geo_xyz/15,9,1 0.0 P _part/87/_geo_xyz/15,9,2 0.0 P _part/87/_geo_xyz/16,0,0 0.0 P _part/87/_geo_xyz/16,0,1 0.0 P _part/87/_geo_xyz/16,0,2 0.0 P _part/87/_geo_xyz/16,1,0 0.0 P _part/87/_geo_xyz/16,1,1 0.0 P _part/87/_geo_xyz/16,1,2 0.0 P _part/87/_geo_xyz/16,10,0 0.0 P _part/87/_geo_xyz/16,10,1 0.0 P _part/87/_geo_xyz/16,10,2 0.0 P _part/87/_geo_xyz/16,11,0 0.0 P _part/87/_geo_xyz/16,11,1 0.0 P _part/87/_geo_xyz/16,11,2 0.0 P _part/87/_geo_xyz/16,12,0 0.0 P _part/87/_geo_xyz/16,12,1 0.0 P _part/87/_geo_xyz/16,12,2 0.0 P _part/87/_geo_xyz/16,13,0 0.0 P _part/87/_geo_xyz/16,13,1 0.0 P _part/87/_geo_xyz/16,13,2 0.0 P _part/87/_geo_xyz/16,14,0 0.0 P _part/87/_geo_xyz/16,14,1 0.0 P _part/87/_geo_xyz/16,14,2 0.0 P _part/87/_geo_xyz/16,15,0 0.0 P _part/87/_geo_xyz/16,15,1 0.0 P _part/87/_geo_xyz/16,15,2 0.0 P _part/87/_geo_xyz/16,16,0 0.0 P _part/87/_geo_xyz/16,16,1 0.0 P _part/87/_geo_xyz/16,16,2 0.0 P _part/87/_geo_xyz/16,17,0 0.0 P _part/87/_geo_xyz/16,17,1 0.0 P _part/87/_geo_xyz/16,17,2 0.0 P _part/87/_geo_xyz/16,2,0 0.0 P _part/87/_geo_xyz/16,2,1 0.0 P _part/87/_geo_xyz/16,2,2 0.0 P _part/87/_geo_xyz/16,3,0 0.0 P _part/87/_geo_xyz/16,3,1 0.0 P _part/87/_geo_xyz/16,3,2 0.0 P _part/87/_geo_xyz/16,4,0 0.0 P _part/87/_geo_xyz/16,4,1 0.0 P _part/87/_geo_xyz/16,4,2 0.0 P _part/87/_geo_xyz/16,5,0 0.0 P _part/87/_geo_xyz/16,5,1 0.0 P _part/87/_geo_xyz/16,5,2 0.0 P _part/87/_geo_xyz/16,6,0 0.0 P _part/87/_geo_xyz/16,6,1 0.0 P _part/87/_geo_xyz/16,6,2 0.0 P _part/87/_geo_xyz/16,7,0 0.0 P _part/87/_geo_xyz/16,7,1 0.0 P _part/87/_geo_xyz/16,7,2 0.0 P _part/87/_geo_xyz/16,8,0 0.0 P _part/87/_geo_xyz/16,8,1 0.0 P _part/87/_geo_xyz/16,8,2 0.0 P _part/87/_geo_xyz/16,9,0 0.0 P _part/87/_geo_xyz/16,9,1 0.0 P _part/87/_geo_xyz/16,9,2 0.0 P _part/87/_geo_xyz/17,0,0 0.0 P _part/87/_geo_xyz/17,0,1 0.0 P _part/87/_geo_xyz/17,0,2 0.0 P _part/87/_geo_xyz/17,1,0 0.0 P _part/87/_geo_xyz/17,1,1 0.0 P _part/87/_geo_xyz/17,1,2 0.0 P _part/87/_geo_xyz/17,10,0 0.0 P _part/87/_geo_xyz/17,10,1 0.0 P _part/87/_geo_xyz/17,10,2 0.0 P _part/87/_geo_xyz/17,11,0 0.0 P _part/87/_geo_xyz/17,11,1 0.0 P _part/87/_geo_xyz/17,11,2 0.0 P _part/87/_geo_xyz/17,12,0 0.0 P _part/87/_geo_xyz/17,12,1 0.0 P _part/87/_geo_xyz/17,12,2 0.0 P _part/87/_geo_xyz/17,13,0 0.0 P _part/87/_geo_xyz/17,13,1 0.0 P _part/87/_geo_xyz/17,13,2 0.0 P _part/87/_geo_xyz/17,14,0 0.0 P _part/87/_geo_xyz/17,14,1 0.0 P _part/87/_geo_xyz/17,14,2 0.0 P _part/87/_geo_xyz/17,15,0 0.0 P _part/87/_geo_xyz/17,15,1 0.0 P _part/87/_geo_xyz/17,15,2 0.0 P _part/87/_geo_xyz/17,16,0 0.0 P _part/87/_geo_xyz/17,16,1 0.0 P _part/87/_geo_xyz/17,16,2 0.0 P _part/87/_geo_xyz/17,17,0 0.0 P _part/87/_geo_xyz/17,17,1 0.0 P _part/87/_geo_xyz/17,17,2 0.0 P _part/87/_geo_xyz/17,2,0 0.0 P _part/87/_geo_xyz/17,2,1 0.0 P _part/87/_geo_xyz/17,2,2 0.0 P _part/87/_geo_xyz/17,3,0 0.0 P _part/87/_geo_xyz/17,3,1 0.0 P _part/87/_geo_xyz/17,3,2 0.0 P _part/87/_geo_xyz/17,4,0 0.0 P _part/87/_geo_xyz/17,4,1 0.0 P _part/87/_geo_xyz/17,4,2 0.0 P _part/87/_geo_xyz/17,5,0 0.0 P _part/87/_geo_xyz/17,5,1 0.0 P _part/87/_geo_xyz/17,5,2 0.0 P _part/87/_geo_xyz/17,6,0 0.0 P _part/87/_geo_xyz/17,6,1 0.0 P _part/87/_geo_xyz/17,6,2 0.0 P _part/87/_geo_xyz/17,7,0 0.0 P _part/87/_geo_xyz/17,7,1 0.0 P _part/87/_geo_xyz/17,7,2 0.0 P _part/87/_geo_xyz/17,8,0 0.0 P _part/87/_geo_xyz/17,8,1 0.0 P _part/87/_geo_xyz/17,8,2 0.0 P _part/87/_geo_xyz/17,9,0 0.0 P _part/87/_geo_xyz/17,9,1 0.0 P _part/87/_geo_xyz/17,9,2 0.0 P _part/87/_geo_xyz/18,0,0 0.0 P _part/87/_geo_xyz/18,0,1 0.0 P _part/87/_geo_xyz/18,0,2 0.0 P _part/87/_geo_xyz/18,1,0 0.0 P _part/87/_geo_xyz/18,1,1 0.0 P _part/87/_geo_xyz/18,1,2 0.0 P _part/87/_geo_xyz/18,10,0 0.0 P _part/87/_geo_xyz/18,10,1 0.0 P _part/87/_geo_xyz/18,10,2 0.0 P _part/87/_geo_xyz/18,11,0 0.0 P _part/87/_geo_xyz/18,11,1 0.0 P _part/87/_geo_xyz/18,11,2 0.0 P _part/87/_geo_xyz/18,12,0 0.0 P _part/87/_geo_xyz/18,12,1 0.0 P _part/87/_geo_xyz/18,12,2 0.0 P _part/87/_geo_xyz/18,13,0 0.0 P _part/87/_geo_xyz/18,13,1 0.0 P _part/87/_geo_xyz/18,13,2 0.0 P _part/87/_geo_xyz/18,14,0 0.0 P _part/87/_geo_xyz/18,14,1 0.0 P _part/87/_geo_xyz/18,14,2 0.0 P _part/87/_geo_xyz/18,15,0 0.0 P _part/87/_geo_xyz/18,15,1 0.0 P _part/87/_geo_xyz/18,15,2 0.0 P _part/87/_geo_xyz/18,16,0 0.0 P _part/87/_geo_xyz/18,16,1 0.0 P _part/87/_geo_xyz/18,16,2 0.0 P _part/87/_geo_xyz/18,17,0 0.0 P _part/87/_geo_xyz/18,17,1 0.0 P _part/87/_geo_xyz/18,17,2 0.0 P _part/87/_geo_xyz/18,2,0 0.0 P _part/87/_geo_xyz/18,2,1 0.0 P _part/87/_geo_xyz/18,2,2 0.0 P _part/87/_geo_xyz/18,3,0 0.0 P _part/87/_geo_xyz/18,3,1 0.0 P _part/87/_geo_xyz/18,3,2 0.0 P _part/87/_geo_xyz/18,4,0 0.0 P _part/87/_geo_xyz/18,4,1 0.0 P _part/87/_geo_xyz/18,4,2 0.0 P _part/87/_geo_xyz/18,5,0 0.0 P _part/87/_geo_xyz/18,5,1 0.0 P _part/87/_geo_xyz/18,5,2 0.0 P _part/87/_geo_xyz/18,6,0 0.0 P _part/87/_geo_xyz/18,6,1 0.0 P _part/87/_geo_xyz/18,6,2 0.0 P _part/87/_geo_xyz/18,7,0 0.0 P _part/87/_geo_xyz/18,7,1 0.0 P _part/87/_geo_xyz/18,7,2 0.0 P _part/87/_geo_xyz/18,8,0 0.0 P _part/87/_geo_xyz/18,8,1 0.0 P _part/87/_geo_xyz/18,8,2 0.0 P _part/87/_geo_xyz/18,9,0 0.0 P _part/87/_geo_xyz/18,9,1 0.0 P _part/87/_geo_xyz/18,9,2 0.0 P _part/87/_geo_xyz/19,0,0 0.0 P _part/87/_geo_xyz/19,0,1 0.0 P _part/87/_geo_xyz/19,0,2 0.0 P _part/87/_geo_xyz/19,1,0 0.0 P _part/87/_geo_xyz/19,1,1 0.0 P _part/87/_geo_xyz/19,1,2 0.0 P _part/87/_geo_xyz/19,10,0 0.0 P _part/87/_geo_xyz/19,10,1 0.0 P _part/87/_geo_xyz/19,10,2 0.0 P _part/87/_geo_xyz/19,11,0 0.0 P _part/87/_geo_xyz/19,11,1 0.0 P _part/87/_geo_xyz/19,11,2 0.0 P _part/87/_geo_xyz/19,12,0 0.0 P _part/87/_geo_xyz/19,12,1 0.0 P _part/87/_geo_xyz/19,12,2 0.0 P _part/87/_geo_xyz/19,13,0 0.0 P _part/87/_geo_xyz/19,13,1 0.0 P _part/87/_geo_xyz/19,13,2 0.0 P _part/87/_geo_xyz/19,14,0 0.0 P _part/87/_geo_xyz/19,14,1 0.0 P _part/87/_geo_xyz/19,14,2 0.0 P _part/87/_geo_xyz/19,15,0 0.0 P _part/87/_geo_xyz/19,15,1 0.0 P _part/87/_geo_xyz/19,15,2 0.0 P _part/87/_geo_xyz/19,16,0 0.0 P _part/87/_geo_xyz/19,16,1 0.0 P _part/87/_geo_xyz/19,16,2 0.0 P _part/87/_geo_xyz/19,17,0 0.0 P _part/87/_geo_xyz/19,17,1 0.0 P _part/87/_geo_xyz/19,17,2 0.0 P _part/87/_geo_xyz/19,2,0 0.0 P _part/87/_geo_xyz/19,2,1 0.0 P _part/87/_geo_xyz/19,2,2 0.0 P _part/87/_geo_xyz/19,3,0 0.0 P _part/87/_geo_xyz/19,3,1 0.0 P _part/87/_geo_xyz/19,3,2 0.0 P _part/87/_geo_xyz/19,4,0 0.0 P _part/87/_geo_xyz/19,4,1 0.0 P _part/87/_geo_xyz/19,4,2 0.0 P _part/87/_geo_xyz/19,5,0 0.0 P _part/87/_geo_xyz/19,5,1 0.0 P _part/87/_geo_xyz/19,5,2 0.0 P _part/87/_geo_xyz/19,6,0 0.0 P _part/87/_geo_xyz/19,6,1 0.0 P _part/87/_geo_xyz/19,6,2 0.0 P _part/87/_geo_xyz/19,7,0 0.0 P _part/87/_geo_xyz/19,7,1 0.0 P _part/87/_geo_xyz/19,7,2 0.0 P _part/87/_geo_xyz/19,8,0 0.0 P _part/87/_geo_xyz/19,8,1 0.0 P _part/87/_geo_xyz/19,8,2 0.0 P _part/87/_geo_xyz/19,9,0 0.0 P _part/87/_geo_xyz/19,9,1 0.0 P _part/87/_geo_xyz/19,9,2 0.0 P _part/87/_geo_xyz/2,0,0 0.0 P _part/87/_geo_xyz/2,0,1 2.0 P _part/87/_geo_xyz/2,0,2 2.0 P _part/87/_geo_xyz/2,1,0 1.414213538 P _part/87/_geo_xyz/2,1,1 1.414213538 P _part/87/_geo_xyz/2,1,2 2.0 P _part/87/_geo_xyz/2,10,0 0.0 P _part/87/_geo_xyz/2,10,1 0.0 P _part/87/_geo_xyz/2,10,2 0.0 P _part/87/_geo_xyz/2,11,0 0.0 P _part/87/_geo_xyz/2,11,1 0.0 P _part/87/_geo_xyz/2,11,2 0.0 P _part/87/_geo_xyz/2,12,0 0.0 P _part/87/_geo_xyz/2,12,1 0.0 P _part/87/_geo_xyz/2,12,2 0.0 P _part/87/_geo_xyz/2,13,0 0.0 P _part/87/_geo_xyz/2,13,1 0.0 P _part/87/_geo_xyz/2,13,2 0.0 P _part/87/_geo_xyz/2,14,0 0.0 P _part/87/_geo_xyz/2,14,1 0.0 P _part/87/_geo_xyz/2,14,2 0.0 P _part/87/_geo_xyz/2,15,0 0.0 P _part/87/_geo_xyz/2,15,1 0.0 P _part/87/_geo_xyz/2,15,2 0.0 P _part/87/_geo_xyz/2,16,0 0.0 P _part/87/_geo_xyz/2,16,1 0.0 P _part/87/_geo_xyz/2,16,2 0.0 P _part/87/_geo_xyz/2,17,0 0.0 P _part/87/_geo_xyz/2,17,1 0.0 P _part/87/_geo_xyz/2,17,2 0.0 P _part/87/_geo_xyz/2,2,0 2.0 P _part/87/_geo_xyz/2,2,1 -0.000000087 P _part/87/_geo_xyz/2,2,2 2.0 P _part/87/_geo_xyz/2,3,0 1.414213538 P _part/87/_geo_xyz/2,3,1 -1.414213538 P _part/87/_geo_xyz/2,3,2 2.0 P _part/87/_geo_xyz/2,4,0 -0.000000175 P _part/87/_geo_xyz/2,4,1 -2.0 P _part/87/_geo_xyz/2,4,2 2.0 P _part/87/_geo_xyz/2,5,0 -0.000000175 P _part/87/_geo_xyz/2,5,1 -2.0 P _part/87/_geo_xyz/2,5,2 2.0 P _part/87/_geo_xyz/2,6,0 -1.414213777 P _part/87/_geo_xyz/2,6,1 -1.414213300 P _part/87/_geo_xyz/2,6,2 2.0 P _part/87/_geo_xyz/2,7,0 -2.0 P _part/87/_geo_xyz/2,7,1 0.000000024 P _part/87/_geo_xyz/2,7,2 2.0 P _part/87/_geo_xyz/2,8,0 -1.414213061 P _part/87/_geo_xyz/2,8,1 1.414214015 P _part/87/_geo_xyz/2,8,2 2.0 P _part/87/_geo_xyz/2,9,0 0.000000350 P _part/87/_geo_xyz/2,9,1 2.0 P _part/87/_geo_xyz/2,9,2 2.0 P _part/87/_geo_xyz/3,0,0 0.0 P _part/87/_geo_xyz/3,0,1 2.0 P _part/87/_geo_xyz/3,0,2 3.0 P _part/87/_geo_xyz/3,1,0 1.414213538 P _part/87/_geo_xyz/3,1,1 1.414213538 P _part/87/_geo_xyz/3,1,2 3.0 P _part/87/_geo_xyz/3,10,0 0.0 P _part/87/_geo_xyz/3,10,1 0.0 P _part/87/_geo_xyz/3,10,2 0.0 P _part/87/_geo_xyz/3,11,0 0.0 P _part/87/_geo_xyz/3,11,1 0.0 P _part/87/_geo_xyz/3,11,2 0.0 P _part/87/_geo_xyz/3,12,0 0.0 P _part/87/_geo_xyz/3,12,1 0.0 P _part/87/_geo_xyz/3,12,2 0.0 P _part/87/_geo_xyz/3,13,0 0.0 P _part/87/_geo_xyz/3,13,1 0.0 P _part/87/_geo_xyz/3,13,2 0.0 P _part/87/_geo_xyz/3,14,0 0.0 P _part/87/_geo_xyz/3,14,1 0.0 P _part/87/_geo_xyz/3,14,2 0.0 P _part/87/_geo_xyz/3,15,0 0.0 P _part/87/_geo_xyz/3,15,1 0.0 P _part/87/_geo_xyz/3,15,2 0.0 P _part/87/_geo_xyz/3,16,0 0.0 P _part/87/_geo_xyz/3,16,1 0.0 P _part/87/_geo_xyz/3,16,2 0.0 P _part/87/_geo_xyz/3,17,0 0.0 P _part/87/_geo_xyz/3,17,1 0.0 P _part/87/_geo_xyz/3,17,2 0.0 P _part/87/_geo_xyz/3,2,0 2.0 P _part/87/_geo_xyz/3,2,1 -0.000000087 P _part/87/_geo_xyz/3,2,2 3.0 P _part/87/_geo_xyz/3,3,0 1.414213538 P _part/87/_geo_xyz/3,3,1 -1.414213538 P _part/87/_geo_xyz/3,3,2 3.0 P _part/87/_geo_xyz/3,4,0 -0.000000175 P _part/87/_geo_xyz/3,4,1 -2.0 P _part/87/_geo_xyz/3,4,2 3.0 P _part/87/_geo_xyz/3,5,0 -0.000000175 P _part/87/_geo_xyz/3,5,1 -2.0 P _part/87/_geo_xyz/3,5,2 3.0 P _part/87/_geo_xyz/3,6,0 -1.414213777 P _part/87/_geo_xyz/3,6,1 -1.414213300 P _part/87/_geo_xyz/3,6,2 3.0 P _part/87/_geo_xyz/3,7,0 -2.0 P _part/87/_geo_xyz/3,7,1 0.000000024 P _part/87/_geo_xyz/3,7,2 3.0 P _part/87/_geo_xyz/3,8,0 -1.414213061 P _part/87/_geo_xyz/3,8,1 1.414214015 P _part/87/_geo_xyz/3,8,2 3.0 P _part/87/_geo_xyz/3,9,0 0.000000350 P _part/87/_geo_xyz/3,9,1 2.0 P _part/87/_geo_xyz/3,9,2 3.0 P _part/87/_geo_xyz/4,0,0 0.0 P _part/87/_geo_xyz/4,0,1 2.0 P _part/87/_geo_xyz/4,0,2 4.0 P _part/87/_geo_xyz/4,1,0 1.414213538 P _part/87/_geo_xyz/4,1,1 1.414213538 P _part/87/_geo_xyz/4,1,2 4.0 P _part/87/_geo_xyz/4,10,0 0.0 P _part/87/_geo_xyz/4,10,1 0.0 P _part/87/_geo_xyz/4,10,2 0.0 P _part/87/_geo_xyz/4,11,0 0.0 P _part/87/_geo_xyz/4,11,1 0.0 P _part/87/_geo_xyz/4,11,2 0.0 P _part/87/_geo_xyz/4,12,0 0.0 P _part/87/_geo_xyz/4,12,1 0.0 P _part/87/_geo_xyz/4,12,2 0.0 P _part/87/_geo_xyz/4,13,0 0.0 P _part/87/_geo_xyz/4,13,1 0.0 P _part/87/_geo_xyz/4,13,2 0.0 P _part/87/_geo_xyz/4,14,0 0.0 P _part/87/_geo_xyz/4,14,1 0.0 P _part/87/_geo_xyz/4,14,2 0.0 P _part/87/_geo_xyz/4,15,0 0.0 P _part/87/_geo_xyz/4,15,1 0.0 P _part/87/_geo_xyz/4,15,2 0.0 P _part/87/_geo_xyz/4,16,0 0.0 P _part/87/_geo_xyz/4,16,1 0.0 P _part/87/_geo_xyz/4,16,2 0.0 P _part/87/_geo_xyz/4,17,0 0.0 P _part/87/_geo_xyz/4,17,1 0.0 P _part/87/_geo_xyz/4,17,2 0.0 P _part/87/_geo_xyz/4,2,0 2.0 P _part/87/_geo_xyz/4,2,1 -0.000000087 P _part/87/_geo_xyz/4,2,2 4.0 P _part/87/_geo_xyz/4,3,0 1.414213538 P _part/87/_geo_xyz/4,3,1 -1.414213538 P _part/87/_geo_xyz/4,3,2 4.0 P _part/87/_geo_xyz/4,4,0 -0.000000175 P _part/87/_geo_xyz/4,4,1 -2.0 P _part/87/_geo_xyz/4,4,2 4.0 P _part/87/_geo_xyz/4,5,0 -0.000000175 P _part/87/_geo_xyz/4,5,1 -2.0 P _part/87/_geo_xyz/4,5,2 4.0 P _part/87/_geo_xyz/4,6,0 -1.414213777 P _part/87/_geo_xyz/4,6,1 -1.414213300 P _part/87/_geo_xyz/4,6,2 4.0 P _part/87/_geo_xyz/4,7,0 -2.0 P _part/87/_geo_xyz/4,7,1 0.000000024 P _part/87/_geo_xyz/4,7,2 4.0 P _part/87/_geo_xyz/4,8,0 -1.414213061 P _part/87/_geo_xyz/4,8,1 1.414214015 P _part/87/_geo_xyz/4,8,2 4.0 P _part/87/_geo_xyz/4,9,0 0.000000350 P _part/87/_geo_xyz/4,9,1 2.0 P _part/87/_geo_xyz/4,9,2 4.0 P _part/87/_geo_xyz/5,0,0 0.0 P _part/87/_geo_xyz/5,0,1 2.0 P _part/87/_geo_xyz/5,0,2 5.0 P _part/87/_geo_xyz/5,1,0 1.414213538 P _part/87/_geo_xyz/5,1,1 1.414213538 P _part/87/_geo_xyz/5,1,2 5.0 P _part/87/_geo_xyz/5,10,0 0.0 P _part/87/_geo_xyz/5,10,1 0.0 P _part/87/_geo_xyz/5,10,2 0.0 P _part/87/_geo_xyz/5,11,0 0.0 P _part/87/_geo_xyz/5,11,1 0.0 P _part/87/_geo_xyz/5,11,2 0.0 P _part/87/_geo_xyz/5,12,0 0.0 P _part/87/_geo_xyz/5,12,1 0.0 P _part/87/_geo_xyz/5,12,2 0.0 P _part/87/_geo_xyz/5,13,0 0.0 P _part/87/_geo_xyz/5,13,1 0.0 P _part/87/_geo_xyz/5,13,2 0.0 P _part/87/_geo_xyz/5,14,0 0.0 P _part/87/_geo_xyz/5,14,1 0.0 P _part/87/_geo_xyz/5,14,2 0.0 P _part/87/_geo_xyz/5,15,0 0.0 P _part/87/_geo_xyz/5,15,1 0.0 P _part/87/_geo_xyz/5,15,2 0.0 P _part/87/_geo_xyz/5,16,0 0.0 P _part/87/_geo_xyz/5,16,1 0.0 P _part/87/_geo_xyz/5,16,2 0.0 P _part/87/_geo_xyz/5,17,0 0.0 P _part/87/_geo_xyz/5,17,1 0.0 P _part/87/_geo_xyz/5,17,2 0.0 P _part/87/_geo_xyz/5,2,0 2.0 P _part/87/_geo_xyz/5,2,1 -0.000000087 P _part/87/_geo_xyz/5,2,2 5.0 P _part/87/_geo_xyz/5,3,0 1.414213538 P _part/87/_geo_xyz/5,3,1 -1.414213538 P _part/87/_geo_xyz/5,3,2 5.0 P _part/87/_geo_xyz/5,4,0 -0.000000175 P _part/87/_geo_xyz/5,4,1 -2.0 P _part/87/_geo_xyz/5,4,2 5.0 P _part/87/_geo_xyz/5,5,0 -0.000000175 P _part/87/_geo_xyz/5,5,1 -2.0 P _part/87/_geo_xyz/5,5,2 5.0 P _part/87/_geo_xyz/5,6,0 -1.414213777 P _part/87/_geo_xyz/5,6,1 -1.414213300 P _part/87/_geo_xyz/5,6,2 5.0 P _part/87/_geo_xyz/5,7,0 -2.0 P _part/87/_geo_xyz/5,7,1 0.000000024 P _part/87/_geo_xyz/5,7,2 5.0 P _part/87/_geo_xyz/5,8,0 -1.414213061 P _part/87/_geo_xyz/5,8,1 1.414214015 P _part/87/_geo_xyz/5,8,2 5.0 P _part/87/_geo_xyz/5,9,0 0.000000350 P _part/87/_geo_xyz/5,9,1 2.0 P _part/87/_geo_xyz/5,9,2 5.0 P _part/87/_geo_xyz/6,0,0 0.0 P _part/87/_geo_xyz/6,0,1 2.0 P _part/87/_geo_xyz/6,0,2 6.0 P _part/87/_geo_xyz/6,1,0 1.414213538 P _part/87/_geo_xyz/6,1,1 1.414213538 P _part/87/_geo_xyz/6,1,2 6.0 P _part/87/_geo_xyz/6,10,0 0.0 P _part/87/_geo_xyz/6,10,1 0.0 P _part/87/_geo_xyz/6,10,2 0.0 P _part/87/_geo_xyz/6,11,0 0.0 P _part/87/_geo_xyz/6,11,1 0.0 P _part/87/_geo_xyz/6,11,2 0.0 P _part/87/_geo_xyz/6,12,0 0.0 P _part/87/_geo_xyz/6,12,1 0.0 P _part/87/_geo_xyz/6,12,2 0.0 P _part/87/_geo_xyz/6,13,0 0.0 P _part/87/_geo_xyz/6,13,1 0.0 P _part/87/_geo_xyz/6,13,2 0.0 P _part/87/_geo_xyz/6,14,0 0.0 P _part/87/_geo_xyz/6,14,1 0.0 P _part/87/_geo_xyz/6,14,2 0.0 P _part/87/_geo_xyz/6,15,0 0.0 P _part/87/_geo_xyz/6,15,1 0.0 P _part/87/_geo_xyz/6,15,2 0.0 P _part/87/_geo_xyz/6,16,0 0.0 P _part/87/_geo_xyz/6,16,1 0.0 P _part/87/_geo_xyz/6,16,2 0.0 P _part/87/_geo_xyz/6,17,0 0.0 P _part/87/_geo_xyz/6,17,1 0.0 P _part/87/_geo_xyz/6,17,2 0.0 P _part/87/_geo_xyz/6,2,0 2.0 P _part/87/_geo_xyz/6,2,1 -0.000000087 P _part/87/_geo_xyz/6,2,2 6.0 P _part/87/_geo_xyz/6,3,0 1.414213538 P _part/87/_geo_xyz/6,3,1 -1.414213538 P _part/87/_geo_xyz/6,3,2 6.0 P _part/87/_geo_xyz/6,4,0 -0.000000175 P _part/87/_geo_xyz/6,4,1 -2.0 P _part/87/_geo_xyz/6,4,2 6.0 P _part/87/_geo_xyz/6,5,0 -0.000000175 P _part/87/_geo_xyz/6,5,1 -2.0 P _part/87/_geo_xyz/6,5,2 6.0 P _part/87/_geo_xyz/6,6,0 -1.414213777 P _part/87/_geo_xyz/6,6,1 -1.414213300 P _part/87/_geo_xyz/6,6,2 6.0 P _part/87/_geo_xyz/6,7,0 -2.0 P _part/87/_geo_xyz/6,7,1 0.000000024 P _part/87/_geo_xyz/6,7,2 6.0 P _part/87/_geo_xyz/6,8,0 -1.414213061 P _part/87/_geo_xyz/6,8,1 1.414214015 P _part/87/_geo_xyz/6,8,2 6.0 P _part/87/_geo_xyz/6,9,0 0.000000350 P _part/87/_geo_xyz/6,9,1 2.0 P _part/87/_geo_xyz/6,9,2 6.0 P _part/87/_geo_xyz/7,0,0 0.0 P _part/87/_geo_xyz/7,0,1 2.0 P _part/87/_geo_xyz/7,0,2 7.0 P _part/87/_geo_xyz/7,1,0 1.414213538 P _part/87/_geo_xyz/7,1,1 1.414213538 P _part/87/_geo_xyz/7,1,2 7.0 P _part/87/_geo_xyz/7,10,0 0.0 P _part/87/_geo_xyz/7,10,1 0.0 P _part/87/_geo_xyz/7,10,2 0.0 P _part/87/_geo_xyz/7,11,0 0.0 P _part/87/_geo_xyz/7,11,1 0.0 P _part/87/_geo_xyz/7,11,2 0.0 P _part/87/_geo_xyz/7,12,0 0.0 P _part/87/_geo_xyz/7,12,1 0.0 P _part/87/_geo_xyz/7,12,2 0.0 P _part/87/_geo_xyz/7,13,0 0.0 P _part/87/_geo_xyz/7,13,1 0.0 P _part/87/_geo_xyz/7,13,2 0.0 P _part/87/_geo_xyz/7,14,0 0.0 P _part/87/_geo_xyz/7,14,1 0.0 P _part/87/_geo_xyz/7,14,2 0.0 P _part/87/_geo_xyz/7,15,0 0.0 P _part/87/_geo_xyz/7,15,1 0.0 P _part/87/_geo_xyz/7,15,2 0.0 P _part/87/_geo_xyz/7,16,0 0.0 P _part/87/_geo_xyz/7,16,1 0.0 P _part/87/_geo_xyz/7,16,2 0.0 P _part/87/_geo_xyz/7,17,0 0.0 P _part/87/_geo_xyz/7,17,1 0.0 P _part/87/_geo_xyz/7,17,2 0.0 P _part/87/_geo_xyz/7,2,0 2.0 P _part/87/_geo_xyz/7,2,1 -0.000000087 P _part/87/_geo_xyz/7,2,2 7.0 P _part/87/_geo_xyz/7,3,0 1.414213538 P _part/87/_geo_xyz/7,3,1 -1.414213538 P _part/87/_geo_xyz/7,3,2 7.0 P _part/87/_geo_xyz/7,4,0 -0.000000175 P _part/87/_geo_xyz/7,4,1 -2.0 P _part/87/_geo_xyz/7,4,2 7.0 P _part/87/_geo_xyz/7,5,0 -0.000000175 P _part/87/_geo_xyz/7,5,1 -2.0 P _part/87/_geo_xyz/7,5,2 7.0 P _part/87/_geo_xyz/7,6,0 -1.414213777 P _part/87/_geo_xyz/7,6,1 -1.414213300 P _part/87/_geo_xyz/7,6,2 7.0 P _part/87/_geo_xyz/7,7,0 -2.0 P _part/87/_geo_xyz/7,7,1 0.000000024 P _part/87/_geo_xyz/7,7,2 7.0 P _part/87/_geo_xyz/7,8,0 -1.414213061 P _part/87/_geo_xyz/7,8,1 1.414214015 P _part/87/_geo_xyz/7,8,2 7.0 P _part/87/_geo_xyz/7,9,0 0.000000350 P _part/87/_geo_xyz/7,9,1 2.0 P _part/87/_geo_xyz/7,9,2 7.0 P _part/87/_geo_xyz/8,0,0 0.0 P _part/87/_geo_xyz/8,0,1 0.0 P _part/87/_geo_xyz/8,0,2 0.0 P _part/87/_geo_xyz/8,1,0 0.0 P _part/87/_geo_xyz/8,1,1 0.0 P _part/87/_geo_xyz/8,1,2 0.0 P _part/87/_geo_xyz/8,10,0 0.0 P _part/87/_geo_xyz/8,10,1 0.0 P _part/87/_geo_xyz/8,10,2 0.0 P _part/87/_geo_xyz/8,11,0 0.0 P _part/87/_geo_xyz/8,11,1 0.0 P _part/87/_geo_xyz/8,11,2 0.0 P _part/87/_geo_xyz/8,12,0 0.0 P _part/87/_geo_xyz/8,12,1 0.0 P _part/87/_geo_xyz/8,12,2 0.0 P _part/87/_geo_xyz/8,13,0 0.0 P _part/87/_geo_xyz/8,13,1 0.0 P _part/87/_geo_xyz/8,13,2 0.0 P _part/87/_geo_xyz/8,14,0 0.0 P _part/87/_geo_xyz/8,14,1 0.0 P _part/87/_geo_xyz/8,14,2 0.0 P _part/87/_geo_xyz/8,15,0 0.0 P _part/87/_geo_xyz/8,15,1 0.0 P _part/87/_geo_xyz/8,15,2 0.0 P _part/87/_geo_xyz/8,16,0 0.0 P _part/87/_geo_xyz/8,16,1 0.0 P _part/87/_geo_xyz/8,16,2 0.0 P _part/87/_geo_xyz/8,17,0 0.0 P _part/87/_geo_xyz/8,17,1 0.0 P _part/87/_geo_xyz/8,17,2 0.0 P _part/87/_geo_xyz/8,2,0 0.0 P _part/87/_geo_xyz/8,2,1 0.0 P _part/87/_geo_xyz/8,2,2 0.0 P _part/87/_geo_xyz/8,3,0 0.0 P _part/87/_geo_xyz/8,3,1 0.0 P _part/87/_geo_xyz/8,3,2 0.0 P _part/87/_geo_xyz/8,4,0 0.0 P _part/87/_geo_xyz/8,4,1 0.0 P _part/87/_geo_xyz/8,4,2 0.0 P _part/87/_geo_xyz/8,5,0 0.0 P _part/87/_geo_xyz/8,5,1 0.0 P _part/87/_geo_xyz/8,5,2 0.0 P _part/87/_geo_xyz/8,6,0 0.0 P _part/87/_geo_xyz/8,6,1 0.0 P _part/87/_geo_xyz/8,6,2 0.0 P _part/87/_geo_xyz/8,7,0 0.0 P _part/87/_geo_xyz/8,7,1 0.0 P _part/87/_geo_xyz/8,7,2 0.0 P _part/87/_geo_xyz/8,8,0 0.0 P _part/87/_geo_xyz/8,8,1 0.0 P _part/87/_geo_xyz/8,8,2 0.0 P _part/87/_geo_xyz/8,9,0 0.0 P _part/87/_geo_xyz/8,9,1 0.0 P _part/87/_geo_xyz/8,9,2 0.0 P _part/87/_geo_xyz/9,0,0 0.0 P _part/87/_geo_xyz/9,0,1 0.0 P _part/87/_geo_xyz/9,0,2 0.0 P _part/87/_geo_xyz/9,1,0 0.0 P _part/87/_geo_xyz/9,1,1 0.0 P _part/87/_geo_xyz/9,1,2 0.0 P _part/87/_geo_xyz/9,10,0 0.0 P _part/87/_geo_xyz/9,10,1 0.0 P _part/87/_geo_xyz/9,10,2 0.0 P _part/87/_geo_xyz/9,11,0 0.0 P _part/87/_geo_xyz/9,11,1 0.0 P _part/87/_geo_xyz/9,11,2 0.0 P _part/87/_geo_xyz/9,12,0 0.0 P _part/87/_geo_xyz/9,12,1 0.0 P _part/87/_geo_xyz/9,12,2 0.0 P _part/87/_geo_xyz/9,13,0 0.0 P _part/87/_geo_xyz/9,13,1 0.0 P _part/87/_geo_xyz/9,13,2 0.0 P _part/87/_geo_xyz/9,14,0 0.0 P _part/87/_geo_xyz/9,14,1 0.0 P _part/87/_geo_xyz/9,14,2 0.0 P _part/87/_geo_xyz/9,15,0 0.0 P _part/87/_geo_xyz/9,15,1 0.0 P _part/87/_geo_xyz/9,15,2 0.0 P _part/87/_geo_xyz/9,16,0 0.0 P _part/87/_geo_xyz/9,16,1 0.0 P _part/87/_geo_xyz/9,16,2 0.0 P _part/87/_geo_xyz/9,17,0 0.0 P _part/87/_geo_xyz/9,17,1 0.0 P _part/87/_geo_xyz/9,17,2 0.0 P _part/87/_geo_xyz/9,2,0 0.0 P _part/87/_geo_xyz/9,2,1 0.0 P _part/87/_geo_xyz/9,2,2 0.0 P _part/87/_geo_xyz/9,3,0 0.0 P _part/87/_geo_xyz/9,3,1 0.0 P _part/87/_geo_xyz/9,3,2 0.0 P _part/87/_geo_xyz/9,4,0 0.0 P _part/87/_geo_xyz/9,4,1 0.0 P _part/87/_geo_xyz/9,4,2 0.0 P _part/87/_geo_xyz/9,5,0 0.0 P _part/87/_geo_xyz/9,5,1 0.0 P _part/87/_geo_xyz/9,5,2 0.0 P _part/87/_geo_xyz/9,6,0 0.0 P _part/87/_geo_xyz/9,6,1 0.0 P _part/87/_geo_xyz/9,6,2 0.0 P _part/87/_geo_xyz/9,7,0 0.0 P _part/87/_geo_xyz/9,7,1 0.0 P _part/87/_geo_xyz/9,7,2 0.0 P _part/87/_geo_xyz/9,8,0 0.0 P _part/87/_geo_xyz/9,8,1 0.0 P _part/87/_geo_xyz/9,8,2 0.0 P _part/87/_geo_xyz/9,9,0 0.0 P _part/87/_geo_xyz/9,9,1 0.0 P _part/87/_geo_xyz/9,9,2 0.0 P _part/87/_geo_xyz/i_count 20 P _part/87/_geo_xyz/j_count 18 P _part/87/_geo_xyz/k_count 3 P _part/87/_locked/0,0 0 P _part/87/_locked/0,1 0 P _part/87/_locked/0,10 0 P _part/87/_locked/0,11 0 P _part/87/_locked/0,12 0 P _part/87/_locked/0,13 0 P _part/87/_locked/0,14 0 P _part/87/_locked/0,15 0 P _part/87/_locked/0,16 0 P _part/87/_locked/0,17 0 P _part/87/_locked/0,2 0 P _part/87/_locked/0,3 0 P _part/87/_locked/0,4 0 P _part/87/_locked/0,5 0 P _part/87/_locked/0,6 0 P _part/87/_locked/0,7 0 P _part/87/_locked/0,8 0 P _part/87/_locked/0,9 0 P _part/87/_locked/1,0 0 P _part/87/_locked/1,1 0 P _part/87/_locked/1,10 0 P _part/87/_locked/1,11 0 P _part/87/_locked/1,12 0 P _part/87/_locked/1,13 0 P _part/87/_locked/1,14 0 P _part/87/_locked/1,15 0 P _part/87/_locked/1,16 0 P _part/87/_locked/1,17 0 P _part/87/_locked/1,2 0 P _part/87/_locked/1,3 0 P _part/87/_locked/1,4 0 P _part/87/_locked/1,5 0 P _part/87/_locked/1,6 0 P _part/87/_locked/1,7 0 P _part/87/_locked/1,8 0 P _part/87/_locked/1,9 0 P _part/87/_locked/10,0 0 P _part/87/_locked/10,1 0 P _part/87/_locked/10,10 0 P _part/87/_locked/10,11 0 P _part/87/_locked/10,12 0 P _part/87/_locked/10,13 0 P _part/87/_locked/10,14 0 P _part/87/_locked/10,15 0 P _part/87/_locked/10,16 0 P _part/87/_locked/10,17 0 P _part/87/_locked/10,2 0 P _part/87/_locked/10,3 0 P _part/87/_locked/10,4 0 P _part/87/_locked/10,5 0 P _part/87/_locked/10,6 0 P _part/87/_locked/10,7 0 P _part/87/_locked/10,8 0 P _part/87/_locked/10,9 0 P _part/87/_locked/11,0 0 P _part/87/_locked/11,1 0 P _part/87/_locked/11,10 0 P _part/87/_locked/11,11 0 P _part/87/_locked/11,12 0 P _part/87/_locked/11,13 0 P _part/87/_locked/11,14 0 P _part/87/_locked/11,15 0 P _part/87/_locked/11,16 0 P _part/87/_locked/11,17 0 P _part/87/_locked/11,2 0 P _part/87/_locked/11,3 0 P _part/87/_locked/11,4 0 P _part/87/_locked/11,5 0 P _part/87/_locked/11,6 0 P _part/87/_locked/11,7 0 P _part/87/_locked/11,8 0 P _part/87/_locked/11,9 0 P _part/87/_locked/12,0 0 P _part/87/_locked/12,1 0 P _part/87/_locked/12,10 0 P _part/87/_locked/12,11 0 P _part/87/_locked/12,12 0 P _part/87/_locked/12,13 0 P _part/87/_locked/12,14 0 P _part/87/_locked/12,15 0 P _part/87/_locked/12,16 0 P _part/87/_locked/12,17 0 P _part/87/_locked/12,2 0 P _part/87/_locked/12,3 0 P _part/87/_locked/12,4 0 P _part/87/_locked/12,5 0 P _part/87/_locked/12,6 0 P _part/87/_locked/12,7 0 P _part/87/_locked/12,8 0 P _part/87/_locked/12,9 0 P _part/87/_locked/13,0 0 P _part/87/_locked/13,1 0 P _part/87/_locked/13,10 0 P _part/87/_locked/13,11 0 P _part/87/_locked/13,12 0 P _part/87/_locked/13,13 0 P _part/87/_locked/13,14 0 P _part/87/_locked/13,15 0 P _part/87/_locked/13,16 0 P _part/87/_locked/13,17 0 P _part/87/_locked/13,2 0 P _part/87/_locked/13,3 0 P _part/87/_locked/13,4 0 P _part/87/_locked/13,5 0 P _part/87/_locked/13,6 0 P _part/87/_locked/13,7 0 P _part/87/_locked/13,8 0 P _part/87/_locked/13,9 0 P _part/87/_locked/14,0 0 P _part/87/_locked/14,1 0 P _part/87/_locked/14,10 0 P _part/87/_locked/14,11 0 P _part/87/_locked/14,12 0 P _part/87/_locked/14,13 0 P _part/87/_locked/14,14 0 P _part/87/_locked/14,15 0 P _part/87/_locked/14,16 0 P _part/87/_locked/14,17 0 P _part/87/_locked/14,2 0 P _part/87/_locked/14,3 0 P _part/87/_locked/14,4 0 P _part/87/_locked/14,5 0 P _part/87/_locked/14,6 0 P _part/87/_locked/14,7 0 P _part/87/_locked/14,8 0 P _part/87/_locked/14,9 0 P _part/87/_locked/15,0 0 P _part/87/_locked/15,1 0 P _part/87/_locked/15,10 0 P _part/87/_locked/15,11 0 P _part/87/_locked/15,12 0 P _part/87/_locked/15,13 0 P _part/87/_locked/15,14 0 P _part/87/_locked/15,15 0 P _part/87/_locked/15,16 0 P _part/87/_locked/15,17 0 P _part/87/_locked/15,2 0 P _part/87/_locked/15,3 0 P _part/87/_locked/15,4 0 P _part/87/_locked/15,5 0 P _part/87/_locked/15,6 0 P _part/87/_locked/15,7 0 P _part/87/_locked/15,8 0 P _part/87/_locked/15,9 0 P _part/87/_locked/16,0 0 P _part/87/_locked/16,1 0 P _part/87/_locked/16,10 0 P _part/87/_locked/16,11 0 P _part/87/_locked/16,12 0 P _part/87/_locked/16,13 0 P _part/87/_locked/16,14 0 P _part/87/_locked/16,15 0 P _part/87/_locked/16,16 0 P _part/87/_locked/16,17 0 P _part/87/_locked/16,2 0 P _part/87/_locked/16,3 0 P _part/87/_locked/16,4 0 P _part/87/_locked/16,5 0 P _part/87/_locked/16,6 0 P _part/87/_locked/16,7 0 P _part/87/_locked/16,8 0 P _part/87/_locked/16,9 0 P _part/87/_locked/17,0 0 P _part/87/_locked/17,1 0 P _part/87/_locked/17,10 0 P _part/87/_locked/17,11 0 P _part/87/_locked/17,12 0 P _part/87/_locked/17,13 0 P _part/87/_locked/17,14 0 P _part/87/_locked/17,15 0 P _part/87/_locked/17,16 0 P _part/87/_locked/17,17 0 P _part/87/_locked/17,2 0 P _part/87/_locked/17,3 0 P _part/87/_locked/17,4 0 P _part/87/_locked/17,5 0 P _part/87/_locked/17,6 0 P _part/87/_locked/17,7 0 P _part/87/_locked/17,8 0 P _part/87/_locked/17,9 0 P _part/87/_locked/18,0 0 P _part/87/_locked/18,1 0 P _part/87/_locked/18,10 0 P _part/87/_locked/18,11 0 P _part/87/_locked/18,12 0 P _part/87/_locked/18,13 0 P _part/87/_locked/18,14 0 P _part/87/_locked/18,15 0 P _part/87/_locked/18,16 0 P _part/87/_locked/18,17 0 P _part/87/_locked/18,2 0 P _part/87/_locked/18,3 0 P _part/87/_locked/18,4 0 P _part/87/_locked/18,5 0 P _part/87/_locked/18,6 0 P _part/87/_locked/18,7 0 P _part/87/_locked/18,8 0 P _part/87/_locked/18,9 0 P _part/87/_locked/19,0 0 P _part/87/_locked/19,1 0 P _part/87/_locked/19,10 0 P _part/87/_locked/19,11 0 P _part/87/_locked/19,12 0 P _part/87/_locked/19,13 0 P _part/87/_locked/19,14 0 P _part/87/_locked/19,15 0 P _part/87/_locked/19,16 0 P _part/87/_locked/19,17 0 P _part/87/_locked/19,2 0 P _part/87/_locked/19,3 0 P _part/87/_locked/19,4 0 P _part/87/_locked/19,5 0 P _part/87/_locked/19,6 0 P _part/87/_locked/19,7 0 P _part/87/_locked/19,8 0 P _part/87/_locked/19,9 0 P _part/87/_locked/2,0 0 P _part/87/_locked/2,1 0 P _part/87/_locked/2,10 0 P _part/87/_locked/2,11 0 P _part/87/_locked/2,12 0 P _part/87/_locked/2,13 0 P _part/87/_locked/2,14 0 P _part/87/_locked/2,15 0 P _part/87/_locked/2,16 0 P _part/87/_locked/2,17 0 P _part/87/_locked/2,2 0 P _part/87/_locked/2,3 0 P _part/87/_locked/2,4 0 P _part/87/_locked/2,5 0 P _part/87/_locked/2,6 0 P _part/87/_locked/2,7 0 P _part/87/_locked/2,8 0 P _part/87/_locked/2,9 0 P _part/87/_locked/3,0 0 P _part/87/_locked/3,1 0 P _part/87/_locked/3,10 0 P _part/87/_locked/3,11 0 P _part/87/_locked/3,12 0 P _part/87/_locked/3,13 0 P _part/87/_locked/3,14 0 P _part/87/_locked/3,15 0 P _part/87/_locked/3,16 0 P _part/87/_locked/3,17 0 P _part/87/_locked/3,2 0 P _part/87/_locked/3,3 0 P _part/87/_locked/3,4 0 P _part/87/_locked/3,5 0 P _part/87/_locked/3,6 0 P _part/87/_locked/3,7 0 P _part/87/_locked/3,8 0 P _part/87/_locked/3,9 0 P _part/87/_locked/4,0 0 P _part/87/_locked/4,1 0 P _part/87/_locked/4,10 0 P _part/87/_locked/4,11 0 P _part/87/_locked/4,12 0 P _part/87/_locked/4,13 0 P _part/87/_locked/4,14 0 P _part/87/_locked/4,15 0 P _part/87/_locked/4,16 0 P _part/87/_locked/4,17 0 P _part/87/_locked/4,2 0 P _part/87/_locked/4,3 0 P _part/87/_locked/4,4 0 P _part/87/_locked/4,5 0 P _part/87/_locked/4,6 0 P _part/87/_locked/4,7 0 P _part/87/_locked/4,8 0 P _part/87/_locked/4,9 0 P _part/87/_locked/5,0 0 P _part/87/_locked/5,1 0 P _part/87/_locked/5,10 0 P _part/87/_locked/5,11 0 P _part/87/_locked/5,12 0 P _part/87/_locked/5,13 0 P _part/87/_locked/5,14 0 P _part/87/_locked/5,15 0 P _part/87/_locked/5,16 0 P _part/87/_locked/5,17 0 P _part/87/_locked/5,2 0 P _part/87/_locked/5,3 0 P _part/87/_locked/5,4 0 P _part/87/_locked/5,5 0 P _part/87/_locked/5,6 0 P _part/87/_locked/5,7 0 P _part/87/_locked/5,8 0 P _part/87/_locked/5,9 0 P _part/87/_locked/6,0 0 P _part/87/_locked/6,1 0 P _part/87/_locked/6,10 0 P _part/87/_locked/6,11 0 P _part/87/_locked/6,12 0 P _part/87/_locked/6,13 0 P _part/87/_locked/6,14 0 P _part/87/_locked/6,15 0 P _part/87/_locked/6,16 0 P _part/87/_locked/6,17 0 P _part/87/_locked/6,2 0 P _part/87/_locked/6,3 0 P _part/87/_locked/6,4 0 P _part/87/_locked/6,5 0 P _part/87/_locked/6,6 0 P _part/87/_locked/6,7 0 P _part/87/_locked/6,8 0 P _part/87/_locked/6,9 0 P _part/87/_locked/7,0 0 P _part/87/_locked/7,1 0 P _part/87/_locked/7,10 0 P _part/87/_locked/7,11 0 P _part/87/_locked/7,12 0 P _part/87/_locked/7,13 0 P _part/87/_locked/7,14 0 P _part/87/_locked/7,15 0 P _part/87/_locked/7,16 0 P _part/87/_locked/7,17 0 P _part/87/_locked/7,2 0 P _part/87/_locked/7,3 0 P _part/87/_locked/7,4 0 P _part/87/_locked/7,5 0 P _part/87/_locked/7,6 0 P _part/87/_locked/7,7 0 P _part/87/_locked/7,8 0 P _part/87/_locked/7,9 0 P _part/87/_locked/8,0 0 P _part/87/_locked/8,1 0 P _part/87/_locked/8,10 0 P _part/87/_locked/8,11 0 P _part/87/_locked/8,12 0 P _part/87/_locked/8,13 0 P _part/87/_locked/8,14 0 P _part/87/_locked/8,15 0 P _part/87/_locked/8,16 0 P _part/87/_locked/8,17 0 P _part/87/_locked/8,2 0 P _part/87/_locked/8,3 0 P _part/87/_locked/8,4 0 P _part/87/_locked/8,5 0 P _part/87/_locked/8,6 0 P _part/87/_locked/8,7 0 P _part/87/_locked/8,8 0 P _part/87/_locked/8,9 0 P _part/87/_locked/9,0 0 P _part/87/_locked/9,1 0 P _part/87/_locked/9,10 0 P _part/87/_locked/9,11 0 P _part/87/_locked/9,12 0 P _part/87/_locked/9,13 0 P _part/87/_locked/9,14 0 P _part/87/_locked/9,15 0 P _part/87/_locked/9,16 0 P _part/87/_locked/9,17 0 P _part/87/_locked/9,2 0 P _part/87/_locked/9,3 0 P _part/87/_locked/9,4 0 P _part/87/_locked/9,5 0 P _part/87/_locked/9,6 0 P _part/87/_locked/9,7 0 P _part/87/_locked/9,8 0 P _part/87/_locked/9,9 0 P _part/87/_locked/i_count 20 P _part/87/_locked/j_count 18 P _part/87/_part_cd 0.075000003 P _part/87/_part_phi 0.0 P _part/87/_part_psi 0.0 P _part/87/_part_rad 2.0 P _part/87/_part_specs_eq 0 P _part/87/_part_specs_invis 0 P _part/87/_part_specs_unused1 0 P _part/87/_part_specs_unused2 0 P _part/87/_part_tex 0 P _part/87/_part_the 0.0 P _part/87/_part_x 0.0 P _part/87/_part_y 0.0 P _part/87/_part_z 0.0 P _part/87/_patt_con 0 P _part/87/_patt_prt 0 P _part/87/_patt_rat 0.0 P _part/87/_r_dim 10 P _part/87/_s_dim 8 P _part/87/_scon 37.676635742 P _part/87/_top_s1 0.632812500 P _part/87/_top_s2 0.753906250 P _part/87/_top_t1 0.254882812 P _part/87/_top_t2 0.295898438 P _part/9/_aero_x_os 0.0 P _part/9/_aero_y_os 0.0 P _part/9/_aero_z_os 0.0 P _part/9/_area_frnt 0.0 P _part/9/_area_side 0.0 P _part/9/_area_vert 0.0 P _part/9/_bot_s1 0.755999982 P _part/9/_bot_s2 1.0 P _part/9/_bot_t1 0.0 P _part/9/_bot_t2 0.384000003 P _part/9/_damp 1.883831739 P _part/9/_geo_xyz/0,0,0 0.0 P _part/9/_geo_xyz/0,0,1 0.0 P _part/9/_geo_xyz/0,0,2 -0.820209980 P _part/9/_geo_xyz/0,1,0 0.0 P _part/9/_geo_xyz/0,1,1 0.139720470 P _part/9/_geo_xyz/0,1,2 -0.716699481 P _part/9/_geo_xyz/0,10,0 0.0 P _part/9/_geo_xyz/0,10,1 -0.134547234 P _part/9/_geo_xyz/0,10,2 -0.069849081 P _part/9/_geo_xyz/0,11,0 0.0 P _part/9/_geo_xyz/0,11,1 -0.113846451 P _part/9/_geo_xyz/0,11,2 -0.597703397 P _part/9/_geo_xyz/0,12,0 0.0 P _part/9/_geo_xyz/0,12,1 -0.082795277 P _part/9/_geo_xyz/0,12,2 -0.747768998 P _part/9/_geo_xyz/0,13,0 0.0 P _part/9/_geo_xyz/0,13,1 0.0 P _part/9/_geo_xyz/0,13,2 -0.820209980 P _part/9/_geo_xyz/0,14,0 0.0 P _part/9/_geo_xyz/0,14,1 0.0 P _part/9/_geo_xyz/0,14,2 0.0 P _part/9/_geo_xyz/0,15,0 0.0 P _part/9/_geo_xyz/0,15,1 0.0 P _part/9/_geo_xyz/0,15,2 0.0 P _part/9/_geo_xyz/0,16,0 0.0 P _part/9/_geo_xyz/0,16,1 0.0 P _part/9/_geo_xyz/0,16,2 0.0 P _part/9/_geo_xyz/0,17,0 0.0 P _part/9/_geo_xyz/0,17,1 0.0 P _part/9/_geo_xyz/0,17,2 0.0 P _part/9/_geo_xyz/0,2,0 0.0 P _part/9/_geo_xyz/0,2,1 0.227692902 P _part/9/_geo_xyz/0,2,2 -0.468307078 P _part/9/_geo_xyz/0,3,0 0.0 P _part/9/_geo_xyz/0,3,1 0.263917327 P _part/9/_geo_xyz/0,3,2 -0.018110236 P _part/9/_geo_xyz/0,4,0 0.0 P _part/9/_geo_xyz/0,4,1 0.238043293 P _part/9/_geo_xyz/0,4,2 0.628740191 P _part/9/_geo_xyz/0,5,0 0.0 P _part/9/_geo_xyz/0,5,1 0.213162914 P _part/9/_geo_xyz/0,5,2 0.820209980 P _part/9/_geo_xyz/0,6,0 0.0 P _part/9/_geo_xyz/0,6,1 0.0 P _part/9/_geo_xyz/0,6,2 2.460629940 P _part/9/_geo_xyz/0,7,0 0.0 P _part/9/_geo_xyz/0,7,1 0.0 P _part/9/_geo_xyz/0,7,2 2.460629940 P _part/9/_geo_xyz/0,8,0 0.0 P _part/9/_geo_xyz/0,8,1 -0.092678614 P _part/9/_geo_xyz/0,8,2 0.820209980 P _part/9/_geo_xyz/0,9,0 0.0 P _part/9/_geo_xyz/0,9,1 -0.103496060 P _part/9/_geo_xyz/0,9,2 0.628740191 P _part/9/_geo_xyz/1,0,0 6.561679840 P _part/9/_geo_xyz/1,0,1 0.0 P _part/9/_geo_xyz/1,0,2 -0.820209980 P _part/9/_geo_xyz/1,1,0 6.561679840 P _part/9/_geo_xyz/1,1,1 0.139720470 P _part/9/_geo_xyz/1,1,2 -0.716699481 P _part/9/_geo_xyz/1,10,0 6.561679840 P _part/9/_geo_xyz/1,10,1 -0.134547234 P _part/9/_geo_xyz/1,10,2 -0.069849081 P _part/9/_geo_xyz/1,11,0 6.561679840 P _part/9/_geo_xyz/1,11,1 -0.113846451 P _part/9/_geo_xyz/1,11,2 -0.597703397 P _part/9/_geo_xyz/1,12,0 6.561679840 P _part/9/_geo_xyz/1,12,1 -0.082795277 P _part/9/_geo_xyz/1,12,2 -0.747768998 P _part/9/_geo_xyz/1,13,0 6.561679840 P _part/9/_geo_xyz/1,13,1 0.0 P _part/9/_geo_xyz/1,13,2 -0.820209980 P _part/9/_geo_xyz/1,14,0 0.0 P _part/9/_geo_xyz/1,14,1 0.0 P _part/9/_geo_xyz/1,14,2 0.0 P _part/9/_geo_xyz/1,15,0 0.0 P _part/9/_geo_xyz/1,15,1 0.0 P _part/9/_geo_xyz/1,15,2 0.0 P _part/9/_geo_xyz/1,16,0 0.0 P _part/9/_geo_xyz/1,16,1 0.0 P _part/9/_geo_xyz/1,16,2 0.0 P _part/9/_geo_xyz/1,17,0 0.0 P _part/9/_geo_xyz/1,17,1 0.0 P _part/9/_geo_xyz/1,17,2 0.0 P _part/9/_geo_xyz/1,2,0 6.561679840 P _part/9/_geo_xyz/1,2,1 0.227692902 P _part/9/_geo_xyz/1,2,2 -0.468307078 P _part/9/_geo_xyz/1,3,0 6.561679840 P _part/9/_geo_xyz/1,3,1 0.263917327 P _part/9/_geo_xyz/1,3,2 -0.018110236 P _part/9/_geo_xyz/1,4,0 6.561679840 P _part/9/_geo_xyz/1,4,1 0.238043293 P _part/9/_geo_xyz/1,4,2 0.628740191 P _part/9/_geo_xyz/1,5,0 6.561679840 P _part/9/_geo_xyz/1,5,1 0.213162914 P _part/9/_geo_xyz/1,5,2 0.820209980 P _part/9/_geo_xyz/1,6,0 6.561679840 P _part/9/_geo_xyz/1,6,1 0.0 P _part/9/_geo_xyz/1,6,2 2.460629940 P _part/9/_geo_xyz/1,7,0 6.561679840 P _part/9/_geo_xyz/1,7,1 0.0 P _part/9/_geo_xyz/1,7,2 2.460629940 P _part/9/_geo_xyz/1,8,0 6.561679840 P _part/9/_geo_xyz/1,8,1 -0.092678614 P _part/9/_geo_xyz/1,8,2 0.820209980 P _part/9/_geo_xyz/1,9,0 6.561679840 P _part/9/_geo_xyz/1,9,1 -0.103496060 P _part/9/_geo_xyz/1,9,2 0.628740191 P _part/9/_geo_xyz/10,0,0 32.808399200 P _part/9/_geo_xyz/10,0,1 0.0 P _part/9/_geo_xyz/10,0,2 -0.820209980 P _part/9/_geo_xyz/10,1,0 32.808399200 P _part/9/_geo_xyz/10,1,1 0.139720470 P _part/9/_geo_xyz/10,1,2 -0.716699481 P _part/9/_geo_xyz/10,10,0 32.808399200 P _part/9/_geo_xyz/10,10,1 -0.134547234 P _part/9/_geo_xyz/10,10,2 -0.069849081 P _part/9/_geo_xyz/10,11,0 32.808399200 P _part/9/_geo_xyz/10,11,1 -0.113846451 P _part/9/_geo_xyz/10,11,2 -0.597703397 P _part/9/_geo_xyz/10,12,0 32.808399200 P _part/9/_geo_xyz/10,12,1 -0.082795277 P _part/9/_geo_xyz/10,12,2 -0.747768998 P _part/9/_geo_xyz/10,13,0 32.808399200 P _part/9/_geo_xyz/10,13,1 0.0 P _part/9/_geo_xyz/10,13,2 -0.820209980 P _part/9/_geo_xyz/10,14,0 0.0 P _part/9/_geo_xyz/10,14,1 0.0 P _part/9/_geo_xyz/10,14,2 0.0 P _part/9/_geo_xyz/10,15,0 0.0 P _part/9/_geo_xyz/10,15,1 0.0 P _part/9/_geo_xyz/10,15,2 0.0 P _part/9/_geo_xyz/10,16,0 0.0 P _part/9/_geo_xyz/10,16,1 0.0 P _part/9/_geo_xyz/10,16,2 0.0 P _part/9/_geo_xyz/10,17,0 0.0 P _part/9/_geo_xyz/10,17,1 0.0 P _part/9/_geo_xyz/10,17,2 0.0 P _part/9/_geo_xyz/10,2,0 32.808399200 P _part/9/_geo_xyz/10,2,1 0.227692902 P _part/9/_geo_xyz/10,2,2 -0.468307078 P _part/9/_geo_xyz/10,3,0 32.808399200 P _part/9/_geo_xyz/10,3,1 0.263917327 P _part/9/_geo_xyz/10,3,2 -0.018110236 P _part/9/_geo_xyz/10,4,0 32.808399200 P _part/9/_geo_xyz/10,4,1 0.238043293 P _part/9/_geo_xyz/10,4,2 0.628740191 P _part/9/_geo_xyz/10,5,0 32.808399200 P _part/9/_geo_xyz/10,5,1 0.106581457 P _part/9/_geo_xyz/10,5,2 1.640419960 P _part/9/_geo_xyz/10,6,0 32.808399200 P _part/9/_geo_xyz/10,6,1 0.0 P _part/9/_geo_xyz/10,6,2 2.460629940 P _part/9/_geo_xyz/10,7,0 32.808399200 P _part/9/_geo_xyz/10,7,1 0.0 P _part/9/_geo_xyz/10,7,2 2.460629940 P _part/9/_geo_xyz/10,8,0 32.808399200 P _part/9/_geo_xyz/10,8,1 -0.046339307 P _part/9/_geo_xyz/10,8,2 1.640419960 P _part/9/_geo_xyz/10,9,0 32.808399200 P _part/9/_geo_xyz/10,9,1 -0.103496060 P _part/9/_geo_xyz/10,9,2 0.628740191 P _part/9/_geo_xyz/11,0,0 39.370079041 P _part/9/_geo_xyz/11,0,1 0.0 P _part/9/_geo_xyz/11,0,2 -0.820209980 P _part/9/_geo_xyz/11,1,0 39.370079041 P _part/9/_geo_xyz/11,1,1 0.139720470 P _part/9/_geo_xyz/11,1,2 -0.716699481 P _part/9/_geo_xyz/11,10,0 39.370079041 P _part/9/_geo_xyz/11,10,1 -0.134547234 P _part/9/_geo_xyz/11,10,2 -0.069849081 P _part/9/_geo_xyz/11,11,0 39.370079041 P _part/9/_geo_xyz/11,11,1 -0.113846451 P _part/9/_geo_xyz/11,11,2 -0.597703397 P _part/9/_geo_xyz/11,12,0 39.370079041 P _part/9/_geo_xyz/11,12,1 -0.082795277 P _part/9/_geo_xyz/11,12,2 -0.747768998 P _part/9/_geo_xyz/11,13,0 39.370079041 P _part/9/_geo_xyz/11,13,1 0.0 P _part/9/_geo_xyz/11,13,2 -0.820209980 P _part/9/_geo_xyz/11,14,0 0.0 P _part/9/_geo_xyz/11,14,1 0.0 P _part/9/_geo_xyz/11,14,2 0.0 P _part/9/_geo_xyz/11,15,0 0.0 P _part/9/_geo_xyz/11,15,1 0.0 P _part/9/_geo_xyz/11,15,2 0.0 P _part/9/_geo_xyz/11,16,0 0.0 P _part/9/_geo_xyz/11,16,1 0.0 P _part/9/_geo_xyz/11,16,2 0.0 P _part/9/_geo_xyz/11,17,0 0.0 P _part/9/_geo_xyz/11,17,1 0.0 P _part/9/_geo_xyz/11,17,2 0.0 P _part/9/_geo_xyz/11,2,0 39.370079041 P _part/9/_geo_xyz/11,2,1 0.227692902 P _part/9/_geo_xyz/11,2,2 -0.468307078 P _part/9/_geo_xyz/11,3,0 39.370079041 P _part/9/_geo_xyz/11,3,1 0.263917327 P _part/9/_geo_xyz/11,3,2 -0.018110236 P _part/9/_geo_xyz/11,4,0 39.370079041 P _part/9/_geo_xyz/11,4,1 0.238043293 P _part/9/_geo_xyz/11,4,2 0.628740191 P _part/9/_geo_xyz/11,5,0 39.370079041 P _part/9/_geo_xyz/11,5,1 0.106581457 P _part/9/_geo_xyz/11,5,2 1.640419960 P _part/9/_geo_xyz/11,6,0 39.370079041 P _part/9/_geo_xyz/11,6,1 0.0 P _part/9/_geo_xyz/11,6,2 2.460629940 P _part/9/_geo_xyz/11,7,0 39.370079041 P _part/9/_geo_xyz/11,7,1 0.0 P _part/9/_geo_xyz/11,7,2 2.460629940 P _part/9/_geo_xyz/11,8,0 39.370079041 P _part/9/_geo_xyz/11,8,1 -0.046339307 P _part/9/_geo_xyz/11,8,2 1.640419960 P _part/9/_geo_xyz/11,9,0 39.370079041 P _part/9/_geo_xyz/11,9,1 -0.103496060 P _part/9/_geo_xyz/11,9,2 0.628740191 P _part/9/_geo_xyz/12,0,0 39.370079041 P _part/9/_geo_xyz/12,0,1 0.0 P _part/9/_geo_xyz/12,0,2 -0.820209980 P _part/9/_geo_xyz/12,1,0 39.370079041 P _part/9/_geo_xyz/12,1,1 0.139720470 P _part/9/_geo_xyz/12,1,2 -0.716699481 P _part/9/_geo_xyz/12,10,0 39.370079041 P _part/9/_geo_xyz/12,10,1 -0.134547234 P _part/9/_geo_xyz/12,10,2 -0.069849081 P _part/9/_geo_xyz/12,11,0 39.370079041 P _part/9/_geo_xyz/12,11,1 -0.113846451 P _part/9/_geo_xyz/12,11,2 -0.597703397 P _part/9/_geo_xyz/12,12,0 39.370079041 P _part/9/_geo_xyz/12,12,1 -0.082795277 P _part/9/_geo_xyz/12,12,2 -0.747768998 P _part/9/_geo_xyz/12,13,0 39.370079041 P _part/9/_geo_xyz/12,13,1 0.0 P _part/9/_geo_xyz/12,13,2 -0.820209980 P _part/9/_geo_xyz/12,14,0 0.0 P _part/9/_geo_xyz/12,14,1 0.0 P _part/9/_geo_xyz/12,14,2 0.0 P _part/9/_geo_xyz/12,15,0 0.0 P _part/9/_geo_xyz/12,15,1 0.0 P _part/9/_geo_xyz/12,15,2 0.0 P _part/9/_geo_xyz/12,16,0 0.0 P _part/9/_geo_xyz/12,16,1 0.0 P _part/9/_geo_xyz/12,16,2 0.0 P _part/9/_geo_xyz/12,17,0 0.0 P _part/9/_geo_xyz/12,17,1 0.0 P _part/9/_geo_xyz/12,17,2 0.0 P _part/9/_geo_xyz/12,2,0 39.370079041 P _part/9/_geo_xyz/12,2,1 0.227692902 P _part/9/_geo_xyz/12,2,2 -0.468307078 P _part/9/_geo_xyz/12,3,0 39.370079041 P _part/9/_geo_xyz/12,3,1 0.263917327 P _part/9/_geo_xyz/12,3,2 -0.018110236 P _part/9/_geo_xyz/12,4,0 39.370079041 P _part/9/_geo_xyz/12,4,1 0.238043293 P _part/9/_geo_xyz/12,4,2 0.628740191 P _part/9/_geo_xyz/12,5,0 39.370079041 P _part/9/_geo_xyz/12,5,1 0.213162914 P _part/9/_geo_xyz/12,5,2 0.820209980 P _part/9/_geo_xyz/12,6,0 39.370079041 P _part/9/_geo_xyz/12,6,1 0.0 P _part/9/_geo_xyz/12,6,2 2.460629940 P _part/9/_geo_xyz/12,7,0 39.370079041 P _part/9/_geo_xyz/12,7,1 0.0 P _part/9/_geo_xyz/12,7,2 2.460629940 P _part/9/_geo_xyz/12,8,0 39.370079041 P _part/9/_geo_xyz/12,8,1 -0.092678614 P _part/9/_geo_xyz/12,8,2 0.820209980 P _part/9/_geo_xyz/12,9,0 39.370079041 P _part/9/_geo_xyz/12,9,1 -0.103496060 P _part/9/_geo_xyz/12,9,2 0.628740191 P _part/9/_geo_xyz/13,0,0 45.931758881 P _part/9/_geo_xyz/13,0,1 0.0 P _part/9/_geo_xyz/13,0,2 -0.820209980 P _part/9/_geo_xyz/13,1,0 45.931758881 P _part/9/_geo_xyz/13,1,1 0.139720470 P _part/9/_geo_xyz/13,1,2 -0.716699481 P _part/9/_geo_xyz/13,10,0 45.931758881 P _part/9/_geo_xyz/13,10,1 -0.134547234 P _part/9/_geo_xyz/13,10,2 -0.069849081 P _part/9/_geo_xyz/13,11,0 45.931758881 P _part/9/_geo_xyz/13,11,1 -0.113846451 P _part/9/_geo_xyz/13,11,2 -0.597703397 P _part/9/_geo_xyz/13,12,0 45.931758881 P _part/9/_geo_xyz/13,12,1 -0.082795277 P _part/9/_geo_xyz/13,12,2 -0.747768998 P _part/9/_geo_xyz/13,13,0 45.931758881 P _part/9/_geo_xyz/13,13,1 0.0 P _part/9/_geo_xyz/13,13,2 -0.820209980 P _part/9/_geo_xyz/13,14,0 0.0 P _part/9/_geo_xyz/13,14,1 0.0 P _part/9/_geo_xyz/13,14,2 0.0 P _part/9/_geo_xyz/13,15,0 0.0 P _part/9/_geo_xyz/13,15,1 0.0 P _part/9/_geo_xyz/13,15,2 0.0 P _part/9/_geo_xyz/13,16,0 0.0 P _part/9/_geo_xyz/13,16,1 0.0 P _part/9/_geo_xyz/13,16,2 0.0 P _part/9/_geo_xyz/13,17,0 0.0 P _part/9/_geo_xyz/13,17,1 0.0 P _part/9/_geo_xyz/13,17,2 0.0 P _part/9/_geo_xyz/13,2,0 45.931758881 P _part/9/_geo_xyz/13,2,1 0.227692902 P _part/9/_geo_xyz/13,2,2 -0.468307078 P _part/9/_geo_xyz/13,3,0 45.931758881 P _part/9/_geo_xyz/13,3,1 0.263917327 P _part/9/_geo_xyz/13,3,2 -0.018110236 P _part/9/_geo_xyz/13,4,0 45.931758881 P _part/9/_geo_xyz/13,4,1 0.238043293 P _part/9/_geo_xyz/13,4,2 0.628740191 P _part/9/_geo_xyz/13,5,0 45.931758881 P _part/9/_geo_xyz/13,5,1 0.213162914 P _part/9/_geo_xyz/13,5,2 0.820209980 P _part/9/_geo_xyz/13,6,0 45.931758881 P _part/9/_geo_xyz/13,6,1 0.0 P _part/9/_geo_xyz/13,6,2 2.460629940 P _part/9/_geo_xyz/13,7,0 45.931758881 P _part/9/_geo_xyz/13,7,1 0.0 P _part/9/_geo_xyz/13,7,2 2.460629940 P _part/9/_geo_xyz/13,8,0 45.931758881 P _part/9/_geo_xyz/13,8,1 -0.092678614 P _part/9/_geo_xyz/13,8,2 0.820209980 P _part/9/_geo_xyz/13,9,0 45.931758881 P _part/9/_geo_xyz/13,9,1 -0.103496060 P _part/9/_geo_xyz/13,9,2 0.628740191 P _part/9/_geo_xyz/14,0,0 45.931758881 P _part/9/_geo_xyz/14,0,1 0.0 P _part/9/_geo_xyz/14,0,2 -0.820209980 P _part/9/_geo_xyz/14,1,0 45.931758881 P _part/9/_geo_xyz/14,1,1 0.139720470 P _part/9/_geo_xyz/14,1,2 -0.716699481 P _part/9/_geo_xyz/14,10,0 45.931758881 P _part/9/_geo_xyz/14,10,1 -0.134547234 P _part/9/_geo_xyz/14,10,2 -0.069849081 P _part/9/_geo_xyz/14,11,0 45.931758881 P _part/9/_geo_xyz/14,11,1 -0.113846451 P _part/9/_geo_xyz/14,11,2 -0.597703397 P _part/9/_geo_xyz/14,12,0 45.931758881 P _part/9/_geo_xyz/14,12,1 -0.082795277 P _part/9/_geo_xyz/14,12,2 -0.747768998 P _part/9/_geo_xyz/14,13,0 45.931758881 P _part/9/_geo_xyz/14,13,1 0.0 P _part/9/_geo_xyz/14,13,2 -0.820209980 P _part/9/_geo_xyz/14,14,0 0.0 P _part/9/_geo_xyz/14,14,1 0.0 P _part/9/_geo_xyz/14,14,2 0.0 P _part/9/_geo_xyz/14,15,0 0.0 P _part/9/_geo_xyz/14,15,1 0.0 P _part/9/_geo_xyz/14,15,2 0.0 P _part/9/_geo_xyz/14,16,0 0.0 P _part/9/_geo_xyz/14,16,1 0.0 P _part/9/_geo_xyz/14,16,2 0.0 P _part/9/_geo_xyz/14,17,0 0.0 P _part/9/_geo_xyz/14,17,1 0.0 P _part/9/_geo_xyz/14,17,2 0.0 P _part/9/_geo_xyz/14,2,0 45.931758881 P _part/9/_geo_xyz/14,2,1 0.227692902 P _part/9/_geo_xyz/14,2,2 -0.468307078 P _part/9/_geo_xyz/14,3,0 45.931758881 P _part/9/_geo_xyz/14,3,1 0.263917327 P _part/9/_geo_xyz/14,3,2 -0.018110236 P _part/9/_geo_xyz/14,4,0 45.931758881 P _part/9/_geo_xyz/14,4,1 0.238043293 P _part/9/_geo_xyz/14,4,2 0.628740191 P _part/9/_geo_xyz/14,5,0 45.931758881 P _part/9/_geo_xyz/14,5,1 0.213162914 P _part/9/_geo_xyz/14,5,2 0.820209980 P _part/9/_geo_xyz/14,6,0 45.931758881 P _part/9/_geo_xyz/14,6,1 0.0 P _part/9/_geo_xyz/14,6,2 2.460629940 P _part/9/_geo_xyz/14,7,0 45.931758881 P _part/9/_geo_xyz/14,7,1 0.0 P _part/9/_geo_xyz/14,7,2 2.460629940 P _part/9/_geo_xyz/14,8,0 45.931758881 P _part/9/_geo_xyz/14,8,1 -0.092678614 P _part/9/_geo_xyz/14,8,2 0.820209980 P _part/9/_geo_xyz/14,9,0 45.931758881 P _part/9/_geo_xyz/14,9,1 -0.103496060 P _part/9/_geo_xyz/14,9,2 0.628740191 P _part/9/_geo_xyz/15,0,0 52.493438721 P _part/9/_geo_xyz/15,0,1 0.0 P _part/9/_geo_xyz/15,0,2 -0.820209980 P _part/9/_geo_xyz/15,1,0 52.493438721 P _part/9/_geo_xyz/15,1,1 0.139720470 P _part/9/_geo_xyz/15,1,2 -0.716699481 P _part/9/_geo_xyz/15,10,0 52.493438721 P _part/9/_geo_xyz/15,10,1 -0.134547234 P _part/9/_geo_xyz/15,10,2 -0.069849081 P _part/9/_geo_xyz/15,11,0 52.493438721 P _part/9/_geo_xyz/15,11,1 -0.113846451 P _part/9/_geo_xyz/15,11,2 -0.597703397 P _part/9/_geo_xyz/15,12,0 52.493438721 P _part/9/_geo_xyz/15,12,1 -0.082795277 P _part/9/_geo_xyz/15,12,2 -0.747768998 P _part/9/_geo_xyz/15,13,0 52.493438721 P _part/9/_geo_xyz/15,13,1 0.0 P _part/9/_geo_xyz/15,13,2 -0.820209980 P _part/9/_geo_xyz/15,14,0 0.0 P _part/9/_geo_xyz/15,14,1 0.0 P _part/9/_geo_xyz/15,14,2 0.0 P _part/9/_geo_xyz/15,15,0 0.0 P _part/9/_geo_xyz/15,15,1 0.0 P _part/9/_geo_xyz/15,15,2 0.0 P _part/9/_geo_xyz/15,16,0 0.0 P _part/9/_geo_xyz/15,16,1 0.0 P _part/9/_geo_xyz/15,16,2 0.0 P _part/9/_geo_xyz/15,17,0 0.0 P _part/9/_geo_xyz/15,17,1 0.0 P _part/9/_geo_xyz/15,17,2 0.0 P _part/9/_geo_xyz/15,2,0 52.493438721 P _part/9/_geo_xyz/15,2,1 0.227692902 P _part/9/_geo_xyz/15,2,2 -0.468307078 P _part/9/_geo_xyz/15,3,0 52.493438721 P _part/9/_geo_xyz/15,3,1 0.263917327 P _part/9/_geo_xyz/15,3,2 -0.018110236 P _part/9/_geo_xyz/15,4,0 52.493438721 P _part/9/_geo_xyz/15,4,1 0.238043293 P _part/9/_geo_xyz/15,4,2 0.628740191 P _part/9/_geo_xyz/15,5,0 52.493438721 P _part/9/_geo_xyz/15,5,1 0.213162914 P _part/9/_geo_xyz/15,5,2 0.820209980 P _part/9/_geo_xyz/15,6,0 52.493438721 P _part/9/_geo_xyz/15,6,1 0.0 P _part/9/_geo_xyz/15,6,2 2.460629940 P _part/9/_geo_xyz/15,7,0 52.493438721 P _part/9/_geo_xyz/15,7,1 0.0 P _part/9/_geo_xyz/15,7,2 2.460629940 P _part/9/_geo_xyz/15,8,0 52.493438721 P _part/9/_geo_xyz/15,8,1 -0.092678614 P _part/9/_geo_xyz/15,8,2 0.820209980 P _part/9/_geo_xyz/15,9,0 52.493438721 P _part/9/_geo_xyz/15,9,1 -0.103496060 P _part/9/_geo_xyz/15,9,2 0.628740191 P _part/9/_geo_xyz/16,0,0 52.493438721 P _part/9/_geo_xyz/16,0,1 0.0 P _part/9/_geo_xyz/16,0,2 -0.820209980 P _part/9/_geo_xyz/16,1,0 52.493438721 P _part/9/_geo_xyz/16,1,1 0.139720470 P _part/9/_geo_xyz/16,1,2 -0.716699481 P _part/9/_geo_xyz/16,10,0 52.493438721 P _part/9/_geo_xyz/16,10,1 -0.134547234 P _part/9/_geo_xyz/16,10,2 -0.069849081 P _part/9/_geo_xyz/16,11,0 52.493438721 P _part/9/_geo_xyz/16,11,1 -0.113846451 P _part/9/_geo_xyz/16,11,2 -0.597703397 P _part/9/_geo_xyz/16,12,0 52.493438721 P _part/9/_geo_xyz/16,12,1 -0.082795277 P _part/9/_geo_xyz/16,12,2 -0.747768998 P _part/9/_geo_xyz/16,13,0 52.493438721 P _part/9/_geo_xyz/16,13,1 0.0 P _part/9/_geo_xyz/16,13,2 -0.820209980 P _part/9/_geo_xyz/16,14,0 0.0 P _part/9/_geo_xyz/16,14,1 0.0 P _part/9/_geo_xyz/16,14,2 0.0 P _part/9/_geo_xyz/16,15,0 0.0 P _part/9/_geo_xyz/16,15,1 0.0 P _part/9/_geo_xyz/16,15,2 0.0 P _part/9/_geo_xyz/16,16,0 0.0 P _part/9/_geo_xyz/16,16,1 0.0 P _part/9/_geo_xyz/16,16,2 0.0 P _part/9/_geo_xyz/16,17,0 0.0 P _part/9/_geo_xyz/16,17,1 0.0 P _part/9/_geo_xyz/16,17,2 0.0 P _part/9/_geo_xyz/16,2,0 52.493438721 P _part/9/_geo_xyz/16,2,1 0.227692902 P _part/9/_geo_xyz/16,2,2 -0.468307078 P _part/9/_geo_xyz/16,3,0 52.493438721 P _part/9/_geo_xyz/16,3,1 0.263917327 P _part/9/_geo_xyz/16,3,2 -0.018110236 P _part/9/_geo_xyz/16,4,0 52.493438721 P _part/9/_geo_xyz/16,4,1 0.238043293 P _part/9/_geo_xyz/16,4,2 0.628740191 P _part/9/_geo_xyz/16,5,0 52.493438721 P _part/9/_geo_xyz/16,5,1 0.213162914 P _part/9/_geo_xyz/16,5,2 0.820209980 P _part/9/_geo_xyz/16,6,0 52.493438721 P _part/9/_geo_xyz/16,6,1 0.0 P _part/9/_geo_xyz/16,6,2 2.460629940 P _part/9/_geo_xyz/16,7,0 52.493438721 P _part/9/_geo_xyz/16,7,1 0.0 P _part/9/_geo_xyz/16,7,2 2.460629940 P _part/9/_geo_xyz/16,8,0 52.493438721 P _part/9/_geo_xyz/16,8,1 -0.092678614 P _part/9/_geo_xyz/16,8,2 0.820209980 P _part/9/_geo_xyz/16,9,0 52.493438721 P _part/9/_geo_xyz/16,9,1 -0.103496060 P _part/9/_geo_xyz/16,9,2 0.628740191 P _part/9/_geo_xyz/17,0,0 0.0 P _part/9/_geo_xyz/17,0,1 0.0 P _part/9/_geo_xyz/17,0,2 0.0 P _part/9/_geo_xyz/17,1,0 0.0 P _part/9/_geo_xyz/17,1,1 0.0 P _part/9/_geo_xyz/17,1,2 0.0 P _part/9/_geo_xyz/17,10,0 0.0 P _part/9/_geo_xyz/17,10,1 0.0 P _part/9/_geo_xyz/17,10,2 0.0 P _part/9/_geo_xyz/17,11,0 0.0 P _part/9/_geo_xyz/17,11,1 0.0 P _part/9/_geo_xyz/17,11,2 0.0 P _part/9/_geo_xyz/17,12,0 0.0 P _part/9/_geo_xyz/17,12,1 0.0 P _part/9/_geo_xyz/17,12,2 0.0 P _part/9/_geo_xyz/17,13,0 0.0 P _part/9/_geo_xyz/17,13,1 0.0 P _part/9/_geo_xyz/17,13,2 0.0 P _part/9/_geo_xyz/17,14,0 0.0 P _part/9/_geo_xyz/17,14,1 0.0 P _part/9/_geo_xyz/17,14,2 0.0 P _part/9/_geo_xyz/17,15,0 0.0 P _part/9/_geo_xyz/17,15,1 0.0 P _part/9/_geo_xyz/17,15,2 0.0 P _part/9/_geo_xyz/17,16,0 0.0 P _part/9/_geo_xyz/17,16,1 0.0 P _part/9/_geo_xyz/17,16,2 0.0 P _part/9/_geo_xyz/17,17,0 0.0 P _part/9/_geo_xyz/17,17,1 0.0 P _part/9/_geo_xyz/17,17,2 0.0 P _part/9/_geo_xyz/17,2,0 0.0 P _part/9/_geo_xyz/17,2,1 0.0 P _part/9/_geo_xyz/17,2,2 0.0 P _part/9/_geo_xyz/17,3,0 0.0 P _part/9/_geo_xyz/17,3,1 0.0 P _part/9/_geo_xyz/17,3,2 0.0 P _part/9/_geo_xyz/17,4,0 0.0 P _part/9/_geo_xyz/17,4,1 0.0 P _part/9/_geo_xyz/17,4,2 0.0 P _part/9/_geo_xyz/17,5,0 0.0 P _part/9/_geo_xyz/17,5,1 0.0 P _part/9/_geo_xyz/17,5,2 0.0 P _part/9/_geo_xyz/17,6,0 0.0 P _part/9/_geo_xyz/17,6,1 0.0 P _part/9/_geo_xyz/17,6,2 0.0 P _part/9/_geo_xyz/17,7,0 0.0 P _part/9/_geo_xyz/17,7,1 0.0 P _part/9/_geo_xyz/17,7,2 0.0 P _part/9/_geo_xyz/17,8,0 0.0 P _part/9/_geo_xyz/17,8,1 0.0 P _part/9/_geo_xyz/17,8,2 0.0 P _part/9/_geo_xyz/17,9,0 0.0 P _part/9/_geo_xyz/17,9,1 0.0 P _part/9/_geo_xyz/17,9,2 0.0 P _part/9/_geo_xyz/18,0,0 0.0 P _part/9/_geo_xyz/18,0,1 0.0 P _part/9/_geo_xyz/18,0,2 0.0 P _part/9/_geo_xyz/18,1,0 0.0 P _part/9/_geo_xyz/18,1,1 0.0 P _part/9/_geo_xyz/18,1,2 0.0 P _part/9/_geo_xyz/18,10,0 0.0 P _part/9/_geo_xyz/18,10,1 0.0 P _part/9/_geo_xyz/18,10,2 0.0 P _part/9/_geo_xyz/18,11,0 0.0 P _part/9/_geo_xyz/18,11,1 0.0 P _part/9/_geo_xyz/18,11,2 0.0 P _part/9/_geo_xyz/18,12,0 0.0 P _part/9/_geo_xyz/18,12,1 0.0 P _part/9/_geo_xyz/18,12,2 0.0 P _part/9/_geo_xyz/18,13,0 0.0 P _part/9/_geo_xyz/18,13,1 0.0 P _part/9/_geo_xyz/18,13,2 0.0 P _part/9/_geo_xyz/18,14,0 0.0 P _part/9/_geo_xyz/18,14,1 0.0 P _part/9/_geo_xyz/18,14,2 0.0 P _part/9/_geo_xyz/18,15,0 0.0 P _part/9/_geo_xyz/18,15,1 0.0 P _part/9/_geo_xyz/18,15,2 0.0 P _part/9/_geo_xyz/18,16,0 0.0 P _part/9/_geo_xyz/18,16,1 0.0 P _part/9/_geo_xyz/18,16,2 0.0 P _part/9/_geo_xyz/18,17,0 0.0 P _part/9/_geo_xyz/18,17,1 0.0 P _part/9/_geo_xyz/18,17,2 0.0 P _part/9/_geo_xyz/18,2,0 0.0 P _part/9/_geo_xyz/18,2,1 0.0 P _part/9/_geo_xyz/18,2,2 0.0 P _part/9/_geo_xyz/18,3,0 0.0 P _part/9/_geo_xyz/18,3,1 0.0 P _part/9/_geo_xyz/18,3,2 0.0 P _part/9/_geo_xyz/18,4,0 0.0 P _part/9/_geo_xyz/18,4,1 0.0 P _part/9/_geo_xyz/18,4,2 0.0 P _part/9/_geo_xyz/18,5,0 0.0 P _part/9/_geo_xyz/18,5,1 0.0 P _part/9/_geo_xyz/18,5,2 0.0 P _part/9/_geo_xyz/18,6,0 0.0 P _part/9/_geo_xyz/18,6,1 0.0 P _part/9/_geo_xyz/18,6,2 0.0 P _part/9/_geo_xyz/18,7,0 0.0 P _part/9/_geo_xyz/18,7,1 0.0 P _part/9/_geo_xyz/18,7,2 0.0 P _part/9/_geo_xyz/18,8,0 0.0 P _part/9/_geo_xyz/18,8,1 0.0 P _part/9/_geo_xyz/18,8,2 0.0 P _part/9/_geo_xyz/18,9,0 0.0 P _part/9/_geo_xyz/18,9,1 0.0 P _part/9/_geo_xyz/18,9,2 0.0 P _part/9/_geo_xyz/19,0,0 0.0 P _part/9/_geo_xyz/19,0,1 0.0 P _part/9/_geo_xyz/19,0,2 0.0 P _part/9/_geo_xyz/19,1,0 0.0 P _part/9/_geo_xyz/19,1,1 0.0 P _part/9/_geo_xyz/19,1,2 0.0 P _part/9/_geo_xyz/19,10,0 0.0 P _part/9/_geo_xyz/19,10,1 0.0 P _part/9/_geo_xyz/19,10,2 0.0 P _part/9/_geo_xyz/19,11,0 0.0 P _part/9/_geo_xyz/19,11,1 0.0 P _part/9/_geo_xyz/19,11,2 0.0 P _part/9/_geo_xyz/19,12,0 0.0 P _part/9/_geo_xyz/19,12,1 0.0 P _part/9/_geo_xyz/19,12,2 0.0 P _part/9/_geo_xyz/19,13,0 0.0 P _part/9/_geo_xyz/19,13,1 0.0 P _part/9/_geo_xyz/19,13,2 0.0 P _part/9/_geo_xyz/19,14,0 0.0 P _part/9/_geo_xyz/19,14,1 0.0 P _part/9/_geo_xyz/19,14,2 0.0 P _part/9/_geo_xyz/19,15,0 0.0 P _part/9/_geo_xyz/19,15,1 0.0 P _part/9/_geo_xyz/19,15,2 0.0 P _part/9/_geo_xyz/19,16,0 0.0 P _part/9/_geo_xyz/19,16,1 0.0 P _part/9/_geo_xyz/19,16,2 0.0 P _part/9/_geo_xyz/19,17,0 0.0 P _part/9/_geo_xyz/19,17,1 0.0 P _part/9/_geo_xyz/19,17,2 0.0 P _part/9/_geo_xyz/19,2,0 0.0 P _part/9/_geo_xyz/19,2,1 0.0 P _part/9/_geo_xyz/19,2,2 0.0 P _part/9/_geo_xyz/19,3,0 0.0 P _part/9/_geo_xyz/19,3,1 0.0 P _part/9/_geo_xyz/19,3,2 0.0 P _part/9/_geo_xyz/19,4,0 0.0 P _part/9/_geo_xyz/19,4,1 0.0 P _part/9/_geo_xyz/19,4,2 0.0 P _part/9/_geo_xyz/19,5,0 0.0 P _part/9/_geo_xyz/19,5,1 0.0 P _part/9/_geo_xyz/19,5,2 0.0 P _part/9/_geo_xyz/19,6,0 0.0 P _part/9/_geo_xyz/19,6,1 0.0 P _part/9/_geo_xyz/19,6,2 0.0 P _part/9/_geo_xyz/19,7,0 0.0 P _part/9/_geo_xyz/19,7,1 0.0 P _part/9/_geo_xyz/19,7,2 0.0 P _part/9/_geo_xyz/19,8,0 0.0 P _part/9/_geo_xyz/19,8,1 0.0 P _part/9/_geo_xyz/19,8,2 0.0 P _part/9/_geo_xyz/19,9,0 0.0 P _part/9/_geo_xyz/19,9,1 0.0 P _part/9/_geo_xyz/19,9,2 0.0 P _part/9/_geo_xyz/2,0,0 6.561679840 P _part/9/_geo_xyz/2,0,1 0.0 P _part/9/_geo_xyz/2,0,2 -0.820209980 P _part/9/_geo_xyz/2,1,0 6.561679840 P _part/9/_geo_xyz/2,1,1 0.139720470 P _part/9/_geo_xyz/2,1,2 -0.716699481 P _part/9/_geo_xyz/2,10,0 6.561679840 P _part/9/_geo_xyz/2,10,1 -0.134547234 P _part/9/_geo_xyz/2,10,2 -0.069849081 P _part/9/_geo_xyz/2,11,0 6.561679840 P _part/9/_geo_xyz/2,11,1 -0.113846451 P _part/9/_geo_xyz/2,11,2 -0.597703397 P _part/9/_geo_xyz/2,12,0 6.561679840 P _part/9/_geo_xyz/2,12,1 -0.082795277 P _part/9/_geo_xyz/2,12,2 -0.747768998 P _part/9/_geo_xyz/2,13,0 6.561679840 P _part/9/_geo_xyz/2,13,1 0.0 P _part/9/_geo_xyz/2,13,2 -0.820209980 P _part/9/_geo_xyz/2,14,0 0.0 P _part/9/_geo_xyz/2,14,1 0.0 P _part/9/_geo_xyz/2,14,2 0.0 P _part/9/_geo_xyz/2,15,0 0.0 P _part/9/_geo_xyz/2,15,1 0.0 P _part/9/_geo_xyz/2,15,2 0.0 P _part/9/_geo_xyz/2,16,0 0.0 P _part/9/_geo_xyz/2,16,1 0.0 P _part/9/_geo_xyz/2,16,2 0.0 P _part/9/_geo_xyz/2,17,0 0.0 P _part/9/_geo_xyz/2,17,1 0.0 P _part/9/_geo_xyz/2,17,2 0.0 P _part/9/_geo_xyz/2,2,0 6.561679840 P _part/9/_geo_xyz/2,2,1 0.227692902 P _part/9/_geo_xyz/2,2,2 -0.468307078 P _part/9/_geo_xyz/2,3,0 6.561679840 P _part/9/_geo_xyz/2,3,1 0.263917327 P _part/9/_geo_xyz/2,3,2 -0.018110236 P _part/9/_geo_xyz/2,4,0 6.561679840 P _part/9/_geo_xyz/2,4,1 0.238043293 P _part/9/_geo_xyz/2,4,2 0.628740191 P _part/9/_geo_xyz/2,5,0 6.561679840 P _part/9/_geo_xyz/2,5,1 0.106581457 P _part/9/_geo_xyz/2,5,2 1.640419960 P _part/9/_geo_xyz/2,6,0 6.561679840 P _part/9/_geo_xyz/2,6,1 0.0 P _part/9/_geo_xyz/2,6,2 2.460629940 P _part/9/_geo_xyz/2,7,0 6.561679840 P _part/9/_geo_xyz/2,7,1 0.0 P _part/9/_geo_xyz/2,7,2 2.460629940 P _part/9/_geo_xyz/2,8,0 6.561679840 P _part/9/_geo_xyz/2,8,1 -0.046339307 P _part/9/_geo_xyz/2,8,2 1.640419960 P _part/9/_geo_xyz/2,9,0 6.561679840 P _part/9/_geo_xyz/2,9,1 -0.103496060 P _part/9/_geo_xyz/2,9,2 0.628740191 P _part/9/_geo_xyz/3,0,0 13.123359680 P _part/9/_geo_xyz/3,0,1 0.0 P _part/9/_geo_xyz/3,0,2 -0.820209980 P _part/9/_geo_xyz/3,1,0 13.123359680 P _part/9/_geo_xyz/3,1,1 0.139720470 P _part/9/_geo_xyz/3,1,2 -0.716699481 P _part/9/_geo_xyz/3,10,0 13.123359680 P _part/9/_geo_xyz/3,10,1 -0.134547234 P _part/9/_geo_xyz/3,10,2 -0.069849081 P _part/9/_geo_xyz/3,11,0 13.123359680 P _part/9/_geo_xyz/3,11,1 -0.113846451 P _part/9/_geo_xyz/3,11,2 -0.597703397 P _part/9/_geo_xyz/3,12,0 13.123359680 P _part/9/_geo_xyz/3,12,1 -0.082795277 P _part/9/_geo_xyz/3,12,2 -0.747768998 P _part/9/_geo_xyz/3,13,0 13.123359680 P _part/9/_geo_xyz/3,13,1 0.0 P _part/9/_geo_xyz/3,13,2 -0.820209980 P _part/9/_geo_xyz/3,14,0 0.0 P _part/9/_geo_xyz/3,14,1 0.0 P _part/9/_geo_xyz/3,14,2 0.0 P _part/9/_geo_xyz/3,15,0 0.0 P _part/9/_geo_xyz/3,15,1 0.0 P _part/9/_geo_xyz/3,15,2 0.0 P _part/9/_geo_xyz/3,16,0 0.0 P _part/9/_geo_xyz/3,16,1 0.0 P _part/9/_geo_xyz/3,16,2 0.0 P _part/9/_geo_xyz/3,17,0 0.0 P _part/9/_geo_xyz/3,17,1 0.0 P _part/9/_geo_xyz/3,17,2 0.0 P _part/9/_geo_xyz/3,2,0 13.123359680 P _part/9/_geo_xyz/3,2,1 0.227692902 P _part/9/_geo_xyz/3,2,2 -0.468307078 P _part/9/_geo_xyz/3,3,0 13.123359680 P _part/9/_geo_xyz/3,3,1 0.263917327 P _part/9/_geo_xyz/3,3,2 -0.018110236 P _part/9/_geo_xyz/3,4,0 13.123359680 P _part/9/_geo_xyz/3,4,1 0.238043293 P _part/9/_geo_xyz/3,4,2 0.628740191 P _part/9/_geo_xyz/3,5,0 13.123359680 P _part/9/_geo_xyz/3,5,1 0.106581457 P _part/9/_geo_xyz/3,5,2 1.640419960 P _part/9/_geo_xyz/3,6,0 13.123359680 P _part/9/_geo_xyz/3,6,1 0.0 P _part/9/_geo_xyz/3,6,2 2.460629940 P _part/9/_geo_xyz/3,7,0 13.123359680 P _part/9/_geo_xyz/3,7,1 0.0 P _part/9/_geo_xyz/3,7,2 2.460629940 P _part/9/_geo_xyz/3,8,0 13.123359680 P _part/9/_geo_xyz/3,8,1 -0.046339307 P _part/9/_geo_xyz/3,8,2 1.640419960 P _part/9/_geo_xyz/3,9,0 13.123359680 P _part/9/_geo_xyz/3,9,1 -0.103496060 P _part/9/_geo_xyz/3,9,2 0.628740191 P _part/9/_geo_xyz/4,0,0 13.123359680 P _part/9/_geo_xyz/4,0,1 0.0 P _part/9/_geo_xyz/4,0,2 -0.820209980 P _part/9/_geo_xyz/4,1,0 13.123359680 P _part/9/_geo_xyz/4,1,1 0.139720470 P _part/9/_geo_xyz/4,1,2 -0.716699481 P _part/9/_geo_xyz/4,10,0 13.123359680 P _part/9/_geo_xyz/4,10,1 -0.134547234 P _part/9/_geo_xyz/4,10,2 -0.069849081 P _part/9/_geo_xyz/4,11,0 13.123359680 P _part/9/_geo_xyz/4,11,1 -0.113846451 P _part/9/_geo_xyz/4,11,2 -0.597703397 P _part/9/_geo_xyz/4,12,0 13.123359680 P _part/9/_geo_xyz/4,12,1 -0.082795277 P _part/9/_geo_xyz/4,12,2 -0.747768998 P _part/9/_geo_xyz/4,13,0 13.123359680 P _part/9/_geo_xyz/4,13,1 0.0 P _part/9/_geo_xyz/4,13,2 -0.820209980 P _part/9/_geo_xyz/4,14,0 0.0 P _part/9/_geo_xyz/4,14,1 0.0 P _part/9/_geo_xyz/4,14,2 0.0 P _part/9/_geo_xyz/4,15,0 0.0 P _part/9/_geo_xyz/4,15,1 0.0 P _part/9/_geo_xyz/4,15,2 0.0 P _part/9/_geo_xyz/4,16,0 0.0 P _part/9/_geo_xyz/4,16,1 0.0 P _part/9/_geo_xyz/4,16,2 0.0 P _part/9/_geo_xyz/4,17,0 0.0 P _part/9/_geo_xyz/4,17,1 0.0 P _part/9/_geo_xyz/4,17,2 0.0 P _part/9/_geo_xyz/4,2,0 13.123359680 P _part/9/_geo_xyz/4,2,1 0.227692902 P _part/9/_geo_xyz/4,2,2 -0.468307078 P _part/9/_geo_xyz/4,3,0 13.123359680 P _part/9/_geo_xyz/4,3,1 0.263917327 P _part/9/_geo_xyz/4,3,2 -0.018110236 P _part/9/_geo_xyz/4,4,0 13.123359680 P _part/9/_geo_xyz/4,4,1 0.238043293 P _part/9/_geo_xyz/4,4,2 0.628740191 P _part/9/_geo_xyz/4,5,0 13.123359680 P _part/9/_geo_xyz/4,5,1 0.106581457 P _part/9/_geo_xyz/4,5,2 1.640419960 P _part/9/_geo_xyz/4,6,0 13.123359680 P _part/9/_geo_xyz/4,6,1 0.0 P _part/9/_geo_xyz/4,6,2 2.460629940 P _part/9/_geo_xyz/4,7,0 13.123359680 P _part/9/_geo_xyz/4,7,1 0.0 P _part/9/_geo_xyz/4,7,2 2.460629940 P _part/9/_geo_xyz/4,8,0 13.123359680 P _part/9/_geo_xyz/4,8,1 -0.046339307 P _part/9/_geo_xyz/4,8,2 1.640419960 P _part/9/_geo_xyz/4,9,0 13.123359680 P _part/9/_geo_xyz/4,9,1 -0.103496060 P _part/9/_geo_xyz/4,9,2 0.628740191 P _part/9/_geo_xyz/5,0,0 19.685039520 P _part/9/_geo_xyz/5,0,1 0.0 P _part/9/_geo_xyz/5,0,2 -0.820209980 P _part/9/_geo_xyz/5,1,0 19.685039520 P _part/9/_geo_xyz/5,1,1 0.139720470 P _part/9/_geo_xyz/5,1,2 -0.716699481 P _part/9/_geo_xyz/5,10,0 19.685039520 P _part/9/_geo_xyz/5,10,1 -0.134547234 P _part/9/_geo_xyz/5,10,2 -0.069849081 P _part/9/_geo_xyz/5,11,0 19.685039520 P _part/9/_geo_xyz/5,11,1 -0.113846451 P _part/9/_geo_xyz/5,11,2 -0.597703397 P _part/9/_geo_xyz/5,12,0 19.685039520 P _part/9/_geo_xyz/5,12,1 -0.082795277 P _part/9/_geo_xyz/5,12,2 -0.747768998 P _part/9/_geo_xyz/5,13,0 19.685039520 P _part/9/_geo_xyz/5,13,1 0.0 P _part/9/_geo_xyz/5,13,2 -0.820209980 P _part/9/_geo_xyz/5,14,0 0.0 P _part/9/_geo_xyz/5,14,1 0.0 P _part/9/_geo_xyz/5,14,2 0.0 P _part/9/_geo_xyz/5,15,0 0.0 P _part/9/_geo_xyz/5,15,1 0.0 P _part/9/_geo_xyz/5,15,2 0.0 P _part/9/_geo_xyz/5,16,0 0.0 P _part/9/_geo_xyz/5,16,1 0.0 P _part/9/_geo_xyz/5,16,2 0.0 P _part/9/_geo_xyz/5,17,0 0.0 P _part/9/_geo_xyz/5,17,1 0.0 P _part/9/_geo_xyz/5,17,2 0.0 P _part/9/_geo_xyz/5,2,0 19.685039520 P _part/9/_geo_xyz/5,2,1 0.227692902 P _part/9/_geo_xyz/5,2,2 -0.468307078 P _part/9/_geo_xyz/5,3,0 19.685039520 P _part/9/_geo_xyz/5,3,1 0.263917327 P _part/9/_geo_xyz/5,3,2 -0.018110236 P _part/9/_geo_xyz/5,4,0 19.685039520 P _part/9/_geo_xyz/5,4,1 0.238043293 P _part/9/_geo_xyz/5,4,2 0.628740191 P _part/9/_geo_xyz/5,5,0 19.685039520 P _part/9/_geo_xyz/5,5,1 0.106581457 P _part/9/_geo_xyz/5,5,2 1.640419960 P _part/9/_geo_xyz/5,6,0 19.685039520 P _part/9/_geo_xyz/5,6,1 0.0 P _part/9/_geo_xyz/5,6,2 2.460629940 P _part/9/_geo_xyz/5,7,0 19.685039520 P _part/9/_geo_xyz/5,7,1 0.0 P _part/9/_geo_xyz/5,7,2 2.460629940 P _part/9/_geo_xyz/5,8,0 19.685039520 P _part/9/_geo_xyz/5,8,1 -0.046339307 P _part/9/_geo_xyz/5,8,2 1.640419960 P _part/9/_geo_xyz/5,9,0 19.685039520 P _part/9/_geo_xyz/5,9,1 -0.103496060 P _part/9/_geo_xyz/5,9,2 0.628740191 P _part/9/_geo_xyz/6,0,0 19.685039520 P _part/9/_geo_xyz/6,0,1 0.0 P _part/9/_geo_xyz/6,0,2 -0.820209980 P _part/9/_geo_xyz/6,1,0 19.685039520 P _part/9/_geo_xyz/6,1,1 0.139720470 P _part/9/_geo_xyz/6,1,2 -0.716699481 P _part/9/_geo_xyz/6,10,0 19.685039520 P _part/9/_geo_xyz/6,10,1 -0.134547234 P _part/9/_geo_xyz/6,10,2 -0.069849081 P _part/9/_geo_xyz/6,11,0 19.685039520 P _part/9/_geo_xyz/6,11,1 -0.113846451 P _part/9/_geo_xyz/6,11,2 -0.597703397 P _part/9/_geo_xyz/6,12,0 19.685039520 P _part/9/_geo_xyz/6,12,1 -0.082795277 P _part/9/_geo_xyz/6,12,2 -0.747768998 P _part/9/_geo_xyz/6,13,0 19.685039520 P _part/9/_geo_xyz/6,13,1 0.0 P _part/9/_geo_xyz/6,13,2 -0.820209980 P _part/9/_geo_xyz/6,14,0 0.0 P _part/9/_geo_xyz/6,14,1 0.0 P _part/9/_geo_xyz/6,14,2 0.0 P _part/9/_geo_xyz/6,15,0 0.0 P _part/9/_geo_xyz/6,15,1 0.0 P _part/9/_geo_xyz/6,15,2 0.0 P _part/9/_geo_xyz/6,16,0 0.0 P _part/9/_geo_xyz/6,16,1 0.0 P _part/9/_geo_xyz/6,16,2 0.0 P _part/9/_geo_xyz/6,17,0 0.0 P _part/9/_geo_xyz/6,17,1 0.0 P _part/9/_geo_xyz/6,17,2 0.0 P _part/9/_geo_xyz/6,2,0 19.685039520 P _part/9/_geo_xyz/6,2,1 0.227692902 P _part/9/_geo_xyz/6,2,2 -0.468307078 P _part/9/_geo_xyz/6,3,0 19.685039520 P _part/9/_geo_xyz/6,3,1 0.263917327 P _part/9/_geo_xyz/6,3,2 -0.018110236 P _part/9/_geo_xyz/6,4,0 19.685039520 P _part/9/_geo_xyz/6,4,1 0.238043293 P _part/9/_geo_xyz/6,4,2 0.628740191 P _part/9/_geo_xyz/6,5,0 19.685039520 P _part/9/_geo_xyz/6,5,1 0.106581457 P _part/9/_geo_xyz/6,5,2 1.640419960 P _part/9/_geo_xyz/6,6,0 19.685039520 P _part/9/_geo_xyz/6,6,1 0.0 P _part/9/_geo_xyz/6,6,2 2.460629940 P _part/9/_geo_xyz/6,7,0 19.685039520 P _part/9/_geo_xyz/6,7,1 0.0 P _part/9/_geo_xyz/6,7,2 2.460629940 P _part/9/_geo_xyz/6,8,0 19.685039520 P _part/9/_geo_xyz/6,8,1 -0.046339307 P _part/9/_geo_xyz/6,8,2 1.640419960 P _part/9/_geo_xyz/6,9,0 19.685039520 P _part/9/_geo_xyz/6,9,1 -0.103496060 P _part/9/_geo_xyz/6,9,2 0.628740191 P _part/9/_geo_xyz/7,0,0 26.246719360 P _part/9/_geo_xyz/7,0,1 0.0 P _part/9/_geo_xyz/7,0,2 -0.820209980 P _part/9/_geo_xyz/7,1,0 26.246719360 P _part/9/_geo_xyz/7,1,1 0.139720470 P _part/9/_geo_xyz/7,1,2 -0.716699481 P _part/9/_geo_xyz/7,10,0 26.246719360 P _part/9/_geo_xyz/7,10,1 -0.134547234 P _part/9/_geo_xyz/7,10,2 -0.069849081 P _part/9/_geo_xyz/7,11,0 26.246719360 P _part/9/_geo_xyz/7,11,1 -0.113846451 P _part/9/_geo_xyz/7,11,2 -0.597703397 P _part/9/_geo_xyz/7,12,0 26.246719360 P _part/9/_geo_xyz/7,12,1 -0.082795277 P _part/9/_geo_xyz/7,12,2 -0.747768998 P _part/9/_geo_xyz/7,13,0 26.246719360 P _part/9/_geo_xyz/7,13,1 0.0 P _part/9/_geo_xyz/7,13,2 -0.820209980 P _part/9/_geo_xyz/7,14,0 0.0 P _part/9/_geo_xyz/7,14,1 0.0 P _part/9/_geo_xyz/7,14,2 0.0 P _part/9/_geo_xyz/7,15,0 0.0 P _part/9/_geo_xyz/7,15,1 0.0 P _part/9/_geo_xyz/7,15,2 0.0 P _part/9/_geo_xyz/7,16,0 0.0 P _part/9/_geo_xyz/7,16,1 0.0 P _part/9/_geo_xyz/7,16,2 0.0 P _part/9/_geo_xyz/7,17,0 0.0 P _part/9/_geo_xyz/7,17,1 0.0 P _part/9/_geo_xyz/7,17,2 0.0 P _part/9/_geo_xyz/7,2,0 26.246719360 P _part/9/_geo_xyz/7,2,1 0.227692902 P _part/9/_geo_xyz/7,2,2 -0.468307078 P _part/9/_geo_xyz/7,3,0 26.246719360 P _part/9/_geo_xyz/7,3,1 0.263917327 P _part/9/_geo_xyz/7,3,2 -0.018110236 P _part/9/_geo_xyz/7,4,0 26.246719360 P _part/9/_geo_xyz/7,4,1 0.238043293 P _part/9/_geo_xyz/7,4,2 0.628740191 P _part/9/_geo_xyz/7,5,0 26.246719360 P _part/9/_geo_xyz/7,5,1 0.106581457 P _part/9/_geo_xyz/7,5,2 1.640419960 P _part/9/_geo_xyz/7,6,0 26.246719360 P _part/9/_geo_xyz/7,6,1 0.0 P _part/9/_geo_xyz/7,6,2 2.460629940 P _part/9/_geo_xyz/7,7,0 26.246719360 P _part/9/_geo_xyz/7,7,1 0.0 P _part/9/_geo_xyz/7,7,2 2.460629940 P _part/9/_geo_xyz/7,8,0 26.246719360 P _part/9/_geo_xyz/7,8,1 -0.046339307 P _part/9/_geo_xyz/7,8,2 1.640419960 P _part/9/_geo_xyz/7,9,0 26.246719360 P _part/9/_geo_xyz/7,9,1 -0.103496060 P _part/9/_geo_xyz/7,9,2 0.628740191 P _part/9/_geo_xyz/8,0,0 26.246719360 P _part/9/_geo_xyz/8,0,1 0.0 P _part/9/_geo_xyz/8,0,2 -0.820209980 P _part/9/_geo_xyz/8,1,0 26.246719360 P _part/9/_geo_xyz/8,1,1 0.139720470 P _part/9/_geo_xyz/8,1,2 -0.716699481 P _part/9/_geo_xyz/8,10,0 26.246719360 P _part/9/_geo_xyz/8,10,1 -0.134547234 P _part/9/_geo_xyz/8,10,2 -0.069849081 P _part/9/_geo_xyz/8,11,0 26.246719360 P _part/9/_geo_xyz/8,11,1 -0.113846451 P _part/9/_geo_xyz/8,11,2 -0.597703397 P _part/9/_geo_xyz/8,12,0 26.246719360 P _part/9/_geo_xyz/8,12,1 -0.082795277 P _part/9/_geo_xyz/8,12,2 -0.747768998 P _part/9/_geo_xyz/8,13,0 26.246719360 P _part/9/_geo_xyz/8,13,1 0.0 P _part/9/_geo_xyz/8,13,2 -0.820209980 P _part/9/_geo_xyz/8,14,0 0.0 P _part/9/_geo_xyz/8,14,1 0.0 P _part/9/_geo_xyz/8,14,2 0.0 P _part/9/_geo_xyz/8,15,0 0.0 P _part/9/_geo_xyz/8,15,1 0.0 P _part/9/_geo_xyz/8,15,2 0.0 P _part/9/_geo_xyz/8,16,0 0.0 P _part/9/_geo_xyz/8,16,1 0.0 P _part/9/_geo_xyz/8,16,2 0.0 P _part/9/_geo_xyz/8,17,0 0.0 P _part/9/_geo_xyz/8,17,1 0.0 P _part/9/_geo_xyz/8,17,2 0.0 P _part/9/_geo_xyz/8,2,0 26.246719360 P _part/9/_geo_xyz/8,2,1 0.227692902 P _part/9/_geo_xyz/8,2,2 -0.468307078 P _part/9/_geo_xyz/8,3,0 26.246719360 P _part/9/_geo_xyz/8,3,1 0.263917327 P _part/9/_geo_xyz/8,3,2 -0.018110236 P _part/9/_geo_xyz/8,4,0 26.246719360 P _part/9/_geo_xyz/8,4,1 0.238043293 P _part/9/_geo_xyz/8,4,2 0.628740191 P _part/9/_geo_xyz/8,5,0 26.246719360 P _part/9/_geo_xyz/8,5,1 0.106581457 P _part/9/_geo_xyz/8,5,2 1.640419960 P _part/9/_geo_xyz/8,6,0 26.246719360 P _part/9/_geo_xyz/8,6,1 0.0 P _part/9/_geo_xyz/8,6,2 2.460629940 P _part/9/_geo_xyz/8,7,0 26.246719360 P _part/9/_geo_xyz/8,7,1 0.0 P _part/9/_geo_xyz/8,7,2 2.460629940 P _part/9/_geo_xyz/8,8,0 26.246719360 P _part/9/_geo_xyz/8,8,1 -0.046339307 P _part/9/_geo_xyz/8,8,2 1.640419960 P _part/9/_geo_xyz/8,9,0 26.246719360 P _part/9/_geo_xyz/8,9,1 -0.103496060 P _part/9/_geo_xyz/8,9,2 0.628740191 P _part/9/_geo_xyz/9,0,0 32.808399200 P _part/9/_geo_xyz/9,0,1 0.0 P _part/9/_geo_xyz/9,0,2 -0.820209980 P _part/9/_geo_xyz/9,1,0 32.808399200 P _part/9/_geo_xyz/9,1,1 0.139720470 P _part/9/_geo_xyz/9,1,2 -0.716699481 P _part/9/_geo_xyz/9,10,0 32.808399200 P _part/9/_geo_xyz/9,10,1 -0.134547234 P _part/9/_geo_xyz/9,10,2 -0.069849081 P _part/9/_geo_xyz/9,11,0 32.808399200 P _part/9/_geo_xyz/9,11,1 -0.113846451 P _part/9/_geo_xyz/9,11,2 -0.597703397 P _part/9/_geo_xyz/9,12,0 32.808399200 P _part/9/_geo_xyz/9,12,1 -0.082795277 P _part/9/_geo_xyz/9,12,2 -0.747768998 P _part/9/_geo_xyz/9,13,0 32.808399200 P _part/9/_geo_xyz/9,13,1 0.0 P _part/9/_geo_xyz/9,13,2 -0.820209980 P _part/9/_geo_xyz/9,14,0 0.0 P _part/9/_geo_xyz/9,14,1 0.0 P _part/9/_geo_xyz/9,14,2 0.0 P _part/9/_geo_xyz/9,15,0 0.0 P _part/9/_geo_xyz/9,15,1 0.0 P _part/9/_geo_xyz/9,15,2 0.0 P _part/9/_geo_xyz/9,16,0 0.0 P _part/9/_geo_xyz/9,16,1 0.0 P _part/9/_geo_xyz/9,16,2 0.0 P _part/9/_geo_xyz/9,17,0 0.0 P _part/9/_geo_xyz/9,17,1 0.0 P _part/9/_geo_xyz/9,17,2 0.0 P _part/9/_geo_xyz/9,2,0 32.808399200 P _part/9/_geo_xyz/9,2,1 0.227692902 P _part/9/_geo_xyz/9,2,2 -0.468307078 P _part/9/_geo_xyz/9,3,0 32.808399200 P _part/9/_geo_xyz/9,3,1 0.263917327 P _part/9/_geo_xyz/9,3,2 -0.018110236 P _part/9/_geo_xyz/9,4,0 32.808399200 P _part/9/_geo_xyz/9,4,1 0.238043293 P _part/9/_geo_xyz/9,4,2 0.628740191 P _part/9/_geo_xyz/9,5,0 32.808399200 P _part/9/_geo_xyz/9,5,1 0.106581457 P _part/9/_geo_xyz/9,5,2 1.640419960 P _part/9/_geo_xyz/9,6,0 32.808399200 P _part/9/_geo_xyz/9,6,1 0.0 P _part/9/_geo_xyz/9,6,2 2.460629940 P _part/9/_geo_xyz/9,7,0 32.808399200 P _part/9/_geo_xyz/9,7,1 0.0 P _part/9/_geo_xyz/9,7,2 2.460629940 P _part/9/_geo_xyz/9,8,0 32.808399200 P _part/9/_geo_xyz/9,8,1 -0.046339307 P _part/9/_geo_xyz/9,8,2 1.640419960 P _part/9/_geo_xyz/9,9,0 32.808399200 P _part/9/_geo_xyz/9,9,1 -0.103496060 P _part/9/_geo_xyz/9,9,2 0.628740191 P _part/9/_geo_xyz/i_count 20 P _part/9/_geo_xyz/j_count 18 P _part/9/_geo_xyz/k_count 3 P _part/9/_locked/0,0 0 P _part/9/_locked/0,1 0 P _part/9/_locked/0,10 0 P _part/9/_locked/0,11 0 P _part/9/_locked/0,12 0 P _part/9/_locked/0,13 0 P _part/9/_locked/0,14 0 P _part/9/_locked/0,15 0 P _part/9/_locked/0,16 0 P _part/9/_locked/0,17 0 P _part/9/_locked/0,2 0 P _part/9/_locked/0,3 0 P _part/9/_locked/0,4 0 P _part/9/_locked/0,5 0 P _part/9/_locked/0,6 0 P _part/9/_locked/0,7 0 P _part/9/_locked/0,8 0 P _part/9/_locked/0,9 0 P _part/9/_locked/1,0 0 P _part/9/_locked/1,1 0 P _part/9/_locked/1,10 0 P _part/9/_locked/1,11 0 P _part/9/_locked/1,12 0 P _part/9/_locked/1,13 0 P _part/9/_locked/1,14 0 P _part/9/_locked/1,15 0 P _part/9/_locked/1,16 0 P _part/9/_locked/1,17 0 P _part/9/_locked/1,2 0 P _part/9/_locked/1,3 0 P _part/9/_locked/1,4 0 P _part/9/_locked/1,5 0 P _part/9/_locked/1,6 0 P _part/9/_locked/1,7 0 P _part/9/_locked/1,8 0 P _part/9/_locked/1,9 0 P _part/9/_locked/10,0 0 P _part/9/_locked/10,1 0 P _part/9/_locked/10,10 0 P _part/9/_locked/10,11 0 P _part/9/_locked/10,12 0 P _part/9/_locked/10,13 0 P _part/9/_locked/10,14 0 P _part/9/_locked/10,15 0 P _part/9/_locked/10,16 0 P _part/9/_locked/10,17 0 P _part/9/_locked/10,2 0 P _part/9/_locked/10,3 0 P _part/9/_locked/10,4 0 P _part/9/_locked/10,5 0 P _part/9/_locked/10,6 0 P _part/9/_locked/10,7 0 P _part/9/_locked/10,8 0 P _part/9/_locked/10,9 0 P _part/9/_locked/11,0 0 P _part/9/_locked/11,1 0 P _part/9/_locked/11,10 0 P _part/9/_locked/11,11 0 P _part/9/_locked/11,12 0 P _part/9/_locked/11,13 0 P _part/9/_locked/11,14 0 P _part/9/_locked/11,15 0 P _part/9/_locked/11,16 0 P _part/9/_locked/11,17 0 P _part/9/_locked/11,2 0 P _part/9/_locked/11,3 0 P _part/9/_locked/11,4 0 P _part/9/_locked/11,5 0 P _part/9/_locked/11,6 0 P _part/9/_locked/11,7 0 P _part/9/_locked/11,8 0 P _part/9/_locked/11,9 0 P _part/9/_locked/12,0 0 P _part/9/_locked/12,1 0 P _part/9/_locked/12,10 0 P _part/9/_locked/12,11 0 P _part/9/_locked/12,12 0 P _part/9/_locked/12,13 0 P _part/9/_locked/12,14 0 P _part/9/_locked/12,15 0 P _part/9/_locked/12,16 0 P _part/9/_locked/12,17 0 P _part/9/_locked/12,2 0 P _part/9/_locked/12,3 0 P _part/9/_locked/12,4 0 P _part/9/_locked/12,5 0 P _part/9/_locked/12,6 0 P _part/9/_locked/12,7 0 P _part/9/_locked/12,8 0 P _part/9/_locked/12,9 0 P _part/9/_locked/13,0 0 P _part/9/_locked/13,1 0 P _part/9/_locked/13,10 0 P _part/9/_locked/13,11 0 P _part/9/_locked/13,12 0 P _part/9/_locked/13,13 0 P _part/9/_locked/13,14 0 P _part/9/_locked/13,15 0 P _part/9/_locked/13,16 0 P _part/9/_locked/13,17 0 P _part/9/_locked/13,2 0 P _part/9/_locked/13,3 0 P _part/9/_locked/13,4 0 P _part/9/_locked/13,5 0 P _part/9/_locked/13,6 0 P _part/9/_locked/13,7 0 P _part/9/_locked/13,8 0 P _part/9/_locked/13,9 0 P _part/9/_locked/14,0 0 P _part/9/_locked/14,1 0 P _part/9/_locked/14,10 0 P _part/9/_locked/14,11 0 P _part/9/_locked/14,12 0 P _part/9/_locked/14,13 0 P _part/9/_locked/14,14 0 P _part/9/_locked/14,15 0 P _part/9/_locked/14,16 0 P _part/9/_locked/14,17 0 P _part/9/_locked/14,2 0 P _part/9/_locked/14,3 0 P _part/9/_locked/14,4 0 P _part/9/_locked/14,5 0 P _part/9/_locked/14,6 0 P _part/9/_locked/14,7 0 P _part/9/_locked/14,8 0 P _part/9/_locked/14,9 0 P _part/9/_locked/15,0 0 P _part/9/_locked/15,1 0 P _part/9/_locked/15,10 0 P _part/9/_locked/15,11 0 P _part/9/_locked/15,12 0 P _part/9/_locked/15,13 0 P _part/9/_locked/15,14 0 P _part/9/_locked/15,15 0 P _part/9/_locked/15,16 0 P _part/9/_locked/15,17 0 P _part/9/_locked/15,2 0 P _part/9/_locked/15,3 0 P _part/9/_locked/15,4 0 P _part/9/_locked/15,5 0 P _part/9/_locked/15,6 0 P _part/9/_locked/15,7 0 P _part/9/_locked/15,8 0 P _part/9/_locked/15,9 0 P _part/9/_locked/16,0 0 P _part/9/_locked/16,1 0 P _part/9/_locked/16,10 0 P _part/9/_locked/16,11 0 P _part/9/_locked/16,12 0 P _part/9/_locked/16,13 0 P _part/9/_locked/16,14 0 P _part/9/_locked/16,15 0 P _part/9/_locked/16,16 0 P _part/9/_locked/16,17 0 P _part/9/_locked/16,2 0 P _part/9/_locked/16,3 0 P _part/9/_locked/16,4 0 P _part/9/_locked/16,5 0 P _part/9/_locked/16,6 0 P _part/9/_locked/16,7 0 P _part/9/_locked/16,8 0 P _part/9/_locked/16,9 0 P _part/9/_locked/17,0 0 P _part/9/_locked/17,1 0 P _part/9/_locked/17,10 0 P _part/9/_locked/17,11 0 P _part/9/_locked/17,12 0 P _part/9/_locked/17,13 0 P _part/9/_locked/17,14 0 P _part/9/_locked/17,15 0 P _part/9/_locked/17,16 0 P _part/9/_locked/17,17 0 P _part/9/_locked/17,2 0 P _part/9/_locked/17,3 0 P _part/9/_locked/17,4 0 P _part/9/_locked/17,5 0 P _part/9/_locked/17,6 0 P _part/9/_locked/17,7 0 P _part/9/_locked/17,8 0 P _part/9/_locked/17,9 0 P _part/9/_locked/18,0 0 P _part/9/_locked/18,1 0 P _part/9/_locked/18,10 0 P _part/9/_locked/18,11 0 P _part/9/_locked/18,12 0 P _part/9/_locked/18,13 0 P _part/9/_locked/18,14 0 P _part/9/_locked/18,15 0 P _part/9/_locked/18,16 0 P _part/9/_locked/18,17 0 P _part/9/_locked/18,2 0 P _part/9/_locked/18,3 0 P _part/9/_locked/18,4 0 P _part/9/_locked/18,5 0 P _part/9/_locked/18,6 0 P _part/9/_locked/18,7 0 P _part/9/_locked/18,8 0 P _part/9/_locked/18,9 0 P _part/9/_locked/19,0 0 P _part/9/_locked/19,1 0 P _part/9/_locked/19,10 0 P _part/9/_locked/19,11 0 P _part/9/_locked/19,12 0 P _part/9/_locked/19,13 0 P _part/9/_locked/19,14 0 P _part/9/_locked/19,15 0 P _part/9/_locked/19,16 0 P _part/9/_locked/19,17 0 P _part/9/_locked/19,2 0 P _part/9/_locked/19,3 0 P _part/9/_locked/19,4 0 P _part/9/_locked/19,5 0 P _part/9/_locked/19,6 0 P _part/9/_locked/19,7 0 P _part/9/_locked/19,8 0 P _part/9/_locked/19,9 0 P _part/9/_locked/2,0 0 P _part/9/_locked/2,1 0 P _part/9/_locked/2,10 0 P _part/9/_locked/2,11 0 P _part/9/_locked/2,12 0 P _part/9/_locked/2,13 0 P _part/9/_locked/2,14 0 P _part/9/_locked/2,15 0 P _part/9/_locked/2,16 0 P _part/9/_locked/2,17 0 P _part/9/_locked/2,2 0 P _part/9/_locked/2,3 0 P _part/9/_locked/2,4 0 P _part/9/_locked/2,5 0 P _part/9/_locked/2,6 0 P _part/9/_locked/2,7 0 P _part/9/_locked/2,8 0 P _part/9/_locked/2,9 0 P _part/9/_locked/3,0 0 P _part/9/_locked/3,1 0 P _part/9/_locked/3,10 0 P _part/9/_locked/3,11 0 P _part/9/_locked/3,12 0 P _part/9/_locked/3,13 0 P _part/9/_locked/3,14 0 P _part/9/_locked/3,15 0 P _part/9/_locked/3,16 0 P _part/9/_locked/3,17 0 P _part/9/_locked/3,2 0 P _part/9/_locked/3,3 0 P _part/9/_locked/3,4 0 P _part/9/_locked/3,5 0 P _part/9/_locked/3,6 0 P _part/9/_locked/3,7 0 P _part/9/_locked/3,8 0 P _part/9/_locked/3,9 0 P _part/9/_locked/4,0 0 P _part/9/_locked/4,1 0 P _part/9/_locked/4,10 0 P _part/9/_locked/4,11 0 P _part/9/_locked/4,12 0 P _part/9/_locked/4,13 0 P _part/9/_locked/4,14 0 P _part/9/_locked/4,15 0 P _part/9/_locked/4,16 0 P _part/9/_locked/4,17 0 P _part/9/_locked/4,2 0 P _part/9/_locked/4,3 0 P _part/9/_locked/4,4 0 P _part/9/_locked/4,5 0 P _part/9/_locked/4,6 0 P _part/9/_locked/4,7 0 P _part/9/_locked/4,8 0 P _part/9/_locked/4,9 0 P _part/9/_locked/5,0 0 P _part/9/_locked/5,1 0 P _part/9/_locked/5,10 0 P _part/9/_locked/5,11 0 P _part/9/_locked/5,12 0 P _part/9/_locked/5,13 0 P _part/9/_locked/5,14 0 P _part/9/_locked/5,15 0 P _part/9/_locked/5,16 0 P _part/9/_locked/5,17 0 P _part/9/_locked/5,2 0 P _part/9/_locked/5,3 0 P _part/9/_locked/5,4 0 P _part/9/_locked/5,5 0 P _part/9/_locked/5,6 0 P _part/9/_locked/5,7 0 P _part/9/_locked/5,8 0 P _part/9/_locked/5,9 0 P _part/9/_locked/6,0 0 P _part/9/_locked/6,1 0 P _part/9/_locked/6,10 0 P _part/9/_locked/6,11 0 P _part/9/_locked/6,12 0 P _part/9/_locked/6,13 0 P _part/9/_locked/6,14 0 P _part/9/_locked/6,15 0 P _part/9/_locked/6,16 0 P _part/9/_locked/6,17 0 P _part/9/_locked/6,2 0 P _part/9/_locked/6,3 0 P _part/9/_locked/6,4 0 P _part/9/_locked/6,5 0 P _part/9/_locked/6,6 0 P _part/9/_locked/6,7 0 P _part/9/_locked/6,8 0 P _part/9/_locked/6,9 0 P _part/9/_locked/7,0 0 P _part/9/_locked/7,1 0 P _part/9/_locked/7,10 0 P _part/9/_locked/7,11 0 P _part/9/_locked/7,12 0 P _part/9/_locked/7,13 0 P _part/9/_locked/7,14 0 P _part/9/_locked/7,15 0 P _part/9/_locked/7,16 0 P _part/9/_locked/7,17 0 P _part/9/_locked/7,2 0 P _part/9/_locked/7,3 0 P _part/9/_locked/7,4 0 P _part/9/_locked/7,5 0 P _part/9/_locked/7,6 0 P _part/9/_locked/7,7 0 P _part/9/_locked/7,8 0 P _part/9/_locked/7,9 0 P _part/9/_locked/8,0 0 P _part/9/_locked/8,1 0 P _part/9/_locked/8,10 0 P _part/9/_locked/8,11 0 P _part/9/_locked/8,12 0 P _part/9/_locked/8,13 0 P _part/9/_locked/8,14 0 P _part/9/_locked/8,15 0 P _part/9/_locked/8,16 0 P _part/9/_locked/8,17 0 P _part/9/_locked/8,2 0 P _part/9/_locked/8,3 0 P _part/9/_locked/8,4 0 P _part/9/_locked/8,5 0 P _part/9/_locked/8,6 0 P _part/9/_locked/8,7 0 P _part/9/_locked/8,8 0 P _part/9/_locked/8,9 0 P _part/9/_locked/9,0 0 P _part/9/_locked/9,1 0 P _part/9/_locked/9,10 0 P _part/9/_locked/9,11 0 P _part/9/_locked/9,12 0 P _part/9/_locked/9,13 0 P _part/9/_locked/9,14 0 P _part/9/_locked/9,15 0 P _part/9/_locked/9,16 0 P _part/9/_locked/9,17 0 P _part/9/_locked/9,2 0 P _part/9/_locked/9,3 0 P _part/9/_locked/9,4 0 P _part/9/_locked/9,5 0 P _part/9/_locked/9,6 0 P _part/9/_locked/9,7 0 P _part/9/_locked/9,8 0 P _part/9/_locked/9,9 0 P _part/9/_locked/i_count 20 P _part/9/_locked/j_count 18 P _part/9/_part_cd 0.075000003 P _part/9/_part_phi 0.0 P _part/9/_part_psi 0.0 P _part/9/_part_rad 2.0 P _part/9/_part_specs_eq 1 P _part/9/_part_specs_invis 0 P _part/9/_part_specs_unused1 0 P _part/9/_part_specs_unused2 0 P _part/9/_part_tex 1 P _part/9/_part_the 0.0 P _part/9/_part_x 0.0 P _part/9/_part_y 0.0 P _part/9/_part_z 0.0 P _part/9/_patt_con 0 P _part/9/_patt_prt 0 P _part/9/_patt_rat 0.0 P _part/9/_r_dim 14 P _part/9/_s_dim 16 P _part/9/_scon 37.676635742 P _part/9/_top_s1 0.755999982 P _part/9/_top_s2 1.0 P _part/9/_top_t1 0.0 P _part/9/_top_t2 0.384000003 P _part/count 95 P _prop/count 8 P _sbrk/0/_type 0 P _sbrk/1/_type 0 P _sbrk/2/_type 0 P _sbrk/3/_type 0 P _sbrk/count 4 P _slid/0/_slider_has_dataref 0 P _slid/0/_slider_power 0.0 P _slid/0/_slider_time 0.0 P _slid/1/_slider_has_dataref 0 P _slid/1/_slider_power 0.0 P _slid/1/_slider_time 0.0 P _slid/10/_slider_has_dataref 0 P _slid/10/_slider_power 0.0 P _slid/10/_slider_time 0.0 P _slid/11/_slider_has_dataref 0 P _slid/11/_slider_power 0.0 P _slid/11/_slider_time 0.0 P _slid/12/_slider_has_dataref 0 P _slid/12/_slider_power 0.0 P _slid/12/_slider_time 0.0 P _slid/13/_slider_has_dataref 0 P _slid/13/_slider_power 0.0 P _slid/13/_slider_time 0.0 P _slid/14/_slider_has_dataref 0 P _slid/14/_slider_power 0.0 P _slid/14/_slider_time 0.0 P _slid/15/_slider_has_dataref 0 P _slid/15/_slider_power 0.0 P _slid/15/_slider_time 0.0 P _slid/16/_slider_has_dataref 0 P _slid/16/_slider_power 0.0 P _slid/16/_slider_time 0.0 P _slid/17/_slider_has_dataref 0 P _slid/17/_slider_power 0.0 P _slid/17/_slider_time 0.0 P _slid/18/_slider_has_dataref 0 P _slid/18/_slider_power 0.0 P _slid/18/_slider_time 0.0 P _slid/19/_slider_has_dataref 0 P _slid/19/_slider_power 0.0 P _slid/19/_slider_time 0.0 P _slid/2/_slider_has_dataref 0 P _slid/2/_slider_power 0.0 P _slid/2/_slider_time 0.0 P _slid/20/_slider_has_dataref 0 P _slid/20/_slider_power 0.0 P _slid/20/_slider_time 0.0 P _slid/21/_slider_has_dataref 0 P _slid/21/_slider_power 0.0 P _slid/21/_slider_time 0.0 P _slid/22/_slider_has_dataref 0 P _slid/22/_slider_power 0.0 P _slid/22/_slider_time 0.0 P _slid/23/_slider_has_dataref 0 P _slid/23/_slider_power 0.0 P _slid/23/_slider_time 0.0 P _slid/3/_slider_has_dataref 0 P _slid/3/_slider_power 0.0 P _slid/3/_slider_time 0.0 P _slid/4/_slider_has_dataref 0 P _slid/4/_slider_power 0.0 P _slid/4/_slider_time 0.0 P _slid/5/_slider_has_dataref 0 P _slid/5/_slider_power 0.0 P _slid/5/_slider_time 0.0 P _slid/6/_slider_has_dataref 0 P _slid/6/_slider_power 0.0 P _slid/6/_slider_time 0.0 P _slid/7/_slider_has_dataref 0 P _slid/7/_slider_power 0.0 P _slid/7/_slider_time 0.0 P _slid/8/_slider_has_dataref 0 P _slid/8/_slider_power 0.0 P _slid/8/_slider_time 0.0 P _slid/9/_slider_has_dataref 0 P _slid/9/_slider_power 0.0 P _slid/9/_slider_time 0.0 P _slid/count 24 P _wing/16/_AR_geo 0.0 P _wing/16/_Croot 1.640419960 P _wing/16/_Ctip 1.640419960 P _wing/16/_TR_geo 0.0 P _wing/16/_afl_file_R0 NACA 0009 (symmetrical).afl P _wing/16/_afl_file_R1 NACA 0009 (symmetrical).afl P _wing/16/_afl_file_T0 NACA 0009 (symmetrical).afl P _wing/16/_afl_file_T1 NACA 0009 (symmetrical).afl P _wing/16/_ailn1/0 0 P _wing/16/_ailn1/1 0 P _wing/16/_ailn1/2 0 P _wing/16/_ailn1/3 0 P _wing/16/_ailn1/4 0 P _wing/16/_ailn1/5 0 P _wing/16/_ailn1/6 0 P _wing/16/_ailn1/7 0 P _wing/16/_ailn1/8 0 P _wing/16/_ailn1/9 0 P _wing/16/_ailn1/count 10 P _wing/16/_ailn2/0 0 P _wing/16/_ailn2/1 0 P _wing/16/_ailn2/2 0 P _wing/16/_ailn2/3 0 P _wing/16/_ailn2/4 0 P _wing/16/_ailn2/5 0 P _wing/16/_ailn2/6 0 P _wing/16/_ailn2/7 0 P _wing/16/_ailn2/8 0 P _wing/16/_ailn2/9 0 P _wing/16/_ailn2/count 10 P _wing/16/_average_mac 0.0 P _wing/16/_base_wash_subsonic 0.0 P _wing/16/_base_wash_supersonic 0.0 P _wing/16/_c_off_for_RIB/0 0.0 P _wing/16/_c_off_for_RIB/1 0.0 P _wing/16/_c_off_for_RIB/10 0.0 P _wing/16/_c_off_for_RIB/2 0.0 P _wing/16/_c_off_for_RIB/3 0.0 P _wing/16/_c_off_for_RIB/4 0.0 P _wing/16/_c_off_for_RIB/5 0.0 P _wing/16/_c_off_for_RIB/6 0.0 P _wing/16/_c_off_for_RIB/7 0.0 P _wing/16/_c_off_for_RIB/8 0.0 P _wing/16/_c_off_for_RIB/9 0.0 P _wing/16/_c_off_for_RIB/count 11 P _wing/16/_c_rat_for_RIB/0 1.0 P _wing/16/_c_rat_for_RIB/1 1.0 P _wing/16/_c_rat_for_RIB/10 1.0 P _wing/16/_c_rat_for_RIB/2 1.0 P _wing/16/_c_rat_for_RIB/3 1.0 P _wing/16/_c_rat_for_RIB/4 1.0 P _wing/16/_c_rat_for_RIB/5 1.0 P _wing/16/_c_rat_for_RIB/6 1.0 P _wing/16/_c_rat_for_RIB/7 1.0 P _wing/16/_c_rat_for_RIB/8 1.0 P _wing/16/_c_rat_for_RIB/9 1.0 P _wing/16/_c_rat_for_RIB/count 11 P _wing/16/_chord_for_RIB/0 1.640419960 P _wing/16/_chord_for_RIB/1 1.640419960 P _wing/16/_chord_for_RIB/10 0.0 P _wing/16/_chord_for_RIB/2 1.640419960 P _wing/16/_chord_for_RIB/3 1.640419960 P _wing/16/_chord_for_RIB/4 1.640419960 P _wing/16/_chord_for_RIB/5 0.0 P _wing/16/_chord_for_RIB/6 0.0 P _wing/16/_chord_for_RIB/7 0.0 P _wing/16/_chord_for_RIB/8 0.0 P _wing/16/_chord_for_RIB/9 0.0 P _wing/16/_chord_for_RIB/count 11 P _wing/16/_chord_piv 0.250000000 P _wing/16/_crib_x_arm/0 0.0 P _wing/16/_crib_x_arm/1 -4.101049900 P _wing/16/_crib_x_arm/10 0.0 P _wing/16/_crib_x_arm/2 -8.202099800 P _wing/16/_crib_x_arm/3 -8.202099800 P _wing/16/_crib_x_arm/4 -8.202099800 P _wing/16/_crib_x_arm/5 0.0 P _wing/16/_crib_x_arm/6 0.0 P _wing/16/_crib_x_arm/7 0.0 P _wing/16/_crib_x_arm/8 0.0 P _wing/16/_crib_x_arm/9 0.0 P _wing/16/_crib_x_arm/count 11 P _wing/16/_crib_y_arm/0 12.303149223 P _wing/16/_crib_y_arm/1 12.303149223 P _wing/16/_crib_y_arm/10 0.0 P _wing/16/_crib_y_arm/2 12.303149223 P _wing/16/_crib_y_arm/3 12.303149223 P _wing/16/_crib_y_arm/4 12.303149223 P _wing/16/_crib_y_arm/5 0.0 P _wing/16/_crib_y_arm/6 0.0 P _wing/16/_crib_y_arm/7 0.0 P _wing/16/_crib_y_arm/8 0.0 P _wing/16/_crib_y_arm/9 0.0 P _wing/16/_crib_y_arm/count 11 P _wing/16/_crib_z_arm/0 32.808399200 P _wing/16/_crib_z_arm/1 32.808399200 P _wing/16/_crib_z_arm/10 0.0 P _wing/16/_crib_z_arm/2 32.808399200 P _wing/16/_crib_z_arm/3 32.808399200 P _wing/16/_crib_z_arm/4 32.808399200 P _wing/16/_crib_z_arm/5 0.0 P _wing/16/_crib_z_arm/6 0.0 P _wing/16/_crib_z_arm/7 0.0 P _wing/16/_crib_z_arm/8 0.0 P _wing/16/_crib_z_arm/9 0.0 P _wing/16/_crib_z_arm/count 11 P _wing/16/_delta_fac 0.0 P _wing/16/_dihed_design 0.0 P _wing/16/_dihed_full_up 0.0 P _wing/16/_dihed_root_inc 0.0 P _wing/16/_elev1/0 1 P _wing/16/_elev1/1 1 P _wing/16/_elev1/2 1 P _wing/16/_elev1/3 1 P _wing/16/_elev1/4 0 P _wing/16/_elev1/5 0 P _wing/16/_elev1/6 0 P _wing/16/_elev1/7 0 P _wing/16/_elev1/8 0 P _wing/16/_elev1/9 0 P _wing/16/_elev1/count 10 P _wing/16/_elev2/0 0 P _wing/16/_elev2/1 0 P _wing/16/_elev2/2 0 P _wing/16/_elev2/3 0 P _wing/16/_elev2/4 0 P _wing/16/_elev2/5 0 P _wing/16/_elev2/6 0 P _wing/16/_elev2/7 0 P _wing/16/_elev2/8 0 P _wing/16/_elev2/9 0 P _wing/16/_elev2/count 10 P _wing/16/_els 2 P _wing/16/_flap1/0 0 P _wing/16/_flap1/1 0 P _wing/16/_flap1/2 0 P _wing/16/_flap1/3 0 P _wing/16/_flap1/4 0 P _wing/16/_flap1/5 0 P _wing/16/_flap1/6 0 P _wing/16/_flap1/7 0 P _wing/16/_flap1/8 0 P _wing/16/_flap1/9 0 P _wing/16/_flap1/count 10 P _wing/16/_flap2/0 0 P _wing/16/_flap2/1 0 P _wing/16/_flap2/2 0 P _wing/16/_flap2/3 0 P _wing/16/_flap2/4 0 P _wing/16/_flap2/5 0 P _wing/16/_flap2/6 0 P _wing/16/_flap2/7 0 P _wing/16/_flap2/8 0 P _wing/16/_flap2/9 0 P _wing/16/_flap2/count 10 P _wing/16/_inc_ailn1/0 0 P _wing/16/_inc_ailn1/1 0 P _wing/16/_inc_ailn1/2 0 P _wing/16/_inc_ailn1/3 0 P _wing/16/_inc_ailn1/4 0 P _wing/16/_inc_ailn1/5 0 P _wing/16/_inc_ailn1/6 0 P _wing/16/_inc_ailn1/7 0 P _wing/16/_inc_ailn1/8 0 P _wing/16/_inc_ailn1/9 0 P _wing/16/_inc_ailn1/count 10 P _wing/16/_inc_ailn2/0 0 P _wing/16/_inc_ailn2/1 0 P _wing/16/_inc_ailn2/2 0 P _wing/16/_inc_ailn2/3 0 P _wing/16/_inc_ailn2/4 0 P _wing/16/_inc_ailn2/5 0 P _wing/16/_inc_ailn2/6 0 P _wing/16/_inc_ailn2/7 0 P _wing/16/_inc_ailn2/8 0 P _wing/16/_inc_ailn2/9 0 P _wing/16/_inc_ailn2/count 10 P _wing/16/_inc_elev1/0 0 P _wing/16/_inc_elev1/1 0 P _wing/16/_inc_elev1/2 0 P _wing/16/_inc_elev1/3 0 P _wing/16/_inc_elev1/4 0 P _wing/16/_inc_elev1/5 0 P _wing/16/_inc_elev1/6 0 P _wing/16/_inc_elev1/7 0 P _wing/16/_inc_elev1/8 0 P _wing/16/_inc_elev1/9 0 P _wing/16/_inc_elev1/count 10 P _wing/16/_inc_elev2/0 0 P _wing/16/_inc_elev2/1 0 P _wing/16/_inc_elev2/2 0 P _wing/16/_inc_elev2/3 0 P _wing/16/_inc_elev2/4 0 P _wing/16/_inc_elev2/5 0 P _wing/16/_inc_elev2/6 0 P _wing/16/_inc_elev2/7 0 P _wing/16/_inc_elev2/8 0 P _wing/16/_inc_elev2/9 0 P _wing/16/_inc_elev2/count 10 P _wing/16/_inc_incid/0 0.0 P _wing/16/_inc_incid/1 0.0 P _wing/16/_inc_incid/2 0.0 P _wing/16/_inc_incid/3 0.0 P _wing/16/_inc_incid/4 0.0 P _wing/16/_inc_incid/5 0.0 P _wing/16/_inc_incid/6 0.0 P _wing/16/_inc_incid/7 0.0 P _wing/16/_inc_incid/8 0.0 P _wing/16/_inc_incid/9 0.0 P _wing/16/_inc_incid/count 10 P _wing/16/_inc_rudd1/0 0 P _wing/16/_inc_rudd1/1 0 P _wing/16/_inc_rudd1/2 0 P _wing/16/_inc_rudd1/3 0 P _wing/16/_inc_rudd1/4 0 P _wing/16/_inc_rudd1/5 0 P _wing/16/_inc_rudd1/6 0 P _wing/16/_inc_rudd1/7 0 P _wing/16/_inc_rudd1/8 0 P _wing/16/_inc_rudd1/9 0 P _wing/16/_inc_rudd1/count 10 P _wing/16/_inc_rudd2/0 0 P _wing/16/_inc_rudd2/1 0 P _wing/16/_inc_rudd2/2 0 P _wing/16/_inc_rudd2/3 0 P _wing/16/_inc_rudd2/4 0 P _wing/16/_inc_rudd2/5 0 P _wing/16/_inc_rudd2/6 0 P _wing/16/_inc_rudd2/7 0 P _wing/16/_inc_rudd2/8 0 P _wing/16/_inc_rudd2/9 0 P _wing/16/_inc_rudd2/count 10 P _wing/16/_inc_trim/0 0 P _wing/16/_inc_trim/1 0 P _wing/16/_inc_trim/2 0 P _wing/16/_inc_trim/3 0 P _wing/16/_inc_trim/4 0 P _wing/16/_inc_trim/5 0 P _wing/16/_inc_trim/6 0 P _wing/16/_inc_trim/7 0 P _wing/16/_inc_trim/8 0 P _wing/16/_inc_trim/9 0 P _wing/16/_inc_trim/count 10 P _wing/16/_inc_vect/0 0 P _wing/16/_inc_vect/1 0 P _wing/16/_inc_vect/2 0 P _wing/16/_inc_vect/3 0 P _wing/16/_inc_vect/4 0 P _wing/16/_inc_vect/5 0 P _wing/16/_inc_vect/6 0 P _wing/16/_inc_vect/7 0 P _wing/16/_inc_vect/8 0 P _wing/16/_inc_vect/9 0 P _wing/16/_inc_vect/count 10 P _wing/16/_incid_chrd_inc 0.0 P _wing/16/_incid_full_up 0.0 P _wing/16/_incidence/0 0.0 P _wing/16/_incidence/1 0.0 P _wing/16/_incidence/2 0.0 P _wing/16/_incidence/3 0.0 P _wing/16/_incidence/4 0.0 P _wing/16/_incidence/5 0.0 P _wing/16/_incidence/6 0.0 P _wing/16/_incidence/7 0.0 P _wing/16/_incidence/8 0.0 P _wing/16/_incidence/9 0.0 P _wing/16/_incidence/count 10 P _wing/16/_is_right_mult -1.0 P _wing/16/_oswalds_e 0.0 P _wing/16/_retract_max 0.0 P _wing/16/_retract_now 0.0 P _wing/16/_rudd1/0 0 P _wing/16/_rudd1/1 0 P _wing/16/_rudd1/2 0 P _wing/16/_rudd1/3 0 P _wing/16/_rudd1/4 0 P _wing/16/_rudd1/5 0 P _wing/16/_rudd1/6 0 P _wing/16/_rudd1/7 0 P _wing/16/_rudd1/8 0 P _wing/16/_rudd1/9 0 P _wing/16/_rudd1/count 10 P _wing/16/_rudd2/0 0 P _wing/16/_rudd2/1 0 P _wing/16/_rudd2/2 0 P _wing/16/_rudd2/3 0 P _wing/16/_rudd2/4 0 P _wing/16/_rudd2/5 0 P _wing/16/_rudd2/6 0 P _wing/16/_rudd2/7 0 P _wing/16/_rudd2/8 0 P _wing/16/_rudd2/9 0 P _wing/16/_rudd2/count 10 P _wing/16/_sbrk1/0 0 P _wing/16/_sbrk1/1 0 P _wing/16/_sbrk1/2 0 P _wing/16/_sbrk1/3 0 P _wing/16/_sbrk1/4 0 P _wing/16/_sbrk1/5 0 P _wing/16/_sbrk1/6 0 P _wing/16/_sbrk1/7 0 P _wing/16/_sbrk1/8 0 P _wing/16/_sbrk1/9 0 P _wing/16/_sbrk1/count 10 P _wing/16/_sbrk2/0 0 P _wing/16/_sbrk2/1 0 P _wing/16/_sbrk2/2 0 P _wing/16/_sbrk2/3 0 P _wing/16/_sbrk2/4 0 P _wing/16/_sbrk2/5 0 P _wing/16/_sbrk2/6 0 P _wing/16/_sbrk2/7 0 P _wing/16/_sbrk2/8 0 P _wing/16/_sbrk2/9 0 P _wing/16/_sbrk2/count 10 P _wing/16/_semilen_JND 0.0 P _wing/16/_semilen_SEG 8.202099800 P _wing/16/_sla1_effect 0.0 P _wing/16/_sla2_effect 0.0 P _wing/16/_slat1/0 0 P _wing/16/_slat1/1 0 P _wing/16/_slat1/2 0 P _wing/16/_slat1/3 0 P _wing/16/_slat1/4 0 P _wing/16/_slat1/5 0 P _wing/16/_slat1/6 0 P _wing/16/_slat1/7 0 P _wing/16/_slat1/8 0 P _wing/16/_slat1/9 0 P _wing/16/_slat1/count 10 P _wing/16/_slat2/0 0 P _wing/16/_slat2/1 0 P _wing/16/_slat2/2 0 P _wing/16/_slat2/3 0 P _wing/16/_slat2/4 0 P _wing/16/_slat2/5 0 P _wing/16/_slat2/6 0 P _wing/16/_slat2/7 0 P _wing/16/_slat2/8 0 P _wing/16/_slat2/9 0 P _wing/16/_slat2/count 10 P _wing/16/_spoi1/0 0 P _wing/16/_spoi1/1 0 P _wing/16/_spoi1/2 0 P _wing/16/_spoi1/3 0 P _wing/16/_spoi1/4 0 P _wing/16/_spoi1/5 0 P _wing/16/_spoi1/6 0 P _wing/16/_spoi1/7 0 P _wing/16/_spoi1/8 0 P _wing/16/_spoi1/9 0 P _wing/16/_spoi1/count 10 P _wing/16/_spoi2/0 0 P _wing/16/_spoi2/1 0 P _wing/16/_spoi2/2 0 P _wing/16/_spoi2/3 0 P _wing/16/_spoi2/4 0 P _wing/16/_spoi2/5 0 P _wing/16/_spoi2/6 0 P _wing/16/_spoi2/7 0 P _wing/16/_spoi2/8 0 P _wing/16/_spoi2/9 0 P _wing/16/_spoi2/count 10 P _wing/16/_sweep_design 0.0 P _wing/16/_sweep_full_aft 0.0 P _wing/16/_sweep_root_inc 0.0 P _wing/16/_var_dihed 0 P _wing/16/_var_incid 0 P _wing/16/_var_retract 0 P _wing/16/_var_sweep 0 P _wing/16/_yawbr/0 0 P _wing/16/_yawbr/1 0 P _wing/16/_yawbr/2 0 P _wing/16/_yawbr/3 0 P _wing/16/_yawbr/4 0 P _wing/16/_yawbr/5 0 P _wing/16/_yawbr/6 0 P _wing/16/_yawbr/7 0 P _wing/16/_yawbr/8 0 P _wing/16/_yawbr/9 0 P _wing/16/_yawbr/count 10 P _wing/17/_AR_geo 0.0 P _wing/17/_Croot 1.640419960 P _wing/17/_Ctip 1.640419960 P _wing/17/_TR_geo 0.0 P _wing/17/_afl_file_R0 NACA 0009 (symmetrical).afl P _wing/17/_afl_file_R1 NACA 0009 (symmetrical).afl P _wing/17/_afl_file_T0 NACA 0009 (symmetrical).afl P _wing/17/_afl_file_T1 NACA 0009 (symmetrical).afl P _wing/17/_ailn1/0 0 P _wing/17/_ailn1/1 0 P _wing/17/_ailn1/2 0 P _wing/17/_ailn1/3 0 P _wing/17/_ailn1/4 0 P _wing/17/_ailn1/5 0 P _wing/17/_ailn1/6 0 P _wing/17/_ailn1/7 0 P _wing/17/_ailn1/8 0 P _wing/17/_ailn1/9 0 P _wing/17/_ailn1/count 10 P _wing/17/_ailn2/0 0 P _wing/17/_ailn2/1 0 P _wing/17/_ailn2/2 0 P _wing/17/_ailn2/3 0 P _wing/17/_ailn2/4 0 P _wing/17/_ailn2/5 0 P _wing/17/_ailn2/6 0 P _wing/17/_ailn2/7 0 P _wing/17/_ailn2/8 0 P _wing/17/_ailn2/9 0 P _wing/17/_ailn2/count 10 P _wing/17/_average_mac 0.0 P _wing/17/_base_wash_subsonic 0.0 P _wing/17/_base_wash_supersonic 0.0 P _wing/17/_c_off_for_RIB/0 0.0 P _wing/17/_c_off_for_RIB/1 0.0 P _wing/17/_c_off_for_RIB/10 0.0 P _wing/17/_c_off_for_RIB/2 0.0 P _wing/17/_c_off_for_RIB/3 0.0 P _wing/17/_c_off_for_RIB/4 0.0 P _wing/17/_c_off_for_RIB/5 0.0 P _wing/17/_c_off_for_RIB/6 0.0 P _wing/17/_c_off_for_RIB/7 0.0 P _wing/17/_c_off_for_RIB/8 0.0 P _wing/17/_c_off_for_RIB/9 0.0 P _wing/17/_c_off_for_RIB/count 11 P _wing/17/_c_rat_for_RIB/0 1.0 P _wing/17/_c_rat_for_RIB/1 1.0 P _wing/17/_c_rat_for_RIB/10 1.0 P _wing/17/_c_rat_for_RIB/2 1.0 P _wing/17/_c_rat_for_RIB/3 1.0 P _wing/17/_c_rat_for_RIB/4 1.0 P _wing/17/_c_rat_for_RIB/5 1.0 P _wing/17/_c_rat_for_RIB/6 1.0 P _wing/17/_c_rat_for_RIB/7 1.0 P _wing/17/_c_rat_for_RIB/8 1.0 P _wing/17/_c_rat_for_RIB/9 1.0 P _wing/17/_c_rat_for_RIB/count 11 P _wing/17/_chord_for_RIB/0 1.640419960 P _wing/17/_chord_for_RIB/1 1.640419960 P _wing/17/_chord_for_RIB/10 0.0 P _wing/17/_chord_for_RIB/2 1.640419960 P _wing/17/_chord_for_RIB/3 1.640419960 P _wing/17/_chord_for_RIB/4 1.640419960 P _wing/17/_chord_for_RIB/5 0.0 P _wing/17/_chord_for_RIB/6 0.0 P _wing/17/_chord_for_RIB/7 0.0 P _wing/17/_chord_for_RIB/8 0.0 P _wing/17/_chord_for_RIB/9 0.0 P _wing/17/_chord_for_RIB/count 11 P _wing/17/_chord_piv 0.250000000 P _wing/17/_crib_x_arm/0 0.0 P _wing/17/_crib_x_arm/1 4.101049900 P _wing/17/_crib_x_arm/10 0.0 P _wing/17/_crib_x_arm/2 8.202099800 P _wing/17/_crib_x_arm/3 8.202099800 P _wing/17/_crib_x_arm/4 8.202099800 P _wing/17/_crib_x_arm/5 0.0 P _wing/17/_crib_x_arm/6 0.0 P _wing/17/_crib_x_arm/7 0.0 P _wing/17/_crib_x_arm/8 0.0 P _wing/17/_crib_x_arm/9 0.0 P _wing/17/_crib_x_arm/count 11 P _wing/17/_crib_y_arm/0 12.303149223 P _wing/17/_crib_y_arm/1 12.303149223 P _wing/17/_crib_y_arm/10 0.0 P _wing/17/_crib_y_arm/2 12.303149223 P _wing/17/_crib_y_arm/3 12.303149223 P _wing/17/_crib_y_arm/4 12.303149223 P _wing/17/_crib_y_arm/5 0.0 P _wing/17/_crib_y_arm/6 0.0 P _wing/17/_crib_y_arm/7 0.0 P _wing/17/_crib_y_arm/8 0.0 P _wing/17/_crib_y_arm/9 0.0 P _wing/17/_crib_y_arm/count 11 P _wing/17/_crib_z_arm/0 32.808399200 P _wing/17/_crib_z_arm/1 32.808399200 P _wing/17/_crib_z_arm/10 0.0 P _wing/17/_crib_z_arm/2 32.808399200 P _wing/17/_crib_z_arm/3 32.808399200 P _wing/17/_crib_z_arm/4 32.808399200 P _wing/17/_crib_z_arm/5 0.0 P _wing/17/_crib_z_arm/6 0.0 P _wing/17/_crib_z_arm/7 0.0 P _wing/17/_crib_z_arm/8 0.0 P _wing/17/_crib_z_arm/9 0.0 P _wing/17/_crib_z_arm/count 11 P _wing/17/_delta_fac 0.0 P _wing/17/_dihed_design 0.0 P _wing/17/_dihed_full_up 0.0 P _wing/17/_dihed_root_inc 0.0 P _wing/17/_elev1/0 1 P _wing/17/_elev1/1 1 P _wing/17/_elev1/2 1 P _wing/17/_elev1/3 1 P _wing/17/_elev1/4 0 P _wing/17/_elev1/5 0 P _wing/17/_elev1/6 0 P _wing/17/_elev1/7 0 P _wing/17/_elev1/8 0 P _wing/17/_elev1/9 0 P _wing/17/_elev1/count 10 P _wing/17/_elev2/0 0 P _wing/17/_elev2/1 0 P _wing/17/_elev2/2 0 P _wing/17/_elev2/3 0 P _wing/17/_elev2/4 0 P _wing/17/_elev2/5 0 P _wing/17/_elev2/6 0 P _wing/17/_elev2/7 0 P _wing/17/_elev2/8 0 P _wing/17/_elev2/9 0 P _wing/17/_elev2/count 10 P _wing/17/_els 2 P _wing/17/_flap1/0 0 P _wing/17/_flap1/1 0 P _wing/17/_flap1/2 0 P _wing/17/_flap1/3 0 P _wing/17/_flap1/4 0 P _wing/17/_flap1/5 0 P _wing/17/_flap1/6 0 P _wing/17/_flap1/7 0 P _wing/17/_flap1/8 0 P _wing/17/_flap1/9 0 P _wing/17/_flap1/count 10 P _wing/17/_flap2/0 0 P _wing/17/_flap2/1 0 P _wing/17/_flap2/2 0 P _wing/17/_flap2/3 0 P _wing/17/_flap2/4 0 P _wing/17/_flap2/5 0 P _wing/17/_flap2/6 0 P _wing/17/_flap2/7 0 P _wing/17/_flap2/8 0 P _wing/17/_flap2/9 0 P _wing/17/_flap2/count 10 P _wing/17/_inc_ailn1/0 0 P _wing/17/_inc_ailn1/1 0 P _wing/17/_inc_ailn1/2 0 P _wing/17/_inc_ailn1/3 0 P _wing/17/_inc_ailn1/4 0 P _wing/17/_inc_ailn1/5 0 P _wing/17/_inc_ailn1/6 0 P _wing/17/_inc_ailn1/7 0 P _wing/17/_inc_ailn1/8 0 P _wing/17/_inc_ailn1/9 0 P _wing/17/_inc_ailn1/count 10 P _wing/17/_inc_ailn2/0 0 P _wing/17/_inc_ailn2/1 0 P _wing/17/_inc_ailn2/2 0 P _wing/17/_inc_ailn2/3 0 P _wing/17/_inc_ailn2/4 0 P _wing/17/_inc_ailn2/5 0 P _wing/17/_inc_ailn2/6 0 P _wing/17/_inc_ailn2/7 0 P _wing/17/_inc_ailn2/8 0 P _wing/17/_inc_ailn2/9 0 P _wing/17/_inc_ailn2/count 10 P _wing/17/_inc_elev1/0 0 P _wing/17/_inc_elev1/1 0 P _wing/17/_inc_elev1/2 0 P _wing/17/_inc_elev1/3 0 P _wing/17/_inc_elev1/4 0 P _wing/17/_inc_elev1/5 0 P _wing/17/_inc_elev1/6 0 P _wing/17/_inc_elev1/7 0 P _wing/17/_inc_elev1/8 0 P _wing/17/_inc_elev1/9 0 P _wing/17/_inc_elev1/count 10 P _wing/17/_inc_elev2/0 0 P _wing/17/_inc_elev2/1 0 P _wing/17/_inc_elev2/2 0 P _wing/17/_inc_elev2/3 0 P _wing/17/_inc_elev2/4 0 P _wing/17/_inc_elev2/5 0 P _wing/17/_inc_elev2/6 0 P _wing/17/_inc_elev2/7 0 P _wing/17/_inc_elev2/8 0 P _wing/17/_inc_elev2/9 0 P _wing/17/_inc_elev2/count 10 P _wing/17/_inc_incid/0 0.0 P _wing/17/_inc_incid/1 0.0 P _wing/17/_inc_incid/2 0.0 P _wing/17/_inc_incid/3 0.0 P _wing/17/_inc_incid/4 0.0 P _wing/17/_inc_incid/5 0.0 P _wing/17/_inc_incid/6 0.0 P _wing/17/_inc_incid/7 0.0 P _wing/17/_inc_incid/8 0.0 P _wing/17/_inc_incid/9 0.0 P _wing/17/_inc_incid/count 10 P _wing/17/_inc_rudd1/0 0 P _wing/17/_inc_rudd1/1 0 P _wing/17/_inc_rudd1/2 0 P _wing/17/_inc_rudd1/3 0 P _wing/17/_inc_rudd1/4 0 P _wing/17/_inc_rudd1/5 0 P _wing/17/_inc_rudd1/6 0 P _wing/17/_inc_rudd1/7 0 P _wing/17/_inc_rudd1/8 0 P _wing/17/_inc_rudd1/9 0 P _wing/17/_inc_rudd1/count 10 P _wing/17/_inc_rudd2/0 0 P _wing/17/_inc_rudd2/1 0 P _wing/17/_inc_rudd2/2 0 P _wing/17/_inc_rudd2/3 0 P _wing/17/_inc_rudd2/4 0 P _wing/17/_inc_rudd2/5 0 P _wing/17/_inc_rudd2/6 0 P _wing/17/_inc_rudd2/7 0 P _wing/17/_inc_rudd2/8 0 P _wing/17/_inc_rudd2/9 0 P _wing/17/_inc_rudd2/count 10 P _wing/17/_inc_trim/0 0 P _wing/17/_inc_trim/1 0 P _wing/17/_inc_trim/2 0 P _wing/17/_inc_trim/3 0 P _wing/17/_inc_trim/4 0 P _wing/17/_inc_trim/5 0 P _wing/17/_inc_trim/6 0 P _wing/17/_inc_trim/7 0 P _wing/17/_inc_trim/8 0 P _wing/17/_inc_trim/9 0 P _wing/17/_inc_trim/count 10 P _wing/17/_inc_vect/0 0 P _wing/17/_inc_vect/1 0 P _wing/17/_inc_vect/2 0 P _wing/17/_inc_vect/3 0 P _wing/17/_inc_vect/4 0 P _wing/17/_inc_vect/5 0 P _wing/17/_inc_vect/6 0 P _wing/17/_inc_vect/7 0 P _wing/17/_inc_vect/8 0 P _wing/17/_inc_vect/9 0 P _wing/17/_inc_vect/count 10 P _wing/17/_incid_chrd_inc 0.0 P _wing/17/_incid_full_up 0.0 P _wing/17/_incidence/0 0.0 P _wing/17/_incidence/1 0.0 P _wing/17/_incidence/2 0.0 P _wing/17/_incidence/3 0.0 P _wing/17/_incidence/4 0.0 P _wing/17/_incidence/5 0.0 P _wing/17/_incidence/6 0.0 P _wing/17/_incidence/7 0.0 P _wing/17/_incidence/8 0.0 P _wing/17/_incidence/9 0.0 P _wing/17/_incidence/count 10 P _wing/17/_is_right_mult 1.0 P _wing/17/_oswalds_e 0.0 P _wing/17/_retract_max 0.0 P _wing/17/_retract_now 0.0 P _wing/17/_rudd1/0 0 P _wing/17/_rudd1/1 0 P _wing/17/_rudd1/2 0 P _wing/17/_rudd1/3 0 P _wing/17/_rudd1/4 0 P _wing/17/_rudd1/5 0 P _wing/17/_rudd1/6 0 P _wing/17/_rudd1/7 0 P _wing/17/_rudd1/8 0 P _wing/17/_rudd1/9 0 P _wing/17/_rudd1/count 10 P _wing/17/_rudd2/0 0 P _wing/17/_rudd2/1 0 P _wing/17/_rudd2/2 0 P _wing/17/_rudd2/3 0 P _wing/17/_rudd2/4 0 P _wing/17/_rudd2/5 0 P _wing/17/_rudd2/6 0 P _wing/17/_rudd2/7 0 P _wing/17/_rudd2/8 0 P _wing/17/_rudd2/9 0 P _wing/17/_rudd2/count 10 P _wing/17/_sbrk1/0 0 P _wing/17/_sbrk1/1 0 P _wing/17/_sbrk1/2 0 P _wing/17/_sbrk1/3 0 P _wing/17/_sbrk1/4 0 P _wing/17/_sbrk1/5 0 P _wing/17/_sbrk1/6 0 P _wing/17/_sbrk1/7 0 P _wing/17/_sbrk1/8 0 P _wing/17/_sbrk1/9 0 P _wing/17/_sbrk1/count 10 P _wing/17/_sbrk2/0 0 P _wing/17/_sbrk2/1 0 P _wing/17/_sbrk2/2 0 P _wing/17/_sbrk2/3 0 P _wing/17/_sbrk2/4 0 P _wing/17/_sbrk2/5 0 P _wing/17/_sbrk2/6 0 P _wing/17/_sbrk2/7 0 P _wing/17/_sbrk2/8 0 P _wing/17/_sbrk2/9 0 P _wing/17/_sbrk2/count 10 P _wing/17/_semilen_JND 0.0 P _wing/17/_semilen_SEG 8.202099800 P _wing/17/_sla1_effect 0.0 P _wing/17/_sla2_effect 0.0 P _wing/17/_slat1/0 0 P _wing/17/_slat1/1 0 P _wing/17/_slat1/2 0 P _wing/17/_slat1/3 0 P _wing/17/_slat1/4 0 P _wing/17/_slat1/5 0 P _wing/17/_slat1/6 0 P _wing/17/_slat1/7 0 P _wing/17/_slat1/8 0 P _wing/17/_slat1/9 0 P _wing/17/_slat1/count 10 P _wing/17/_slat2/0 0 P _wing/17/_slat2/1 0 P _wing/17/_slat2/2 0 P _wing/17/_slat2/3 0 P _wing/17/_slat2/4 0 P _wing/17/_slat2/5 0 P _wing/17/_slat2/6 0 P _wing/17/_slat2/7 0 P _wing/17/_slat2/8 0 P _wing/17/_slat2/9 0 P _wing/17/_slat2/count 10 P _wing/17/_spoi1/0 0 P _wing/17/_spoi1/1 0 P _wing/17/_spoi1/2 0 P _wing/17/_spoi1/3 0 P _wing/17/_spoi1/4 0 P _wing/17/_spoi1/5 0 P _wing/17/_spoi1/6 0 P _wing/17/_spoi1/7 0 P _wing/17/_spoi1/8 0 P _wing/17/_spoi1/9 0 P _wing/17/_spoi1/count 10 P _wing/17/_spoi2/0 0 P _wing/17/_spoi2/1 0 P _wing/17/_spoi2/2 0 P _wing/17/_spoi2/3 0 P _wing/17/_spoi2/4 0 P _wing/17/_spoi2/5 0 P _wing/17/_spoi2/6 0 P _wing/17/_spoi2/7 0 P _wing/17/_spoi2/8 0 P _wing/17/_spoi2/9 0 P _wing/17/_spoi2/count 10 P _wing/17/_sweep_design 0.0 P _wing/17/_sweep_full_aft 0.0 P _wing/17/_sweep_root_inc 0.0 P _wing/17/_var_dihed 0 P _wing/17/_var_incid 0 P _wing/17/_var_retract 0 P _wing/17/_var_sweep 0 P _wing/17/_yawbr/0 0 P _wing/17/_yawbr/1 0 P _wing/17/_yawbr/2 0 P _wing/17/_yawbr/3 0 P _wing/17/_yawbr/4 0 P _wing/17/_yawbr/5 0 P _wing/17/_yawbr/6 0 P _wing/17/_yawbr/7 0 P _wing/17/_yawbr/8 0 P _wing/17/_yawbr/9 0 P _wing/17/_yawbr/count 10 P _wing/18/_AR_geo 0.0 P _wing/18/_Croot 0.032808397 P _wing/18/_Ctip 0.032808397 P _wing/18/_TR_geo 0.0 P _wing/18/_afl_file_R0 NACA 0009 (symmetrical).afl P _wing/18/_afl_file_R1 NACA 0009 (symmetrical).afl P _wing/18/_afl_file_T0 NACA 0009 (symmetrical).afl P _wing/18/_afl_file_T1 NACA 0009 (symmetrical).afl P _wing/18/_ailn1/0 0 P _wing/18/_ailn1/1 0 P _wing/18/_ailn1/2 0 P _wing/18/_ailn1/3 0 P _wing/18/_ailn1/4 0 P _wing/18/_ailn1/5 0 P _wing/18/_ailn1/6 0 P _wing/18/_ailn1/7 0 P _wing/18/_ailn1/8 0 P _wing/18/_ailn1/9 0 P _wing/18/_ailn1/count 10 P _wing/18/_ailn2/0 0 P _wing/18/_ailn2/1 0 P _wing/18/_ailn2/2 0 P _wing/18/_ailn2/3 0 P _wing/18/_ailn2/4 0 P _wing/18/_ailn2/5 0 P _wing/18/_ailn2/6 0 P _wing/18/_ailn2/7 0 P _wing/18/_ailn2/8 0 P _wing/18/_ailn2/9 0 P _wing/18/_ailn2/count 10 P _wing/18/_average_mac 0.0 P _wing/18/_base_wash_subsonic 0.0 P _wing/18/_base_wash_supersonic 0.0 P _wing/18/_c_off_for_RIB/0 0.0 P _wing/18/_c_off_for_RIB/1 0.0 P _wing/18/_c_off_for_RIB/10 0.0 P _wing/18/_c_off_for_RIB/2 0.0 P _wing/18/_c_off_for_RIB/3 0.0 P _wing/18/_c_off_for_RIB/4 0.0 P _wing/18/_c_off_for_RIB/5 0.0 P _wing/18/_c_off_for_RIB/6 0.0 P _wing/18/_c_off_for_RIB/7 0.0 P _wing/18/_c_off_for_RIB/8 0.0 P _wing/18/_c_off_for_RIB/9 0.0 P _wing/18/_c_off_for_RIB/count 11 P _wing/18/_c_rat_for_RIB/0 1.0 P _wing/18/_c_rat_for_RIB/1 1.0 P _wing/18/_c_rat_for_RIB/10 1.0 P _wing/18/_c_rat_for_RIB/2 1.0 P _wing/18/_c_rat_for_RIB/3 1.0 P _wing/18/_c_rat_for_RIB/4 1.0 P _wing/18/_c_rat_for_RIB/5 1.0 P _wing/18/_c_rat_for_RIB/6 1.0 P _wing/18/_c_rat_for_RIB/7 1.0 P _wing/18/_c_rat_for_RIB/8 1.0 P _wing/18/_c_rat_for_RIB/9 1.0 P _wing/18/_c_rat_for_RIB/count 11 P _wing/18/_chord_for_RIB/0 0.032808397 P _wing/18/_chord_for_RIB/1 0.032808397 P _wing/18/_chord_for_RIB/10 0.0 P _wing/18/_chord_for_RIB/2 0.032808397 P _wing/18/_chord_for_RIB/3 0.032808397 P _wing/18/_chord_for_RIB/4 0.032808397 P _wing/18/_chord_for_RIB/5 0.0 P _wing/18/_chord_for_RIB/6 0.0 P _wing/18/_chord_for_RIB/7 0.0 P _wing/18/_chord_for_RIB/8 0.0 P _wing/18/_chord_for_RIB/9 0.0 P _wing/18/_chord_for_RIB/count 11 P _wing/18/_chord_piv 0.250000000 P _wing/18/_crib_x_arm/0 0.0 P _wing/18/_crib_x_arm/1 -0.000000501 P _wing/18/_crib_x_arm/10 0.0 P _wing/18/_crib_x_arm/2 -0.000001002 P _wing/18/_crib_x_arm/3 -0.000001503 P _wing/18/_crib_x_arm/4 -0.000002004 P _wing/18/_crib_x_arm/5 0.0 P _wing/18/_crib_x_arm/6 0.0 P _wing/18/_crib_x_arm/7 0.0 P _wing/18/_crib_x_arm/8 0.0 P _wing/18/_crib_x_arm/9 0.0 P _wing/18/_crib_x_arm/count 11 P _wing/18/_crib_y_arm/0 0.0 P _wing/18/_crib_y_arm/1 3.075787306 P _wing/18/_crib_y_arm/10 0.0 P _wing/18/_crib_y_arm/2 6.151574612 P _wing/18/_crib_y_arm/3 9.227361679 P _wing/18/_crib_y_arm/4 12.303149223 P _wing/18/_crib_y_arm/5 0.0 P _wing/18/_crib_y_arm/6 0.0 P _wing/18/_crib_y_arm/7 0.0 P _wing/18/_crib_y_arm/8 0.0 P _wing/18/_crib_y_arm/9 0.0 P _wing/18/_crib_y_arm/count 11 P _wing/18/_crib_z_arm/0 32.808399200 P _wing/18/_crib_z_arm/1 32.808399200 P _wing/18/_crib_z_arm/10 0.0 P _wing/18/_crib_z_arm/2 32.808399200 P _wing/18/_crib_z_arm/3 32.808399200 P _wing/18/_crib_z_arm/4 32.808399200 P _wing/18/_crib_z_arm/5 0.0 P _wing/18/_crib_z_arm/6 0.0 P _wing/18/_crib_z_arm/7 0.0 P _wing/18/_crib_z_arm/8 0.0 P _wing/18/_crib_z_arm/9 0.0 P _wing/18/_crib_z_arm/count 11 P _wing/18/_delta_fac 0.0 P _wing/18/_dihed_design 90.0 P _wing/18/_dihed_full_up 0.0 P _wing/18/_dihed_root_inc 0.0 P _wing/18/_elev1/0 0 P _wing/18/_elev1/1 0 P _wing/18/_elev1/2 0 P _wing/18/_elev1/3 0 P _wing/18/_elev1/4 0 P _wing/18/_elev1/5 0 P _wing/18/_elev1/6 0 P _wing/18/_elev1/7 0 P _wing/18/_elev1/8 0 P _wing/18/_elev1/9 0 P _wing/18/_elev1/count 10 P _wing/18/_elev2/0 0 P _wing/18/_elev2/1 0 P _wing/18/_elev2/2 0 P _wing/18/_elev2/3 0 P _wing/18/_elev2/4 0 P _wing/18/_elev2/5 0 P _wing/18/_elev2/6 0 P _wing/18/_elev2/7 0 P _wing/18/_elev2/8 0 P _wing/18/_elev2/9 0 P _wing/18/_elev2/count 10 P _wing/18/_els 4 P _wing/18/_flap1/0 0 P _wing/18/_flap1/1 0 P _wing/18/_flap1/2 0 P _wing/18/_flap1/3 0 P _wing/18/_flap1/4 0 P _wing/18/_flap1/5 0 P _wing/18/_flap1/6 0 P _wing/18/_flap1/7 0 P _wing/18/_flap1/8 0 P _wing/18/_flap1/9 0 P _wing/18/_flap1/count 10 P _wing/18/_flap2/0 0 P _wing/18/_flap2/1 0 P _wing/18/_flap2/2 0 P _wing/18/_flap2/3 0 P _wing/18/_flap2/4 0 P _wing/18/_flap2/5 0 P _wing/18/_flap2/6 0 P _wing/18/_flap2/7 0 P _wing/18/_flap2/8 0 P _wing/18/_flap2/9 0 P _wing/18/_flap2/count 10 P _wing/18/_inc_ailn1/0 0 P _wing/18/_inc_ailn1/1 0 P _wing/18/_inc_ailn1/2 0 P _wing/18/_inc_ailn1/3 0 P _wing/18/_inc_ailn1/4 0 P _wing/18/_inc_ailn1/5 0 P _wing/18/_inc_ailn1/6 0 P _wing/18/_inc_ailn1/7 0 P _wing/18/_inc_ailn1/8 0 P _wing/18/_inc_ailn1/9 0 P _wing/18/_inc_ailn1/count 10 P _wing/18/_inc_ailn2/0 0 P _wing/18/_inc_ailn2/1 0 P _wing/18/_inc_ailn2/2 0 P _wing/18/_inc_ailn2/3 0 P _wing/18/_inc_ailn2/4 0 P _wing/18/_inc_ailn2/5 0 P _wing/18/_inc_ailn2/6 0 P _wing/18/_inc_ailn2/7 0 P _wing/18/_inc_ailn2/8 0 P _wing/18/_inc_ailn2/9 0 P _wing/18/_inc_ailn2/count 10 P _wing/18/_inc_elev1/0 0 P _wing/18/_inc_elev1/1 0 P _wing/18/_inc_elev1/2 0 P _wing/18/_inc_elev1/3 0 P _wing/18/_inc_elev1/4 0 P _wing/18/_inc_elev1/5 0 P _wing/18/_inc_elev1/6 0 P _wing/18/_inc_elev1/7 0 P _wing/18/_inc_elev1/8 0 P _wing/18/_inc_elev1/9 0 P _wing/18/_inc_elev1/count 10 P _wing/18/_inc_elev2/0 0 P _wing/18/_inc_elev2/1 0 P _wing/18/_inc_elev2/2 0 P _wing/18/_inc_elev2/3 0 P _wing/18/_inc_elev2/4 0 P _wing/18/_inc_elev2/5 0 P _wing/18/_inc_elev2/6 0 P _wing/18/_inc_elev2/7 0 P _wing/18/_inc_elev2/8 0 P _wing/18/_inc_elev2/9 0 P _wing/18/_inc_elev2/count 10 P _wing/18/_inc_incid/0 0.0 P _wing/18/_inc_incid/1 0.0 P _wing/18/_inc_incid/2 0.0 P _wing/18/_inc_incid/3 0.0 P _wing/18/_inc_incid/4 0.0 P _wing/18/_inc_incid/5 0.0 P _wing/18/_inc_incid/6 0.0 P _wing/18/_inc_incid/7 0.0 P _wing/18/_inc_incid/8 0.0 P _wing/18/_inc_incid/9 0.0 P _wing/18/_inc_incid/count 10 P _wing/18/_inc_rudd1/0 0 P _wing/18/_inc_rudd1/1 0 P _wing/18/_inc_rudd1/2 0 P _wing/18/_inc_rudd1/3 0 P _wing/18/_inc_rudd1/4 0 P _wing/18/_inc_rudd1/5 0 P _wing/18/_inc_rudd1/6 0 P _wing/18/_inc_rudd1/7 0 P _wing/18/_inc_rudd1/8 0 P _wing/18/_inc_rudd1/9 0 P _wing/18/_inc_rudd1/count 10 P _wing/18/_inc_rudd2/0 0 P _wing/18/_inc_rudd2/1 0 P _wing/18/_inc_rudd2/2 0 P _wing/18/_inc_rudd2/3 0 P _wing/18/_inc_rudd2/4 0 P _wing/18/_inc_rudd2/5 0 P _wing/18/_inc_rudd2/6 0 P _wing/18/_inc_rudd2/7 0 P _wing/18/_inc_rudd2/8 0 P _wing/18/_inc_rudd2/9 0 P _wing/18/_inc_rudd2/count 10 P _wing/18/_inc_trim/0 0 P _wing/18/_inc_trim/1 0 P _wing/18/_inc_trim/2 0 P _wing/18/_inc_trim/3 0 P _wing/18/_inc_trim/4 0 P _wing/18/_inc_trim/5 0 P _wing/18/_inc_trim/6 0 P _wing/18/_inc_trim/7 0 P _wing/18/_inc_trim/8 0 P _wing/18/_inc_trim/9 0 P _wing/18/_inc_trim/count 10 P _wing/18/_inc_vect/0 0 P _wing/18/_inc_vect/1 0 P _wing/18/_inc_vect/2 0 P _wing/18/_inc_vect/3 0 P _wing/18/_inc_vect/4 0 P _wing/18/_inc_vect/5 0 P _wing/18/_inc_vect/6 0 P _wing/18/_inc_vect/7 0 P _wing/18/_inc_vect/8 0 P _wing/18/_inc_vect/9 0 P _wing/18/_inc_vect/count 10 P _wing/18/_incid_chrd_inc 0.0 P _wing/18/_incid_full_up 0.0 P _wing/18/_incidence/0 0.0 P _wing/18/_incidence/1 0.0 P _wing/18/_incidence/2 0.0 P _wing/18/_incidence/3 0.0 P _wing/18/_incidence/4 0.0 P _wing/18/_incidence/5 0.0 P _wing/18/_incidence/6 0.0 P _wing/18/_incidence/7 0.0 P _wing/18/_incidence/8 0.0 P _wing/18/_incidence/9 0.0 P _wing/18/_incidence/count 10 P _wing/18/_is_right_mult 1.0 P _wing/18/_oswalds_e 0.0 P _wing/18/_retract_max 0.0 P _wing/18/_retract_now 0.0 P _wing/18/_rudd1/0 0 P _wing/18/_rudd1/1 0 P _wing/18/_rudd1/2 0 P _wing/18/_rudd1/3 0 P _wing/18/_rudd1/4 0 P _wing/18/_rudd1/5 0 P _wing/18/_rudd1/6 0 P _wing/18/_rudd1/7 0 P _wing/18/_rudd1/8 0 P _wing/18/_rudd1/9 0 P _wing/18/_rudd1/count 10 P _wing/18/_rudd2/0 0 P _wing/18/_rudd2/1 0 P _wing/18/_rudd2/2 0 P _wing/18/_rudd2/3 0 P _wing/18/_rudd2/4 0 P _wing/18/_rudd2/5 0 P _wing/18/_rudd2/6 0 P _wing/18/_rudd2/7 0 P _wing/18/_rudd2/8 0 P _wing/18/_rudd2/9 0 P _wing/18/_rudd2/count 10 P _wing/18/_sbrk1/0 0 P _wing/18/_sbrk1/1 0 P _wing/18/_sbrk1/2 0 P _wing/18/_sbrk1/3 0 P _wing/18/_sbrk1/4 0 P _wing/18/_sbrk1/5 0 P _wing/18/_sbrk1/6 0 P _wing/18/_sbrk1/7 0 P _wing/18/_sbrk1/8 0 P _wing/18/_sbrk1/9 0 P _wing/18/_sbrk1/count 10 P _wing/18/_sbrk2/0 0 P _wing/18/_sbrk2/1 0 P _wing/18/_sbrk2/2 0 P _wing/18/_sbrk2/3 0 P _wing/18/_sbrk2/4 0 P _wing/18/_sbrk2/5 0 P _wing/18/_sbrk2/6 0 P _wing/18/_sbrk2/7 0 P _wing/18/_sbrk2/8 0 P _wing/18/_sbrk2/9 0 P _wing/18/_sbrk2/count 10 P _wing/18/_semilen_JND 0.0 P _wing/18/_semilen_SEG 12.303149223 P _wing/18/_sla1_effect 0.0 P _wing/18/_sla2_effect 0.0 P _wing/18/_slat1/0 0 P _wing/18/_slat1/1 0 P _wing/18/_slat1/2 0 P _wing/18/_slat1/3 0 P _wing/18/_slat1/4 0 P _wing/18/_slat1/5 0 P _wing/18/_slat1/6 0 P _wing/18/_slat1/7 0 P _wing/18/_slat1/8 0 P _wing/18/_slat1/9 0 P _wing/18/_slat1/count 10 P _wing/18/_slat2/0 0 P _wing/18/_slat2/1 0 P _wing/18/_slat2/2 0 P _wing/18/_slat2/3 0 P _wing/18/_slat2/4 0 P _wing/18/_slat2/5 0 P _wing/18/_slat2/6 0 P _wing/18/_slat2/7 0 P _wing/18/_slat2/8 0 P _wing/18/_slat2/9 0 P _wing/18/_slat2/count 10 P _wing/18/_spoi1/0 0 P _wing/18/_spoi1/1 0 P _wing/18/_spoi1/2 0 P _wing/18/_spoi1/3 0 P _wing/18/_spoi1/4 0 P _wing/18/_spoi1/5 0 P _wing/18/_spoi1/6 0 P _wing/18/_spoi1/7 0 P _wing/18/_spoi1/8 0 P _wing/18/_spoi1/9 0 P _wing/18/_spoi1/count 10 P _wing/18/_spoi2/0 0 P _wing/18/_spoi2/1 0 P _wing/18/_spoi2/2 0 P _wing/18/_spoi2/3 0 P _wing/18/_spoi2/4 0 P _wing/18/_spoi2/5 0 P _wing/18/_spoi2/6 0 P _wing/18/_spoi2/7 0 P _wing/18/_spoi2/8 0 P _wing/18/_spoi2/9 0 P _wing/18/_spoi2/count 10 P _wing/18/_sweep_design 0.0 P _wing/18/_sweep_full_aft 0.0 P _wing/18/_sweep_root_inc 0.0 P _wing/18/_var_dihed 0 P _wing/18/_var_incid 0 P _wing/18/_var_retract 0 P _wing/18/_var_sweep 0 P _wing/18/_yawbr/0 0 P _wing/18/_yawbr/1 0 P _wing/18/_yawbr/2 0 P _wing/18/_yawbr/3 0 P _wing/18/_yawbr/4 0 P _wing/18/_yawbr/5 0 P _wing/18/_yawbr/6 0 P _wing/18/_yawbr/7 0 P _wing/18/_yawbr/8 0 P _wing/18/_yawbr/9 0 P _wing/18/_yawbr/count 10 P _wing/8/_AR_geo 0.0 P _wing/8/_Croot 3.280839920 P _wing/8/_Ctip 3.280839920 P _wing/8/_TR_geo 0.0 P _wing/8/_afl_file_R0 NACA 2412 (popular).afl P _wing/8/_afl_file_R1 NACA 2412 (popular).afl P _wing/8/_afl_file_T0 NACA 2412 (popular).afl P _wing/8/_afl_file_T1 NACA 2412 (popular).afl P _wing/8/_ailn1/0 0 P _wing/8/_ailn1/1 0 P _wing/8/_ailn1/2 0 P _wing/8/_ailn1/3 0 P _wing/8/_ailn1/4 0 P _wing/8/_ailn1/5 0 P _wing/8/_ailn1/6 1 P _wing/8/_ailn1/7 1 P _wing/8/_ailn1/8 0 P _wing/8/_ailn1/9 0 P _wing/8/_ailn1/count 10 P _wing/8/_ailn2/0 0 P _wing/8/_ailn2/1 0 P _wing/8/_ailn2/2 0 P _wing/8/_ailn2/3 0 P _wing/8/_ailn2/4 0 P _wing/8/_ailn2/5 0 P _wing/8/_ailn2/6 0 P _wing/8/_ailn2/7 0 P _wing/8/_ailn2/8 0 P _wing/8/_ailn2/9 0 P _wing/8/_ailn2/count 10 P _wing/8/_average_mac 0.0 P _wing/8/_base_wash_subsonic 0.0 P _wing/8/_base_wash_supersonic 0.0 P _wing/8/_c_off_for_RIB/0 0.0 P _wing/8/_c_off_for_RIB/1 0.0 P _wing/8/_c_off_for_RIB/10 0.0 P _wing/8/_c_off_for_RIB/2 0.0 P _wing/8/_c_off_for_RIB/3 0.0 P _wing/8/_c_off_for_RIB/4 0.0 P _wing/8/_c_off_for_RIB/5 0.0 P _wing/8/_c_off_for_RIB/6 0.0 P _wing/8/_c_off_for_RIB/7 0.0 P _wing/8/_c_off_for_RIB/8 0.0 P _wing/8/_c_off_for_RIB/9 0.0 P _wing/8/_c_off_for_RIB/count 11 P _wing/8/_c_rat_for_RIB/0 1.0 P _wing/8/_c_rat_for_RIB/1 1.0 P _wing/8/_c_rat_for_RIB/10 1.0 P _wing/8/_c_rat_for_RIB/2 1.0 P _wing/8/_c_rat_for_RIB/3 1.0 P _wing/8/_c_rat_for_RIB/4 1.0 P _wing/8/_c_rat_for_RIB/5 1.0 P _wing/8/_c_rat_for_RIB/6 1.0 P _wing/8/_c_rat_for_RIB/7 1.0 P _wing/8/_c_rat_for_RIB/8 1.0 P _wing/8/_c_rat_for_RIB/9 1.0 P _wing/8/_c_rat_for_RIB/count 11 P _wing/8/_chord_for_RIB/0 3.280839920 P _wing/8/_chord_for_RIB/1 3.280839920 P _wing/8/_chord_for_RIB/10 0.0 P _wing/8/_chord_for_RIB/2 3.280839920 P _wing/8/_chord_for_RIB/3 3.280839920 P _wing/8/_chord_for_RIB/4 3.280839920 P _wing/8/_chord_for_RIB/5 3.280839920 P _wing/8/_chord_for_RIB/6 3.280839920 P _wing/8/_chord_for_RIB/7 3.280839920 P _wing/8/_chord_for_RIB/8 3.280839920 P _wing/8/_chord_for_RIB/9 0.0 P _wing/8/_chord_for_RIB/count 11 P _wing/8/_chord_piv 0.250000000 P _wing/8/_crib_x_arm/0 0.0 P _wing/8/_crib_x_arm/1 -6.561679840 P _wing/8/_crib_x_arm/10 0.0 P _wing/8/_crib_x_arm/2 -13.123359680 P _wing/8/_crib_x_arm/3 -19.685039520 P _wing/8/_crib_x_arm/4 -26.246719360 P _wing/8/_crib_x_arm/5 -32.808399200 P _wing/8/_crib_x_arm/6 -39.370079041 P _wing/8/_crib_x_arm/7 -45.931758881 P _wing/8/_crib_x_arm/8 -52.493438721 P _wing/8/_crib_x_arm/9 0.0 P _wing/8/_crib_x_arm/count 11 P _wing/8/_crib_y_arm/0 0.0 P _wing/8/_crib_y_arm/1 0.0 P _wing/8/_crib_y_arm/10 0.0 P _wing/8/_crib_y_arm/2 0.0 P _wing/8/_crib_y_arm/3 0.0 P _wing/8/_crib_y_arm/4 0.0 P _wing/8/_crib_y_arm/5 0.0 P _wing/8/_crib_y_arm/6 0.0 P _wing/8/_crib_y_arm/7 0.0 P _wing/8/_crib_y_arm/8 0.0 P _wing/8/_crib_y_arm/9 0.0 P _wing/8/_crib_y_arm/count 11 P _wing/8/_crib_z_arm/0 0.0 P _wing/8/_crib_z_arm/1 0.0 P _wing/8/_crib_z_arm/10 0.0 P _wing/8/_crib_z_arm/2 0.0 P _wing/8/_crib_z_arm/3 0.0 P _wing/8/_crib_z_arm/4 0.0 P _wing/8/_crib_z_arm/5 0.0 P _wing/8/_crib_z_arm/6 0.0 P _wing/8/_crib_z_arm/7 0.0 P _wing/8/_crib_z_arm/8 0.0 P _wing/8/_crib_z_arm/9 0.0 P _wing/8/_crib_z_arm/count 11 P _wing/8/_delta_fac 0.0 P _wing/8/_dihed_design 0.0 P _wing/8/_dihed_full_up 180.0 P _wing/8/_dihed_root_inc 0.0 P _wing/8/_elev1/0 0 P _wing/8/_elev1/1 0 P _wing/8/_elev1/2 0 P _wing/8/_elev1/3 0 P _wing/8/_elev1/4 0 P _wing/8/_elev1/5 0 P _wing/8/_elev1/6 0 P _wing/8/_elev1/7 0 P _wing/8/_elev1/8 0 P _wing/8/_elev1/9 0 P _wing/8/_elev1/count 10 P _wing/8/_elev2/0 0 P _wing/8/_elev2/1 0 P _wing/8/_elev2/2 0 P _wing/8/_elev2/3 0 P _wing/8/_elev2/4 0 P _wing/8/_elev2/5 0 P _wing/8/_elev2/6 0 P _wing/8/_elev2/7 0 P _wing/8/_elev2/8 0 P _wing/8/_elev2/9 0 P _wing/8/_elev2/count 10 P _wing/8/_els 8 P _wing/8/_flap1/0 1 P _wing/8/_flap1/1 0 P _wing/8/_flap1/2 0 P _wing/8/_flap1/3 0 P _wing/8/_flap1/4 0 P _wing/8/_flap1/5 0 P _wing/8/_flap1/6 0 P _wing/8/_flap1/7 0 P _wing/8/_flap1/8 0 P _wing/8/_flap1/9 0 P _wing/8/_flap1/count 10 P _wing/8/_flap2/0 0 P _wing/8/_flap2/1 0 P _wing/8/_flap2/2 0 P _wing/8/_flap2/3 0 P _wing/8/_flap2/4 0 P _wing/8/_flap2/5 0 P _wing/8/_flap2/6 0 P _wing/8/_flap2/7 0 P _wing/8/_flap2/8 0 P _wing/8/_flap2/9 0 P _wing/8/_flap2/count 10 P _wing/8/_inc_ailn1/0 0 P _wing/8/_inc_ailn1/1 0 P _wing/8/_inc_ailn1/2 0 P _wing/8/_inc_ailn1/3 0 P _wing/8/_inc_ailn1/4 0 P _wing/8/_inc_ailn1/5 0 P _wing/8/_inc_ailn1/6 0 P _wing/8/_inc_ailn1/7 0 P _wing/8/_inc_ailn1/8 0 P _wing/8/_inc_ailn1/9 0 P _wing/8/_inc_ailn1/count 10 P _wing/8/_inc_ailn2/0 0 P _wing/8/_inc_ailn2/1 0 P _wing/8/_inc_ailn2/2 0 P _wing/8/_inc_ailn2/3 0 P _wing/8/_inc_ailn2/4 0 P _wing/8/_inc_ailn2/5 0 P _wing/8/_inc_ailn2/6 0 P _wing/8/_inc_ailn2/7 0 P _wing/8/_inc_ailn2/8 0 P _wing/8/_inc_ailn2/9 0 P _wing/8/_inc_ailn2/count 10 P _wing/8/_inc_elev1/0 0 P _wing/8/_inc_elev1/1 0 P _wing/8/_inc_elev1/2 0 P _wing/8/_inc_elev1/3 0 P _wing/8/_inc_elev1/4 0 P _wing/8/_inc_elev1/5 0 P _wing/8/_inc_elev1/6 0 P _wing/8/_inc_elev1/7 0 P _wing/8/_inc_elev1/8 0 P _wing/8/_inc_elev1/9 0 P _wing/8/_inc_elev1/count 10 P _wing/8/_inc_elev2/0 0 P _wing/8/_inc_elev2/1 0 P _wing/8/_inc_elev2/2 0 P _wing/8/_inc_elev2/3 0 P _wing/8/_inc_elev2/4 0 P _wing/8/_inc_elev2/5 0 P _wing/8/_inc_elev2/6 0 P _wing/8/_inc_elev2/7 0 P _wing/8/_inc_elev2/8 0 P _wing/8/_inc_elev2/9 0 P _wing/8/_inc_elev2/count 10 P _wing/8/_inc_incid/0 0.0 P _wing/8/_inc_incid/1 0.0 P _wing/8/_inc_incid/2 0.0 P _wing/8/_inc_incid/3 0.0 P _wing/8/_inc_incid/4 0.0 P _wing/8/_inc_incid/5 0.0 P _wing/8/_inc_incid/6 0.0 P _wing/8/_inc_incid/7 0.0 P _wing/8/_inc_incid/8 0.0 P _wing/8/_inc_incid/9 0.0 P _wing/8/_inc_incid/count 10 P _wing/8/_inc_rudd1/0 0 P _wing/8/_inc_rudd1/1 0 P _wing/8/_inc_rudd1/2 0 P _wing/8/_inc_rudd1/3 0 P _wing/8/_inc_rudd1/4 0 P _wing/8/_inc_rudd1/5 0 P _wing/8/_inc_rudd1/6 0 P _wing/8/_inc_rudd1/7 0 P _wing/8/_inc_rudd1/8 0 P _wing/8/_inc_rudd1/9 0 P _wing/8/_inc_rudd1/count 10 P _wing/8/_inc_rudd2/0 0 P _wing/8/_inc_rudd2/1 0 P _wing/8/_inc_rudd2/2 0 P _wing/8/_inc_rudd2/3 0 P _wing/8/_inc_rudd2/4 0 P _wing/8/_inc_rudd2/5 0 P _wing/8/_inc_rudd2/6 0 P _wing/8/_inc_rudd2/7 0 P _wing/8/_inc_rudd2/8 0 P _wing/8/_inc_rudd2/9 0 P _wing/8/_inc_rudd2/count 10 P _wing/8/_inc_trim/0 0 P _wing/8/_inc_trim/1 0 P _wing/8/_inc_trim/2 0 P _wing/8/_inc_trim/3 0 P _wing/8/_inc_trim/4 0 P _wing/8/_inc_trim/5 0 P _wing/8/_inc_trim/6 0 P _wing/8/_inc_trim/7 0 P _wing/8/_inc_trim/8 0 P _wing/8/_inc_trim/9 0 P _wing/8/_inc_trim/count 10 P _wing/8/_inc_vect/0 0 P _wing/8/_inc_vect/1 0 P _wing/8/_inc_vect/2 0 P _wing/8/_inc_vect/3 0 P _wing/8/_inc_vect/4 0 P _wing/8/_inc_vect/5 0 P _wing/8/_inc_vect/6 0 P _wing/8/_inc_vect/7 0 P _wing/8/_inc_vect/8 0 P _wing/8/_inc_vect/9 0 P _wing/8/_inc_vect/count 10 P _wing/8/_incid_chrd_inc 0.0 P _wing/8/_incid_full_up 90.0 P _wing/8/_incidence/0 0.0 P _wing/8/_incidence/1 0.0 P _wing/8/_incidence/2 0.0 P _wing/8/_incidence/3 0.0 P _wing/8/_incidence/4 0.0 P _wing/8/_incidence/5 0.0 P _wing/8/_incidence/6 0.0 P _wing/8/_incidence/7 0.0 P _wing/8/_incidence/8 0.0 P _wing/8/_incidence/9 0.0 P _wing/8/_incidence/count 10 P _wing/8/_is_right_mult -1.0 P _wing/8/_oswalds_e 0.0 P _wing/8/_retract_max 1.0 P _wing/8/_retract_now 0.0 P _wing/8/_rudd1/0 0 P _wing/8/_rudd1/1 0 P _wing/8/_rudd1/2 0 P _wing/8/_rudd1/3 0 P _wing/8/_rudd1/4 0 P _wing/8/_rudd1/5 0 P _wing/8/_rudd1/6 0 P _wing/8/_rudd1/7 0 P _wing/8/_rudd1/8 0 P _wing/8/_rudd1/9 0 P _wing/8/_rudd1/count 10 P _wing/8/_rudd2/0 0 P _wing/8/_rudd2/1 0 P _wing/8/_rudd2/2 0 P _wing/8/_rudd2/3 0 P _wing/8/_rudd2/4 0 P _wing/8/_rudd2/5 0 P _wing/8/_rudd2/6 0 P _wing/8/_rudd2/7 0 P _wing/8/_rudd2/8 0 P _wing/8/_rudd2/9 0 P _wing/8/_rudd2/count 10 P _wing/8/_sbrk1/0 0 P _wing/8/_sbrk1/1 0 P _wing/8/_sbrk1/2 0 P _wing/8/_sbrk1/3 0 P _wing/8/_sbrk1/4 0 P _wing/8/_sbrk1/5 0 P _wing/8/_sbrk1/6 0 P _wing/8/_sbrk1/7 0 P _wing/8/_sbrk1/8 0 P _wing/8/_sbrk1/9 0 P _wing/8/_sbrk1/count 10 P _wing/8/_sbrk2/0 0 P _wing/8/_sbrk2/1 0 P _wing/8/_sbrk2/2 0 P _wing/8/_sbrk2/3 0 P _wing/8/_sbrk2/4 0 P _wing/8/_sbrk2/5 0 P _wing/8/_sbrk2/6 0 P _wing/8/_sbrk2/7 0 P _wing/8/_sbrk2/8 0 P _wing/8/_sbrk2/9 0 P _wing/8/_sbrk2/count 10 P _wing/8/_semilen_JND 0.0 P _wing/8/_semilen_SEG 52.493438721 P _wing/8/_sla1_effect 0.0 P _wing/8/_sla2_effect 0.0 P _wing/8/_slat1/0 0 P _wing/8/_slat1/1 0 P _wing/8/_slat1/2 0 P _wing/8/_slat1/3 0 P _wing/8/_slat1/4 0 P _wing/8/_slat1/5 0 P _wing/8/_slat1/6 0 P _wing/8/_slat1/7 0 P _wing/8/_slat1/8 0 P _wing/8/_slat1/9 0 P _wing/8/_slat1/count 10 P _wing/8/_slat2/0 0 P _wing/8/_slat2/1 0 P _wing/8/_slat2/2 0 P _wing/8/_slat2/3 0 P _wing/8/_slat2/4 0 P _wing/8/_slat2/5 0 P _wing/8/_slat2/6 0 P _wing/8/_slat2/7 0 P _wing/8/_slat2/8 0 P _wing/8/_slat2/9 0 P _wing/8/_slat2/count 10 P _wing/8/_spoi1/0 0 P _wing/8/_spoi1/1 0 P _wing/8/_spoi1/2 0 P _wing/8/_spoi1/3 0 P _wing/8/_spoi1/4 0 P _wing/8/_spoi1/5 0 P _wing/8/_spoi1/6 0 P _wing/8/_spoi1/7 0 P _wing/8/_spoi1/8 0 P _wing/8/_spoi1/9 0 P _wing/8/_spoi1/count 10 P _wing/8/_spoi2/0 0 P _wing/8/_spoi2/1 0 P _wing/8/_spoi2/2 0 P _wing/8/_spoi2/3 0 P _wing/8/_spoi2/4 0 P _wing/8/_spoi2/5 0 P _wing/8/_spoi2/6 0 P _wing/8/_spoi2/7 0 P _wing/8/_spoi2/8 0 P _wing/8/_spoi2/9 0 P _wing/8/_spoi2/count 10 P _wing/8/_sweep_design 0.0 P _wing/8/_sweep_full_aft 90.0 P _wing/8/_sweep_root_inc 0.0 P _wing/8/_var_dihed 1 P _wing/8/_var_incid 1 P _wing/8/_var_retract 0 P _wing/8/_var_sweep 1 P _wing/8/_yawbr/0 0 P _wing/8/_yawbr/1 0 P _wing/8/_yawbr/2 0 P _wing/8/_yawbr/3 0 P _wing/8/_yawbr/4 0 P _wing/8/_yawbr/5 0 P _wing/8/_yawbr/6 0 P _wing/8/_yawbr/7 0 P _wing/8/_yawbr/8 0 P _wing/8/_yawbr/9 0 P _wing/8/_yawbr/count 10 P _wing/9/_AR_geo 0.0 P _wing/9/_Croot 3.280839920 P _wing/9/_Ctip 3.280839920 P _wing/9/_TR_geo 0.0 P _wing/9/_afl_file_R0 NACA 2412 (popular).afl P _wing/9/_afl_file_R1 NACA 2412 (popular).afl P _wing/9/_afl_file_T0 NACA 2412 (popular).afl P _wing/9/_afl_file_T1 NACA 2412 (popular).afl P _wing/9/_ailn1/0 0 P _wing/9/_ailn1/1 0 P _wing/9/_ailn1/2 0 P _wing/9/_ailn1/3 0 P _wing/9/_ailn1/4 0 P _wing/9/_ailn1/5 0 P _wing/9/_ailn1/6 1 P _wing/9/_ailn1/7 1 P _wing/9/_ailn1/8 0 P _wing/9/_ailn1/9 0 P _wing/9/_ailn1/count 10 P _wing/9/_ailn2/0 0 P _wing/9/_ailn2/1 0 P _wing/9/_ailn2/2 0 P _wing/9/_ailn2/3 0 P _wing/9/_ailn2/4 0 P _wing/9/_ailn2/5 0 P _wing/9/_ailn2/6 0 P _wing/9/_ailn2/7 0 P _wing/9/_ailn2/8 0 P _wing/9/_ailn2/9 0 P _wing/9/_ailn2/count 10 P _wing/9/_average_mac 0.0 P _wing/9/_base_wash_subsonic 0.0 P _wing/9/_base_wash_supersonic 0.0 P _wing/9/_c_off_for_RIB/0 0.0 P _wing/9/_c_off_for_RIB/1 0.0 P _wing/9/_c_off_for_RIB/10 0.0 P _wing/9/_c_off_for_RIB/2 0.0 P _wing/9/_c_off_for_RIB/3 0.0 P _wing/9/_c_off_for_RIB/4 0.0 P _wing/9/_c_off_for_RIB/5 0.0 P _wing/9/_c_off_for_RIB/6 0.0 P _wing/9/_c_off_for_RIB/7 0.0 P _wing/9/_c_off_for_RIB/8 0.0 P _wing/9/_c_off_for_RIB/9 0.0 P _wing/9/_c_off_for_RIB/count 11 P _wing/9/_c_rat_for_RIB/0 1.0 P _wing/9/_c_rat_for_RIB/1 1.0 P _wing/9/_c_rat_for_RIB/10 1.0 P _wing/9/_c_rat_for_RIB/2 1.0 P _wing/9/_c_rat_for_RIB/3 1.0 P _wing/9/_c_rat_for_RIB/4 1.0 P _wing/9/_c_rat_for_RIB/5 1.0 P _wing/9/_c_rat_for_RIB/6 1.0 P _wing/9/_c_rat_for_RIB/7 1.0 P _wing/9/_c_rat_for_RIB/8 1.0 P _wing/9/_c_rat_for_RIB/9 1.0 P _wing/9/_c_rat_for_RIB/count 11 P _wing/9/_chord_for_RIB/0 3.280839920 P _wing/9/_chord_for_RIB/1 3.280839920 P _wing/9/_chord_for_RIB/10 0.0 P _wing/9/_chord_for_RIB/2 3.280839920 P _wing/9/_chord_for_RIB/3 3.280839920 P _wing/9/_chord_for_RIB/4 3.280839920 P _wing/9/_chord_for_RIB/5 3.280839920 P _wing/9/_chord_for_RIB/6 3.280839920 P _wing/9/_chord_for_RIB/7 3.280839920 P _wing/9/_chord_for_RIB/8 3.280839920 P _wing/9/_chord_for_RIB/9 0.0 P _wing/9/_chord_for_RIB/count 11 P _wing/9/_chord_piv 0.250000000 P _wing/9/_crib_x_arm/0 0.0 P _wing/9/_crib_x_arm/1 6.561679840 P _wing/9/_crib_x_arm/10 0.0 P _wing/9/_crib_x_arm/2 13.123359680 P _wing/9/_crib_x_arm/3 19.685039520 P _wing/9/_crib_x_arm/4 26.246719360 P _wing/9/_crib_x_arm/5 32.808399200 P _wing/9/_crib_x_arm/6 39.370079041 P _wing/9/_crib_x_arm/7 45.931758881 P _wing/9/_crib_x_arm/8 52.493438721 P _wing/9/_crib_x_arm/9 0.0 P _wing/9/_crib_x_arm/count 11 P _wing/9/_crib_y_arm/0 0.0 P _wing/9/_crib_y_arm/1 0.0 P _wing/9/_crib_y_arm/10 0.0 P _wing/9/_crib_y_arm/2 0.0 P _wing/9/_crib_y_arm/3 0.0 P _wing/9/_crib_y_arm/4 0.0 P _wing/9/_crib_y_arm/5 0.0 P _wing/9/_crib_y_arm/6 0.0 P _wing/9/_crib_y_arm/7 0.0 P _wing/9/_crib_y_arm/8 0.0 P _wing/9/_crib_y_arm/9 0.0 P _wing/9/_crib_y_arm/count 11 P _wing/9/_crib_z_arm/0 0.0 P _wing/9/_crib_z_arm/1 0.0 P _wing/9/_crib_z_arm/10 0.0 P _wing/9/_crib_z_arm/2 0.0 P _wing/9/_crib_z_arm/3 0.0 P _wing/9/_crib_z_arm/4 0.0 P _wing/9/_crib_z_arm/5 0.0 P _wing/9/_crib_z_arm/6 0.0 P _wing/9/_crib_z_arm/7 0.0 P _wing/9/_crib_z_arm/8 0.0 P _wing/9/_crib_z_arm/9 0.0 P _wing/9/_crib_z_arm/count 11 P _wing/9/_delta_fac 0.0 P _wing/9/_dihed_design 0.0 P _wing/9/_dihed_full_up 180.0 P _wing/9/_dihed_root_inc 0.0 P _wing/9/_elev1/0 0 P _wing/9/_elev1/1 0 P _wing/9/_elev1/2 0 P _wing/9/_elev1/3 0 P _wing/9/_elev1/4 0 P _wing/9/_elev1/5 0 P _wing/9/_elev1/6 0 P _wing/9/_elev1/7 0 P _wing/9/_elev1/8 0 P _wing/9/_elev1/9 0 P _wing/9/_elev1/count 10 P _wing/9/_elev2/0 0 P _wing/9/_elev2/1 0 P _wing/9/_elev2/2 0 P _wing/9/_elev2/3 0 P _wing/9/_elev2/4 0 P _wing/9/_elev2/5 0 P _wing/9/_elev2/6 0 P _wing/9/_elev2/7 0 P _wing/9/_elev2/8 0 P _wing/9/_elev2/9 0 P _wing/9/_elev2/count 10 P _wing/9/_els 8 P _wing/9/_flap1/0 1 P _wing/9/_flap1/1 0 P _wing/9/_flap1/2 0 P _wing/9/_flap1/3 0 P _wing/9/_flap1/4 0 P _wing/9/_flap1/5 0 P _wing/9/_flap1/6 0 P _wing/9/_flap1/7 0 P _wing/9/_flap1/8 0 P _wing/9/_flap1/9 0 P _wing/9/_flap1/count 10 P _wing/9/_flap2/0 0 P _wing/9/_flap2/1 0 P _wing/9/_flap2/2 0 P _wing/9/_flap2/3 0 P _wing/9/_flap2/4 0 P _wing/9/_flap2/5 0 P _wing/9/_flap2/6 0 P _wing/9/_flap2/7 0 P _wing/9/_flap2/8 0 P _wing/9/_flap2/9 0 P _wing/9/_flap2/count 10 P _wing/9/_inc_ailn1/0 0 P _wing/9/_inc_ailn1/1 0 P _wing/9/_inc_ailn1/2 0 P _wing/9/_inc_ailn1/3 0 P _wing/9/_inc_ailn1/4 0 P _wing/9/_inc_ailn1/5 0 P _wing/9/_inc_ailn1/6 0 P _wing/9/_inc_ailn1/7 0 P _wing/9/_inc_ailn1/8 0 P _wing/9/_inc_ailn1/9 0 P _wing/9/_inc_ailn1/count 10 P _wing/9/_inc_ailn2/0 0 P _wing/9/_inc_ailn2/1 0 P _wing/9/_inc_ailn2/2 0 P _wing/9/_inc_ailn2/3 0 P _wing/9/_inc_ailn2/4 0 P _wing/9/_inc_ailn2/5 0 P _wing/9/_inc_ailn2/6 0 P _wing/9/_inc_ailn2/7 0 P _wing/9/_inc_ailn2/8 0 P _wing/9/_inc_ailn2/9 0 P _wing/9/_inc_ailn2/count 10 P _wing/9/_inc_elev1/0 0 P _wing/9/_inc_elev1/1 0 P _wing/9/_inc_elev1/2 0 P _wing/9/_inc_elev1/3 0 P _wing/9/_inc_elev1/4 0 P _wing/9/_inc_elev1/5 0 P _wing/9/_inc_elev1/6 0 P _wing/9/_inc_elev1/7 0 P _wing/9/_inc_elev1/8 0 P _wing/9/_inc_elev1/9 0 P _wing/9/_inc_elev1/count 10 P _wing/9/_inc_elev2/0 0 P _wing/9/_inc_elev2/1 0 P _wing/9/_inc_elev2/2 0 P _wing/9/_inc_elev2/3 0 P _wing/9/_inc_elev2/4 0 P _wing/9/_inc_elev2/5 0 P _wing/9/_inc_elev2/6 0 P _wing/9/_inc_elev2/7 0 P _wing/9/_inc_elev2/8 0 P _wing/9/_inc_elev2/9 0 P _wing/9/_inc_elev2/count 10 P _wing/9/_inc_incid/0 0.0 P _wing/9/_inc_incid/1 0.0 P _wing/9/_inc_incid/2 0.0 P _wing/9/_inc_incid/3 0.0 P _wing/9/_inc_incid/4 0.0 P _wing/9/_inc_incid/5 0.0 P _wing/9/_inc_incid/6 0.0 P _wing/9/_inc_incid/7 0.0 P _wing/9/_inc_incid/8 0.0 P _wing/9/_inc_incid/9 0.0 P _wing/9/_inc_incid/count 10 P _wing/9/_inc_rudd1/0 0 P _wing/9/_inc_rudd1/1 0 P _wing/9/_inc_rudd1/2 0 P _wing/9/_inc_rudd1/3 0 P _wing/9/_inc_rudd1/4 0 P _wing/9/_inc_rudd1/5 0 P _wing/9/_inc_rudd1/6 0 P _wing/9/_inc_rudd1/7 0 P _wing/9/_inc_rudd1/8 0 P _wing/9/_inc_rudd1/9 0 P _wing/9/_inc_rudd1/count 10 P _wing/9/_inc_rudd2/0 0 P _wing/9/_inc_rudd2/1 0 P _wing/9/_inc_rudd2/2 0 P _wing/9/_inc_rudd2/3 0 P _wing/9/_inc_rudd2/4 0 P _wing/9/_inc_rudd2/5 0 P _wing/9/_inc_rudd2/6 0 P _wing/9/_inc_rudd2/7 0 P _wing/9/_inc_rudd2/8 0 P _wing/9/_inc_rudd2/9 0 P _wing/9/_inc_rudd2/count 10 P _wing/9/_inc_trim/0 0 P _wing/9/_inc_trim/1 0 P _wing/9/_inc_trim/2 0 P _wing/9/_inc_trim/3 0 P _wing/9/_inc_trim/4 0 P _wing/9/_inc_trim/5 0 P _wing/9/_inc_trim/6 0 P _wing/9/_inc_trim/7 0 P _wing/9/_inc_trim/8 0 P _wing/9/_inc_trim/9 0 P _wing/9/_inc_trim/count 10 P _wing/9/_inc_vect/0 0 P _wing/9/_inc_vect/1 0 P _wing/9/_inc_vect/2 0 P _wing/9/_inc_vect/3 0 P _wing/9/_inc_vect/4 0 P _wing/9/_inc_vect/5 0 P _wing/9/_inc_vect/6 0 P _wing/9/_inc_vect/7 0 P _wing/9/_inc_vect/8 0 P _wing/9/_inc_vect/9 0 P _wing/9/_inc_vect/count 10 P _wing/9/_incid_chrd_inc 0.0 P _wing/9/_incid_full_up 90.0 P _wing/9/_incidence/0 0.0 P _wing/9/_incidence/1 0.0 P _wing/9/_incidence/2 0.0 P _wing/9/_incidence/3 0.0 P _wing/9/_incidence/4 0.0 P _wing/9/_incidence/5 0.0 P _wing/9/_incidence/6 0.0 P _wing/9/_incidence/7 0.0 P _wing/9/_incidence/8 0.0 P _wing/9/_incidence/9 0.0 P _wing/9/_incidence/count 10 P _wing/9/_is_right_mult 1.0 P _wing/9/_oswalds_e 0.0 P _wing/9/_retract_max 1.0 P _wing/9/_retract_now 0.0 P _wing/9/_rudd1/0 0 P _wing/9/_rudd1/1 0 P _wing/9/_rudd1/2 0 P _wing/9/_rudd1/3 0 P _wing/9/_rudd1/4 0 P _wing/9/_rudd1/5 0 P _wing/9/_rudd1/6 0 P _wing/9/_rudd1/7 0 P _wing/9/_rudd1/8 0 P _wing/9/_rudd1/9 0 P _wing/9/_rudd1/count 10 P _wing/9/_rudd2/0 0 P _wing/9/_rudd2/1 0 P _wing/9/_rudd2/2 0 P _wing/9/_rudd2/3 0 P _wing/9/_rudd2/4 0 P _wing/9/_rudd2/5 0 P _wing/9/_rudd2/6 0 P _wing/9/_rudd2/7 0 P _wing/9/_rudd2/8 0 P _wing/9/_rudd2/9 0 P _wing/9/_rudd2/count 10 P _wing/9/_sbrk1/0 0 P _wing/9/_sbrk1/1 0 P _wing/9/_sbrk1/2 0 P _wing/9/_sbrk1/3 0 P _wing/9/_sbrk1/4 0 P _wing/9/_sbrk1/5 0 P _wing/9/_sbrk1/6 0 P _wing/9/_sbrk1/7 0 P _wing/9/_sbrk1/8 0 P _wing/9/_sbrk1/9 0 P _wing/9/_sbrk1/count 10 P _wing/9/_sbrk2/0 0 P _wing/9/_sbrk2/1 0 P _wing/9/_sbrk2/2 0 P _wing/9/_sbrk2/3 0 P _wing/9/_sbrk2/4 0 P _wing/9/_sbrk2/5 0 P _wing/9/_sbrk2/6 0 P _wing/9/_sbrk2/7 0 P _wing/9/_sbrk2/8 0 P _wing/9/_sbrk2/9 0 P _wing/9/_sbrk2/count 10 P _wing/9/_semilen_JND 0.0 P _wing/9/_semilen_SEG 52.493438721 P _wing/9/_sla1_effect 0.0 P _wing/9/_sla2_effect 0.0 P _wing/9/_slat1/0 0 P _wing/9/_slat1/1 0 P _wing/9/_slat1/2 0 P _wing/9/_slat1/3 0 P _wing/9/_slat1/4 0 P _wing/9/_slat1/5 0 P _wing/9/_slat1/6 0 P _wing/9/_slat1/7 0 P _wing/9/_slat1/8 0 P _wing/9/_slat1/9 0 P _wing/9/_slat1/count 10 P _wing/9/_slat2/0 0 P _wing/9/_slat2/1 0 P _wing/9/_slat2/2 0 P _wing/9/_slat2/3 0 P _wing/9/_slat2/4 0 P _wing/9/_slat2/5 0 P _wing/9/_slat2/6 0 P _wing/9/_slat2/7 0 P _wing/9/_slat2/8 0 P _wing/9/_slat2/9 0 P _wing/9/_slat2/count 10 P _wing/9/_spoi1/0 0 P _wing/9/_spoi1/1 0 P _wing/9/_spoi1/2 0 P _wing/9/_spoi1/3 0 P _wing/9/_spoi1/4 0 P _wing/9/_spoi1/5 0 P _wing/9/_spoi1/6 0 P _wing/9/_spoi1/7 0 P _wing/9/_spoi1/8 0 P _wing/9/_spoi1/9 0 P _wing/9/_spoi1/count 10 P _wing/9/_spoi2/0 0 P _wing/9/_spoi2/1 0 P _wing/9/_spoi2/2 0 P _wing/9/_spoi2/3 0 P _wing/9/_spoi2/4 0 P _wing/9/_spoi2/5 0 P _wing/9/_spoi2/6 0 P _wing/9/_spoi2/7 0 P _wing/9/_spoi2/8 0 P _wing/9/_spoi2/9 0 P _wing/9/_spoi2/count 10 P _wing/9/_sweep_design 0.0 P _wing/9/_sweep_full_aft 90.0 P _wing/9/_sweep_root_inc 0.0 P _wing/9/_var_dihed 1 P _wing/9/_var_incid 1 P _wing/9/_var_retract 0 P _wing/9/_var_sweep 1 P _wing/9/_yawbr/0 0 P _wing/9/_yawbr/1 0 P _wing/9/_yawbr/2 0 P _wing/9/_yawbr/3 0 P _wing/9/_yawbr/4 0 P _wing/9/_yawbr/5 0 P _wing/9/_yawbr/6 0 P _wing/9/_yawbr/7 0 P _wing/9/_yawbr/8 0 P _wing/9/_yawbr/9 0 P _wing/9/_yawbr/count 10 P _wing/count 56 P _wpna/count 24 P acf/_AShi_G_k 0.0 P acf/_AShi_Gdot_k 0.0 P acf/_AShi_alpha_k 0.0 P acf/_AShi_alphadot_k 0.0 P acf/_AShi_beta_k 0.0 P acf/_AShi_betadot_k 0.0 P acf/_AShi_hdng_rat 1.0 P acf/_AShi_max_G 0.0 P acf/_AShi_max_alpha 0.0 P acf/_AShi_max_beta 0.0 P acf/_AShi_max_phidot 0.0 P acf/_AShi_phi_V 1.0 P acf/_AShi_phidot_k 0.0 P acf/_AShi_psi_V 1.0 P acf/_AShi_ptch_rat 1.0 P acf/_AShi_roll_rat 1.0 P acf/_AShi_the_V 1.0 P acf/_ASlo_hdng_rat 1.0 P acf/_ASlo_max_phi 0.0 P acf/_ASlo_max_phidot 0.0 P acf/_ASlo_max_psidot 0.0 P acf/_ASlo_max_the 0.0 P acf/_ASlo_max_thedot 0.0 P acf/_ASlo_phi_V 0.0 P acf/_ASlo_phi_k 0.0 P acf/_ASlo_phidot_k 0.0 P acf/_ASlo_psi_V 0.0 P acf/_ASlo_psidot_k 0.0 P acf/_ASlo_ptch_rat 1.0 P acf/_ASlo_roll_rat 1.0 P acf/_ASlo_the_V 0.0 P acf/_ASlo_the_k 0.0 P acf/_ASlo_thedot_k 0.0 P acf/_B_ref 0.0 P acf/_CHT_is_C 1 P acf/_C_ref 0.0 P acf/_EGT_is_C 0 P acf/_Gneg 0.0 P acf/_Gpos 99.900001526 P acf/_HUD_del_x 240.0 P acf/_HUD_del_y 120.0 P acf/_ITT_is_C 1 P acf/_Jxx_unitmass 0.0 P acf/_Jyy_unitmass 0.0 P acf/_Jzz_unitmass 0.0 P acf/_Mmo 0.0 P acf/_RSC_idlespeed_ENGN 0.0 P acf/_RSC_maxgreen_ENGN 1.0 P acf/_RSC_mingov_ENGN 0.0 P acf/_RSC_mingreen_ENGN 0.0 P acf/_RSC_redline_ENGN 1.0 P acf/_SFC_ROC 9.970000267 P acf/_SFC_alt_hi_JET 0.0 P acf/_SFC_alt_hi_PRP 0.0 P acf/_SFC_alt_lo_JET 0.0 P acf/_SFC_alt_lo_PRP 0.0 P acf/_SFC_full_hi_JET 0.550000012 P acf/_SFC_full_hi_PRP 0.439999998 P acf/_SFC_full_lo_JET 0.550000012 P acf/_SFC_full_lo_PRP 0.439999998 P acf/_SFC_half_hi_JET 0.550000012 P acf/_SFC_half_hi_PRP 0.439999998 P acf/_SFC_half_lo_JET 0.550000012 P acf/_SFC_half_lo_PRP 0.439999998 P acf/_S_ref 0.0 P acf/_TOGA_discos_servos 0 P acf/_TRQ_is_pcnt 0 P acf/_V_ref_ms 0.0 P acf/_Vfe1_kts 999.0 P acf/_Vfem_kts 0.0 P acf/_Vle_kts 99999.0 P acf/_Vmca_kts 0.0 P acf/_Vne_kts 400.0 P acf/_Vno_kts 0.0 P acf/_Vs_kts 0.0 P acf/_Vso_kts 0.0 P acf/_Vyse_kts 0.0 P acf/_add_def_exceeds_base_def 0 P acf/_additonal_gear_flatplate 0.0 P acf/_ailn1_cratR 0.500000000 P acf/_ailn1_cratT 0.500000000 P acf/_ailn1_dn 90.0 P acf/_ailn1_flaps 0.0 P acf/_ailn1_pitch 0.0 P acf/_ailn1_rat1 1.0 P acf/_ailn1_rat2 1.0 P acf/_ailn1_up 90.0 P acf/_ailn1_v1 0.0 P acf/_ailn1_v2 0.0 P acf/_ailn2_cratR 0.0 P acf/_ailn2_cratT 0.0 P acf/_ailn2_dn 0.0 P acf/_ailn2_flaps 0.0 P acf/_ailn2_pitch 0.0 P acf/_ailn2_rat1 1.0 P acf/_ailn2_rat2 1.0 P acf/_ailn2_up 0.0 P acf/_ailn2_v1 0.0 P acf/_ailn2_v2 0.0 P acf/_ailn_def_time 0.0 P acf/_ailn_dn_trim 5.0 P acf/_ailn_tab 0.0 P acf/_ailn_trim_time 20.0 P acf/_ailn_up_trim 5.0 P acf/_air_refuel/0 0.0 P acf/_air_refuel/1 0.0 P acf/_air_refuel/2 0.0 P acf/_air_refuel/count 3 P acf/_alt_always_armed 0 P acf/_alt_hard_locked_on_cws 0 P acf/_alta_x_ctr 0.0 P acf/_anchor_xyz_acf/0 0.0 P acf/_anchor_xyz_acf/1 0.0 P acf/_anchor_xyz_acf/2 0.0 P acf/_anchor_xyz_acf/count 3 P acf/_ann_triggers_caution/0 0 P acf/_ann_triggers_caution/1 0 P acf/_ann_triggers_caution/10 0 P acf/_ann_triggers_caution/100 0 P acf/_ann_triggers_caution/101 0 P acf/_ann_triggers_caution/102 0 P acf/_ann_triggers_caution/103 0 P acf/_ann_triggers_caution/104 0 P acf/_ann_triggers_caution/105 0 P acf/_ann_triggers_caution/106 0 P acf/_ann_triggers_caution/107 0 P acf/_ann_triggers_caution/108 0 P acf/_ann_triggers_caution/109 0 P acf/_ann_triggers_caution/11 0 P acf/_ann_triggers_caution/110 0 P acf/_ann_triggers_caution/111 0 P acf/_ann_triggers_caution/112 0 P acf/_ann_triggers_caution/113 0 P acf/_ann_triggers_caution/114 0 P acf/_ann_triggers_caution/115 0 P acf/_ann_triggers_caution/116 0 P acf/_ann_triggers_caution/117 0 P acf/_ann_triggers_caution/118 0 P acf/_ann_triggers_caution/119 0 P acf/_ann_triggers_caution/12 0 P acf/_ann_triggers_caution/120 0 P acf/_ann_triggers_caution/121 0 P acf/_ann_triggers_caution/122 0 P acf/_ann_triggers_caution/123 0 P acf/_ann_triggers_caution/124 0 P acf/_ann_triggers_caution/125 0 P acf/_ann_triggers_caution/126 0 P acf/_ann_triggers_caution/127 0 P acf/_ann_triggers_caution/128 0 P acf/_ann_triggers_caution/129 0 P acf/_ann_triggers_caution/13 0 P acf/_ann_triggers_caution/130 0 P acf/_ann_triggers_caution/131 0 P acf/_ann_triggers_caution/132 0 P acf/_ann_triggers_caution/133 0 P acf/_ann_triggers_caution/134 0 P acf/_ann_triggers_caution/135 0 P acf/_ann_triggers_caution/136 0 P acf/_ann_triggers_caution/137 0 P acf/_ann_triggers_caution/138 0 P acf/_ann_triggers_caution/139 0 P acf/_ann_triggers_caution/14 0 P acf/_ann_triggers_caution/140 0 P acf/_ann_triggers_caution/141 0 P acf/_ann_triggers_caution/142 0 P acf/_ann_triggers_caution/143 0 P acf/_ann_triggers_caution/144 0 P acf/_ann_triggers_caution/145 0 P acf/_ann_triggers_caution/146 0 P acf/_ann_triggers_caution/147 0 P acf/_ann_triggers_caution/148 0 P acf/_ann_triggers_caution/149 0 P acf/_ann_triggers_caution/15 0 P acf/_ann_triggers_caution/150 0 P acf/_ann_triggers_caution/151 0 P acf/_ann_triggers_caution/152 0 P acf/_ann_triggers_caution/153 0 P acf/_ann_triggers_caution/154 0 P acf/_ann_triggers_caution/155 0 P acf/_ann_triggers_caution/156 0 P acf/_ann_triggers_caution/157 0 P acf/_ann_triggers_caution/158 0 P acf/_ann_triggers_caution/159 0 P acf/_ann_triggers_caution/16 0 P acf/_ann_triggers_caution/160 0 P acf/_ann_triggers_caution/161 0 P acf/_ann_triggers_caution/162 0 P acf/_ann_triggers_caution/163 0 P acf/_ann_triggers_caution/164 0 P acf/_ann_triggers_caution/165 0 P acf/_ann_triggers_caution/166 0 P acf/_ann_triggers_caution/167 0 P acf/_ann_triggers_caution/168 0 P acf/_ann_triggers_caution/169 0 P acf/_ann_triggers_caution/17 0 P acf/_ann_triggers_caution/170 0 P acf/_ann_triggers_caution/171 0 P acf/_ann_triggers_caution/172 0 P acf/_ann_triggers_caution/173 0 P acf/_ann_triggers_caution/174 0 P acf/_ann_triggers_caution/175 0 P acf/_ann_triggers_caution/176 0 P acf/_ann_triggers_caution/177 0 P acf/_ann_triggers_caution/178 0 P acf/_ann_triggers_caution/179 0 P acf/_ann_triggers_caution/18 0 P acf/_ann_triggers_caution/180 0 P acf/_ann_triggers_caution/181 0 P acf/_ann_triggers_caution/182 0 P acf/_ann_triggers_caution/19 0 P acf/_ann_triggers_caution/2 0 P acf/_ann_triggers_caution/20 0 P acf/_ann_triggers_caution/21 0 P acf/_ann_triggers_caution/22 0 P acf/_ann_triggers_caution/23 0 P acf/_ann_triggers_caution/24 0 P acf/_ann_triggers_caution/25 0 P acf/_ann_triggers_caution/26 0 P acf/_ann_triggers_caution/27 0 P acf/_ann_triggers_caution/28 0 P acf/_ann_triggers_caution/29 0 P acf/_ann_triggers_caution/3 0 P acf/_ann_triggers_caution/30 0 P acf/_ann_triggers_caution/31 0 P acf/_ann_triggers_caution/32 0 P acf/_ann_triggers_caution/33 0 P acf/_ann_triggers_caution/34 0 P acf/_ann_triggers_caution/35 0 P acf/_ann_triggers_caution/36 0 P acf/_ann_triggers_caution/37 0 P acf/_ann_triggers_caution/38 0 P acf/_ann_triggers_caution/39 0 P acf/_ann_triggers_caution/4 0 P acf/_ann_triggers_caution/40 0 P acf/_ann_triggers_caution/41 0 P acf/_ann_triggers_caution/42 0 P acf/_ann_triggers_caution/43 0 P acf/_ann_triggers_caution/44 0 P acf/_ann_triggers_caution/45 0 P acf/_ann_triggers_caution/46 0 P acf/_ann_triggers_caution/47 0 P acf/_ann_triggers_caution/48 0 P acf/_ann_triggers_caution/49 0 P acf/_ann_triggers_caution/5 0 P acf/_ann_triggers_caution/50 0 P acf/_ann_triggers_caution/51 0 P acf/_ann_triggers_caution/52 0 P acf/_ann_triggers_caution/53 0 P acf/_ann_triggers_caution/54 0 P acf/_ann_triggers_caution/55 0 P acf/_ann_triggers_caution/56 0 P acf/_ann_triggers_caution/57 0 P acf/_ann_triggers_caution/58 0 P acf/_ann_triggers_caution/59 0 P acf/_ann_triggers_caution/6 0 P acf/_ann_triggers_caution/60 0 P acf/_ann_triggers_caution/61 0 P acf/_ann_triggers_caution/62 0 P acf/_ann_triggers_caution/63 0 P acf/_ann_triggers_caution/64 0 P acf/_ann_triggers_caution/65 0 P acf/_ann_triggers_caution/66 0 P acf/_ann_triggers_caution/67 0 P acf/_ann_triggers_caution/68 0 P acf/_ann_triggers_caution/69 0 P acf/_ann_triggers_caution/7 0 P acf/_ann_triggers_caution/70 0 P acf/_ann_triggers_caution/71 0 P acf/_ann_triggers_caution/72 0 P acf/_ann_triggers_caution/73 0 P acf/_ann_triggers_caution/74 0 P acf/_ann_triggers_caution/75 0 P acf/_ann_triggers_caution/76 0 P acf/_ann_triggers_caution/77 0 P acf/_ann_triggers_caution/78 0 P acf/_ann_triggers_caution/79 0 P acf/_ann_triggers_caution/8 0 P acf/_ann_triggers_caution/80 0 P acf/_ann_triggers_caution/81 0 P acf/_ann_triggers_caution/82 0 P acf/_ann_triggers_caution/83 0 P acf/_ann_triggers_caution/84 0 P acf/_ann_triggers_caution/85 0 P acf/_ann_triggers_caution/86 0 P acf/_ann_triggers_caution/87 0 P acf/_ann_triggers_caution/88 0 P acf/_ann_triggers_caution/89 0 P acf/_ann_triggers_caution/9 0 P acf/_ann_triggers_caution/90 0 P acf/_ann_triggers_caution/91 0 P acf/_ann_triggers_caution/92 0 P acf/_ann_triggers_caution/93 0 P acf/_ann_triggers_caution/94 0 P acf/_ann_triggers_caution/95 0 P acf/_ann_triggers_caution/96 0 P acf/_ann_triggers_caution/97 0 P acf/_ann_triggers_caution/98 0 P acf/_ann_triggers_caution/99 0 P acf/_ann_triggers_caution/count 183 P acf/_ann_triggers_warning/0 0 P acf/_ann_triggers_warning/1 0 P acf/_ann_triggers_warning/10 0 P acf/_ann_triggers_warning/100 0 P acf/_ann_triggers_warning/101 0 P acf/_ann_triggers_warning/102 0 P acf/_ann_triggers_warning/103 0 P acf/_ann_triggers_warning/104 0 P acf/_ann_triggers_warning/105 0 P acf/_ann_triggers_warning/106 0 P acf/_ann_triggers_warning/107 0 P acf/_ann_triggers_warning/108 0 P acf/_ann_triggers_warning/109 0 P acf/_ann_triggers_warning/11 0 P acf/_ann_triggers_warning/110 0 P acf/_ann_triggers_warning/111 0 P acf/_ann_triggers_warning/112 0 P acf/_ann_triggers_warning/113 0 P acf/_ann_triggers_warning/114 0 P acf/_ann_triggers_warning/115 0 P acf/_ann_triggers_warning/116 0 P acf/_ann_triggers_warning/117 0 P acf/_ann_triggers_warning/118 0 P acf/_ann_triggers_warning/119 0 P acf/_ann_triggers_warning/12 0 P acf/_ann_triggers_warning/120 0 P acf/_ann_triggers_warning/121 0 P acf/_ann_triggers_warning/122 0 P acf/_ann_triggers_warning/123 0 P acf/_ann_triggers_warning/124 0 P acf/_ann_triggers_warning/125 0 P acf/_ann_triggers_warning/126 0 P acf/_ann_triggers_warning/127 0 P acf/_ann_triggers_warning/128 0 P acf/_ann_triggers_warning/129 0 P acf/_ann_triggers_warning/13 0 P acf/_ann_triggers_warning/130 0 P acf/_ann_triggers_warning/131 0 P acf/_ann_triggers_warning/132 0 P acf/_ann_triggers_warning/133 0 P acf/_ann_triggers_warning/134 0 P acf/_ann_triggers_warning/135 0 P acf/_ann_triggers_warning/136 0 P acf/_ann_triggers_warning/137 0 P acf/_ann_triggers_warning/138 0 P acf/_ann_triggers_warning/139 0 P acf/_ann_triggers_warning/14 0 P acf/_ann_triggers_warning/140 0 P acf/_ann_triggers_warning/141 0 P acf/_ann_triggers_warning/142 0 P acf/_ann_triggers_warning/143 0 P acf/_ann_triggers_warning/144 0 P acf/_ann_triggers_warning/145 0 P acf/_ann_triggers_warning/146 0 P acf/_ann_triggers_warning/147 0 P acf/_ann_triggers_warning/148 0 P acf/_ann_triggers_warning/149 0 P acf/_ann_triggers_warning/15 0 P acf/_ann_triggers_warning/150 0 P acf/_ann_triggers_warning/151 0 P acf/_ann_triggers_warning/152 0 P acf/_ann_triggers_warning/153 0 P acf/_ann_triggers_warning/154 0 P acf/_ann_triggers_warning/155 0 P acf/_ann_triggers_warning/156 0 P acf/_ann_triggers_warning/157 0 P acf/_ann_triggers_warning/158 0 P acf/_ann_triggers_warning/159 0 P acf/_ann_triggers_warning/16 0 P acf/_ann_triggers_warning/160 0 P acf/_ann_triggers_warning/161 0 P acf/_ann_triggers_warning/162 0 P acf/_ann_triggers_warning/163 0 P acf/_ann_triggers_warning/164 0 P acf/_ann_triggers_warning/165 0 P acf/_ann_triggers_warning/166 0 P acf/_ann_triggers_warning/167 0 P acf/_ann_triggers_warning/168 0 P acf/_ann_triggers_warning/169 0 P acf/_ann_triggers_warning/17 0 P acf/_ann_triggers_warning/170 0 P acf/_ann_triggers_warning/171 0 P acf/_ann_triggers_warning/172 0 P acf/_ann_triggers_warning/173 0 P acf/_ann_triggers_warning/174 0 P acf/_ann_triggers_warning/175 0 P acf/_ann_triggers_warning/176 0 P acf/_ann_triggers_warning/177 0 P acf/_ann_triggers_warning/178 0 P acf/_ann_triggers_warning/179 0 P acf/_ann_triggers_warning/18 0 P acf/_ann_triggers_warning/180 0 P acf/_ann_triggers_warning/181 0 P acf/_ann_triggers_warning/182 0 P acf/_ann_triggers_warning/19 0 P acf/_ann_triggers_warning/2 0 P acf/_ann_triggers_warning/20 0 P acf/_ann_triggers_warning/21 0 P acf/_ann_triggers_warning/22 0 P acf/_ann_triggers_warning/23 0 P acf/_ann_triggers_warning/24 0 P acf/_ann_triggers_warning/25 0 P acf/_ann_triggers_warning/26 0 P acf/_ann_triggers_warning/27 0 P acf/_ann_triggers_warning/28 0 P acf/_ann_triggers_warning/29 0 P acf/_ann_triggers_warning/3 0 P acf/_ann_triggers_warning/30 0 P acf/_ann_triggers_warning/31 0 P acf/_ann_triggers_warning/32 0 P acf/_ann_triggers_warning/33 0 P acf/_ann_triggers_warning/34 0 P acf/_ann_triggers_warning/35 0 P acf/_ann_triggers_warning/36 0 P acf/_ann_triggers_warning/37 0 P acf/_ann_triggers_warning/38 0 P acf/_ann_triggers_warning/39 0 P acf/_ann_triggers_warning/4 0 P acf/_ann_triggers_warning/40 0 P acf/_ann_triggers_warning/41 0 P acf/_ann_triggers_warning/42 0 P acf/_ann_triggers_warning/43 0 P acf/_ann_triggers_warning/44 0 P acf/_ann_triggers_warning/45 0 P acf/_ann_triggers_warning/46 0 P acf/_ann_triggers_warning/47 0 P acf/_ann_triggers_warning/48 0 P acf/_ann_triggers_warning/49 0 P acf/_ann_triggers_warning/5 0 P acf/_ann_triggers_warning/50 0 P acf/_ann_triggers_warning/51 0 P acf/_ann_triggers_warning/52 0 P acf/_ann_triggers_warning/53 0 P acf/_ann_triggers_warning/54 0 P acf/_ann_triggers_warning/55 0 P acf/_ann_triggers_warning/56 0 P acf/_ann_triggers_warning/57 0 P acf/_ann_triggers_warning/58 0 P acf/_ann_triggers_warning/59 0 P acf/_ann_triggers_warning/6 0 P acf/_ann_triggers_warning/60 0 P acf/_ann_triggers_warning/61 0 P acf/_ann_triggers_warning/62 0 P acf/_ann_triggers_warning/63 0 P acf/_ann_triggers_warning/64 0 P acf/_ann_triggers_warning/65 0 P acf/_ann_triggers_warning/66 0 P acf/_ann_triggers_warning/67 0 P acf/_ann_triggers_warning/68 0 P acf/_ann_triggers_warning/69 0 P acf/_ann_triggers_warning/7 0 P acf/_ann_triggers_warning/70 0 P acf/_ann_triggers_warning/71 0 P acf/_ann_triggers_warning/72 0 P acf/_ann_triggers_warning/73 0 P acf/_ann_triggers_warning/74 0 P acf/_ann_triggers_warning/75 0 P acf/_ann_triggers_warning/76 0 P acf/_ann_triggers_warning/77 0 P acf/_ann_triggers_warning/78 0 P acf/_ann_triggers_warning/79 0 P acf/_ann_triggers_warning/8 0 P acf/_ann_triggers_warning/80 0 P acf/_ann_triggers_warning/81 0 P acf/_ann_triggers_warning/82 0 P acf/_ann_triggers_warning/83 0 P acf/_ann_triggers_warning/84 0 P acf/_ann_triggers_warning/85 0 P acf/_ann_triggers_warning/86 0 P acf/_ann_triggers_warning/87 0 P acf/_ann_triggers_warning/88 0 P acf/_ann_triggers_warning/89 0 P acf/_ann_triggers_warning/9 0 P acf/_ann_triggers_warning/90 0 P acf/_ann_triggers_warning/91 0 P acf/_ann_triggers_warning/92 0 P acf/_ann_triggers_warning/93 0 P acf/_ann_triggers_warning/94 0 P acf/_ann_triggers_warning/95 0 P acf/_ann_triggers_warning/96 0 P acf/_ann_triggers_warning/97 0 P acf/_ann_triggers_warning/98 0 P acf/_ann_triggers_warning/99 0 P acf/_ann_triggers_warning/count 183 P acf/_ann_wav_file/10 alarm.wav P acf/_ann_wav_file/100 autopilot_fail.wav P acf/_ann_wav_file/101 autopilot_fail.wav P acf/_ann_wav_file/102 autopilot_fail.wav P acf/_ann_wav_file/103 autopilot_fail.wav P acf/_ann_wav_file/104 autopilot_fail.wav P acf/_ann_wav_file/105 autopilot_fail.wav P acf/_ann_wav_file/106 autopilot_fail.wav P acf/_ann_wav_file/107 autopilot_fail.wav P acf/_ann_wav_file/108 autopilot_fail.wav P acf/_ann_wav_file/117 silence.wav P acf/_ann_wav_file/118 silence.wav P acf/_ann_wav_file/119 silence.wav P acf/_ann_wav_file/120 silence.wav P acf/_ann_wav_file/121 silence.wav P acf/_ann_wav_file/122 silence.wav P acf/_ann_wav_file/123 silence.wav P acf/_ann_wav_file/124 silence.wav P acf/_ann_wav_file/125 silence.wav P acf/_ann_wav_file/126 silence.wav P acf/_ann_wav_file/127 silence.wav P acf/_ann_wav_file/128 silence.wav P acf/_ann_wav_file/129 silence.wav P acf/_ann_wav_file/13 silence.wav P acf/_ann_wav_file/130 silence.wav P acf/_ann_wav_file/131 silence.wav P acf/_ann_wav_file/132 silence.wav P acf/_ann_wav_file/133 autopilot_disco.wav P acf/_ann_wav_file/134 autopilot_disco.wav P acf/_ann_wav_file/135 autopilot_disco.wav P acf/_ann_wav_file/136 autopilot_disco.wav P acf/_ann_wav_file/137 autopilot_disco.wav P acf/_ann_wav_file/138 autopilot_disco.wav P acf/_ann_wav_file/139 autopilot_disco.wav P acf/_ann_wav_file/140 autopilot_disco.wav P acf/_ann_wav_file/141 autopilot_fail.wav P acf/_ann_wav_file/142 autopilot_fail.wav P acf/_ann_wav_file/143 autopilot_fail.wav P acf/_ann_wav_file/144 autopilot_fail.wav P acf/_ann_wav_file/145 autopilot_fail.wav P acf/_ann_wav_file/146 autopilot_fail.wav P acf/_ann_wav_file/147 autopilot_fail.wav P acf/_ann_wav_file/148 autopilot_fail.wav P acf/_ann_wav_file/15 RafaleC flap.wav P acf/_ann_wav_file/16 autopilot_disco.wav P acf/_ann_wav_file/166 autopilot_disco.wav P acf/_ann_wav_file/167 autopilot_fail.wav P acf/_ann_wav_file/168 alarm.wav P acf/_ann_wav_file/169 autopilot_fail.wav P acf/_ann_wav_file/17 gear.wav P acf/_ann_wav_file/170 autopilot_disco.wav P acf/_ann_wav_file/171 mute.wav P acf/_ann_wav_file/172 mute.wav P acf/_ann_wav_file/173 mute.wav P acf/_ann_wav_file/174 Eclipse APU.wav P acf/_ann_wav_file/175 mute.wav P acf/_ann_wav_file/177 autopilot_fail.wav P acf/_ann_wav_file/178 mute.wav P acf/_ann_wav_file/179 mute.wav P acf/_ann_wav_file/18 autopilot_disco.wav P acf/_ann_wav_file/180 bankangle.wav P acf/_ann_wav_file/181 mute.wav P acf/_ann_wav_file/182 mute.wav P acf/_ann_wav_file/19 autopilot_disco.wav P acf/_ann_wav_file/20 mute.wav P acf/_ann_wav_file/21 autopilot_fail.wav P acf/_ann_wav_file/22 autopilot_fail.wav P acf/_ann_wav_file/23 autopilot_fail.wav P acf/_ann_wav_file/24 autopilot_fail.wav P acf/_ann_wav_file/25 autopilot_fail.wav P acf/_ann_wav_file/26 autopilot_fail.wav P acf/_ann_wav_file/27 autopilot_fail.wav P acf/_ann_wav_file/28 autopilot_fail.wav P acf/_ann_wav_file/29 Warning.wav P acf/_ann_wav_file/3 autopilotdisco.wav P acf/_ann_wav_file/30 Warning.wav P acf/_ann_wav_file/31 Warning.wav P acf/_ann_wav_file/32 Warning.wav P acf/_ann_wav_file/33 Warning.wav P acf/_ann_wav_file/34 Warning.wav P acf/_ann_wav_file/35 Warning.wav P acf/_ann_wav_file/36 Warning.wav P acf/_ann_wav_file/37 autopilot_fail.wav P acf/_ann_wav_file/38 autopilot_fail.wav P acf/_ann_wav_file/39 autopilot_fail.wav P acf/_ann_wav_file/4 alarm.wav P acf/_ann_wav_file/40 autopilot_fail.wav P acf/_ann_wav_file/41 autopilot_fail.wav P acf/_ann_wav_file/42 autopilot_fail.wav P acf/_ann_wav_file/43 autopilot_fail.wav P acf/_ann_wav_file/44 autopilot_fail.wav P acf/_ann_wav_file/45 Warning.wav P acf/_ann_wav_file/46 Warning.wav P acf/_ann_wav_file/47 Warning.wav P acf/_ann_wav_file/48 Warning.wav P acf/_ann_wav_file/49 Warning.wav P acf/_ann_wav_file/5 Warning.wav P acf/_ann_wav_file/50 Warning.wav P acf/_ann_wav_file/51 Warning.wav P acf/_ann_wav_file/52 Warning.wav P acf/_ann_wav_file/53 Warning.wav P acf/_ann_wav_file/54 Warning.wav P acf/_ann_wav_file/55 Warning.wav P acf/_ann_wav_file/56 Warning.wav P acf/_ann_wav_file/57 Warning.wav P acf/_ann_wav_file/58 Warning.wav P acf/_ann_wav_file/59 Warning.wav P acf/_ann_wav_file/6 VIGGEN37 avionics.wav P acf/_ann_wav_file/60 Warning.wav P acf/_ann_wav_file/61 Firebell.wav P acf/_ann_wav_file/62 Firebell.wav P acf/_ann_wav_file/63 Firebell.wav P acf/_ann_wav_file/64 Firebell.wav P acf/_ann_wav_file/65 Firebell.wav P acf/_ann_wav_file/66 Firebell.wav P acf/_ann_wav_file/67 Firebell.wav P acf/_ann_wav_file/68 Firebell.wav P acf/_ann_wav_file/69 silence.wav P acf/_ann_wav_file/7 Warning.wav P acf/_ann_wav_file/70 silence.wav P acf/_ann_wav_file/71 silence.wav P acf/_ann_wav_file/72 silence.wav P acf/_ann_wav_file/73 silence.wav P acf/_ann_wav_file/74 silence.wav P acf/_ann_wav_file/75 silence.wav P acf/_ann_wav_file/76 silence.wav P acf/_ann_wav_file/77 Warning.wav P acf/_ann_wav_file/78 Warning.wav P acf/_ann_wav_file/79 Warning.wav P acf/_ann_wav_file/8 mute.wav P acf/_ann_wav_file/80 Warning.wav P acf/_ann_wav_file/81 Warning.wav P acf/_ann_wav_file/82 Warning.wav P acf/_ann_wav_file/83 Warning.wav P acf/_ann_wav_file/84 Warning.wav P acf/_ann_wav_file/85 CUSTOM/ P acf/_ann_wav_file/86 CUSTOM/ P acf/_ann_wav_file/87 CUSTOM/ P acf/_ann_wav_file/88 CUSTOM/ P acf/_ann_wav_file/89 CUSTOM/ P acf/_ann_wav_file/9 TooLowTerrain.wav P acf/_ann_wav_file/90 CUSTOM/ P acf/_ann_wav_file/91 CUSTOM/ P acf/_ann_wav_file/92 CUSTOM/ P acf/_ann_wav_file/93 autopilot_fail.wav P acf/_ann_wav_file/94 autopilot_fail.wav P acf/_ann_wav_file/95 autopilot_fail.wav P acf/_ann_wav_file/96 autopilot_fail.wav P acf/_ann_wav_file/97 autopilot_fail.wav P acf/_ann_wav_file/98 autopilot_fail.wav P acf/_ann_wav_file/99 autopilot_fail.wav P acf/_ann_wav_file/count 183 P acf/_anti_ice_EQ 0 P acf/_ap_crs_src 0 P acf/_apt_req_plane 0 P acf/_arresting_EQ 0 P acf/_asi_is_kts 1 P acf/_auto_cowl_flaps_EQ 0 P acf/_auto_engage_BC_deg 0.0 P acf/_auto_omega_fire 0.0 P acf/_auto_omega_idle 0.0 P acf/_auto_omega_open 0.0 P acf/_auto_pressurization 0 P acf/_auto_psi_for_dir_change 180.0 P acf/_auto_rpm_EQ 0 P acf/_auto_rpm_with_tvec 0 P acf/_auto_sync_EQ 0 P acf/_auto_trim_EQ 0 P acf/_autoland_dis_req_mtr 1852.0 P acf/_average_mac_acf 0.0 P acf/_avionics_EQ 0 P acf/_bagg_1/0 0.0 P acf/_bagg_1/1 0.0 P acf/_bagg_1/2 0.0 P acf/_bagg_1/count 3 P acf/_bagg_2/0 0.0 P acf/_bagg_2/1 0.0 P acf/_bagg_2/2 0.0 P acf/_bagg_2/count 3 P acf/_bagg_3/0 0.0 P acf/_bagg_3/1 0.0 P acf/_bagg_3/2 0.0 P acf/_bagg_3/count 3 P acf/_bagg_4/0 0.0 P acf/_bagg_4/1 0.0 P acf/_bagg_4/2 0.0 P acf/_bagg_4/count 3 P acf/_ball_ang_acc_deg_ss 0.0 P acf/_ball_ang_def_deg 0.0 P acf/_ball_ang_spd_deg_s 0.0 P acf/_battery_watt_hr_max 1000.0 P acf/_beacon_EQ 0 P acf/_beacon_is_strobe 0 P acf/_beta_pitch 0.0 P acf/_beta_prop_EQ 0 P acf/_blown_flap_cl_inc 0.0 P acf/_blown_flap_min_engag 0.0 P acf/_blown_flap_throt_red 0.0 P acf/_board_1/0 0.0 P acf/_board_1/1 0.0 P acf/_board_1/2 0.0 P acf/_board_1/count 3 P acf/_board_2/0 0.0 P acf/_board_2/1 0.0 P acf/_board_2/2 0.0 P acf/_board_2/count 3 P acf/_boost_max_seconds 0.0 P acf/_boost_ratio 0.0 P acf/_bouncer_can_float/0 0 P acf/_bouncer_can_float/1 0 P acf/_bouncer_can_float/10 0 P acf/_bouncer_can_float/11 0 P acf/_bouncer_can_float/12 0 P acf/_bouncer_can_float/13 0 P acf/_bouncer_can_float/2 0 P acf/_bouncer_can_float/3 0 P acf/_bouncer_can_float/4 0 P acf/_bouncer_can_float/5 0 P acf/_bouncer_can_float/6 0 P acf/_bouncer_can_float/7 0 P acf/_bouncer_can_float/8 0 P acf/_bouncer_can_float/9 0 P acf/_bouncer_can_float/count 14 P acf/_bouncer_damp_k/0,0 0.0 P acf/_bouncer_damp_k/0,1 0.0 P acf/_bouncer_damp_k/0,2 0.0 P acf/_bouncer_damp_k/1,0 0.0 P acf/_bouncer_damp_k/1,1 0.0 P acf/_bouncer_damp_k/1,2 0.0 P acf/_bouncer_damp_k/10,0 0.0 P acf/_bouncer_damp_k/10,1 0.0 P acf/_bouncer_damp_k/10,2 0.0 P acf/_bouncer_damp_k/11,0 0.0 P acf/_bouncer_damp_k/11,1 0.0 P acf/_bouncer_damp_k/11,2 0.0 P acf/_bouncer_damp_k/12,0 0.0 P acf/_bouncer_damp_k/12,1 0.0 P acf/_bouncer_damp_k/12,2 0.0 P acf/_bouncer_damp_k/13,0 0.0 P acf/_bouncer_damp_k/13,1 0.0 P acf/_bouncer_damp_k/13,2 0.0 P acf/_bouncer_damp_k/2,0 0.0 P acf/_bouncer_damp_k/2,1 0.0 P acf/_bouncer_damp_k/2,2 0.0 P acf/_bouncer_damp_k/3,0 0.0 P acf/_bouncer_damp_k/3,1 0.0 P acf/_bouncer_damp_k/3,2 0.0 P acf/_bouncer_damp_k/4,0 0.0 P acf/_bouncer_damp_k/4,1 0.0 P acf/_bouncer_damp_k/4,2 0.0 P acf/_bouncer_damp_k/5,0 0.0 P acf/_bouncer_damp_k/5,1 0.0 P acf/_bouncer_damp_k/5,2 0.0 P acf/_bouncer_damp_k/6,0 0.0 P acf/_bouncer_damp_k/6,1 0.0 P acf/_bouncer_damp_k/6,2 0.0 P acf/_bouncer_damp_k/7,0 0.0 P acf/_bouncer_damp_k/7,1 0.0 P acf/_bouncer_damp_k/7,2 0.0 P acf/_bouncer_damp_k/8,0 0.0 P acf/_bouncer_damp_k/8,1 0.0 P acf/_bouncer_damp_k/8,2 0.0 P acf/_bouncer_damp_k/9,0 0.0 P acf/_bouncer_damp_k/9,1 0.0 P acf/_bouncer_damp_k/9,2 0.0 P acf/_bouncer_damp_k/i_count 14 P acf/_bouncer_damp_k/j_count 3 P acf/_bouncer_exists/0 0 P acf/_bouncer_exists/1 0 P acf/_bouncer_exists/10 0 P acf/_bouncer_exists/11 0 P acf/_bouncer_exists/12 0 P acf/_bouncer_exists/13 0 P acf/_bouncer_exists/2 0 P acf/_bouncer_exists/3 0 P acf/_bouncer_exists/4 0 P acf/_bouncer_exists/5 0 P acf/_bouncer_exists/6 0 P acf/_bouncer_exists/7 0 P acf/_bouncer_exists/8 0 P acf/_bouncer_exists/9 0 P acf/_bouncer_exists/count 14 P acf/_bouncer_pos_limit/0 0.0 P acf/_bouncer_pos_limit/1 0.0 P acf/_bouncer_pos_limit/10 0.0 P acf/_bouncer_pos_limit/11 0.0 P acf/_bouncer_pos_limit/12 0.0 P acf/_bouncer_pos_limit/13 0.0 P acf/_bouncer_pos_limit/2 0.0 P acf/_bouncer_pos_limit/3 0.0 P acf/_bouncer_pos_limit/4 0.0 P acf/_bouncer_pos_limit/5 0.0 P acf/_bouncer_pos_limit/6 0.0 P acf/_bouncer_pos_limit/7 0.0 P acf/_bouncer_pos_limit/8 0.0 P acf/_bouncer_pos_limit/9 0.0 P acf/_bouncer_pos_limit/count 14 P acf/_bouncer_rotor_acc/0 0.0 P acf/_bouncer_rotor_acc/1 0.0 P acf/_bouncer_rotor_acc/10 0.0 P acf/_bouncer_rotor_acc/11 0.0 P acf/_bouncer_rotor_acc/12 0.0 P acf/_bouncer_rotor_acc/13 0.0 P acf/_bouncer_rotor_acc/2 0.0 P acf/_bouncer_rotor_acc/3 0.0 P acf/_bouncer_rotor_acc/4 0.0 P acf/_bouncer_rotor_acc/5 0.0 P acf/_bouncer_rotor_acc/6 0.0 P acf/_bouncer_rotor_acc/7 0.0 P acf/_bouncer_rotor_acc/8 0.0 P acf/_bouncer_rotor_acc/9 0.0 P acf/_bouncer_rotor_acc/count 14 P acf/_bouncer_spring_k/0,0 0.0 P acf/_bouncer_spring_k/0,1 0.0 P acf/_bouncer_spring_k/0,2 0.0 P acf/_bouncer_spring_k/1,0 0.0 P acf/_bouncer_spring_k/1,1 0.0 P acf/_bouncer_spring_k/1,2 0.0 P acf/_bouncer_spring_k/10,0 0.0 P acf/_bouncer_spring_k/10,1 0.0 P acf/_bouncer_spring_k/10,2 0.0 P acf/_bouncer_spring_k/11,0 0.0 P acf/_bouncer_spring_k/11,1 0.0 P acf/_bouncer_spring_k/11,2 0.0 P acf/_bouncer_spring_k/12,0 0.0 P acf/_bouncer_spring_k/12,1 0.0 P acf/_bouncer_spring_k/12,2 0.0 P acf/_bouncer_spring_k/13,0 0.0 P acf/_bouncer_spring_k/13,1 0.0 P acf/_bouncer_spring_k/13,2 0.0 P acf/_bouncer_spring_k/2,0 0.0 P acf/_bouncer_spring_k/2,1 0.0 P acf/_bouncer_spring_k/2,2 0.0 P acf/_bouncer_spring_k/3,0 0.0 P acf/_bouncer_spring_k/3,1 0.0 P acf/_bouncer_spring_k/3,2 0.0 P acf/_bouncer_spring_k/4,0 0.0 P acf/_bouncer_spring_k/4,1 0.0 P acf/_bouncer_spring_k/4,2 0.0 P acf/_bouncer_spring_k/5,0 0.0 P acf/_bouncer_spring_k/5,1 0.0 P acf/_bouncer_spring_k/5,2 0.0 P acf/_bouncer_spring_k/6,0 0.0 P acf/_bouncer_spring_k/6,1 0.0 P acf/_bouncer_spring_k/6,2 0.0 P acf/_bouncer_spring_k/7,0 0.0 P acf/_bouncer_spring_k/7,1 0.0 P acf/_bouncer_spring_k/7,2 0.0 P acf/_bouncer_spring_k/8,0 0.0 P acf/_bouncer_spring_k/8,1 0.0 P acf/_bouncer_spring_k/8,2 0.0 P acf/_bouncer_spring_k/9,0 0.0 P acf/_bouncer_spring_k/9,1 0.0 P acf/_bouncer_spring_k/9,2 0.0 P acf/_bouncer_spring_k/i_count 14 P acf/_bouncer_spring_k/j_count 3 P acf/_brake_co 0.750000000 P acf/_burner_inc 0.0 P acf/_bus_bat_is_on/0 0 P acf/_bus_bat_is_on/1 0 P acf/_bus_bat_is_on/2 0 P acf/_bus_bat_is_on/3 0 P acf/_bus_bat_is_on/4 0 P acf/_bus_bat_is_on/5 0 P acf/_bus_bat_is_on/6 0 P acf/_bus_bat_is_on/7 0 P acf/_bus_bat_is_on/count 8 P acf/_bus_gen_is_on/0 0 P acf/_bus_gen_is_on/1 0 P acf/_bus_gen_is_on/2 0 P acf/_bus_gen_is_on/3 0 P acf/_bus_gen_is_on/4 0 P acf/_bus_gen_is_on/5 0 P acf/_bus_gen_is_on/6 0 P acf/_bus_gen_is_on/7 0 P acf/_bus_gen_is_on/count 8 P acf/_buses_always_tied 0 P acf/_bypass_ratio 0.0 P acf/_camera_fov_zoom 60.0 P acf/_camera_offset_phi 0.0 P acf/_camera_offset_psi 0.0 P acf/_camera_offset_the 0.0 P acf/_camera_offset_x 0.0 P acf/_camera_offset_y 0.0 P acf/_camera_offset_z 0.0 P acf/_cgY 0.0 P acf/_cgY_pln_ref 0.0 P acf/_cgZ 0.0 P acf/_cgZ_aft 0.0 P acf/_cgZ_fwd 0.0 P acf/_cgZ_pln_ref 0.0 P acf/_cgZ_ref_ft 0.0 P acf/_chaff_max 0 P acf/_chute_psi/0 0.0 P acf/_chute_psi/1 0.0 P acf/_chute_psi/2 0.0 P acf/_chute_psi/count 3 P acf/_chute_s/0 0.0 P acf/_chute_s/1 0.0 P acf/_chute_s/2 0.0 P acf/_chute_s/count 3 P acf/_chute_the/0 0.0 P acf/_chute_the/1 0.0 P acf/_chute_the/2 0.0 P acf/_chute_the/count 3 P acf/_chute_x/0 0.0 P acf/_chute_x/1 0.0 P acf/_chute_x/2 0.0 P acf/_chute_x/count 3 P acf/_chute_y/0 0.0 P acf/_chute_y/1 0.0 P acf/_chute_y/2 0.0 P acf/_chute_y/count 3 P acf/_chute_z/0 0.0 P acf/_chute_z/1 0.0 P acf/_chute_z/2 0.0 P acf/_chute_z/count 3 P acf/_cockpit_obj_flags 0 P acf/_cockpit_planned_vanish_y/0 590.0 P acf/_cockpit_planned_vanish_y/1 590.0 P acf/_cockpit_planned_vanish_y/2 590.0 P acf/_cockpit_planned_vanish_y/3 590.0 P acf/_cockpit_planned_vanish_y/4 590.0 P acf/_cockpit_planned_vanish_y/5 590.0 P acf/_cockpit_planned_vanish_y/6 590.0 P acf/_cockpit_planned_vanish_y/7 590.0 P acf/_cockpit_planned_vanish_y/8 590.0 P acf/_cockpit_planned_vanish_y/count 9 P acf/_cockpit_type 0 P acf/_cockpit_xyz/0 0.0 P acf/_cockpit_xyz/1 0.0 P acf/_cockpit_xyz/2 0.0 P acf/_cockpit_xyz/count 3 P acf/_collective_EQ 0 P acf/_collective_en 0 P acf/_con_smooth 1 P acf/_cowl_x_ctr 0.0 P acf/_crew_1/0 0.0 P acf/_crew_1/1 0.0 P acf/_crew_1/2 0.0 P acf/_crew_1/count 3 P acf/_crew_2/0 0.0 P acf/_crew_2/1 0.0 P acf/_crew_2/2 0.0 P acf/_crew_2/count 3 P acf/_crit_alt_mtr 0.0 P acf/_cus_dig_dec/0 0 P acf/_cus_dig_dec/1 0 P acf/_cus_dig_dec/10 0 P acf/_cus_dig_dec/11 0 P acf/_cus_dig_dec/12 0 P acf/_cus_dig_dec/13 0 P acf/_cus_dig_dec/14 0 P acf/_cus_dig_dec/15 0 P acf/_cus_dig_dec/16 0 P acf/_cus_dig_dec/17 0 P acf/_cus_dig_dec/18 0 P acf/_cus_dig_dec/19 0 P acf/_cus_dig_dec/2 0 P acf/_cus_dig_dec/20 0 P acf/_cus_dig_dec/21 0 P acf/_cus_dig_dec/22 0 P acf/_cus_dig_dec/23 0 P acf/_cus_dig_dec/24 0 P acf/_cus_dig_dec/25 0 P acf/_cus_dig_dec/26 0 P acf/_cus_dig_dec/27 0 P acf/_cus_dig_dec/28 0 P acf/_cus_dig_dec/29 0 P acf/_cus_dig_dec/3 0 P acf/_cus_dig_dec/30 0 P acf/_cus_dig_dec/31 0 P acf/_cus_dig_dec/32 0 P acf/_cus_dig_dec/33 0 P acf/_cus_dig_dec/34 0 P acf/_cus_dig_dec/35 0 P acf/_cus_dig_dec/36 0 P acf/_cus_dig_dec/37 0 P acf/_cus_dig_dec/38 0 P acf/_cus_dig_dec/39 0 P acf/_cus_dig_dec/4 0 P acf/_cus_dig_dec/40 0 P acf/_cus_dig_dec/41 0 P acf/_cus_dig_dec/42 0 P acf/_cus_dig_dec/43 0 P acf/_cus_dig_dec/44 0 P acf/_cus_dig_dec/45 0 P acf/_cus_dig_dec/46 0 P acf/_cus_dig_dec/47 0 P acf/_cus_dig_dec/48 0 P acf/_cus_dig_dec/49 0 P acf/_cus_dig_dec/5 0 P acf/_cus_dig_dec/6 0 P acf/_cus_dig_dec/7 0 P acf/_cus_dig_dec/8 0 P acf/_cus_dig_dec/9 0 P acf/_cus_dig_dec/count 50 P acf/_cus_dig_dig/0 0 P acf/_cus_dig_dig/1 0 P acf/_cus_dig_dig/10 0 P acf/_cus_dig_dig/11 0 P acf/_cus_dig_dig/12 0 P acf/_cus_dig_dig/13 0 P acf/_cus_dig_dig/14 0 P acf/_cus_dig_dig/15 0 P acf/_cus_dig_dig/16 0 P acf/_cus_dig_dig/17 0 P acf/_cus_dig_dig/18 0 P acf/_cus_dig_dig/19 0 P acf/_cus_dig_dig/2 0 P acf/_cus_dig_dig/20 0 P acf/_cus_dig_dig/21 0 P acf/_cus_dig_dig/22 0 P acf/_cus_dig_dig/23 0 P acf/_cus_dig_dig/24 0 P acf/_cus_dig_dig/25 0 P acf/_cus_dig_dig/26 0 P acf/_cus_dig_dig/27 0 P acf/_cus_dig_dig/28 0 P acf/_cus_dig_dig/29 0 P acf/_cus_dig_dig/3 0 P acf/_cus_dig_dig/30 0 P acf/_cus_dig_dig/31 0 P acf/_cus_dig_dig/32 0 P acf/_cus_dig_dig/33 0 P acf/_cus_dig_dig/34 0 P acf/_cus_dig_dig/35 0 P acf/_cus_dig_dig/36 0 P acf/_cus_dig_dig/37 0 P acf/_cus_dig_dig/38 0 P acf/_cus_dig_dig/39 0 P acf/_cus_dig_dig/4 0 P acf/_cus_dig_dig/40 0 P acf/_cus_dig_dig/41 0 P acf/_cus_dig_dig/42 0 P acf/_cus_dig_dig/43 0 P acf/_cus_dig_dig/44 0 P acf/_cus_dig_dig/45 0 P acf/_cus_dig_dig/46 0 P acf/_cus_dig_dig/47 0 P acf/_cus_dig_dig/48 0 P acf/_cus_dig_dig/49 0 P acf/_cus_dig_dig/5 0 P acf/_cus_dig_dig/6 0 P acf/_cus_dig_dig/7 0 P acf/_cus_dig_dig/8 0 P acf/_cus_dig_dig/9 0 P acf/_cus_dig_dig/count 50 P acf/_cus_dig_offset/0 0.0 P acf/_cus_dig_offset/1 0.0 P acf/_cus_dig_offset/10 0.0 P acf/_cus_dig_offset/11 0.0 P acf/_cus_dig_offset/12 0.0 P acf/_cus_dig_offset/13 0.0 P acf/_cus_dig_offset/14 0.0 P acf/_cus_dig_offset/15 0.0 P acf/_cus_dig_offset/16 0.0 P acf/_cus_dig_offset/17 0.0 P acf/_cus_dig_offset/18 0.0 P acf/_cus_dig_offset/19 0.0 P acf/_cus_dig_offset/2 0.0 P acf/_cus_dig_offset/20 0.0 P acf/_cus_dig_offset/21 0.0 P acf/_cus_dig_offset/22 0.0 P acf/_cus_dig_offset/23 0.0 P acf/_cus_dig_offset/24 0.0 P acf/_cus_dig_offset/25 0.0 P acf/_cus_dig_offset/26 0.0 P acf/_cus_dig_offset/27 0.0 P acf/_cus_dig_offset/28 0.0 P acf/_cus_dig_offset/29 0.0 P acf/_cus_dig_offset/3 0.0 P acf/_cus_dig_offset/30 0.0 P acf/_cus_dig_offset/31 0.0 P acf/_cus_dig_offset/32 0.0 P acf/_cus_dig_offset/33 0.0 P acf/_cus_dig_offset/34 0.0 P acf/_cus_dig_offset/35 0.0 P acf/_cus_dig_offset/36 0.0 P acf/_cus_dig_offset/37 0.0 P acf/_cus_dig_offset/38 0.0 P acf/_cus_dig_offset/39 0.0 P acf/_cus_dig_offset/4 0.0 P acf/_cus_dig_offset/40 0.0 P acf/_cus_dig_offset/41 0.0 P acf/_cus_dig_offset/42 0.0 P acf/_cus_dig_offset/43 0.0 P acf/_cus_dig_offset/44 0.0 P acf/_cus_dig_offset/45 0.0 P acf/_cus_dig_offset/46 0.0 P acf/_cus_dig_offset/47 0.0 P acf/_cus_dig_offset/48 0.0 P acf/_cus_dig_offset/49 0.0 P acf/_cus_dig_offset/5 0.0 P acf/_cus_dig_offset/6 0.0 P acf/_cus_dig_offset/7 0.0 P acf/_cus_dig_offset/8 0.0 P acf/_cus_dig_offset/9 0.0 P acf/_cus_dig_offset/count 50 P acf/_cus_dig_scale/0 0.0 P acf/_cus_dig_scale/1 0.0 P acf/_cus_dig_scale/10 0.0 P acf/_cus_dig_scale/11 0.0 P acf/_cus_dig_scale/12 0.0 P acf/_cus_dig_scale/13 0.0 P acf/_cus_dig_scale/14 0.0 P acf/_cus_dig_scale/15 0.0 P acf/_cus_dig_scale/16 0.0 P acf/_cus_dig_scale/17 0.0 P acf/_cus_dig_scale/18 0.0 P acf/_cus_dig_scale/19 0.0 P acf/_cus_dig_scale/2 0.0 P acf/_cus_dig_scale/20 0.0 P acf/_cus_dig_scale/21 0.0 P acf/_cus_dig_scale/22 0.0 P acf/_cus_dig_scale/23 0.0 P acf/_cus_dig_scale/24 0.0 P acf/_cus_dig_scale/25 0.0 P acf/_cus_dig_scale/26 0.0 P acf/_cus_dig_scale/27 0.0 P acf/_cus_dig_scale/28 0.0 P acf/_cus_dig_scale/29 0.0 P acf/_cus_dig_scale/3 0.0 P acf/_cus_dig_scale/30 0.0 P acf/_cus_dig_scale/31 0.0 P acf/_cus_dig_scale/32 0.0 P acf/_cus_dig_scale/33 0.0 P acf/_cus_dig_scale/34 0.0 P acf/_cus_dig_scale/35 0.0 P acf/_cus_dig_scale/36 0.0 P acf/_cus_dig_scale/37 0.0 P acf/_cus_dig_scale/38 0.0 P acf/_cus_dig_scale/39 0.0 P acf/_cus_dig_scale/4 0.0 P acf/_cus_dig_scale/40 0.0 P acf/_cus_dig_scale/41 0.0 P acf/_cus_dig_scale/42 0.0 P acf/_cus_dig_scale/43 0.0 P acf/_cus_dig_scale/44 0.0 P acf/_cus_dig_scale/45 0.0 P acf/_cus_dig_scale/46 0.0 P acf/_cus_dig_scale/47 0.0 P acf/_cus_dig_scale/48 0.0 P acf/_cus_dig_scale/49 0.0 P acf/_cus_dig_scale/5 0.0 P acf/_cus_dig_scale/6 0.0 P acf/_cus_dig_scale/7 0.0 P acf/_cus_dig_scale/8 0.0 P acf/_cus_dig_scale/9 0.0 P acf/_cus_dig_scale/count 50 P acf/_cus_dig_use/0 0 P acf/_cus_dig_use/1 0 P acf/_cus_dig_use/10 0 P acf/_cus_dig_use/11 0 P acf/_cus_dig_use/12 0 P acf/_cus_dig_use/13 0 P acf/_cus_dig_use/14 0 P acf/_cus_dig_use/15 0 P acf/_cus_dig_use/16 0 P acf/_cus_dig_use/17 0 P acf/_cus_dig_use/18 0 P acf/_cus_dig_use/19 0 P acf/_cus_dig_use/2 0 P acf/_cus_dig_use/20 0 P acf/_cus_dig_use/21 0 P acf/_cus_dig_use/22 0 P acf/_cus_dig_use/23 0 P acf/_cus_dig_use/24 0 P acf/_cus_dig_use/25 0 P acf/_cus_dig_use/26 0 P acf/_cus_dig_use/27 0 P acf/_cus_dig_use/28 0 P acf/_cus_dig_use/29 0 P acf/_cus_dig_use/3 0 P acf/_cus_dig_use/30 0 P acf/_cus_dig_use/31 0 P acf/_cus_dig_use/32 0 P acf/_cus_dig_use/33 0 P acf/_cus_dig_use/34 0 P acf/_cus_dig_use/35 0 P acf/_cus_dig_use/36 0 P acf/_cus_dig_use/37 0 P acf/_cus_dig_use/38 0 P acf/_cus_dig_use/39 0 P acf/_cus_dig_use/4 0 P acf/_cus_dig_use/40 0 P acf/_cus_dig_use/41 0 P acf/_cus_dig_use/42 0 P acf/_cus_dig_use/43 0 P acf/_cus_dig_use/44 0 P acf/_cus_dig_use/45 0 P acf/_cus_dig_use/46 0 P acf/_cus_dig_use/47 0 P acf/_cus_dig_use/48 0 P acf/_cus_dig_use/49 0 P acf/_cus_dig_use/5 0 P acf/_cus_dig_use/6 0 P acf/_cus_dig_use/7 0 P acf/_cus_dig_use/8 0 P acf/_cus_dig_use/9 0 P acf/_cus_dig_use/count 50 P acf/_cus_doub_val/0 0.0 P acf/_cus_doub_val/1 0.0 P acf/_cus_doub_val/10 0.0 P acf/_cus_doub_val/11 0.0 P acf/_cus_doub_val/12 0.0 P acf/_cus_doub_val/13 0.0 P acf/_cus_doub_val/14 0.0 P acf/_cus_doub_val/15 0.0 P acf/_cus_doub_val/16 0.0 P acf/_cus_doub_val/17 0.0 P acf/_cus_doub_val/18 0.0 P acf/_cus_doub_val/19 0.0 P acf/_cus_doub_val/2 0.0 P acf/_cus_doub_val/20 0.0 P acf/_cus_doub_val/21 0.0 P acf/_cus_doub_val/22 0.0 P acf/_cus_doub_val/23 0.0 P acf/_cus_doub_val/24 0.0 P acf/_cus_doub_val/25 0.0 P acf/_cus_doub_val/26 0.0 P acf/_cus_doub_val/27 0.0 P acf/_cus_doub_val/28 0.0 P acf/_cus_doub_val/29 0.0 P acf/_cus_doub_val/3 0.0 P acf/_cus_doub_val/30 0.0 P acf/_cus_doub_val/31 0.0 P acf/_cus_doub_val/32 0.0 P acf/_cus_doub_val/33 0.0 P acf/_cus_doub_val/34 0.0 P acf/_cus_doub_val/35 0.0 P acf/_cus_doub_val/36 0.0 P acf/_cus_doub_val/37 0.0 P acf/_cus_doub_val/38 0.0 P acf/_cus_doub_val/39 0.0 P acf/_cus_doub_val/4 0.0 P acf/_cus_doub_val/40 0.0 P acf/_cus_doub_val/41 0.0 P acf/_cus_doub_val/42 0.0 P acf/_cus_doub_val/43 0.0 P acf/_cus_doub_val/44 0.0 P acf/_cus_doub_val/45 0.0 P acf/_cus_doub_val/46 0.0 P acf/_cus_doub_val/47 0.0 P acf/_cus_doub_val/48 0.0 P acf/_cus_doub_val/49 0.0 P acf/_cus_doub_val/5 0.0 P acf/_cus_doub_val/6 0.0 P acf/_cus_doub_val/7 0.0 P acf/_cus_doub_val/8 0.0 P acf/_cus_doub_val/9 0.0 P acf/_cus_doub_val/count 50 P acf/_cus_non_lin/0 0 P acf/_cus_non_lin/1 0 P acf/_cus_non_lin/10 0 P acf/_cus_non_lin/11 0 P acf/_cus_non_lin/12 0 P acf/_cus_non_lin/13 0 P acf/_cus_non_lin/14 0 P acf/_cus_non_lin/15 0 P acf/_cus_non_lin/16 0 P acf/_cus_non_lin/17 0 P acf/_cus_non_lin/18 0 P acf/_cus_non_lin/19 0 P acf/_cus_non_lin/2 0 P acf/_cus_non_lin/20 0 P acf/_cus_non_lin/21 0 P acf/_cus_non_lin/22 0 P acf/_cus_non_lin/23 0 P acf/_cus_non_lin/24 0 P acf/_cus_non_lin/25 0 P acf/_cus_non_lin/26 0 P acf/_cus_non_lin/27 0 P acf/_cus_non_lin/28 0 P acf/_cus_non_lin/29 0 P acf/_cus_non_lin/3 0 P acf/_cus_non_lin/30 0 P acf/_cus_non_lin/31 0 P acf/_cus_non_lin/32 0 P acf/_cus_non_lin/33 0 P acf/_cus_non_lin/34 0 P acf/_cus_non_lin/35 0 P acf/_cus_non_lin/36 0 P acf/_cus_non_lin/37 0 P acf/_cus_non_lin/38 0 P acf/_cus_non_lin/39 0 P acf/_cus_non_lin/4 0 P acf/_cus_non_lin/40 0 P acf/_cus_non_lin/41 0 P acf/_cus_non_lin/42 0 P acf/_cus_non_lin/43 0 P acf/_cus_non_lin/44 0 P acf/_cus_non_lin/45 0 P acf/_cus_non_lin/46 0 P acf/_cus_non_lin/47 0 P acf/_cus_non_lin/48 0 P acf/_cus_non_lin/49 0 P acf/_cus_non_lin/5 0 P acf/_cus_non_lin/6 0 P acf/_cus_non_lin/7 0 P acf/_cus_non_lin/8 0 P acf/_cus_non_lin/9 0 P acf/_cus_non_lin/count 50 P acf/_cus_rnd_hi_ang/0 360.0 P acf/_cus_rnd_hi_ang/1 360.0 P acf/_cus_rnd_hi_ang/10 360.0 P acf/_cus_rnd_hi_ang/11 360.0 P acf/_cus_rnd_hi_ang/12 360.0 P acf/_cus_rnd_hi_ang/13 360.0 P acf/_cus_rnd_hi_ang/14 360.0 P acf/_cus_rnd_hi_ang/15 360.0 P acf/_cus_rnd_hi_ang/16 360.0 P acf/_cus_rnd_hi_ang/17 360.0 P acf/_cus_rnd_hi_ang/18 360.0 P acf/_cus_rnd_hi_ang/19 360.0 P acf/_cus_rnd_hi_ang/2 360.0 P acf/_cus_rnd_hi_ang/20 360.0 P acf/_cus_rnd_hi_ang/21 360.0 P acf/_cus_rnd_hi_ang/22 360.0 P acf/_cus_rnd_hi_ang/23 360.0 P acf/_cus_rnd_hi_ang/24 360.0 P acf/_cus_rnd_hi_ang/25 360.0 P acf/_cus_rnd_hi_ang/26 360.0 P acf/_cus_rnd_hi_ang/27 360.0 P acf/_cus_rnd_hi_ang/28 360.0 P acf/_cus_rnd_hi_ang/29 360.0 P acf/_cus_rnd_hi_ang/3 360.0 P acf/_cus_rnd_hi_ang/30 360.0 P acf/_cus_rnd_hi_ang/31 360.0 P acf/_cus_rnd_hi_ang/32 360.0 P acf/_cus_rnd_hi_ang/33 360.0 P acf/_cus_rnd_hi_ang/34 360.0 P acf/_cus_rnd_hi_ang/35 360.0 P acf/_cus_rnd_hi_ang/36 360.0 P acf/_cus_rnd_hi_ang/37 360.0 P acf/_cus_rnd_hi_ang/38 360.0 P acf/_cus_rnd_hi_ang/39 360.0 P acf/_cus_rnd_hi_ang/4 360.0 P acf/_cus_rnd_hi_ang/40 360.0 P acf/_cus_rnd_hi_ang/41 360.0 P acf/_cus_rnd_hi_ang/42 360.0 P acf/_cus_rnd_hi_ang/43 360.0 P acf/_cus_rnd_hi_ang/44 360.0 P acf/_cus_rnd_hi_ang/45 360.0 P acf/_cus_rnd_hi_ang/46 360.0 P acf/_cus_rnd_hi_ang/47 360.0 P acf/_cus_rnd_hi_ang/48 360.0 P acf/_cus_rnd_hi_ang/49 360.0 P acf/_cus_rnd_hi_ang/5 360.0 P acf/_cus_rnd_hi_ang/6 360.0 P acf/_cus_rnd_hi_ang/7 360.0 P acf/_cus_rnd_hi_ang/8 360.0 P acf/_cus_rnd_hi_ang/9 360.0 P acf/_cus_rnd_hi_ang/count 50 P acf/_cus_rnd_hi_val/0 1.0 P acf/_cus_rnd_hi_val/1 1.0 P acf/_cus_rnd_hi_val/10 1.0 P acf/_cus_rnd_hi_val/11 1.0 P acf/_cus_rnd_hi_val/12 1.0 P acf/_cus_rnd_hi_val/13 1.0 P acf/_cus_rnd_hi_val/14 1.0 P acf/_cus_rnd_hi_val/15 1.0 P acf/_cus_rnd_hi_val/16 1.0 P acf/_cus_rnd_hi_val/17 1.0 P acf/_cus_rnd_hi_val/18 1.0 P acf/_cus_rnd_hi_val/19 1.0 P acf/_cus_rnd_hi_val/2 1.0 P acf/_cus_rnd_hi_val/20 1.0 P acf/_cus_rnd_hi_val/21 1.0 P acf/_cus_rnd_hi_val/22 1.0 P acf/_cus_rnd_hi_val/23 1.0 P acf/_cus_rnd_hi_val/24 1.0 P acf/_cus_rnd_hi_val/25 1.0 P acf/_cus_rnd_hi_val/26 1.0 P acf/_cus_rnd_hi_val/27 1.0 P acf/_cus_rnd_hi_val/28 1.0 P acf/_cus_rnd_hi_val/29 1.0 P acf/_cus_rnd_hi_val/3 1.0 P acf/_cus_rnd_hi_val/30 1.0 P acf/_cus_rnd_hi_val/31 1.0 P acf/_cus_rnd_hi_val/32 1.0 P acf/_cus_rnd_hi_val/33 1.0 P acf/_cus_rnd_hi_val/34 1.0 P acf/_cus_rnd_hi_val/35 1.0 P acf/_cus_rnd_hi_val/36 1.0 P acf/_cus_rnd_hi_val/37 1.0 P acf/_cus_rnd_hi_val/38 1.0 P acf/_cus_rnd_hi_val/39 1.0 P acf/_cus_rnd_hi_val/4 1.0 P acf/_cus_rnd_hi_val/40 1.0 P acf/_cus_rnd_hi_val/41 1.0 P acf/_cus_rnd_hi_val/42 1.0 P acf/_cus_rnd_hi_val/43 1.0 P acf/_cus_rnd_hi_val/44 1.0 P acf/_cus_rnd_hi_val/45 1.0 P acf/_cus_rnd_hi_val/46 1.0 P acf/_cus_rnd_hi_val/47 1.0 P acf/_cus_rnd_hi_val/48 1.0 P acf/_cus_rnd_hi_val/49 1.0 P acf/_cus_rnd_hi_val/5 1.0 P acf/_cus_rnd_hi_val/6 1.0 P acf/_cus_rnd_hi_val/7 1.0 P acf/_cus_rnd_hi_val/8 1.0 P acf/_cus_rnd_hi_val/9 1.0 P acf/_cus_rnd_hi_val/count 50 P acf/_cus_rnd_label/0 0 P acf/_cus_rnd_label/1 0 P acf/_cus_rnd_label/10 0 P acf/_cus_rnd_label/11 0 P acf/_cus_rnd_label/12 0 P acf/_cus_rnd_label/13 0 P acf/_cus_rnd_label/14 0 P acf/_cus_rnd_label/15 0 P acf/_cus_rnd_label/16 0 P acf/_cus_rnd_label/17 0 P acf/_cus_rnd_label/18 0 P acf/_cus_rnd_label/19 0 P acf/_cus_rnd_label/2 0 P acf/_cus_rnd_label/20 0 P acf/_cus_rnd_label/21 0 P acf/_cus_rnd_label/22 0 P acf/_cus_rnd_label/23 0 P acf/_cus_rnd_label/24 0 P acf/_cus_rnd_label/25 0 P acf/_cus_rnd_label/26 0 P acf/_cus_rnd_label/27 0 P acf/_cus_rnd_label/28 0 P acf/_cus_rnd_label/29 0 P acf/_cus_rnd_label/3 0 P acf/_cus_rnd_label/30 0 P acf/_cus_rnd_label/31 0 P acf/_cus_rnd_label/32 0 P acf/_cus_rnd_label/33 0 P acf/_cus_rnd_label/34 0 P acf/_cus_rnd_label/35 0 P acf/_cus_rnd_label/36 0 P acf/_cus_rnd_label/37 0 P acf/_cus_rnd_label/38 0 P acf/_cus_rnd_label/39 0 P acf/_cus_rnd_label/4 0 P acf/_cus_rnd_label/40 0 P acf/_cus_rnd_label/41 0 P acf/_cus_rnd_label/42 0 P acf/_cus_rnd_label/43 0 P acf/_cus_rnd_label/44 0 P acf/_cus_rnd_label/45 0 P acf/_cus_rnd_label/46 0 P acf/_cus_rnd_label/47 0 P acf/_cus_rnd_label/48 0 P acf/_cus_rnd_label/49 0 P acf/_cus_rnd_label/5 0 P acf/_cus_rnd_label/6 0 P acf/_cus_rnd_label/7 0 P acf/_cus_rnd_label/8 0 P acf/_cus_rnd_label/9 0 P acf/_cus_rnd_label/count 50 P acf/_cus_rnd_lo_ang/0 0.0 P acf/_cus_rnd_lo_ang/1 0.0 P acf/_cus_rnd_lo_ang/10 0.0 P acf/_cus_rnd_lo_ang/11 0.0 P acf/_cus_rnd_lo_ang/12 0.0 P acf/_cus_rnd_lo_ang/13 0.0 P acf/_cus_rnd_lo_ang/14 0.0 P acf/_cus_rnd_lo_ang/15 0.0 P acf/_cus_rnd_lo_ang/16 0.0 P acf/_cus_rnd_lo_ang/17 0.0 P acf/_cus_rnd_lo_ang/18 0.0 P acf/_cus_rnd_lo_ang/19 0.0 P acf/_cus_rnd_lo_ang/2 0.0 P acf/_cus_rnd_lo_ang/20 0.0 P acf/_cus_rnd_lo_ang/21 0.0 P acf/_cus_rnd_lo_ang/22 0.0 P acf/_cus_rnd_lo_ang/23 0.0 P acf/_cus_rnd_lo_ang/24 0.0 P acf/_cus_rnd_lo_ang/25 0.0 P acf/_cus_rnd_lo_ang/26 0.0 P acf/_cus_rnd_lo_ang/27 0.0 P acf/_cus_rnd_lo_ang/28 0.0 P acf/_cus_rnd_lo_ang/29 0.0 P acf/_cus_rnd_lo_ang/3 0.0 P acf/_cus_rnd_lo_ang/30 0.0 P acf/_cus_rnd_lo_ang/31 0.0 P acf/_cus_rnd_lo_ang/32 0.0 P acf/_cus_rnd_lo_ang/33 0.0 P acf/_cus_rnd_lo_ang/34 0.0 P acf/_cus_rnd_lo_ang/35 0.0 P acf/_cus_rnd_lo_ang/36 0.0 P acf/_cus_rnd_lo_ang/37 0.0 P acf/_cus_rnd_lo_ang/38 0.0 P acf/_cus_rnd_lo_ang/39 0.0 P acf/_cus_rnd_lo_ang/4 0.0 P acf/_cus_rnd_lo_ang/40 0.0 P acf/_cus_rnd_lo_ang/41 0.0 P acf/_cus_rnd_lo_ang/42 0.0 P acf/_cus_rnd_lo_ang/43 0.0 P acf/_cus_rnd_lo_ang/44 0.0 P acf/_cus_rnd_lo_ang/45 0.0 P acf/_cus_rnd_lo_ang/46 0.0 P acf/_cus_rnd_lo_ang/47 0.0 P acf/_cus_rnd_lo_ang/48 0.0 P acf/_cus_rnd_lo_ang/49 0.0 P acf/_cus_rnd_lo_ang/5 0.0 P acf/_cus_rnd_lo_ang/6 0.0 P acf/_cus_rnd_lo_ang/7 0.0 P acf/_cus_rnd_lo_ang/8 0.0 P acf/_cus_rnd_lo_ang/9 0.0 P acf/_cus_rnd_lo_ang/count 50 P acf/_cus_rnd_lo_val/0 0.0 P acf/_cus_rnd_lo_val/1 0.0 P acf/_cus_rnd_lo_val/10 0.0 P acf/_cus_rnd_lo_val/11 0.0 P acf/_cus_rnd_lo_val/12 0.0 P acf/_cus_rnd_lo_val/13 0.0 P acf/_cus_rnd_lo_val/14 0.0 P acf/_cus_rnd_lo_val/15 0.0 P acf/_cus_rnd_lo_val/16 0.0 P acf/_cus_rnd_lo_val/17 0.0 P acf/_cus_rnd_lo_val/18 0.0 P acf/_cus_rnd_lo_val/19 0.0 P acf/_cus_rnd_lo_val/2 0.0 P acf/_cus_rnd_lo_val/20 0.0 P acf/_cus_rnd_lo_val/21 0.0 P acf/_cus_rnd_lo_val/22 0.0 P acf/_cus_rnd_lo_val/23 0.0 P acf/_cus_rnd_lo_val/24 0.0 P acf/_cus_rnd_lo_val/25 0.0 P acf/_cus_rnd_lo_val/26 0.0 P acf/_cus_rnd_lo_val/27 0.0 P acf/_cus_rnd_lo_val/28 0.0 P acf/_cus_rnd_lo_val/29 0.0 P acf/_cus_rnd_lo_val/3 0.0 P acf/_cus_rnd_lo_val/30 0.0 P acf/_cus_rnd_lo_val/31 0.0 P acf/_cus_rnd_lo_val/32 0.0 P acf/_cus_rnd_lo_val/33 0.0 P acf/_cus_rnd_lo_val/34 0.0 P acf/_cus_rnd_lo_val/35 0.0 P acf/_cus_rnd_lo_val/36 0.0 P acf/_cus_rnd_lo_val/37 0.0 P acf/_cus_rnd_lo_val/38 0.0 P acf/_cus_rnd_lo_val/39 0.0 P acf/_cus_rnd_lo_val/4 0.0 P acf/_cus_rnd_lo_val/40 0.0 P acf/_cus_rnd_lo_val/41 0.0 P acf/_cus_rnd_lo_val/42 0.0 P acf/_cus_rnd_lo_val/43 0.0 P acf/_cus_rnd_lo_val/44 0.0 P acf/_cus_rnd_lo_val/45 0.0 P acf/_cus_rnd_lo_val/46 0.0 P acf/_cus_rnd_lo_val/47 0.0 P acf/_cus_rnd_lo_val/48 0.0 P acf/_cus_rnd_lo_val/49 0.0 P acf/_cus_rnd_lo_val/5 0.0 P acf/_cus_rnd_lo_val/6 0.0 P acf/_cus_rnd_lo_val/7 0.0 P acf/_cus_rnd_lo_val/8 0.0 P acf/_cus_rnd_lo_val/9 0.0 P acf/_cus_rnd_lo_val/count 50 P acf/_cus_rnd_mirror/0 0 P acf/_cus_rnd_mirror/1 0 P acf/_cus_rnd_mirror/10 0 P acf/_cus_rnd_mirror/11 0 P acf/_cus_rnd_mirror/12 0 P acf/_cus_rnd_mirror/13 0 P acf/_cus_rnd_mirror/14 0 P acf/_cus_rnd_mirror/15 0 P acf/_cus_rnd_mirror/16 0 P acf/_cus_rnd_mirror/17 0 P acf/_cus_rnd_mirror/18 0 P acf/_cus_rnd_mirror/19 0 P acf/_cus_rnd_mirror/2 0 P acf/_cus_rnd_mirror/20 0 P acf/_cus_rnd_mirror/21 0 P acf/_cus_rnd_mirror/22 0 P acf/_cus_rnd_mirror/23 0 P acf/_cus_rnd_mirror/24 0 P acf/_cus_rnd_mirror/25 0 P acf/_cus_rnd_mirror/26 0 P acf/_cus_rnd_mirror/27 0 P acf/_cus_rnd_mirror/28 0 P acf/_cus_rnd_mirror/29 0 P acf/_cus_rnd_mirror/3 0 P acf/_cus_rnd_mirror/30 0 P acf/_cus_rnd_mirror/31 0 P acf/_cus_rnd_mirror/32 0 P acf/_cus_rnd_mirror/33 0 P acf/_cus_rnd_mirror/34 0 P acf/_cus_rnd_mirror/35 0 P acf/_cus_rnd_mirror/36 0 P acf/_cus_rnd_mirror/37 0 P acf/_cus_rnd_mirror/38 0 P acf/_cus_rnd_mirror/39 0 P acf/_cus_rnd_mirror/4 0 P acf/_cus_rnd_mirror/40 0 P acf/_cus_rnd_mirror/41 0 P acf/_cus_rnd_mirror/42 0 P acf/_cus_rnd_mirror/43 0 P acf/_cus_rnd_mirror/44 0 P acf/_cus_rnd_mirror/45 0 P acf/_cus_rnd_mirror/46 0 P acf/_cus_rnd_mirror/47 0 P acf/_cus_rnd_mirror/48 0 P acf/_cus_rnd_mirror/49 0 P acf/_cus_rnd_mirror/5 0 P acf/_cus_rnd_mirror/6 0 P acf/_cus_rnd_mirror/7 0 P acf/_cus_rnd_mirror/8 0 P acf/_cus_rnd_mirror/9 0 P acf/_cus_rnd_mirror/count 50 P acf/_cus_rnd_use/0 0 P acf/_cus_rnd_use/1 0 P acf/_cus_rnd_use/10 0 P acf/_cus_rnd_use/11 0 P acf/_cus_rnd_use/12 0 P acf/_cus_rnd_use/13 0 P acf/_cus_rnd_use/14 0 P acf/_cus_rnd_use/15 0 P acf/_cus_rnd_use/16 0 P acf/_cus_rnd_use/17 0 P acf/_cus_rnd_use/18 0 P acf/_cus_rnd_use/19 0 P acf/_cus_rnd_use/2 0 P acf/_cus_rnd_use/20 0 P acf/_cus_rnd_use/21 0 P acf/_cus_rnd_use/22 0 P acf/_cus_rnd_use/23 0 P acf/_cus_rnd_use/24 0 P acf/_cus_rnd_use/25 0 P acf/_cus_rnd_use/26 0 P acf/_cus_rnd_use/27 0 P acf/_cus_rnd_use/28 0 P acf/_cus_rnd_use/29 0 P acf/_cus_rnd_use/3 0 P acf/_cus_rnd_use/30 0 P acf/_cus_rnd_use/31 0 P acf/_cus_rnd_use/32 0 P acf/_cus_rnd_use/33 0 P acf/_cus_rnd_use/34 0 P acf/_cus_rnd_use/35 0 P acf/_cus_rnd_use/36 0 P acf/_cus_rnd_use/37 0 P acf/_cus_rnd_use/38 0 P acf/_cus_rnd_use/39 0 P acf/_cus_rnd_use/4 0 P acf/_cus_rnd_use/40 0 P acf/_cus_rnd_use/41 0 P acf/_cus_rnd_use/42 0 P acf/_cus_rnd_use/43 0 P acf/_cus_rnd_use/44 0 P acf/_cus_rnd_use/45 0 P acf/_cus_rnd_use/46 0 P acf/_cus_rnd_use/47 0 P acf/_cus_rnd_use/48 0 P acf/_cus_rnd_use/49 0 P acf/_cus_rnd_use/5 0 P acf/_cus_rnd_use/6 0 P acf/_cus_rnd_use/7 0 P acf/_cus_rnd_use/8 0 P acf/_cus_rnd_use/9 0 P acf/_cus_rnd_use/count 50 P acf/_custom_autopilot 0 P acf/_custom_govnr 0 P acf/_cyclic_def_ailn 0.0 P acf/_cyclic_def_elev 0.0 P acf/_cyclic_dn_trim_rat 0.0 P acf/_cyclic_lf_trim_rat 0.0 P acf/_cyclic_rt_trim_rat 0.0 P acf/_cyclic_up_trim_rat 0.0 P acf/_deg_psi_per_deg_def 1.0 P acf/_deg_the_per_deg_def 6.0 P acf/_delta3 0.0 P acf/_diff_coll_with_hdng 0.0 P acf/_diff_coll_with_ptch 0.0 P acf/_diff_coll_with_roll 0.0 P acf/_diff_collective 0.0 P acf/_diff_cycl_with_hdng_lat 0.0 P acf/_diff_cycl_with_hdng_lon 0.0 P acf/_diff_thro_hdng 0.0 P acf/_diff_thro_ptch 0.0 P acf/_diff_thro_roll 0.0 P acf/_disp_rat 0.0 P acf/_dump_altitude 0.0 P acf/_dump_press_below_1k 0 P acf/_elec_drive_eta 0.0 P acf/_elec_fuelP 30.0 P acf/_elec_regen_eta 0.0 P acf/_elev1_cratR 0.250000000 P acf/_elev1_cratT 0.250000000 P acf/_elev1_dn 90.0 P acf/_elev1_rat1 1.0 P acf/_elev1_rat2 1.0 P acf/_elev1_up 90.0 P acf/_elev1_v1 0.0 P acf/_elev1_v2 0.0 P acf/_elev2_cratR 0.0 P acf/_elev2_cratT 0.0 P acf/_elev2_dn 0.0 P acf/_elev2_rat1 1.0 P acf/_elev2_rat2 1.0 P acf/_elev2_up 0.0 P acf/_elev2_v1 0.0 P acf/_elev2_v2 0.0 P acf/_elev_def_time 0.0 P acf/_elev_dn_trim 5.0 P acf/_elev_tab 0.0 P acf/_elev_trim_time 20.0 P acf/_elev_up_trim 10.0 P acf/_elev_with_flap_rat 0.0 P acf/_engine_pwr_rat_from_crit_alt 0.0 P acf/_enter_beta_at_idle_EQ 0 P acf/_esys_amperage/110 0.0 P acf/_esys_amperage/112 0.0 P acf/_esys_amperage/115 0.0 P acf/_esys_amperage/116 0.0 P acf/_esys_amperage/117 0.0 P acf/_esys_amperage/120 0.0 P acf/_esys_amperage/124 0.0 P acf/_esys_amperage/125 0.0 P acf/_esys_amperage/126 0.0 P acf/_esys_amperage/127 0.0 P acf/_esys_amperage/128 0.0 P acf/_esys_amperage/129 0.0 P acf/_esys_amperage/130 0.0 P acf/_esys_amperage/131 0.0 P acf/_esys_amperage/150 0.0 P acf/_esys_amperage/151 0.0 P acf/_esys_amperage/152 0.0 P acf/_esys_amperage/153 0.0 P acf/_esys_amperage/154 0.0 P acf/_esys_amperage/155 0.0 P acf/_esys_amperage/156 0.0 P acf/_esys_amperage/157 0.0 P acf/_esys_amperage/158 0.0 P acf/_esys_amperage/159 0.0 P acf/_esys_amperage/160 0.0 P acf/_esys_amperage/161 0.0 P acf/_esys_amperage/162 0.0 P acf/_esys_amperage/163 0.0 P acf/_esys_amperage/164 0.0 P acf/_esys_amperage/165 0.0 P acf/_esys_amperage/166 0.0 P acf/_esys_amperage/197 0.0 P acf/_esys_amperage/198 0.0 P acf/_esys_amperage/199 0.0 P acf/_esys_amperage/200 0.0 P acf/_esys_amperage/201 0.0 P acf/_esys_amperage/204 0.0 P acf/_esys_amperage/30 0.0 P acf/_esys_amperage/31 0.0 P acf/_esys_amperage/32 0.0 P acf/_esys_amperage/33 0.0 P acf/_esys_amperage/34 0.0 P acf/_esys_amperage/35 0.0 P acf/_esys_amperage/36 0.0 P acf/_esys_amperage/37 0.0 P acf/_esys_amperage/38 0.0 P acf/_esys_amperage/492 0.0 P acf/_esys_amperage/493 0.0 P acf/_esys_amperage/51 0.0 P acf/_esys_amperage/52 0.0 P acf/_esys_amperage/53 0.0 P acf/_esys_amperage/54 0.0 P acf/_esys_amperage/56 0.0 P acf/_esys_amperage/57 0.0 P acf/_esys_amperage/58 0.0 P acf/_esys_amperage/59 0.0 P acf/_esys_amperage/60 0.0 P acf/_esys_amperage/61 0.0 P acf/_esys_amperage/62 0.0 P acf/_esys_amperage/66 0.0 P acf/_esys_amperage/67 0.0 P acf/_esys_amperage/74 0.0 P acf/_esys_amperage/75 0.0 P acf/_esys_amperage/76 0.0 P acf/_esys_amperage/80 0.0 P acf/_esys_amperage/81 0.0 P acf/_esys_amperage/88 0.0 P acf/_esys_amperage/rel_AOA 0.0 P acf/_esys_amperage/rel_HVAC 0.0 P acf/_esys_amperage/rel_adc_comp 0.0 P acf/_esys_amperage/rel_adf1 0.0 P acf/_esys_amperage/rel_adf2 0.0 P acf/_esys_amperage/rel_ail_trim 0.0 P acf/_esys_amperage/rel_auto_comp 0.0 P acf/_esys_amperage/rel_auto_servos 0.0 P acf/_esys_amperage/rel_bus0 0.0 P acf/_esys_amperage/rel_bus1 0.0 P acf/_esys_amperage/rel_bus2 0.0 P acf/_esys_amperage/rel_bus3 0.0 P acf/_esys_amperage/rel_bus4 0.0 P acf/_esys_amperage/rel_bus5 0.0 P acf/_esys_amperage/rel_dice_AOA_heat_0 0.0 P acf/_esys_amperage/rel_dice_AOA_heat_1 0.0 P acf/_esys_amperage/rel_dice_all 0.0 P acf/_esys_amperage/rel_dice_alt_air_a_0 0.0 P acf/_esys_amperage/rel_dice_alt_air_a_1 0.0 P acf/_esys_amperage/rel_dice_brake_heat 0.0 P acf/_esys_amperage/rel_dice_detect 0.0 P acf/_esys_amperage/rel_dice_pitot_heat_0 0.0 P acf/_esys_amperage/rel_dice_pitot_heat_1 0.0 P acf/_esys_amperage/rel_dice_stat_heat_0 0.0 P acf/_esys_amperage/rel_dice_stat_heat_1 0.0 P acf/_esys_amperage/rel_dice_window_heat 0.0 P acf/_esys_amperage/rel_dice_wing_heat_0 0.0 P acf/_esys_amperage/rel_dice_wing_heat_1 0.0 P acf/_esys_amperage/rel_dme 0.0 P acf/_esys_amperage/rel_efis_1 0.0 P acf/_esys_amperage/rel_efis_2 0.0 P acf/_esys_amperage/rel_ele_fp0 0.0 P acf/_esys_amperage/rel_ele_fp1 0.0 P acf/_esys_amperage/rel_ele_fp2 0.0 P acf/_esys_amperage/rel_ele_fp3 0.0 P acf/_esys_amperage/rel_ele_fp4 0.0 P acf/_esys_amperage/rel_ele_fp5 0.0 P acf/_esys_amperage/rel_ele_fp6 0.0 P acf/_esys_amperage/rel_ele_fp7 0.0 P acf/_esys_amperage/rel_elv_trim 0.0 P acf/_esys_amperage/rel_engn_sync 0.0 P acf/_esys_amperage/rel_flap_act 0.0 P acf/_esys_amperage/rel_g_AHRS 0.0 P acf/_esys_amperage/rel_g_gea 0.0 P acf/_esys_amperage/rel_g_gia1 0.0 P acf/_esys_amperage/rel_g_gia2 0.0 P acf/_esys_amperage/rel_g_magmtr 0.0 P acf/_esys_amperage/rel_gear_act 0.0 P acf/_esys_amperage/rel_gen_avio 0.0 P acf/_esys_amperage/rel_gen_esys 0.0 P acf/_esys_amperage/rel_gps1 0.0 P acf/_esys_amperage/rel_gps2 0.0 P acf/_esys_amperage/rel_heatIN0 0.0 P acf/_esys_amperage/rel_heatIN1 0.0 P acf/_esys_amperage/rel_heatIN2 0.0 P acf/_esys_amperage/rel_heatIN3 0.0 P acf/_esys_amperage/rel_heatIN4 0.0 P acf/_esys_amperage/rel_heatIN5 0.0 P acf/_esys_amperage/rel_heatIN6 0.0 P acf/_esys_amperage/rel_heatIN7 0.0 P acf/_esys_amperage/rel_heatPR0 0.0 P acf/_esys_amperage/rel_heatPR1 0.0 P acf/_esys_amperage/rel_heatPR2 0.0 P acf/_esys_amperage/rel_heatPR3 0.0 P acf/_esys_amperage/rel_heatPR4 0.0 P acf/_esys_amperage/rel_heatPR5 0.0 P acf/_esys_amperage/rel_heatPR6 0.0 P acf/_esys_amperage/rel_heatPR7 0.0 P acf/_esys_amperage/rel_inverter0 0.0 P acf/_esys_amperage/rel_inverter1 0.0 P acf/_esys_amperage/rel_lites_beac 0.0 P acf/_esys_amperage/rel_lites_hud 0.0 P acf/_esys_amperage/rel_lites_ins 0.0 P acf/_esys_amperage/rel_lites_land 0.0 P acf/_esys_amperage/rel_lites_nav 0.0 P acf/_esys_amperage/rel_lites_pan 0.0 P acf/_esys_amperage/rel_lites_strobe 0.0 P acf/_esys_amperage/rel_lites_taxi 0.0 P acf/_esys_amperage/rel_marker 0.0 P acf/_esys_amperage/rel_navcom1 0.0 P acf/_esys_amperage/rel_navcom2 0.0 P acf/_esys_amperage/rel_rud_trim 0.0 P acf/_esys_amperage/rel_servo_thro 0.0 P acf/_esys_amperage/rel_slats 0.0 P acf/_esys_amperage/rel_stab_aug 0.0 P acf/_esys_amperage/rel_stall_warn 0.0 P acf/_esys_amperage/rel_startr0 0.0 P acf/_esys_amperage/rel_startr1 0.0 P acf/_esys_amperage/rel_startr2 0.0 P acf/_esys_amperage/rel_startr3 0.0 P acf/_esys_amperage/rel_startr4 0.0 P acf/_esys_amperage/rel_startr5 0.0 P acf/_esys_amperage/rel_startr6 0.0 P acf/_esys_amperage/rel_startr7 0.0 P acf/_esys_amperage/rel_xpndr 0.0 P acf/_esys_amperage/rel_yaw_damp 0.0 P acf/_esys_bus_bit_add/110 0 P acf/_esys_bus_bit_add/112 0 P acf/_esys_bus_bit_add/115 0 P acf/_esys_bus_bit_add/116 0 P acf/_esys_bus_bit_add/117 0 P acf/_esys_bus_bit_add/120 0 P acf/_esys_bus_bit_add/124 0 P acf/_esys_bus_bit_add/125 0 P acf/_esys_bus_bit_add/126 0 P acf/_esys_bus_bit_add/127 0 P acf/_esys_bus_bit_add/128 0 P acf/_esys_bus_bit_add/129 0 P acf/_esys_bus_bit_add/130 0 P acf/_esys_bus_bit_add/131 0 P acf/_esys_bus_bit_add/150 0 P acf/_esys_bus_bit_add/151 0 P acf/_esys_bus_bit_add/152 0 P acf/_esys_bus_bit_add/153 0 P acf/_esys_bus_bit_add/154 0 P acf/_esys_bus_bit_add/155 0 P acf/_esys_bus_bit_add/156 0 P acf/_esys_bus_bit_add/157 0 P acf/_esys_bus_bit_add/158 0 P acf/_esys_bus_bit_add/159 0 P acf/_esys_bus_bit_add/160 0 P acf/_esys_bus_bit_add/161 0 P acf/_esys_bus_bit_add/162 0 P acf/_esys_bus_bit_add/163 0 P acf/_esys_bus_bit_add/164 0 P acf/_esys_bus_bit_add/165 0 P acf/_esys_bus_bit_add/166 0 P acf/_esys_bus_bit_add/197 0 P acf/_esys_bus_bit_add/198 0 P acf/_esys_bus_bit_add/199 0 P acf/_esys_bus_bit_add/200 0 P acf/_esys_bus_bit_add/201 0 P acf/_esys_bus_bit_add/204 0 P acf/_esys_bus_bit_add/30 1 P acf/_esys_bus_bit_add/31 2 P acf/_esys_bus_bit_add/32 0 P acf/_esys_bus_bit_add/33 0 P acf/_esys_bus_bit_add/34 0 P acf/_esys_bus_bit_add/35 0 P acf/_esys_bus_bit_add/36 0 P acf/_esys_bus_bit_add/37 0 P acf/_esys_bus_bit_add/38 0 P acf/_esys_bus_bit_add/492 0 P acf/_esys_bus_bit_add/493 0 P acf/_esys_bus_bit_add/51 0 P acf/_esys_bus_bit_add/52 0 P acf/_esys_bus_bit_add/53 0 P acf/_esys_bus_bit_add/54 0 P acf/_esys_bus_bit_add/56 0 P acf/_esys_bus_bit_add/57 0 P acf/_esys_bus_bit_add/58 0 P acf/_esys_bus_bit_add/59 0 P acf/_esys_bus_bit_add/60 0 P acf/_esys_bus_bit_add/61 0 P acf/_esys_bus_bit_add/62 0 P acf/_esys_bus_bit_add/66 0 P acf/_esys_bus_bit_add/67 0 P acf/_esys_bus_bit_add/74 0 P acf/_esys_bus_bit_add/75 0 P acf/_esys_bus_bit_add/76 0 P acf/_esys_bus_bit_add/80 0 P acf/_esys_bus_bit_add/81 0 P acf/_esys_bus_bit_add/88 0 P acf/_esys_bus_bit_add/rel_AOA 0 P acf/_esys_bus_bit_add/rel_HVAC 0 P acf/_esys_bus_bit_add/rel_adc_comp 0 P acf/_esys_bus_bit_add/rel_adf1 0 P acf/_esys_bus_bit_add/rel_adf2 0 P acf/_esys_bus_bit_add/rel_ail_trim 0 P acf/_esys_bus_bit_add/rel_auto_comp 0 P acf/_esys_bus_bit_add/rel_auto_servos 0 P acf/_esys_bus_bit_add/rel_bus0 1 P acf/_esys_bus_bit_add/rel_bus1 2 P acf/_esys_bus_bit_add/rel_bus2 0 P acf/_esys_bus_bit_add/rel_bus3 0 P acf/_esys_bus_bit_add/rel_bus4 0 P acf/_esys_bus_bit_add/rel_bus5 0 P acf/_esys_bus_bit_add/rel_dice_AOA_heat_0 0 P acf/_esys_bus_bit_add/rel_dice_AOA_heat_1 0 P acf/_esys_bus_bit_add/rel_dice_all 0 P acf/_esys_bus_bit_add/rel_dice_alt_air_a_0 0 P acf/_esys_bus_bit_add/rel_dice_alt_air_a_1 0 P acf/_esys_bus_bit_add/rel_dice_brake_heat 0 P acf/_esys_bus_bit_add/rel_dice_detect 0 P acf/_esys_bus_bit_add/rel_dice_pitot_heat_0 0 P acf/_esys_bus_bit_add/rel_dice_pitot_heat_1 0 P acf/_esys_bus_bit_add/rel_dice_stat_heat_0 0 P acf/_esys_bus_bit_add/rel_dice_stat_heat_1 0 P acf/_esys_bus_bit_add/rel_dice_window_heat 0 P acf/_esys_bus_bit_add/rel_dice_wing_heat_0 0 P acf/_esys_bus_bit_add/rel_dice_wing_heat_1 0 P acf/_esys_bus_bit_add/rel_dme 0 P acf/_esys_bus_bit_add/rel_efis_1 0 P acf/_esys_bus_bit_add/rel_efis_2 0 P acf/_esys_bus_bit_add/rel_ele_fp0 0 P acf/_esys_bus_bit_add/rel_ele_fp1 0 P acf/_esys_bus_bit_add/rel_ele_fp2 0 P acf/_esys_bus_bit_add/rel_ele_fp3 0 P acf/_esys_bus_bit_add/rel_ele_fp4 0 P acf/_esys_bus_bit_add/rel_ele_fp5 0 P acf/_esys_bus_bit_add/rel_ele_fp6 0 P acf/_esys_bus_bit_add/rel_ele_fp7 0 P acf/_esys_bus_bit_add/rel_elv_trim 0 P acf/_esys_bus_bit_add/rel_engn_sync 0 P acf/_esys_bus_bit_add/rel_flap_act 0 P acf/_esys_bus_bit_add/rel_g_AHRS 0 P acf/_esys_bus_bit_add/rel_g_gea 0 P acf/_esys_bus_bit_add/rel_g_gia1 0 P acf/_esys_bus_bit_add/rel_g_gia2 0 P acf/_esys_bus_bit_add/rel_g_magmtr 0 P acf/_esys_bus_bit_add/rel_gear_act 0 P acf/_esys_bus_bit_add/rel_gen_avio 0 P acf/_esys_bus_bit_add/rel_gen_esys 0 P acf/_esys_bus_bit_add/rel_gps1 0 P acf/_esys_bus_bit_add/rel_gps2 0 P acf/_esys_bus_bit_add/rel_heatIN0 0 P acf/_esys_bus_bit_add/rel_heatIN1 0 P acf/_esys_bus_bit_add/rel_heatIN2 0 P acf/_esys_bus_bit_add/rel_heatIN3 0 P acf/_esys_bus_bit_add/rel_heatIN4 0 P acf/_esys_bus_bit_add/rel_heatIN5 0 P acf/_esys_bus_bit_add/rel_heatIN6 0 P acf/_esys_bus_bit_add/rel_heatIN7 0 P acf/_esys_bus_bit_add/rel_heatPR0 0 P acf/_esys_bus_bit_add/rel_heatPR1 0 P acf/_esys_bus_bit_add/rel_heatPR2 0 P acf/_esys_bus_bit_add/rel_heatPR3 0 P acf/_esys_bus_bit_add/rel_heatPR4 0 P acf/_esys_bus_bit_add/rel_heatPR5 0 P acf/_esys_bus_bit_add/rel_heatPR6 0 P acf/_esys_bus_bit_add/rel_heatPR7 0 P acf/_esys_bus_bit_add/rel_inverter0 0 P acf/_esys_bus_bit_add/rel_inverter1 0 P acf/_esys_bus_bit_add/rel_lites_beac 0 P acf/_esys_bus_bit_add/rel_lites_hud 0 P acf/_esys_bus_bit_add/rel_lites_ins 0 P acf/_esys_bus_bit_add/rel_lites_land 0 P acf/_esys_bus_bit_add/rel_lites_nav 0 P acf/_esys_bus_bit_add/rel_lites_pan 0 P acf/_esys_bus_bit_add/rel_lites_strobe 0 P acf/_esys_bus_bit_add/rel_lites_taxi 0 P acf/_esys_bus_bit_add/rel_marker 0 P acf/_esys_bus_bit_add/rel_navcom1 0 P acf/_esys_bus_bit_add/rel_navcom2 0 P acf/_esys_bus_bit_add/rel_rud_trim 0 P acf/_esys_bus_bit_add/rel_servo_thro 0 P acf/_esys_bus_bit_add/rel_slats 0 P acf/_esys_bus_bit_add/rel_stab_aug 0 P acf/_esys_bus_bit_add/rel_stall_warn 0 P acf/_esys_bus_bit_add/rel_startr0 0 P acf/_esys_bus_bit_add/rel_startr1 0 P acf/_esys_bus_bit_add/rel_startr2 0 P acf/_esys_bus_bit_add/rel_startr3 0 P acf/_esys_bus_bit_add/rel_startr4 0 P acf/_esys_bus_bit_add/rel_startr5 0 P acf/_esys_bus_bit_add/rel_startr6 0 P acf/_esys_bus_bit_add/rel_startr7 0 P acf/_esys_bus_bit_add/rel_xpndr 0 P acf/_esys_bus_bit_add/rel_yaw_damp 0 P acf/_exhaust_rat 1.0 P acf/_face_jet 0.0 P acf/_face_rocket 0.0 P acf/_fadec_EQ_mixture 0 P acf/_fadec_EQ_rpm_limit 0 P acf/_fadec_allows_overboost 0 P acf/_fan_rpm_for_n1_100 0.0 P acf/_fbrk_on_td_EQ 0 P acf/_fdir_needed_to_engage_servo 0 P acf/_feather_on_engn_fail_EQ 0 P acf/_feather_on_mixt_pull_EQ 0 P acf/_feather_on_prop_pull_EQ 0 P acf/_feathered_pitch 0.0 P acf/_ff_rat_idle_JET 0.100000001 P acf/_ff_rat_idle_PRP 0.100000001 P acf/_file_writer_version 105101 P acf/_flap1_cd 0.0 P acf/_flap1_cl 0.0 P acf/_flap1_cm 0.0 P acf/_flap1_cratR 0.500000000 P acf/_flap1_cratT 0.500000000 P acf/_flap1_dn/0 0.0 P acf/_flap1_dn/1 0.0 P acf/_flap1_dn/2 0.0 P acf/_flap1_dn/3 0.0 P acf/_flap1_dn/4 0.0 P acf/_flap1_dn/5 0.0 P acf/_flap1_dn/6 0.0 P acf/_flap1_dn/7 0.0 P acf/_flap1_dn/8 0.0 P acf/_flap1_dn/9 0.0 P acf/_flap1_dn/count 10 P acf/_flap1_ptch 0.0 P acf/_flap1_ptch_above_50 0 P acf/_flap1_roll 0.0 P acf/_flap1_roll_above_50 0 P acf/_flap1_type 0 P acf/_flap2_cd 0.0 P acf/_flap2_cl 0.0 P acf/_flap2_cm 0.0 P acf/_flap2_cratR 0.0 P acf/_flap2_cratT 0.0 P acf/_flap2_dn/0 0.0 P acf/_flap2_dn/1 0.0 P acf/_flap2_dn/2 0.0 P acf/_flap2_dn/3 0.0 P acf/_flap2_dn/4 0.0 P acf/_flap2_dn/5 0.0 P acf/_flap2_dn/6 0.0 P acf/_flap2_dn/7 0.0 P acf/_flap2_dn/8 0.0 P acf/_flap2_dn/9 0.0 P acf/_flap2_dn/count 10 P acf/_flap2_ptch 0.0 P acf/_flap2_ptch_above_50 0 P acf/_flap2_roll 0.0 P acf/_flap2_roll_above_50 0 P acf/_flap2_type 0 P acf/_flap_EQ 1 P acf/_flap_arm 0.0 P acf/_flap_deftime 5.0 P acf/_flap_detents 3 P acf/_flap_extend_protect 0 P acf/_flap_pumps 25.0 P acf/_flap_retract_protect 0 P acf/_flap_with_stall_EQ 0 P acf/_flaps_are_infinite 0 P acf/_flaps_with_gear_EQ 0 P acf/_flaps_with_vec_EQ 0 P acf/_flare_max 0 P acf/_fly_like_a_helo 0 P acf/_folding_prop_EQ 0 P acf/_food_1/0 0.0 P acf/_food_1/1 0.0 P acf/_food_1/2 0.0 P acf/_food_1/count 3 P acf/_food_2/0 0.0 P acf/_food_2/1 0.0 P acf/_food_2/2 0.0 P acf/_food_2/count 3 P acf/_frc_damp_brak_lb 0.0 P acf/_frc_damp_hdng_lb 0.0 P acf/_frc_damp_ptch_lb 0.0 P acf/_frc_damp_roll_lb 0.0 P acf/_frc_ground_hdng_lb 0.0 P acf/_frc_ground_ptch_lb 0.0 P acf/_frc_ground_roll_lb 0.0 P acf/_frc_hdng_ias 0.0 P acf/_frc_max_brak_lb 0.0 P acf/_frc_max_hdng_lb 0.0 P acf/_frc_max_ptch_lb 0.0 P acf/_frc_max_roll_lb 0.0 P acf/_frc_ptch_ias 0.0 P acf/_frc_roll_ias 0.0 P acf/_frc_spring_brak_lb 0.0 P acf/_frc_spring_hdng_lb 0.0 P acf/_frc_spring_ptch_lb 0.0 P acf/_frc_spring_roll_lb 0.0 P acf/_frc_stall_freq 0.0 P acf/_frc_stall_lb 0.0 P acf/_frc_stickshaker_freq 0.0 P acf/_frc_stickshaker_lb 0.0 P acf/_frc_turb_hdng_lb 0.0 P acf/_frc_turb_ptch_lb 0.0 P acf/_frc_turb_roll_lb 0.0 P acf/_fuel_1/0 0.0 P acf/_fuel_1/1 0.0 P acf/_fuel_1/2 0.0 P acf/_fuel_1/count 3 P acf/_fuel_2/0 0.0 P acf/_fuel_2/1 0.0 P acf/_fuel_2/2 0.0 P acf/_fuel_2/count 3 P acf/_fuel_intro_time_jet 10.0 P acf/_fuel_intro_time_prop 5.0 P acf/_gear_EQ 1 P acf/_gear_can_ret_on_ground 0 P acf/_gear_extend_protect 0 P acf/_gear_pumps 25.0 P acf/_gear_steer_EN 0 P acf/_gear_warn_above_flap_rat 0.0 P acf/_gear_warn_below_kias 0.0 P acf/_gear_warn_below_thro_pct 0.0 P acf/_get_tow/0 0.0 P acf/_get_tow/1 0.0 P acf/_get_tow/2 0.0 P acf/_get_tow/count 3 P acf/_give_tow/0 0.0 P acf/_give_tow/1 0.0 P acf/_give_tow/2 0.0 P acf/_give_tow/count 3 P acf/_go_to_beta_below_thro 0.0 P acf/_go_to_burner_50_above_thro 0.0 P acf/_go_to_pitch_mode_on_alt_arm 0 P acf/_go_to_revr_below_thro 0.0 P acf/_govnr_EQ 0 P acf/_govnr_del 0.005000000 P acf/_govnr_dot 0.010000000 P acf/_govnr_jrk 0.005000000 P acf/_green_hi_CHT 0.0 P acf/_green_hi_EGT 0.0 P acf/_green_hi_EPR 0.0 P acf/_green_hi_FF 0.0 P acf/_green_hi_ITT 0.0 P acf/_green_hi_MP 0.0 P acf/_green_hi_N1 0.0 P acf/_green_hi_N1_apu 0.0 P acf/_green_hi_N2 0.0 P acf/_green_hi_TRQ 0.0 P acf/_green_hi_bat_amp 0.0 P acf/_green_hi_bat_volt 0.0 P acf/_green_hi_fuelP 0.0 P acf/_green_hi_gen_amp 0.0 P acf/_green_hi_gen_volt 0.0 P acf/_green_hi_oilP 0.0 P acf/_green_hi_oilT 0.0 P acf/_green_hi_pwr 0.0 P acf/_green_hi_vac 0.0 P acf/_green_hi_xmsn_P 0.0 P acf/_green_hi_xmsn_T 0.0 P acf/_green_lo_CHT 0.0 P acf/_green_lo_EGT 0.0 P acf/_green_lo_EPR 0.0 P acf/_green_lo_FF 0.0 P acf/_green_lo_ITT 0.0 P acf/_green_lo_MP 0.0 P acf/_green_lo_N1 0.0 P acf/_green_lo_N1_apu 0.0 P acf/_green_lo_N2 0.0 P acf/_green_lo_TRQ 0.0 P acf/_green_lo_bat_amp 0.0 P acf/_green_lo_bat_volt 0.0 P acf/_green_lo_fuelP 0.0 P acf/_green_lo_gen_amp 0.0 P acf/_green_lo_gen_volt 0.0 P acf/_green_lo_oilP 0.0 P acf/_green_lo_oilT 0.0 P acf/_green_lo_pwr 0.0 P acf/_green_lo_vac 0.0 P acf/_green_lo_xmsn_P 0.0 P acf/_green_lo_xmsn_T 0.0 P acf/_ground_pwr/0 0.0 P acf/_ground_pwr/1 0.0 P acf/_ground_pwr/2 0.0 P acf/_ground_pwr/count 3 P acf/_h_eqlbm 2.0 P acf/_has_ADG 0 P acf/_has_APU_switch 0 P acf/_has_DC_fd 0 P acf/_has_GLASS_hsi 0 P acf/_has_SC_fd 0 P acf/_has_TCAS 1 P acf/_has_a_fuel_any 0 P acf/_has_a_fuel_sel 0 P acf/_has_air_refuel 0 P acf/_has_bagg_1 0 P acf/_has_bagg_2 0 P acf/_has_bagg_3 0 P acf/_has_bagg_4 0 P acf/_has_board_1 0 P acf/_has_board_2 0 P acf/_has_clutch_switch 0 P acf/_has_com_radios 1 P acf/_has_crew_1 0 P acf/_has_crew_2 0 P acf/_has_crossfeed_but 0 P acf/_has_elec_hyd_switch 0 P acf/_has_food_1 0 P acf/_has_food_2 0 P acf/_has_fuel_1 0 P acf/_has_fuel_2 0 P acf/_has_full_bleed_air 0 P acf/_has_get_tow 0 P acf/_has_give_tow 0 P acf/_has_ground_pwr 0 P acf/_has_hsi 0 P acf/_has_litemap_tex_1 0 P acf/_has_litemap_tex_2 0 P acf/_has_pre_rotate 0 P acf/_has_radio_alt_callout 0 P acf/_has_spot_lite 0 P acf/_has_yawdamp_but 0 P acf/_heat_x_ctr 0.0 P acf/_hide_prop_at_90_vect 0 P acf/_hoops_hud_EQ 0 P acf/_hud_tapes_track_vector 0 P acf/_hyd_brakes 0 P acf/_hyd_ele_pump_0123 0 P acf/_hyd_engn_pump_0123/0 0 P acf/_hyd_engn_pump_0123/1 0 P acf/_hyd_engn_pump_0123/2 0 P acf/_hyd_engn_pump_0123/3 0 P acf/_hyd_engn_pump_0123/4 0 P acf/_hyd_engn_pump_0123/5 0 P acf/_hyd_engn_pump_0123/6 0 P acf/_hyd_engn_pump_0123/7 0 P acf/_hyd_engn_pump_0123/count 8 P acf/_hyd_flaps 0 P acf/_hyd_flightcons 0 P acf/_hyd_gear 0 P acf/_hyd_gear_fail_mode 0 P acf/_hyd_gear_fail_speed 0.0 P acf/_hyd_max_p 1.0 P acf/_hyd_max_p_RAT 0.0 P acf/_hyd_max_p_ele 0.0 P acf/_hyd_max_p_eng 0.0 P acf/_hyd_max_p_rot 0.0 P acf/_hyd_nw_steer 0 P acf/_hyd_prop_pump_0123/0 0 P acf/_hyd_prop_pump_0123/1 0 P acf/_hyd_prop_pump_0123/2 0 P acf/_hyd_prop_pump_0123/3 0 P acf/_hyd_prop_pump_0123/4 0 P acf/_hyd_prop_pump_0123/5 0 P acf/_hyd_prop_pump_0123/6 0 P acf/_hyd_prop_pump_0123/7 0 P acf/_hyd_prop_pump_0123/count 8 P acf/_hyd_rat_pump_0123 0 P acf/_idle_rat/0 1.0 P acf/_idle_rat/1 1.0 P acf/_idle_rat/count 2 P acf/_inverted_fuel_oil_EQ 0 P acf/_inverter_for_elec_gyros 0 P acf/_inverter_for_ind_trq 0 P acf/_inverter_for_otto 0 P acf/_inverter_for_radar 0 P acf/_is_airliner 0 P acf/_is_experimental 0 P acf/_is_general_aviation 0 P acf/_is_glider 0 P acf/_is_glossy 0 P acf/_is_helicopter 0 P acf/_is_military 0 P acf/_is_sci_fi 0 P acf/_is_seaplane 0 P acf/_is_ultralight 0 P acf/_is_vtol 0 P acf/_jato_Y 0.0 P acf/_jato_Z 0.0 P acf/_jato_dur 0.0 P acf/_jato_sfc 5.0 P acf/_jato_theta 0.0 P acf/_jato_thrust 0.0 P acf/_jett_is_acft 0 P acf/_jett_is_ret 0 P acf/_jett_is_slung 0 P acf/_jett_is_water 0 P acf/_jett_the 0.0 P acf/_jett_xyz/0 0.0 P acf/_jett_xyz/1 0.0 P acf/_jett_xyz/2 0.0 P acf/_jett_xyz/count 3 P acf/_lo_speed_is_position 0 P acf/_load_alt_presel_on_alt_hit 1 P acf/_load_presel_on_asi_hit 0 P acf/_load_presel_on_vvi_hit 0 P acf/_lock_with_elev_EQ 0 P acf/_m_displaced 0.0 P acf/_m_displaced_Y 0.0 P acf/_m_empty 142.198120117 P acf/_m_fuel_max_tot 0.0 P acf/_m_jett 0.0 P acf/_m_max 164.244338989 P acf/_m_shift 0.0 P acf/_m_shift_dx 0.0 P acf/_m_shift_dz 0.0 P acf/_manual_rad_gyr 0 P acf/_manual_reversion_rat 0.0 P acf/_max_CHT 500.0 P acf/_max_EGT 1450.0 P acf/_max_ITT 680.0 P acf/_max_MP_limit 29.920000076 P acf/_max_bat_amp 60.0 P acf/_max_gen_amp0 60.0 P acf/_max_gen_amp_per_gen/0 60.0 P acf/_max_gen_amp_per_gen/1 0.0 P acf/_max_gen_amp_per_gen/2 0.0 P acf/_max_gen_amp_per_gen/3 0.0 P acf/_max_gen_amp_per_gen/4 0.0 P acf/_max_gen_amp_per_gen/5 0.0 P acf/_max_gen_amp_per_gen/6 0.0 P acf/_max_gen_amp_per_gen/7 0.0 P acf/_max_gen_amp_per_gen/count 8 P acf/_max_mach_eff 0.800000012 P acf/_max_oilT 245.0 P acf/_max_press_diff 0.0 P acf/_max_vac 8.0 P acf/_max_xmsn_P 1.0 P acf/_max_xmsn_T 1.0 P acf/_min_n1 0.0 P acf/_min_n2 0.0 P acf/_min_rwy_len 0.0 P acf/_min_vec_on_ground 0.0 P acf/_mixt_x_ctr 0.0 P acf/_n_ref 0.0 P acf/_new_plot_XP3D_cock/0 1 P acf/_new_plot_XP3D_cock/1 1 P acf/_new_plot_XP3D_cock/2 0 P acf/_new_plot_XP3D_cock/3 0 P acf/_new_plot_XP3D_cock/4 0 P acf/_new_plot_XP3D_cock/count 5 P acf/_new_plot_outer_acf/0 0 P acf/_new_plot_outer_acf/1 0 P acf/_new_plot_outer_acf/2 0 P acf/_new_plot_outer_acf/3 0 P acf/_new_plot_outer_acf/4 1 P acf/_new_plot_outer_acf/count 5 P acf/_no_hydro_force_mult_brakes 1.0 P acf/_no_hydro_force_mult_flcons 1.0 P acf/_nom_bat_volt 24.0 P acf/_nom_fuelP 30.0 P acf/_nom_gen_volt 24.500000000 P acf/_nom_oilP 115.0 P acf/_num_batteries 1 P acf/_num_braking_wheels 0.0 P acf/_num_buses 2 P acf/_num_engn 0 P acf/_num_generators 1 P acf/_num_inverters 2 P acf/_num_prop 0 P acf/_num_xmsn 1 P acf/_nw_side_k 0.0 P acf/_nw_transition_omega 0.0 P acf/_oilT_is_C 0 P acf/_ott_asi_kts_off_for_full_def 20.0 P acf/_ott_asi_sec_into_future 10.0 P acf/_ott_asi_thro_per_sec 0.100000001 P acf/_ott_gls_sec_into_future 8.002954483 P acf/_ott_phi_deg_off_for_full_def 19.997043610 P acf/_ott_phi_sec_into_future 0.300443470 P acf/_ott_phi_sec_to_tune 4.0 P acf/_ott_roll_rate 8.0 P acf/_ott_roll_response 4.002955437 P acf/_ott_the_deg_off_for_full_def 39.994087219 P acf/_ott_the_deg_per_kt 0.200000003 P acf/_ott_the_sec_into_future 0.300443470 P acf/_ott_the_sec_to_tune 4.0 P acf/_otto_ah_source_type 10 P acf/_otto_dg_source_type 10 P acf/_paraf_dur 0.0 P acf/_paraf_max 0 P acf/_paraf_vvi 0.0 P acf/_part_descrip/24 test1 P acf/_part_descrip/56 fuselage P acf/_part_descrip/57 engine to body fairing P acf/_part_descrip/58 ventral static fin P acf/_part_descrip/59 Tail section P acf/_part_descrip/60 Exhaust P acf/_part_descrip/61 JA gun pod useless now P acf/_part_descrip/62 Nose P acf/_part_descrip/63 rear intake P acf/_part_descrip/64 front exhaust P acf/_part_descrip/65 norFaL tnidneG gS raturt P acf/_part_descrip/66 Main wing actuator P acf/_part_descrip/67 Main wing actuator P acf/_part_descrip/68 Main wing actuator P acf/_part_descrip/69 Main wing actuator P acf/_part_descrip/70 Main wing actuator P acf/_part_descrip/71 Main wing actuator P acf/_part_descrip/72 Main wing actuator P acf/_part_descrip/73 single seat canopy P acf/_part_descrip/74 front intake filler P acf/_part_descrip/75 tfeLnaL gnidaeG tS r P acf/_part_descrip/76 Fin Actuator P acf/_part_descrip/77 serpra e P acf/_part_descrip/78 serpra e P acf/_part_descrip/85 Front Wheel Fairing P acf/_part_descrip/86 hgiReG tW raleehnaP P acf/_part_descrip/87 Left Wheel Fairing P acf/_part_descrip/88 nose landing gear fairing P acf/_part_descrip/89 esoNaeG iS rF edgnal P acf/_part_descrip/count 95 P acf/_pe_xyz/0 0.0 P acf/_pe_xyz/1 0.0 P acf/_pe_xyz/2 0.0 P acf/_pe_xyz/count 3 P acf/_phase_hdng_tvect_in_at_00 0 P acf/_phase_hdng_tvect_in_at_90 0 P acf/_phase_ptch_tvect_in_at_00 0 P acf/_phase_ptch_tvect_in_at_90 0 P acf/_phase_roll_tvect_in_at_00 0 P acf/_phase_roll_tvect_in_at_90 0 P acf/_philipp_fms_slot0 0 P acf/_philipp_fms_slot1 0 P acf/_pitch_cyc_with_v1_kts 0.0 P acf/_pitch_cyc_with_v2_deg 0.0 P acf/_pitch_cyc_with_v2_kts 0.0 P acf/_power_max_limit 0.0 P acf/_power_max_thermo 0.0 P acf/_prop_inlet_recovery 0.0 P acf/_prop_pitch_deg_per_sec 45.0 P acf/_prop_x_ctr 0.0 P acf/_ptch_but_adjusts_alt_hold 0 P acf/_puff_LMN/0 0.0 P acf/_puff_LMN/1 0.0 P acf/_puff_LMN/2 0.0 P acf/_puff_LMN/count 3 P acf/_puff_xyz/0 0.0 P acf/_puff_xyz/1 0.0 P acf/_puff_xyz/2 0.0 P acf/_puff_xyz/count 3 P acf/_pwr_is_pcnt 0 P acf/_radio_alt_callout_advance 0.0 P acf/_red_hi_CHT 0.0 P acf/_red_hi_EGT 0.0 P acf/_red_hi_EPR 0.0 P acf/_red_hi_FF 0.0 P acf/_red_hi_ITT 0.0 P acf/_red_hi_MP 0.0 P acf/_red_hi_N1 0.0 P acf/_red_hi_N1_apu 0.0 P acf/_red_hi_N2 0.0 P acf/_red_hi_TRQ 0.0 P acf/_red_hi_bat_amp 0.0 P acf/_red_hi_bat_volt 0.0 P acf/_red_hi_fuelP 0.0 P acf/_red_hi_gen_amp 0.0 P acf/_red_hi_gen_volt 0.0 P acf/_red_hi_oilP 0.0 P acf/_red_hi_oilT 0.0 P acf/_red_hi_pwr 0.0 P acf/_red_hi_vac 0.0 P acf/_red_hi_xmsn_P 0.0 P acf/_red_hi_xmsn_T 0.0 P acf/_red_lo_CHT 0.0 P acf/_red_lo_EGT 0.0 P acf/_red_lo_EPR 0.0 P acf/_red_lo_FF 0.0 P acf/_red_lo_ITT 0.0 P acf/_red_lo_MP 0.0 P acf/_red_lo_N1 0.0 P acf/_red_lo_N1_apu 0.0 P acf/_red_lo_N2 0.0 P acf/_red_lo_TRQ 0.0 P acf/_red_lo_bat_amp 0.0 P acf/_red_lo_bat_volt 0.0 P acf/_red_lo_fuelP 0.0 P acf/_red_lo_gen_amp 0.0 P acf/_red_lo_gen_volt 0.0 P acf/_red_lo_oilP 0.0 P acf/_red_lo_oilT 0.0 P acf/_red_lo_pwr 0.0 P acf/_red_lo_vac 0.0 P acf/_red_lo_xmsn_P 0.0 P acf/_red_lo_xmsn_T 0.0 P acf/_rev_thrust_EQ 0 P acf/_reversed_pitch 0.0 P acf/_reverser_area 0.0 P acf/_revt_on_td_EQ 0 P acf/_rgb_green_arc/0 0.0 P acf/_rgb_green_arc/1 0.0 P acf/_rgb_green_arc/2 0.0 P acf/_rgb_green_arc/count 3 P acf/_rgb_lite_front/0 0.800000012 P acf/_rgb_lite_front/1 0.200000003 P acf/_rgb_lite_front/2 0.200000003 P acf/_rgb_lite_front/count 3 P acf/_rgb_lite_side/0 0.800000012 P acf/_rgb_lite_side/1 0.200000003 P acf/_rgb_lite_side/2 0.200000003 P acf/_rgb_lite_side/count 3 P acf/_rgb_red_arc/0 0.0 P acf/_rgb_red_arc/1 0.0 P acf/_rgb_red_arc/2 0.0 P acf/_rgb_red_arc/count 3 P acf/_rgb_spot1/0 0.0 P acf/_rgb_spot1/1 0.0 P acf/_rgb_spot1/2 0.0 P acf/_rgb_spot1/count 3 P acf/_rgb_spot2/0 0.0 P acf/_rgb_spot2/1 0.0 P acf/_rgb_spot2/2 0.0 P acf/_rgb_spot2/count 3 P acf/_rgb_spot3/0 0.0 P acf/_rgb_spot3/1 0.0 P acf/_rgb_spot3/2 0.0 P acf/_rgb_spot3/count 3 P acf/_rgb_yellow_arc/0 0.0 P acf/_rgb_yellow_arc/1 0.0 P acf/_rgb_yellow_arc/2 0.0 P acf/_rgb_yellow_arc/count 3 P acf/_rock_h_opt 0.0 P acf/_rock_max_opt 0.0 P acf/_rock_max_sl 0.0 P acf/_rock_max_vac 0.0 P acf/_roll_co 0.025000000 P acf/_roll_to_eng_spo1 0.0 P acf/_roll_to_eng_spo2 0.0 P acf/_rotor_mi_rat 1.0 P acf/_rotor_trim_max_aft 0.0 P acf/_rotor_trim_max_fwd 0.0 P acf/_rrpm_trim_rpm_fast 0.0 P acf/_rrpm_trim_rpm_slow 0.0 P acf/_rrpm_trim_time 0.0 P acf/_rudd1_cratR 0.0 P acf/_rudd1_cratT 0.0 P acf/_rudd1_lf 0.0 P acf/_rudd1_rat1 1.0 P acf/_rudd1_rat2 1.0 P acf/_rudd1_rt 0.0 P acf/_rudd1_v1 0.0 P acf/_rudd1_v2 0.0 P acf/_rudd2_cratR 0.0 P acf/_rudd2_cratT 0.0 P acf/_rudd2_lf 0.0 P acf/_rudd2_rat1 1.0 P acf/_rudd2_rat2 1.0 P acf/_rudd2_rt 0.0 P acf/_rudd2_v1 0.0 P acf/_rudd2_v2 0.0 P acf/_rudd_def_time 0.0 P acf/_rudd_lf_trim 5.0 P acf/_rudd_rt_trim 5.0 P acf/_rudd_tab 0.0 P acf/_rudd_trim_time 20.0 P acf/_rudd_with_ailn_rat 0.0 P acf/_rudd_with_fail 0.0 P acf/_rwy_req_pave 0 P acf/_sbrk1_cratR 0.0 P acf/_sbrk1_cratT 0.0 P acf/_sbrk1_up_flit 0.0 P acf/_sbrk1_up_grnd 0.0 P acf/_sbrk2_cratR 0.0 P acf/_sbrk2_cratT 0.0 P acf/_sbrk2_up_flit 0.0 P acf/_sbrk2_up_grnd 0.0 P acf/_sbrk_EQ 0 P acf/_sbrk_on_td_EQ 0 P acf/_scrolldef_x 0.0 P acf/_scrolldef_y 0.0 P acf/_show_ott_asi 0 P acf/_shut_off_fuel_at_min_prop 0 P acf/_size_tot 63.761764526 P acf/_size_x 52.493438721 P acf/_size_y 12.303149223 P acf/_size_z 34.038715363 P acf/_skid_EQ 0 P acf/_slat1_dn/0 0.0 P acf/_slat1_dn/1 0.0 P acf/_slat1_dn/2 0.0 P acf/_slat1_dn/3 0.0 P acf/_slat1_dn/4 0.0 P acf/_slat1_dn/5 0.0 P acf/_slat1_dn/6 0.0 P acf/_slat1_dn/7 0.0 P acf/_slat1_dn/8 0.0 P acf/_slat1_dn/9 0.0 P acf/_slat1_dn/count 10 P acf/_slat1_inc 6.0 P acf/_slat1_type 0 P acf/_slat2_dn/0 0.0 P acf/_slat2_dn/1 0.0 P acf/_slat2_dn/2 0.0 P acf/_slat2_dn/3 0.0 P acf/_slat2_dn/4 0.0 P acf/_slat2_dn/5 0.0 P acf/_slat2_dn/6 0.0 P acf/_slat2_dn/7 0.0 P acf/_slat2_dn/8 0.0 P acf/_slat2_dn/9 0.0 P acf/_slat2_dn/count 10 P acf/_slat2_inc 0.0 P acf/_slat2_type 0 P acf/_slat_EQ 0 P acf/_slat_with_Vind 0.0 P acf/_slat_with_stall_EQ 0 P acf/_slung_len 0.0 P acf/_snd_N1 100.0 P acf/_snd_kias 200.0 P acf/_snd_rpm_eng 2500.0 P acf/_snd_rpm_prp 2500.0 P acf/_solar_cover_part 0.0 P acf/_solar_eta 0.0 P acf/_speedbrake_ext_time 1.0 P acf/_speedbrake_ret_time 1.0 P acf/_speedbrake_size 2.0 P acf/_spoi1_cratR 0.0 P acf/_spoi1_cratT 0.0 P acf/_spoi1_rat1 1.0 P acf/_spoi1_rat2 1.0 P acf/_spoi1_up 0.0 P acf/_spoi1_v1 0.0 P acf/_spoi1_v2 0.0 P acf/_spoi1_with_flit_sbrk 0.0 P acf/_spoi1_with_grnd_sbrk 0.0 P acf/_spoi2_cratR 0.0 P acf/_spoi2_cratT 0.0 P acf/_spoi2_rat1 1.0 P acf/_spoi2_rat2 1.0 P acf/_spoi2_up 0.0 P acf/_spoi2_v1 0.0 P acf/_spoi2_v2 0.0 P acf/_spoi2_with_flit_sbrk 0.0 P acf/_spoi2_with_grnd_sbrk 0.0 P acf/_spool_time_jet 10.0 P acf/_spot1_3d_rgb/0 0.0 P acf/_spot1_3d_rgb/1 0.0 P acf/_spot1_3d_rgb/2 0.0 P acf/_spot1_3d_rgb/count 3 P acf/_spot1_3d_xyz/0 0.0 P acf/_spot1_3d_xyz/1 0.0 P acf/_spot1_3d_xyz/2 0.0 P acf/_spot1_3d_xyz/count 3 P acf/_spot2_3d_rgb/0 0.0 P acf/_spot2_3d_rgb/1 0.0 P acf/_spot2_3d_rgb/2 0.0 P acf/_spot2_3d_rgb/count 3 P acf/_spot2_3d_xyz/0 0.0 P acf/_spot2_3d_xyz/1 0.0 P acf/_spot2_3d_xyz/2 0.0 P acf/_spot2_3d_xyz/count 3 P acf/_spot3_3d_rgb/0 0.0 P acf/_spot3_3d_rgb/1 0.0 P acf/_spot3_3d_rgb/2 0.0 P acf/_spot3_3d_rgb/count 3 P acf/_spot3_3d_xyz/0 0.0 P acf/_spot3_3d_xyz/1 0.0 P acf/_spot3_3d_xyz/2 0.0 P acf/_spot3_3d_xyz/count 3 P acf/_spot_angle/0 0.0 P acf/_spot_angle/1 0.0 P acf/_spot_angle/2 0.0 P acf/_spot_angle/count 3 P acf/_spot_lite_psi_off 0.0 P acf/_spot_lite_the_off 0.0 P acf/_spot_name_3d/0 sim/cockpit2/electrical/panel_brightness_ratio[0] P acf/_spot_name_3d/1 sim/cockpit2/electrical/panel_brightness_ratio[0] P acf/_spot_name_3d/2 sim/cockpit2/electrical/panel_brightness_ratio[0] P acf/_spot_name_3d/count 3 P acf/_spot_psi/0 0.0 P acf/_spot_psi/1 0.0 P acf/_spot_psi/2 0.0 P acf/_spot_psi/count 3 P acf/_spot_size/0 0.010000000 P acf/_spot_size/1 0.010000000 P acf/_spot_size/2 0.010000000 P acf/_spot_size/count 3 P acf/_spot_the/0 0.0 P acf/_spot_the/1 0.0 P acf/_spot_the/2 0.0 P acf/_spot_the/count 3 P acf/_stab_hdng 0.0 P acf/_stab_roll 0.0 P acf/_stab_trim_dn 0.0 P acf/_stab_trim_up 0.0 P acf/_stab_with_Vind 0.0 P acf/_stab_with_flap 0.0 P acf/_stall_warn_aoa 10.0 P acf/_stall_warn_is_variable 0 P acf/_start_on_water 0 P acf/_starter_engages_fuel 0 P acf/_starter_engages_igniter_arm 0 P acf/_starter_engages_igniter_on 0 P acf/_starter_engages_ignition 0 P acf/_starter_is_air_driven 0 P acf/_starter_max_rpm_rat 0.200000003 P acf/_starter_torque_rat 0.250000000 P acf/_stickshaker 0 P acf/_sweep_m1 0.0 P acf/_sweep_m2 0.0 P acf/_sweep_with_flaps_EQ 0 P acf/_sweep_with_vect_EQ 0 P acf/_tail_rot_with_v1_kts 0.0 P acf/_tail_rot_with_v2_deg 0.0 P acf/_tail_rot_with_v2_kts 0.0 P acf/_tail_with_coll 0.0 P acf/_tailnum N809OL P acf/_tailrotor_EQ 0 P acf/_takeoff_trim_11 0.0 P acf/_tank_psi/0 0.0 P acf/_tank_psi/1 0.0 P acf/_tank_psi/2 0.0 P acf/_tank_psi/3 0.0 P acf/_tank_psi/4 0.0 P acf/_tank_psi/5 0.0 P acf/_tank_psi/6 0.0 P acf/_tank_psi/7 0.0 P acf/_tank_psi/8 0.0 P acf/_tank_psi/count 9 P acf/_tank_rat/0 0.0 P acf/_tank_rat/1 0.0 P acf/_tank_rat/2 0.0 P acf/_tank_rat/3 0.0 P acf/_tank_rat/4 0.0 P acf/_tank_rat/5 0.0 P acf/_tank_rat/6 0.0 P acf/_tank_rat/7 0.0 P acf/_tank_rat/8 0.0 P acf/_tank_rat/count 9 P acf/_tank_xyz/0,0 0.0 P acf/_tank_xyz/0,1 0.0 P acf/_tank_xyz/0,2 0.0 P acf/_tank_xyz/1,0 0.0 P acf/_tank_xyz/1,1 0.0 P acf/_tank_xyz/1,2 0.0 P acf/_tank_xyz/2,0 0.0 P acf/_tank_xyz/2,1 0.0 P acf/_tank_xyz/2,2 0.0 P acf/_tank_xyz/3,0 0.0 P acf/_tank_xyz/3,1 0.0 P acf/_tank_xyz/3,2 0.0 P acf/_tank_xyz/4,0 0.0 P acf/_tank_xyz/4,1 0.0 P acf/_tank_xyz/4,2 0.0 P acf/_tank_xyz/5,0 0.0 P acf/_tank_xyz/5,1 0.0 P acf/_tank_xyz/5,2 0.0 P acf/_tank_xyz/6,0 0.0 P acf/_tank_xyz/6,1 0.0 P acf/_tank_xyz/6,2 0.0 P acf/_tank_xyz/7,0 0.0 P acf/_tank_xyz/7,1 0.0 P acf/_tank_xyz/7,2 0.0 P acf/_tank_xyz/8,0 0.0 P acf/_tank_xyz/8,1 0.0 P acf/_tank_xyz/8,2 0.0 P acf/_tank_xyz/i_count 9 P acf/_tank_xyz/j_count 3 P acf/_the_eqlbm 0.0 P acf/_thro_x_ctr 0.0 P acf/_throt_max_fwd_emr 1.0 P acf/_throt_max_fwd_nrm 1.0 P acf/_throt_max_rev 1.0 P acf/_throt_time_jet 0.500000000 P acf/_throt_time_prop 0.500000000 P acf/_thrust_max_limit 0.0 P acf/_thrust_max_thermo 0.0 P acf/_thrust_rev_dep_time 1.0 P acf/_tip_mach_des_100 0.0 P acf/_tip_mach_des_50 0.0 P acf/_tip_weight 0.0 P acf/_total_S 0.0 P acf/_total_elements 0.0 P acf/_trans_loss 0.0 P acf/_trq_max_eng_nm_limit 0.100000001 P acf/_trq_max_eng_nm_thermo 0.0 P acf/_truck_lat_sep 3.0 P acf/_truck_lon_sep 3.0 P acf/_tvec_hdng 0.0 P acf/_tvec_ptch 0.0 P acf/_tvec_roll 0.0 P acf/_use_cus_gear_damping 0 P acf/_use_cus_gear_defs 0 P acf/_use_manual_prop_inc 0 P acf/_vario_type -1 P acf/_vect_EQ 0 P acf/_vect_max_disc 0.0 P acf/_vect_max_nace 0.0 P acf/_vect_min_disc 0.0 P acf/_vect_min_nace 0.0 P acf/_vect_rate 15.0 P acf/_vectarmY 0.0 P acf/_vectarmZ 0.0 P acf/_vvi_but_adjusts_pitch 0 P acf/_warn_airliner 0 P acf/_warn_alt_app_100 0 P acf/_warn_alt_app_1000 0 P acf/_warn_alt_app_200 0 P acf/_warn_alt_app_300 0 P acf/_warn_alt_app_mode 0 P acf/_warn_alt_dep_100 0 P acf/_warn_alt_dep_1000 0 P acf/_warn_alt_dep_200 0 P acf/_warn_alt_dep_300 0 P acf/_warn_alt_dep_mode 0 P acf/_warn_fighter 0 P acf/_warn_fire_EQ 0 P acf/_warn_fuelP 1.0 P acf/_warn_gear 0 P acf/_warn_hirot_EQ 0 P acf/_warn_lorot_EQ 0 P acf/_warn_stall 1 P acf/_warn_transonic_EQ 0 P acf/_warn_verbal_500_agl 0 P acf/_wate_xyz/0 0.0 P acf/_wate_xyz/1 0.0 P acf/_wate_xyz/2 0.0 P acf/_wate_xyz/count 3 P acf/_water_drop_dep_time 0.100000001 P acf/_water_rud_Z 0.0 P acf/_water_rud_area 0.0 P acf/_water_rud_maxdef 0.0 P acf/_water_scoop_dep_time 0.100000001 P acf/_wheel_tire_s1/0 0.509765625 P acf/_wheel_tire_s1/1 0.552734375 P acf/_wheel_tire_s1/count 2 P acf/_wheel_tire_s2/0 0.550781250 P acf/_wheel_tire_s2/1 0.630859375 P acf/_wheel_tire_s2/count 2 P acf/_wheel_tire_t1/0 0.254882812 P acf/_wheel_tire_t1/1 0.254882812 P acf/_wheel_tire_t1/count 2 P acf/_wheel_tire_t2/0 0.295898438 P acf/_wheel_tire_t2/1 0.295898438 P acf/_wheel_tire_t2/count 2 P acf/_windshield_scratchy 0 P acf/_wing_damp_rat 1.0 P acf/_wing_frac_mass 0.250000000 P acf/_wing_mid_dihed_per_g 1.0 P acf/_wing_tilt_ptch 0.0 P acf/_wing_tilt_roll 0.0 P acf/_wiper_ang1/0 0.0 P acf/_wiper_ang1/1 0.0 P acf/_wiper_ang1/2 0.0 P acf/_wiper_ang1/3 0.0 P acf/_wiper_ang1/count 4 P acf/_wiper_ang2/0 0.0 P acf/_wiper_ang2/1 0.0 P acf/_wiper_ang2/2 0.0 P acf/_wiper_ang2/3 0.0 P acf/_wiper_ang2/count 4 P acf/_wiper_max_cycles_second 2.0 P acf/_xmsn_of_engn/0 0 P acf/_xmsn_of_engn/1 0 P acf/_xmsn_of_engn/2 0 P acf/_xmsn_of_engn/3 0 P acf/_xmsn_of_engn/4 0 P acf/_xmsn_of_engn/5 0 P acf/_xmsn_of_engn/6 0 P acf/_xmsn_of_engn/7 0 P acf/_xmsn_of_engn/count 8 P acf/_xmsn_of_prop/0 0 P acf/_xmsn_of_prop/1 0 P acf/_xmsn_of_prop/2 0 P acf/_xmsn_of_prop/3 0 P acf/_xmsn_of_prop/4 0 P acf/_xmsn_of_prop/5 0 P acf/_xmsn_of_prop/6 0 P acf/_xmsn_of_prop/7 0 P acf/_xmsn_of_prop/count 8 P acf/_yawbr_cratR 0.0 P acf/_yawbr_cratT 0.0 P acf/_yawbr_rat1 1.0 P acf/_yawbr_rat2 1.0 P acf/_yawbr_ud 0.0 P acf/_yawbr_v1 0.0 P acf/_yawbr_v2 0.0 P acf/_yawstring_x 512.0 P acf/_yawstring_y 431.0 P acf/_yel_hi_CHT 0 P acf/_yel_hi_EGT 0 P acf/_yel_hi_EPR 0 P acf/_yel_hi_FF 0 P acf/_yel_hi_ITT 0 P acf/_yel_hi_MP 0 P acf/_yel_hi_N1 0 P acf/_yel_hi_N1_apu 0 P acf/_yel_hi_N2 0 P acf/_yel_hi_TRQ 0 P acf/_yel_hi_bat_amp 0 P acf/_yel_hi_bat_volt 0 P acf/_yel_hi_fuelP 0 P acf/_yel_hi_gen_amp 0 P acf/_yel_hi_gen_volt 0 P acf/_yel_hi_oilP 0 P acf/_yel_hi_oilT 0 P acf/_yel_hi_pwr 0 P acf/_yel_hi_vac 0 P acf/_yel_hi_xmsn_P 0 P acf/_yel_hi_xmsn_T 0 P acf/_yel_lo_CHT 0 P acf/_yel_lo_EGT 0 P acf/_yel_lo_EPR 0 P acf/_yel_lo_FF 0 P acf/_yel_lo_ITT 0 P acf/_yel_lo_MP 0 P acf/_yel_lo_N1 0 P acf/_yel_lo_N1_apu 0 P acf/_yel_lo_N2 0 P acf/_yel_lo_TRQ 0 P acf/_yel_lo_bat_amp 0 P acf/_yel_lo_bat_volt 0 P acf/_yel_lo_fuelP 0 P acf/_yel_lo_gen_amp 0 P acf/_yel_lo_gen_volt 0 P acf/_yel_lo_oilP 0 P acf/_yel_lo_oilT 0 P acf/_yel_lo_pwr 0 P acf/_yel_lo_vac 0 P acf/_yel_lo_xmsn_P 0 P acf/_yel_lo_xmsn_T 0 PROPERTIES_END PANEL_2D_BEGIN horiz_vac horizon_GA_vac.png POS 224.000000 307.000000 DATAREF LIGHT_MODE MECHANICAL LIGHT_RHEOSTAT 0 KEY_FRAME 0.000000 0.000000 1.000000 KEY_FRAME 1.000000 1.000000 PANEL_2D_END PANEL_3D_BEGIN horiz_vac_adj horizon_GA_vac_adj. POS 294.000000 303.000000 DATAREF LIGHT_MODE MECHANICAL LIGHT_RHEOSTAT 0 KEY_FRAME 0.000000 0.000000 1.000000 KEY_FRAME 1.000000 1.000000 PANEL_3D_END ================================================ FILE: scripts/xplaneUDPout/__init__.py ================================================ ================================================ FILE: scripts/xplaneUDPout/variables.yaml ================================================ --- # for_pos needs positions 0 to 2 to get the x,y,z position - name: 'for_pos' var_type: 'node' inout: 'out' position: 0 - name: 'for_pos' var_type: 'node' inout: 'out' position: 1 - name: 'for_pos' var_type: 'node' inout: 'out' position: 2 # The quaternion has 4 terms. All are neeeded to carry out # the conversion to roll, pitch and yaw - name: 'quat' var_type: 'node' inout: 'out' position: 0 - name: 'quat' var_type: 'node' inout: 'out' position: 1 - name: 'quat' var_type: 'node' inout: 'out' position: 2 - name: 'quat' var_type: 'node' inout: 'out' position: 3 # Valid for simple HALE # Vertical tip deflection (In positive y-axis) - name: 'pos' var_type: 'node' inout: 'out' # Row position: 16 # Column index: 2 # Horizontal tip deflection - name: 'pos' var_type: 'node' inout: 'out' position: 16 index: 0 # # Simple HALE has two control surfaces # - name: 'control_surface_deflection' # variable name. those in the timestep_info are supported # var_type: 'control_surface' # inout: 'out' # either `in`, `out` or `inout` # position: 0 # control surface index # - name: 'control_surface_deflection' # var_type: 'control_surface' # inout: 'out' # position: 1 # - name: 'control_surface_deflection' # var_type: 'control_surface' # inout: 'out' # position: 2 ... ================================================ FILE: scripts/xplaneUDPout/xplaneUDP.py ================================================ import socket import struct import binascii import pickle import os import numpy as np import sharpy.utils.solver_interface as solver_interface import sharpy.postproc from sharpy.utils.algebra import quat2euler class XPlaneIpNotFound(Exception): args = "Could not find any running XPlane instance in network." class XPlaneTimeout(Exception): args = "XPlane timeout." class XPlaneUdp: """ Class that co-ordinates the sending of data via UDP to X-Plane ``YAML`` file assumed to be have variables in the order of, pos (x,y,z), quat (4 vars), vertical tip deflection, horizontal tip deflection Uses SHARPy UDPout postprocessor """ # Discover X-Plane via Beacon and uses multicast # Constants UDP_PORT = 49000 MCAST_GRP = "239.255.1.1" MCAST_PORT = 49707 EARTH_RADIUS = 6378.137*10 ** 3 # Equatorial radius in [m] MAX_DIHEDRAL = 180 # Degrees MAX_SWEEP = 90 # Degrees HEIGHT_OFFSET = 100 # Meters DEFLECTION_ANGLE_MULTIPLICATOR = 10 def __init__(self, pklFile): # Find location of X-Plane self.BeaconData = self.find_ip_xplane() self._byte_ordering = '<' _host, _port = self.find_local_ip() settings = dict() settings['UDPout'] = { 'output_network_settings': { 'destination_address': [self.BeaconData['IP']], 'destination_ports': [self.BeaconData['Port']], 'address': _host, 'port': _port, }, 'variables_filename': os.getcwd() + '/variables.yaml', } self.timestep_counter = 0 self.data = pklFile self.udpSolver = solver_interface.initialise_solver('UDPout') self.udpSolver.initialise(data, custom_settings=settings['UDPout']) # Longitude and Latitude of LHR (arbitrarily set) self.locLat = 51.470020 # 0 self.locLon = -0.454295 # 0 self.prevX = 0 self.prevY = 0 self.drefPaths = {} self.drefPaths['overrideCS'] = "sim/operation/override/override_control_surfaces" ### Ailerons ### self.drefPaths['leftAileronDef'] = "sim/flightmodel/controls/wing1l_ail1def" self.drefPaths['rightAileronDef'] = "sim/flightmodel/controls/wing1r_ail1def" ### Stabilisers ### self.drefPaths['elevatorDef'] = "sim/flightmodel/controls/hstab1_elv1def" self.drefPaths['rudderDef'] = "sim/flightmodel/controls/vstab1_rud1def" # Cockpit Panel Indicator # Dref for: The indicated pitch on the panel for the first vacuum instrument self.drefPaths['vertIndicator'] = "sim/cockpit/gyros/the_vac_ind_deg" # Dref for: The indicated roll on the panel for the first vacuum instrument self.drefPaths['horzIndicator'] = "sim/cockpit/gyros/phi_vac_ind_deg" # The flightmodel2 section contains data about how the actual aircraft is being drawn # Actual sweep in ratio (0=> no sweep, 1 => max sweep) [float, ratio] self.drefPaths['variableSweep'] = "sim/flightmodel2/controls/wingsweep_ratio" # Acutal dihedral [float, ratio] self.drefPaths['variableDihedral'] = "sim/flightmodel2/controls/dihedral_ratio" # Actual incidence [float, ratio] self.drefPaths['variableIncidence'] = "sim/flightmodel2/controls/incidence_ratio" # Datarefs above achieve the same # self.drefPaths['variableSweep'] = "sim/flightmodel/controls/swdi" # self.drefPaths['variableDihedral'] = "sim/flightmodel/controls/dihed_rat" # self.drefPaths['variableIncidence'] = "sim/flightmodel/controls/incid_ratio" def run(self): # Get values for a given time step vals = self.get_struct_value( self.data, timestep_index=self.timestep_counter) # Encode location information dvisMsg = self.encode_dvis(vals) # Send encoded information self.udpSolver.out_network.send( dvisMsg, self.udpSolver.out_network.clients) # Aeroelastic Information on cockpit panel vals = self.convert_tip_disp_to_angle(vals, [7, 8], 16) indicatorMsg = self.encode_dref( self.drefPaths['vertIndicator'], 7, lstOfVals=vals) self.udpSolver.out_network.send( indicatorMsg, self.udpSolver.out_network.clients) indicatorMsg2 = self.encode_dref( self.drefPaths['horzIndicator'], 8, lstOfVals=vals) self.udpSolver.out_network.send( indicatorMsg2, self.udpSolver.out_network.clients) # The simulation used here has fixed control surfaces vals = np.append(vals, np.asarray([-2.08, -2.08, 0])) # Send control surface information overrideMsg = self.encode_dref(self.drefPaths['overrideCS'], True) self.udpSolver.out_network.send( overrideMsg, self.udpSolver.out_network.clients) leftAilMsg = self.encode_dref( self.drefPaths['leftAileronDef'], 9, lstOfVals=vals) self.udpSolver.out_network.send( leftAilMsg, self.udpSolver.out_network.clients) rightAilMsg = self.encode_dref( self.drefPaths['rightAileronDef'], 10, lstOfVals=vals) self.udpSolver.out_network.send( rightAilMsg, self.udpSolver.out_network.clients) elevatorMsg = self.encode_dref( self.drefPaths['elevatorDef'], 11, lstOfVals=vals) self.udpSolver.out_network.send( elevatorMsg, self.udpSolver.out_network.clients) # Aeroelastic Information on wing vals = self.find_deflection_ratio( vals, [7, 8], [self.MAX_DIHEDRAL, self.MAX_SWEEP]) dihedralMsg = self.encode_dref( self.drefPaths['variableDihedral'], 12, lstOfVals=vals) self.udpSolver.out_network.send( dihedralMsg, self.udpSolver.out_network.clients) sweepMsg = self.encode_dref( self.drefPaths['variableSweep'], 13, lstOfVals=vals) self.udpSolver.out_network.send( sweepMsg, self.udpSolver.out_network.clients) # incidenceMsg = self.encode_dref( # self.drefPaths['variableIncidence'], 14, lstOfVals=vals) # self.udpSolver.out_network.send( # incidenceMsg, self.udpSolver.out_network.clients) # # Incrementthe counter so it accesses the next time step values self.timestep_counter += 1 def get_struct_value(self, data, timestep_index=-1): numVars = len(self.udpSolver.set_of_variables.variables) values = np.zeros(numVars) for i in range(numVars): try: values[i] = self.udpSolver.set_of_variables.variables[i].get_variable_value( data, timestep_index) except: # This is very dangerous!! # Created to handle the fact that the first control surface deflection angle is # not stored in the pickle (list is empty) values[i] = 0 return values def encode_dref(self, drefPath, idx, lstOfVals=np.asarray([])): """Sets any dataref to a given value via UDP The datarefs used by X-Plane can be found at, https://developer.x-plane.com/datarefs/ :: struct dref_struct: { xflt var; xchar dref_path[strDIM]; } N.B. Instructions in the sending data to X-Plane manually state that the dref_path needs to be null-terminated. However, doing so made the dataref unrecognisible to X-Plane. The issue was fixed by removing the null value. The instructions also state the the whole message should have size 509 bytes. However, this made X-Plane throw an error stating that the message is the incorrect length. The issue was resolves by changing the message length to be 512 bytes Therefore, typical message looks like, DREF0 + (4byte value) + dref_path + spaces to complte the whole message to 512 bytes. (N.B. + => append. Don't include them in the actual message) E.G. to switch on anti-ice switch DREF0 + (4byte value of 1) + sim/cockpit/switches/anti_ice_surf_heat_left + spaces to complete to 512 bytes Args: drefPath (str): Dataref path to the variable being set idx (int): Location of value to set in lstOfVals lstOfVals (array): All data Returns: Message to be sent to X-Plane in the required format """ msg = struct.pack('{}5s'.format(self._byte_ordering), b'DREF0') if lstOfVals.size == 0 and type(idx) == bool: # This is to send a boolean value (allows for override command to be sent) # Based on the information from XPlane dataref website, the type should be # int but when int is used for the format, XPlane always reads the value as # 0 instead of what the actual value is. Therefore, the type for packing # needs to be f (float) instead of int msg += struct.pack('{}f'.format(self._byte_ordering), int(idx)) elif lstOfVals.size == 0: raise ValueError( "If list of values is empty, idx must be a boolean to indicate override") else: msg += struct.pack('{}f'.format(self._byte_ordering), lstOfVals[idx]) # Convert dref path which is a string to bytes drefPath = bytes(drefPath, 'utf-8') # Add dreft path and null terminate it msg += struct.pack("{}s".format(len(drefPath)), drefPath) # Find the size of the message msgSize = struct.calcsize('5sf' + "{}s".format(len(drefPath))) # Determine how many blank spaces to include remainder = 512 - msgSize # Append the black spaces to complete to 512 bytes msg += struct.pack('{}x'.format(remainder)) return msg def encode_dvis(self, lstOfVals): """Sends message to X-Plane to turn off flight engine and this sets location of aircraft To run X-Plane as a visual, the flight engine needs to be turned off, this is achieved by sending "DVIS0" followed by the struct: :: struct dvis_struct: { xdob lat_lon_ele[3]; # double precision for lattitude, longitude & elevation above MSL (m) xdob psi_the_phi[3]; # True heading, pitch up, roll right in degrees } Double precision is used to avoid byte-spacing confusion N.B. This should be sent at a higher frame rate than X-Plane for smooth animation Args: lstOfVals (array): Numpy array that contains all the information obtained from pickled sim Returns: Message to be sent to X-Plane in the required format """ msg = struct.pack('{}5s'.format(self._byte_ordering), b'DVIS0') lat, lon = self.convert_to_lat_lon(lstOfVals) msg += struct.pack("{}ddd".format(self._byte_ordering), lat, lon, lstOfVals[2]+self.HEIGHT_OFFSET) # Information from pickle is in quaternion form. Therefore, this needs # to be converted to euler angles eulerAngles = quat2euler(lstOfVals[3:7]) # Above conversion results in radians but X-Plane requires degrees eulerAngles = np.rad2deg(eulerAngles) # Individual naming for clarity roll, pitch, yaw = eulerAngles msg += struct.pack('{}ddd'.format(self._byte_ordering), yaw, pitch, roll) return msg def convert_to_lat_lon(self, values): """Converts x, y positions[m] from simulation to latitude and logitude[degrees] This method is taken from: https://stackoverflow.com/questions/10122055/calculate-longitude-from-distance Args: values (array): Contains all simulation data for a given time step. It is assumed that the x, y position is in the 0th and 1st position respectively Returns: Local latitude [degrees], Local longitude [degrees] """ x = values[0] dx = x - self.prevX y = values[1] dy = y - self.prevY deltaLon = dx/self.EARTH_RADIUS deltaLat = dx/self.EARTH_RADIUS self.locLat = self.locLat + np.sign(dy)*np.rad2deg(deltaLat) self.locLon = self.locLon + np.sign(dx)*np.rad2deg(deltaLon*1.195) return self.locLat, self.locLon def convert_tip_disp_to_angle(self, lstOfVals, indices, arm): """Converts tip displacement(m) to an angle(degrees) Sign conventions are: Positive vertical displacmenet implies up. Positive horizontal displacement implies aft Args: indices (iterable): Refers to the indices that need conversion arm: (float) Semi-span of the wing for tip displacement but any length along the wing can be used as long as it corresponds to be displacement seen lstOfVals (array): Numpy array that contains all the information for a given timestep Returns: Updated lstOfVals array """ for index in indices: angle = np.rad2deg(np.arctan(float(lstOfVals[index])/arm)) lstOfVals[index] = angle return lstOfVals def find_deflection_ratio(self, lstOfVals, indices, limits): """Converts an angle of deflection to ratio with respect to the maximum deflection angle N.B: This step MUST take place after the tip displacement has been converted to angles (degree) Args: indices (iterable): Location within origin array of vals limits (iterable): Value which the ratio is found with respect to (i.e. the maximum value) lstOfVals (array): Numpy array that contains all the information for a given timestep Returns: Updated lstOfVals array """ temp = np.zeros(len(indices)) for i, index in enumerate(indices): temp[i] = float(lstOfVals[index])/limits[i] lstOfVals = np.append(lstOfVals, temp) return lstOfVals def find_local_ip(self): """Finds default IP and open port This automates the process of entering an IP address and port in the settings for SHARPy UDPout post-processor This function has been adapted from: https://stackoverflow.com/questions/166506/finding-local-ip-addresses-using-pythons-stdlib Returns: Single IP address that is the default route and an open port """ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: # Doesn't even have to be reachable s.connect(('10.255.255.255', 1)) IP, port = s.getsockname() except Exception: IP = '127.0.0.1' # This is a randomly chosen number port = 50585 finally: s.close() return IP, port def find_ip_xplane(self): """Finds X-Plane host in network This takes the first X-Plane 10 host it can find. To prevent confusion, ensure only one is running This function is wholesale from: https://github.com/charlylima/XPlaneUDP/blob/master/XPlaneUdp.py Returns: Dictionary containing IP, port, hostname, X-Plane version and role """ self.BeaconData = {} # open socket for multicast group. sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((self.MCAST_GRP, self.MCAST_PORT)) mreq = struct.pack("=4sl", socket.inet_aton( self.MCAST_GRP), socket.INADDR_ANY) sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) sock.settimeout(3.0) while not self.BeaconData: # receive data try: packet, sender = sock.recvfrom(15000) # decode data # * Header header = packet[0:5] if header != b"BECN\x00": print("Unknown packet from "+sender[0]) print(str(len(packet)) + " bytes") print(packet) print(binascii.hexlify(packet)) else: data = packet[5:21] # struct becn_struct # { # uchar beacon_major_version; // 1 at the time of X-Plane 10.40 # uchar beacon_minor_version; // 1 at the time of X-Plane 10.40 # xint application_host_id; // 1 for X-Plane, 2 for PlaneMaker # xint version_number; // 104014 for X-Plane 10.40b14 # uint role; // 1 for master, 2 for extern visual, 3 for IOS # ushort port; // port number X-Plane is listening on # xchr computer_name[strDIM]; // the hostname of the computer # }; beacon_major_version = 0 beacon_minor_version = 0 application_host_id = 0 xplane_version_number = 0 role = 0 port = 0 ( beacon_major_version, # 1 at the time of X-Plane 10.40 beacon_minor_version, # 1 at the time of X-Plane 10.40 application_host_id, # 1 for X-Plane, 2 for PlaneMaker xplane_version_number, # 104014 for X-Plane 10.40b14 role, # 1 for master, 2 for extern visual, 3 for IOS port, # port number X-Plane is listening on ) = struct.unpack(" -r /path/to/ To check that X-Plane is receiving all information being sent, get X-Plane to dump net data to log.txt. Under Settings > Operations & Warnings > Data > dumpt net data to log.txt. Log.txt can be found under the X-Plane 10 folder in your computer. How it works: 1. Finds X-Plane 10 via Beacon (uses multicast) 2. Finds default IP and an open port to send simulation information via 3. Initialises SHARPy UDPout post-processor 4. For every timestep in the simulation, send the information specified in run via UDP to the X-Plane datarefs Adapted from: https://github.com/charlylima/XPlaneUDP/blob/master/XPlaneUdp.py Original is for receiving data from X-Plane and has been adapted to send data to X-Plane See also: X-Plane Manual for sending and receiving info to X-Plane found in installed folder. X-Plane 10 > Instructions > Sending Data to X-plane.rtfd > TXT.rtf """ import argparse import time parser = argparse.ArgumentParser() parser.add_argument('-r', '--restart', help='restart the solution with a given snapshot', type=str, default=None, required=True) args = parser.parse_args() try: with open(args.restart, 'rb') as restart_file: data = pickle.load(restart_file) except FileNotFoundError: raise FileNotFoundError('The file specified for the snapshot \ restart (-r) does not exist. Please check.') xp = XPlaneUdp(data) for _ in range(data.ts + 1): xp.run() # Prevents all simulaiton info from being sent all at once time.sleep(0.03) ================================================ FILE: setup.py ================================================ from setuptools import setup, find_packages, Extension, Command #from skbuild import setup from setuptools.command.build_ext import build_ext import subprocess import re import os class CMakeBuildExt(build_ext): """Custom command to build Submodules packages during installation.""" # def copy_extensions_to_source(self): # "Override the method to prevent copying package files" # pass def finalize_options(self): super().finalize_options() # Process and use os.environ['CUSTOM_CONFIG_SETTINGS'] as needed self.pip_nobuild = os.environ.get('PIP_NOBUILD') def run(self): package_dir = os.path.dirname(os.path.abspath(__file__)) build_dir = package_dir + "/build" cmake_args = [] if self.pip_nobuild=="yes": pass else: if not os.path.isdir(build_dir): os.makedirs(build_dir) subprocess.check_call( ["cmake", ".."] + cmake_args, cwd=build_dir ) subprocess.check_call( ["make", "install", "-j4"], cwd=build_dir ) super().run() def run(): pip_nobuild = os.environ.get('PIP_NOBUILD') package_dir = os.path.dirname(os.path.abspath(__file__)) build_dir = package_dir + "/build" cmake_args = [] if pip_nobuild=="yes": pass else: if not os.path.isdir(build_dir): os.makedirs(build_dir) subprocess.check_call( ["cmake", ".."] + cmake_args, cwd=build_dir ) subprocess.check_call( ["make", "install", "-j4"], cwd=build_dir ) class BuildCommand(Command): """Custom command to build Submodules packages without installation.""" description = 'Build Submodules in lib packages' user_options = [ ('cmake-args=', None, 'Additional CMake arguments'), ] def initialize_options(self): self.cmake_args = None def finalize_options(self): pass def run(self): # Run the CMake build step with additional cmake_args package_dir = os.path.dirname(os.path.abspath(__file__)) build_dir = package_dir + "/build" if not os.path.isdir(build_dir): os.makedirs(build_dir) if self.cmake_args is not None: subprocess.check_call( ["cmake", f"{self.cmake_args}", ".."], cwd=build_dir ) else: subprocess.check_call( ["cmake", ".."], cwd=build_dir ) subprocess.check_call( ["make", "install", "-j4"], cwd=build_dir ) ext_modules = [ Extension('lib', []), # Add more Extension instances for additional extension modules ] this_directory = os.path.abspath(os.path.dirname(__file__)) __version__ = re.findall( r"""__version__ = ["']+([0-9\.]*)["']+""", open(os.path.join(this_directory, "sharpy/version.py")).read(), )[0] with open(os.path.join(this_directory, "README.md"), encoding="utf-8") as f: long_description = f.read() run() setup( name="ic_sharpy", # due to the name sharpy being taken on pypi version=__version__, description="""SHARPy is a nonlinear aeroelastic analysis package developed at the Department of Aeronautics, Imperial College London. It can be used for the structural, aerodynamic and aeroelastic analysis of flexible aircraft, flying wings and wind turbines.""", long_description=long_description, long_description_content_type="text/markdown", keywords="nonlinear aeroelastic structural aerodynamic analysis", author="", author_email="", url="https://github.com/ImperialCollegeLondon/sharpy", license="BSD 3-Clause License", #ext_modules=ext_modules, cmdclass={#"build_ext": CMakeBuildExt, "build_subm": BuildCommand}, packages=find_packages( where='./', include=['sharpy*'], exclude=['tests'] ), # data_files=[ # ("./lib/UVLM/lib", ["libuvlm.so"]), # ("./lib/xbeam/lib", ["libxbeam.so"]) # ], python_requires=">=3.8", install_requires=[ "numpy<2.0", "configobj", "h5py", "scipy<1.14.0", "sympy", "matplotlib", "colorama", "dill", "jupyterlab", "pandas", "control", "openpyxl>=3.0.10", "lxml>=4.4.1", "PySocks", "PyYAML", "jax", "vtk", ], extras_require={ "docs": [ "sphinx", "recommonmark>=0.6.0", "sphinx_rtd_theme>=0.4.3", "nbsphinx>=0.4.3" ], "all": [ "sphinx", "recommonmark>=0.6.0", "sphinx_rtd_theme>=0.4.3", "nbsphinx>=0.4.3" ], }, classifiers=[ "Operating System :: MacOS", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Fortran", "Programming Language :: C++" ], entry_points={ 'console_scripts': ['sharpy=sharpy.sharpy_main:sharpy_run'], } ) ================================================ FILE: sharpy/__init__.py ================================================ from .version import __version__ ================================================ FILE: sharpy/aero/__init__.py ================================================ """Aerodynamic Packages""" ================================================ FILE: sharpy/aero/models/__init__.py ================================================ ================================================ FILE: sharpy/aero/models/aerogrid.py ================================================ """Aerogrid Aerogrid contains all the necessary routines to generate an aerodynamic grid based on the input dictionaries. """ import ctypes as ct import warnings import numpy as np import scipy.interpolate import sharpy.utils.algebra as algebra import sharpy.utils.cout_utils as cout from sharpy.utils.datastructures import AeroTimeStepInfo import sharpy.utils.generator_interface as gen_interface from sharpy.aero.models.grid import Grid class Aerogrid(Grid): """ ``Aerogrid`` is the main object containing information of the grid of panels It is created by the solver :class:`sharpy.solvers.aerogridloader.AerogridLoader` """ def __init__(self): super().__init__() self.dimensions_star = None self.airfoil_db = dict() self.grid_type = "aero" self.n_control_surfaces = 0 self.cs_generators = [] self.initial_strip_z_rot = None def generate(self, data_dict, beam, settings, ts): super().generate(data_dict, beam, settings, ts) # write grid info to screen self.output_info() # allocating initial grid storage self.ini_info = AeroTimeStepInfo(self.dimensions, self.dimensions_star) # Initial panel orientation, used when aligned grid is off self.initial_strip_z_rot = np.zeros([self.n_elem, 3]) if not settings['aligned_grid'] and settings['initial_align']: for i_elem in range(self.n_elem): for i_local_node in range(3): Cab = algebra.crv2rotation(beam.ini_info.psi[i_elem, i_local_node, :]) self.initial_strip_z_rot[i_elem, i_local_node] = \ algebra.angle_between_vectors_sign(settings['freestream_dir'], Cab[:, 1], Cab[:, 2]) # load airfoils db # for i_node in range(self.n_node): for i_elem in range(self.n_elem): for i_local_node in range(self.beam.num_node_elem): try: self.airfoil_db[self.data_dict['airfoil_distribution'][i_elem, i_local_node]] except KeyError: airfoil_coords = self.data_dict['airfoils'][ str(self.data_dict['airfoil_distribution'][i_elem, i_local_node])] self.airfoil_db[self.data_dict['airfoil_distribution'][i_elem, i_local_node]] = ( scipy.interpolate.interp1d(airfoil_coords[:, 0], airfoil_coords[:, 1], kind='quadratic', copy=False, fill_value='extrapolate', assume_sorted=True)) try: self.n_control_surfaces = np.sum(np.unique(self.data_dict['control_surface']) >= 0) except KeyError: pass # Backward compatibility: check whether control surface deflection aero_settings have been specified. If not, create # section with empty list, such that no cs generator is appended try: settings['control_surface_deflection'] except KeyError: settings.update({'control_surface_deflection': [''] * self.n_control_surfaces}) # pad ctrl surfaces dict with empty strings if not defined if len(settings['control_surface_deflection']) != self.n_control_surfaces: undef_ctrl_sfcs = [''] * (self.n_control_surfaces - len(settings['control_surface_deflection'])) settings['control_surface_deflection'].extend(undef_ctrl_sfcs) # initialise generators with_error_initialising_cs = False for i_cs in range(self.n_control_surfaces): if settings['control_surface_deflection'][i_cs] == '': self.cs_generators.append(None) else: cout.cout_wrap('Initialising Control Surface {:g} generator'.format(i_cs), 1) # check that the control surface is not static if self.data_dict['control_surface_type'][i_cs] == 0: raise TypeError('Control surface {:g} is defined as static but there is a control surface generator' 'associated with it'.format(i_cs)) generator_type = gen_interface.generator_from_string( settings['control_surface_deflection'][i_cs]) self.cs_generators.append(generator_type()) try: self.cs_generators[i_cs].initialise( settings['control_surface_deflection_generator_settings'][str(i_cs)]) except KeyError: with_error_initialising_cs = True cout.cout_wrap('Error, unable to locate a settings dictionary for control surface ' '{:g}'.format(i_cs), 4) if with_error_initialising_cs: raise KeyError('Unable to locate settings for at least one control surface.') self.add_timestep() self.generate_mapping() self.generate_zeta(self.beam, self.aero_settings, ts) if 'polars' in self.data_dict: import sharpy.aero.utils.airfoilpolars as ap self.polars = [] nairfoils = np.amax(self.data_dict['airfoil_distribution']) + 1 for iairfoil in range(nairfoils): new_polar = ap.Polar() new_polar.initialise(data_dict['polars'][str(iairfoil)]) self.polars.append(new_polar) def output_info(self): cout.cout_wrap('The aerodynamic grid contains %u surfaces' % self.n_surf, 1) for i_surf in range(self.n_surf): cout.cout_wrap(' Surface %u, M=%u, N=%u' % (i_surf, self.dimensions[i_surf, 0], self.dimensions[i_surf, 1]), 1) cout.cout_wrap(' Wake %u, M=%u, N=%u' % (i_surf, self.dimensions_star[i_surf, 0], self.dimensions_star[i_surf, 1])) cout.cout_wrap(' In total: %u bound panels' % (sum(self.dimensions[:, 0] * self.dimensions[:, 1]))) cout.cout_wrap(' In total: %u wake panels' % (sum(self.dimensions_star[:, 0] * self.dimensions_star[:, 1]))) cout.cout_wrap(' Total number of panels = %u' % (sum(self.dimensions[:, 0] * self.dimensions[:, 1]) + sum(self.dimensions_star[:, 0] * self.dimensions_star[:, 1]))) def calculate_dimensions(self): super().calculate_dimensions() self.dimensions_star = self.dimensions.copy() self.dimensions_star[:, 0] = self.aero_settings['mstar'] def generate_zeta_timestep_info(self, structure_tstep, aero_tstep, beam, settings, it=None, dt=None): if it is None: it = len(beam.timestep_info) - 1 global_node_in_surface = [] for i_surf in range(self.n_surf): global_node_in_surface.append([]) # check that we have control surface information try: self.data_dict['control_surface'] with_control_surfaces = True except KeyError: with_control_surfaces = False # check that we have sweep information try: self.data_dict['sweep'] except KeyError: self.data_dict['sweep'] = np.zeros_like(self.data_dict['twist']) # Define first_twist for backwards compatibility if 'first_twist' not in self.data_dict: self.data_dict['first_twist'] = [True] * self.data_dict['surface_m'].shape[0] # one surface per element for i_elem in range(self.n_elem): i_surf = self.data_dict['surface_distribution'][i_elem] # check if we have to generate a surface here if i_surf == -1: continue for i_local_node in range(len(self.beam.elements[i_elem].global_connectivities)): i_global_node = self.beam.elements[i_elem].global_connectivities[i_local_node] # i_global_node = self.beam.elements[i_elem].global_connectivities[ # self.beam.elements[i_elem].ordering[i_local_node]] if not self.data_dict['aero_node'][i_global_node]: continue if i_global_node in global_node_in_surface[i_surf]: continue else: global_node_in_surface[i_surf].append(i_global_node) # find the i_surf and i_n data from the mapping i_n = -1 ii_surf = -1 for i in range(len(self.struct2aero_mapping[i_global_node])): i_n = self.struct2aero_mapping[i_global_node][i]['i_n'] ii_surf = self.struct2aero_mapping[i_global_node][i]['i_surf'] if ii_surf == i_surf: break # make sure it found it if i_n == -1 or ii_surf == -1: raise AssertionError('Error 12958: Something failed with the mapping in aerogrid.py. Check/report!') # control surface implementation control_surface_info = None if with_control_surfaces: # 1) check that this node and elem have a control surface if self.data_dict['control_surface'][i_elem, i_local_node] >= 0: i_control_surface = self.data_dict['control_surface'][i_elem, i_local_node] # 2) type of control surface + write info control_surface_info = dict() if self.data_dict['control_surface_type'][i_control_surface] == 0: control_surface_info['type'] = 'static' control_surface_info['deflection'] = self.data_dict['control_surface_deflection'][ i_control_surface] control_surface_info['chord'] = self.data_dict['control_surface_chord'][i_control_surface] try: control_surface_info['hinge_coords'] = self.data_dict['control_surface_hinge_coords'][ i_control_surface] except KeyError: control_surface_info['hinge_coords'] = None elif self.data_dict['control_surface_type'][i_control_surface] == 1: control_surface_info['type'] = 'dynamic' control_surface_info['chord'] = self.data_dict['control_surface_chord'][i_control_surface] try: control_surface_info['hinge_coords'] = self.data_dict['control_surface_hinge_coords'][ i_control_surface] except KeyError: control_surface_info['hinge_coords'] = None params = {'it': it} control_surface_info['deflection'], control_surface_info['deflection_dot'] = \ self.cs_generators[i_control_surface](params) elif self.data_dict['control_surface_type'][i_control_surface] == 2: control_surface_info['type'] = 'controlled' try: old_deflection = self.data.aero.timestep_info[-1].control_surface_deflection[ i_control_surface] except AttributeError: try: old_deflection = aero_tstep.control_surface_deflection[i_control_surface] except IndexError: old_deflection = self.data_dict['control_surface_deflection'][i_control_surface] try: control_surface_info['deflection'] = aero_tstep.control_surface_deflection[ i_control_surface] except IndexError: control_surface_info['deflection'] = self.data_dict['control_surface_deflection'][ i_control_surface] if dt is not None: control_surface_info['deflection_dot'] = ( (control_surface_info['deflection'] - old_deflection) / dt) else: control_surface_info['deflection_dot'] = 0.0 control_surface_info['chord'] = self.data_dict['control_surface_chord'][i_control_surface] try: control_surface_info['hinge_coords'] = self.data_dict['control_surface_hinge_coords'][ i_control_surface] except KeyError: control_surface_info['hinge_coords'] = None else: raise NotImplementedError(str(self.data_dict['control_surface_type'][i_control_surface]) + ' control surfaces are not yet implemented') # add sweep for aerogrid warping in constraint defintition # if no constraint_xx.aerogrid warp factor is provided or the constraint is not an actuated type, # this will be ignored ang_warp = 0. if structure_tstep.mb_dict is not None and structure_tstep.mb_prescribed_dict is not None: for i_constraint in range(structure_tstep.mb_dict['num_constraints']): cst_name = f"constraint_{i_constraint:02d}" if ('controller_id' in structure_tstep.mb_dict[cst_name] and 'aerogrid_warp_factor' in structure_tstep.mb_dict[cst_name]): ctrl_id = structure_tstep.mb_dict[cst_name]['controller_id'].decode('UTF-8') f_warp = structure_tstep.mb_dict[cst_name]['aerogrid_warp_factor'][i_elem, i_local_node] ang_z = structure_tstep.mb_prescribed_dict[ctrl_id]['delta_psi'][2] ang_warp += f_warp * ang_z node_info = dict() node_info['i_node'] = i_global_node node_info['i_local_node'] = i_local_node node_info['chord'] = self.data_dict['chord'][i_elem, i_local_node] / np.cos(ang_warp) node_info['eaxis'] = self.data_dict['elastic_axis'][i_elem, i_local_node] node_info['twist'] = self.data_dict['twist'][i_elem, i_local_node] node_info['sweep'] = self.data_dict['sweep'][i_elem, i_local_node] + ang_warp node_info['M'] = self.dimensions[i_surf, 0] node_info['M_distribution'] = self.data_dict['m_distribution'].decode('ascii') node_info['airfoil'] = self.data_dict['airfoil_distribution'][i_elem, i_local_node] node_info['control_surface'] = control_surface_info node_info['beam_coord'] = structure_tstep.pos[i_global_node, :] node_info['pos_dot'] = structure_tstep.pos_dot[i_global_node, :] node_info['beam_psi'] = structure_tstep.psi[i_elem, i_local_node, :] node_info['psi_dot'] = structure_tstep.psi_dot[i_elem, i_local_node, :] node_info['for_delta'] = beam.frame_of_reference_delta[i_elem, i_local_node, :] node_info['elem'] = beam.elements[i_elem] node_info['for_pos'] = structure_tstep.for_pos node_info['cga'] = structure_tstep.cga() if node_info['M_distribution'].lower() == 'user_defined': ielem_in_surf = i_elem - np.sum(self.surface_distribution < i_surf) node_info['user_defined_m_distribution'] = self.data_dict['user_defined_m_distribution'][ str(i_surf)][:, ielem_in_surf, i_local_node] (aero_tstep.zeta[i_surf][:, :, i_n], aero_tstep.zeta_dot[i_surf][:, :, i_n]) = ( generate_strip(node_info, self.airfoil_db, self.aero_settings['aligned_grid'], initial_strip_z_rot=self.initial_strip_z_rot[i_elem, i_local_node], orientation_in=self.aero_settings['freestream_dir'], calculate_zeta_dot=True)) # set junction boundary conditions for later phantom cell creation in UVLM if "junction_boundary_condition" in self.data_dict: if np.any(self.data_dict["junction_boundary_condition"] >= 0): self.generate_phantom_panels_at_junction(aero_tstep) def generate_phantom_panels_at_junction(self, aero_tstep): for i_surf in range(self.n_surf): aero_tstep.flag_zeta_phantom[0, i_surf] = self.data_dict["junction_boundary_condition"][0, i_surf] @staticmethod def compute_gamma_dot(dt, tstep, previous_tsteps): r""" Computes the temporal derivative of circulation (gamma) using finite differences. It will use a first order approximation for the first evaluation (when ``len(previous_tsteps) == 1``), and then second order ones. .. math:: \left.\frac{d\Gamma}{dt}\right|^n \approx \lim_{\Delta t \rightarrow 0}\frac{\Gamma^n-\Gamma^{n-1}}{\Delta t} For the second time step and onwards, the following second order approximation is used: .. math:: \left.\frac{d\Gamma}{dt}\right|^n \approx \lim_{\Delta t \rightarrow 0}\frac{3\Gamma^n -4\Gamma^{n-1}+\Gamma^{n-2}}{2\Delta t} Args: dt (float): delta time for the finite differences tstep (AeroTimeStepInfo): tstep at time n (current) previous_tsteps (list(AeroTimeStepInfo)): previous tstep structure in order: ``[n-N,..., n-2, n-1]`` Returns: float: first derivative of circulation with respect to time See Also: .. py:class:: sharpy.utils.datastructures.AeroTimeStepInfo """ # Check whether the iteration is part of FSI (ie the input is a k-step) or whether it is an only aerodynamic # simulation part_of_fsi = True try: if tstep is previous_tsteps[-1]: part_of_fsi = False except IndexError: for i_surf in range(tstep.n_surf): tstep.gamma_dot[i_surf].fill(0.0) return if len(previous_tsteps) == 0: for i_surf in range(tstep.n_surf): tstep.gamma_dot[i_surf].fill(0.0) if part_of_fsi: for i_surf in range(tstep.n_surf): tstep.gamma_dot[i_surf] = (tstep.gamma[i_surf] - previous_tsteps[-1].gamma[i_surf]) / dt else: for i_surf in range(tstep.n_surf): tstep.gamma_dot[i_surf] = (tstep.gamma[i_surf] - previous_tsteps[-2].gamma[i_surf]) / dt def generate_strip(node_info, airfoil_db, aligned_grid, initial_strip_z_rot, orientation_in=np.array([1, 0, 0]), calculate_zeta_dot=False, first_twist=True): """ Returns a strip of panels in ``A`` frame of reference, it has to be then rotated to simulate angles of attack, etc """ strip_coordinates_a_frame = np.zeros((3, node_info['M'] + 1), dtype=ct.c_double) strip_coordinates_b_frame = np.zeros((3, node_info['M'] + 1), dtype=ct.c_double) zeta_dot_a_frame = np.zeros((3, node_info['M'] + 1), dtype=ct.c_double) # airfoil coordinates # we are going to store everything in the x-z plane of the b # FoR, so that the transformation Cab rotates everything in place. if node_info['M_distribution'] == 'uniform': strip_coordinates_b_frame[1, :] = np.linspace(0.0, 1.0, node_info['M'] + 1) elif node_info['M_distribution'] == '1-cos': domain = np.linspace(0, 1.0, node_info['M'] + 1) strip_coordinates_b_frame[1, :] = 0.5 * (1.0 - np.cos(domain * np.pi)) elif node_info['M_distribution'].lower() == 'user_defined': strip_coordinates_b_frame[1, :] = node_info['user_defined_m_distribution'] else: raise NotImplemented('M_distribution is ' + node_info['M_distribution'] + ' and it is not yet supported') strip_coordinates_b_frame[2, :] = airfoil_db[node_info['airfoil']]( strip_coordinates_b_frame[1, :]) # elastic axis correction for i_M in range(node_info['M'] + 1): strip_coordinates_b_frame[1, i_M] -= node_info['eaxis'] # chord_line_b_frame = strip_coordinates_b_frame[:, -1] - strip_coordinates_b_frame[:, 0] cs_velocity = np.zeros_like(strip_coordinates_b_frame) # control surface deflection if node_info['control_surface'] is not None: b_frame_hinge_coords = strip_coordinates_b_frame[:, node_info['M'] - node_info['control_surface']['chord']] # support for different hinge location for fully articulated control surfaces if node_info['control_surface']['hinge_coords'] is not None: # make sure the hinge coordinates are only applied when M == cs_chord if not node_info['M'] - node_info['control_surface']['chord'] == 0: node_info['control_surface']['hinge_coords'] = None else: b_frame_hinge_coords = node_info['control_surface']['hinge_coords'] for i_M in range(node_info['M'] - node_info['control_surface']['chord'], node_info['M'] + 1): relative_coords = strip_coordinates_b_frame[:, i_M] - b_frame_hinge_coords # rotate the control surface relative_coords = np.dot(algebra.rotation3d_x(-node_info['control_surface']['deflection']), relative_coords) # deflection velocity try: cs_velocity[:, i_M] += np.cross(np.array([-node_info['control_surface']['deflection_dot'], 0.0, 0.0]), relative_coords) except KeyError: pass # restore coordinates relative_coords += b_frame_hinge_coords # substitute with new coordinates strip_coordinates_b_frame[:, i_M] = relative_coords # chord scaling strip_coordinates_b_frame *= node_info['chord'] # twist transformation (rotation around x_b axis) if np.abs(node_info['twist']) > 1e-6: Ctwist = algebra.rotation3d_x(node_info['twist']) else: Ctwist = np.eye(3) # Cab transformation Cab = algebra.crv2rotation(node_info['beam_psi']) if aligned_grid: rot_angle = algebra.angle_between_vectors_sign(orientation_in, Cab[:, 1], Cab[:, 2]) else: rot_angle = initial_strip_z_rot Crot = algebra.rotation3d_z(-rot_angle) c_sweep = np.eye(3) if np.abs(node_info['sweep']) > 1e-6: c_sweep = algebra.rotation3d_z(node_info['sweep']) # transformation from beam to beam prime (with sweep and twist) for i_M in range(node_info['M'] + 1): if first_twist: strip_coordinates_b_frame[:, i_M] = np.dot(c_sweep, np.dot(Crot, np.dot(Ctwist, strip_coordinates_b_frame[:, i_M]))) else: strip_coordinates_b_frame[:, i_M] = np.dot(Ctwist, np.dot(Crot, np.dot(c_sweep, strip_coordinates_b_frame[:, i_M]))) strip_coordinates_a_frame[:, i_M] = np.dot(Cab, strip_coordinates_b_frame[:, i_M]) cs_velocity[:, i_M] = np.dot(Cab, cs_velocity[:, i_M]) # zeta_dot if calculate_zeta_dot: # velocity due to pos_dot for i_M in range(node_info['M'] + 1): zeta_dot_a_frame[:, i_M] += node_info['pos_dot'] # velocity due to psi_dot omega_a = algebra.crv_dot2omega(node_info['beam_psi'], node_info['psi_dot']) for i_M in range(node_info['M'] + 1): zeta_dot_a_frame[:, i_M] += ( np.dot(algebra.skew(omega_a), strip_coordinates_a_frame[:, i_M])) # control surface deflection velocity contribution try: if node_info['control_surface'] is not None: node_info['control_surface']['deflection_dot'] for i_M in range(node_info['M'] + 1): zeta_dot_a_frame[:, i_M] += cs_velocity[:, i_M] except KeyError: pass else: zeta_dot_a_frame = np.zeros((3, node_info['M'] + 1), dtype=ct.c_double) # add node coords for i_M in range(node_info['M'] + 1): strip_coordinates_a_frame[:, i_M] += node_info['beam_coord'] # add quarter-chord disp delta_c = (strip_coordinates_a_frame[:, -1] - strip_coordinates_a_frame[:, 0]) / node_info['M'] if node_info['M_distribution'] == 'uniform': for i_M in range(node_info['M'] + 1): strip_coordinates_a_frame[:, i_M] += 0.25 * delta_c else: warnings.warn("No quarter chord disp of grid for non-uniform grid distributions implemented", UserWarning) # rotation from a to g for i_M in range(node_info['M'] + 1): strip_coordinates_a_frame[:, i_M] = np.dot(node_info['cga'], strip_coordinates_a_frame[:, i_M]) zeta_dot_a_frame[:, i_M] = np.dot(node_info['cga'], zeta_dot_a_frame[:, i_M]) return strip_coordinates_a_frame, zeta_dot_a_frame ================================================ FILE: sharpy/aero/models/grid.py ================================================ """Grid Grid contains """ import ctypes as ct import warnings import numpy as np import sharpy.utils.algebra as algebra import sharpy.utils.generator_interface as gen_interface class Grid(object): """ ``Grid``is the parent class for the lifting surface grid and nonlifting body grids. It is created by the solver :class:`sharpy.solvers.aerogridloader.AerogridLoader` """ def __init__(self): self.data_dict = None self.beam = None self.aero_settings = None self.timestep_info = [] self.ini_info = None self.surface_distribution = None self.surface_m = None self.dimensions = None self.grid_type = None self.n_node = 0 self.n_elem = 0 self.n_surf = 0 self.n_aero_node = 0 self.grid_type = None self.struct2aero_mapping = None self.aero2struct_mapping = [] def generate(self, data_dict, beam, aero_settings, ts): self.data_dict = data_dict self.beam = beam self.aero_settings = aero_settings # key words = safe in aero_settings? --> grid_type # number of total nodes (structural + aero&struc) self.n_node = len(data_dict[self.grid_type + '_node']) # gridtype + '_node' # number of elements self.n_elem = len(data_dict['surface_distribution']) # surface distribution self.surface_distribution = data_dict['surface_distribution'] # number of surfaces temp = set(data_dict['surface_distribution']) #TO-DO: improve: avoid for loops self.n_surf = sum(1 for i in temp if i >= 0) # number of chordwise panels self.surface_m = data_dict['surface_m'] # number of aero nodes self.n_aero_node = sum(data_dict[self.grid_type + '_node']) # get N per surface self.calculate_dimensions() # write grid info to screen # self.output_info() def calculate_dimensions(self): self.dimensions = np.zeros((self.n_surf, 2), dtype=int) for i in range(self.n_surf): # adding M values self.dimensions[i, 0] = self.surface_m[i] # Improvement: # self.aero.dimensions[:, 0] = self.surface_m[:] # count N values (actually, the count result # will be N+1) nodes_in_surface = [] #IMPROVEMENT for i_surf in range(self.n_surf): nodes_in_surface.append([]) # Improvement! for i_elem in range(self.beam.num_elem): nodes = self.beam.elements[i_elem].global_connectivities i_surf = self.surface_distribution[i_elem] if i_surf < 0: continue for i_global_node in nodes: if i_global_node in nodes_in_surface[i_surf]: continue else: nodes_in_surface[i_surf].append(i_global_node) if self.data_dict[self.grid_type + '_node'][i_global_node]: self.dimensions[i_surf, 1] += 1 # accounting for N+1 nodes -> N panels self.dimensions[:, 1] -= 1 def add_timestep(self): try: self.timestep_info.append(self.timestep_info[-1].copy()) except IndexError: self.timestep_info.append(self.ini_info.copy()) def generate_zeta_timestep_info(self, structure_tstep, aero_tstep, beam, aero_settings, it=None, dt=None): if it is None: it = len(beam.timestep_info) - 1 def generate_zeta(self, beam, aero_settings, ts=-1, beam_ts=-1): self.generate_zeta_timestep_info(beam.timestep_info[beam_ts], self.timestep_info[ts], beam, aero_settings) def generate_mapping(self): self.struct2aero_mapping = [[]]*self.n_node surf_n_counter = np.zeros((self.n_surf,), dtype=int) nodes_in_surface = [] for i_surf in range(self.n_surf): nodes_in_surface.append([]) for i_elem in range(self.n_elem): i_surf = self.surface_distribution[i_elem] if i_surf == -1: continue for i_global_node in self.beam.elements[i_elem].reordered_global_connectivities: if not self.data_dict[self.grid_type + '_node'][i_global_node]: continue if i_global_node in nodes_in_surface[i_surf]: continue else: nodes_in_surface[i_surf].append(i_global_node) surf_n_counter[i_surf] += 1 try: self.struct2aero_mapping[i_global_node][0] except IndexError: self.struct2aero_mapping[i_global_node] = [] i_n = surf_n_counter[i_surf] - 1 self.struct2aero_mapping[i_global_node].append({'i_surf': i_surf, 'i_n': i_n}) nodes_in_surface = [] for i_surf in range(self.n_surf): nodes_in_surface.append([]) for i_surf in range(self.n_surf): self.aero2struct_mapping.append([-1]*(surf_n_counter[i_surf])) for i_elem in range(self.n_elem): for i_global_node in self.beam.elements[i_elem].global_connectivities: for i in range(len(self.struct2aero_mapping[i_global_node])): try: i_surf = self.struct2aero_mapping[i_global_node][i]['i_surf'] i_n = self.struct2aero_mapping[i_global_node][i]['i_n'] if i_global_node in nodes_in_surface[i_surf]: continue else: nodes_in_surface[i_surf].append(i_global_node) except KeyError: continue self.aero2struct_mapping[i_surf][i_n] = i_global_node def update_orientation(self, quat, ts=-1): rot = algebra.quat2rotation(quat) self.timestep_info[ts].update_orientation(rot.T) ================================================ FILE: sharpy/aero/models/nonliftingbodygrid.py ================================================ """Nonlifting Body grid Description """ from sharpy.aero.models.grid import Grid from sharpy.utils.datastructures import NonliftingBodyTimeStepInfo import numpy as np import sharpy.utils.algebra as algebra class NonliftingBodyGrid(Grid): """ ``Nonlifting Body Grid`` is the main object containing information of the nonlifting bodygrid, consisting of triangular and quadrilateral panels. It is created by the solver :class:`sharpy.solvers.aerogridloader.AerogridLoader` """ def __init__(self): super().__init__() self.grid_type = 'nonlifting_body' def generate(self, data_dict, beam, nonlifting_body_settings, ts): super().generate(data_dict, beam, nonlifting_body_settings, ts) # allocating initial grid storage self.ini_info = NonliftingBodyTimeStepInfo(self.dimensions) self.add_timestep() self.generate_mapping() self.generate_zeta(self.beam, self.aero_settings, ts) def generate_zeta_timestep_info(self, structure_tstep, nonlifting_body_tstep, beam, aero_settings, it=None, dt=None): super().generate_zeta_timestep_info(structure_tstep, nonlifting_body_tstep, beam, aero_settings, it, dt) for i_surf in range(self.n_surf): # Get Zeta and Zeta_dot (Panel node positions in G frame) nonlifting_body_tstep.zeta[i_surf], nonlifting_body_tstep.zeta_dot[i_surf] = self.get_zeta_and_zeta_dot(i_surf, structure_tstep) def get_zeta_and_zeta_dot(self,i_surf, structure_tstep): numb_radial_nodes = self.dimensions[i_surf][0] +1 matrix_nodes = np.zeros((3, numb_radial_nodes, self.dimensions[i_surf][1]+1)) matrix_nodes_dot = matrix_nodes.copy() array_phi_coordinates = np.linspace(0, 2*np.pi, numb_radial_nodes) # cache sin and cos values array_sin_phi = np.sin(array_phi_coordinates) array_cos_phi = np.cos(array_phi_coordinates) cga_rotation_matrix = structure_tstep.cga() for node_counter, i_global_node in enumerate(self.aero2struct_mapping[i_surf]): # 1) Set B-Frame position # 1a) get cross-sectional fuselage geometry at node if self.data_dict["shape"].decode() == 'specific': a_ellipse = self.data_dict["a_ellipse"][i_global_node] b_ellipse = self.data_dict["b_ellipse"][i_global_node] z_0 =self.data_dict["z_0_ellipse"][i_global_node] if a_ellipse == 0. or b_ellipse == 0.: radius = 0 else: radius = a_ellipse*b_ellipse/np.sqrt( (b_ellipse*array_cos_phi)**2 +(a_ellipse*array_sin_phi)**2) else: radius = self.data_dict["radius"][i_global_node] z_0 = 0 # 1b) Get nodes position in B frame matrix_nodes[1, :, node_counter] = radius*array_cos_phi matrix_nodes[2, :, node_counter] = radius*array_sin_phi + z_0 # 2) A frame # 2a) Convert structural position from B to A frame i_elem, i_local_node = self.get_elment_and_local_node_id(i_surf, i_global_node) psi_node = structure_tstep.psi[i_elem, i_local_node,:] if not (psi_node == [0, 0, 0]).all(): # just perform roation from B to A if psi not 0 Cab = algebra.crv2rotation(psi_node) for idx in range(numb_radial_nodes): matrix_nodes[:, idx, node_counter] = np.dot(Cab, matrix_nodes[:, idx, node_counter]) # 2b) Add beam displacements (expressed in A-frame) for dim in range(3): matrix_nodes[dim, :, node_counter] += structure_tstep.pos[i_global_node,dim] # 2c) Add structural beam velocities (expressed in A-frame) for dim in range(3): # velocity due to pos_dot matrix_nodes_dot[dim, :, node_counter] += structure_tstep.pos_dot[i_global_node, dim] # 2d) Add effect of structural beam rotations an node velocity (expressed in A-frame) psi_dot_node = structure_tstep.psi_dot[i_elem, i_local_node,:] omega_a = algebra.crv_dot2omega(psi_node, psi_dot_node) for idx in range(numb_radial_nodes): matrix_nodes_dot[:, idx, node_counter] += (np.dot(algebra.skew(omega_a), matrix_nodes[:, idx, node_counter])) # 3) Convert position and velocities from A to G frame for idx in range(numb_radial_nodes): matrix_nodes[:, idx, node_counter] = np.dot(cga_rotation_matrix, matrix_nodes[:, idx, node_counter]) matrix_nodes_dot[:, idx, node_counter] = np.dot(cga_rotation_matrix, matrix_nodes_dot[:, idx, node_counter]) return matrix_nodes, matrix_nodes_dot def get_elment_and_local_node_id(self, i_surf, i_global_node): # get beam elements of surface idx_beam_elements_surface = np.where(self.surface_distribution == i_surf)[0] # find element and local node of the global node and return psi for i_elem in idx_beam_elements_surface: if i_global_node in self.beam.elements[i_elem].reordered_global_connectivities: i_local_node = np.where(self.beam.elements[i_elem].reordered_global_connectivities == i_global_node)[0][0] return i_elem, i_local_node raise Exception("The global node %u could not be assigned to any element of surface %u." % (i_global_node, i_surf)) ================================================ FILE: sharpy/aero/utils/__init__.py ================================================ """Aerodynamic Utilities""" ================================================ FILE: sharpy/aero/utils/airfoilpolars.py ================================================ import numpy as np # import sharpy.utils.algebra as algebra from sharpy.utils.constants import deg2rad from scipy.interpolate import interp1d class Polar: """ Airfoil polar object """ def __init__(self): self.cm_interp = None self.cd_interp = None self.cl_interp = None self.table = None self.aoa_cl0_deg = None def initialise(self, table): """ Initialise polar Args: table (np.ndarray): 4-column array containing ``aoa`` (rad), ``cl``, ``cd`` and ``cm`` """ # Store the table if (np.diff(table[:, 0]) > 0.).all(): self.table = table[~np.isnan(table).any(axis=1), :] else: raise RuntimeError("ERROR: angles of attack not ordered") # Look for aoa where CL=0 npoints = self.table.shape[0] matches = [] for ipoint in range(npoints - 1): if self.table[ipoint, 1] == 0.: matches.append(self.table[ipoint, 0]) elif self.table[ipoint, 1] < 0. and self.table[ipoint + 1, 1] > 0: if self.table[ipoint, 0] <= 0.: matches.append(np.interp(0, self.table[ipoint:ipoint+2, 1], self.table[ipoint:ipoint+2, 0])) iaoacl0 = 0 aux = np.abs(matches[0]) for imin in range(len(matches)): if np.abs(matches[imin]) < aux: aux = np.abs(matches[imin]) iaoacl0 = imin self.aoa_cl0_deg = matches[iaoacl0] self.cl_interp = interp1d(self.table[:, 0], self.table[:, 1]) self.cd_interp = interp1d(self.table[:, 0], self.table[:, 2]) self.cm_interp = interp1d(self.table[:, 0], self.table[:, 3]) def get_coefs(self, aoa_deg): cl = self.cl_interp(aoa_deg) cd = self.cd_interp(aoa_deg) cm = self.cm_interp(aoa_deg) return cl[0], cd[0], cm[0] def get_aoa_deg_from_cl_2pi(self, cl): return cl/2/np.pi/deg2rad + self.aoa_cl0_deg def redefine_aoa(self, new_aoa): naoa = len(new_aoa) # Generate the same polar interpolated at different angles of attack # by linear interpolation table = np.zeros((naoa, 4)) table[:, 0] = new_aoa for icol in range(1, 4): table[:, icol] = np.interp(table[:, 0], self.table[:, 0], self.table[:, icol]) new_polar = Polar() new_polar.initialise(table) return new_polar def get_cdcm_from_cl(self, cl): # Computes the cd and cm from cl # It provides the first match after (or before) the AOA of CL=0 cl_max = np.max(self.table[:,1]) cl_min = np.min(self.table[:,1]) if cl_max < cl or cl_min > cl: print(("cl = %.2f out of range, forces at this point will not be corrected" % cl)) cd = 0. cm = 0. else: if cl == 0.: cl_new, cd, cm = self.get_coefs(self.aoa_cl0_deg) elif cl > 0.: dist = np.abs(self.table[:,0] - self.aoa_cl0_deg) min_dist = np.min(dist) i = np.where(dist == min_dist)[0][0] while self.table[i, 1] < cl: i += 1 cd = np.interp(cl, self.table[i-1:i+1, 1], self.table[i-1:i+1, 2]) cm = np.interp(cl, self.table[i-1:i+1, 1], self.table[i-1:i+1, 3]) else: dist = np.abs(self.table[:,0] - self.aoa_cl0_deg) min_dist = np.min(dist) i = np.where(dist == min_dist)[0][0] while self.table[i, 1] > cl: i -= 1 cd = np.interp(cl, self.table[i:i+2, 1], self.table[i:i+2, 2]) cm = np.interp(cl, self.table[i:i+2, 1], self.table[i:i+2, 3]) return float(cd), float(cm) def interpolate(polar1, polar2, coef=0.5): all_aoa = np.sort(np.concatenate((polar1.table[:, 0], polar2.table[:, 0]),)) different_aoa = [] different_aoa.append(all_aoa[0]) for iaoa in range(1, len(all_aoa)): if not all_aoa[iaoa] == different_aoa[-1]: different_aoa.append(all_aoa[iaoa]) new_polar1 = polar1.redefine_aoa(different_aoa) new_polar2 = polar2.redefine_aoa(different_aoa) table = (1. - coef)*new_polar1.table + coef*new_polar2.table new_polar = Polar() new_polar.initialise(table) return new_polar ================================================ FILE: sharpy/aero/utils/mapping.py ================================================ """Force Mapping Utilities""" import numpy as np import sharpy.utils.algebra as algebra def aero2struct_force_mapping(aero_forces, struct2aero_mapping, zeta, pos_def, psi_def, master, conn, cag=np.eye(3), data_dict=None, skip_moments_generated_by_forces = False): r""" Maps the aerodynamic forces at the lattice to the structural nodes The aerodynamic forces from the UVLM are always in the inertial ``G`` frame of reference and have to be transformed to the body or local ``B`` frame of reference in which the structural forces are defined. Since the structural nodes and aerodynamic panels are coincident in a spanwise direction, the aerodynamic forces that correspond to a structural node are the summation of the ``M+1`` forces defined at the lattice at that spanwise location. .. math:: \mathbf{f}_{struct}^B &= \sum\limits_{i=0}^{m+1}C^{BG}\mathbf{f}_{i,aero}^G \\ \mathbf{m}_{struct}^B &= \sum\limits_{i=0}^{m+1}C^{BG}(\mathbf{m}_{i,aero}^G + \tilde{\boldsymbol{\zeta}}^G\mathbf{f}_{i, aero}^G) where :math:`\tilde{\boldsymbol{\zeta}}^G` is the skew-symmetric matrix of the vector between the lattice grid vertex and the structural node. Args: aero_forces (list): Aerodynamic forces from the UVLM in inertial frame of reference struct2aero_mapping (dict): Structural to aerodynamic node mapping zeta (list): Aerodynamic grid coordinates pos_def (np.ndarray): Vector of structural node displacements psi_def (np.ndarray): Vector of structural node rotations (CRVs) master: Unused conn (np.ndarray): Connectivities matrix cag (np.ndarray): Transformation matrix between inertial and body-attached reference ``A`` data_dict (dict): Dictionary containing the grid's information. skip_moments_generated_by_forces (bool): Flag to skip local moment calculation. Returns: np.ndarray: structural forces in an ``n_node x 6`` vector """ n_node, _ = pos_def.shape n_elem, _, _ = psi_def.shape struct_forces = np.zeros((n_node, 6)) nodes = [] for i_elem in range(n_elem): for i_local_node in range(3): i_global_node = conn[i_elem, i_local_node] if i_global_node in nodes: continue nodes.append(i_global_node) for mapping in struct2aero_mapping[i_global_node]: i_surf = mapping['i_surf'] i_n = mapping['i_n'] _, n_m, _ = aero_forces[i_surf].shape crv = psi_def[i_elem, i_local_node, :] cab = algebra.crv2rotation(crv) cbg = np.dot(cab.T, cag) for i_m in range(n_m): struct_forces[i_global_node, 0:3] += np.dot(cbg, aero_forces[i_surf][0:3, i_m, i_n]) struct_forces[i_global_node, 3:6] += np.dot(cbg, aero_forces[i_surf][3:6, i_m, i_n]) """ The calculation of the local moment is skipped for fuselage bodies, since this leads to noticeably asymmetric aeroforces. This transitional solution makes sense since we have only considered the stiff fuselage so far and the pitching moment coming from the fuselage is mainly caused by the longitudinal force distribution. TODO: Correct the calculation of the local moment for the fuselage model. """ if not skip_moments_generated_by_forces: chi_g = zeta[i_surf][:, i_m, i_n] - np.dot(cag.T, pos_def[i_global_node, :]) struct_forces[i_global_node, 3:6] += np.dot(cbg, algebra.cross3(chi_g, aero_forces[i_surf][0:3, i_m, i_n])) return struct_forces def total_forces_moments(forces_nodes_a, pos_def, ref_pos=np.array([0., 0., 0.])): """ Performs a summation of the forces and moments expressed at the structural nodes in the A frame of reference. Note: If you need to transform forces and moments at the nodes from B to A, use the :func:`~sharpy.structure.models.beam.Beam.nodal_b_for_2_a_for()` function. Args: forces_nodes_a (np.array): ``n_node x 6`` vector of forces and moments at the nodes in A pos_def (np.array): ``n_node x 3`` vector of nodal positions in A ref_pos (np.array (optional)): Location in A about which to compute moments. Defaults to ``[0, 0, 0]`` Returns: np.array: Vector of length 6 containing the total forces and moments expressed in A at the desired location. """ num_node = pos_def.shape[0] ra_vec = pos_def - ref_pos total_forces = np.zeros(3) total_moments = np.zeros(3) for i_global_node in range(num_node): total_forces += forces_nodes_a[i_global_node, :3] total_moments += forces_nodes_a[i_global_node, 3:] + algebra.cross3(ra_vec[i_global_node], forces_nodes_a[i_global_node, :3]) return np.concatenate((total_forces, total_moments)) ================================================ FILE: sharpy/aero/utils/utils.py ================================================ """Aero utilities functions""" import numpy as np import sharpy.utils.algebra as algebra from sharpy.utils import algebra as algebra def flightcon_file_parser(fc_dict): fc = fc_dict['FlightCon'] fc['u_inf'] = float(fc['u_inf']) fc['alpha'] = float(fc['alpha'])*np.pi/180.0 fc['beta'] = float(fc['beta'])*np.pi/180.0 fc['rho_inf'] = float(fc['rho_inf']) fc['c_ref'] = float(fc['c_ref']) fc['b_ref'] = float(fc['b_ref']) def alpha_beta_to_direction(alpha, beta): direction = np.array([1, 0, 0]) alpha_rot = algebra.rotation3d_y(alpha) beta_rot = algebra.rotation3d_z(beta) direction = np.dot(beta_rot, np.dot(alpha_rot, direction)) return direction def magnitude_and_direction_of_relative_velocity(displacement, displacement_vel, for_vel, cga, uext, add_rotation=False, rot_vel_g=np.zeros((3)), centre_rot_g=np.zeros((3))): r""" Calculates the magnitude and direction of the relative velocity ``u_rel`` at a local section of the wing. .. math:: u_{rel, i}^G = \bar{U}_{\infty, i}^G - C^{GA}(\chi)(\dot{\eta}_i^A + v^A + \tilde{\omega}^A\eta_i^A where :math:`\bar{U}_{\infty, i}^G` is the average external velocity across all aerodynamic nodes at the relevant cross section. Args: displacement (np.array): Unit vector in the direction of the free stream velocity expressed in A frame. displacement_vel (np.array): Unit vector in the direction of the local chord expressed in A frame. for_vel (np.array): ``A`` frame of reference (FoR) velocity. Expressed in A FoR cga (np.array): Rotation vector from FoR ``G`` to FoR ``A`` uext (np.array): Background flow velocity on solid grid nodes add_rotation (bool): Adds rotation velocity. Probalby needed in steady computations rot_vel_g (np.array): Rotation velocity. Only used if add_rotation = True centre_rot_g (np.array): Centre of rotation. Only used if add_rotation = True Returns: tuple: ``u_rel``, ``dir_u_rel`` expressed in the inertial, ``G`` frame. """ urel = (displacement_vel + for_vel[0:3] + algebra.cross3(for_vel[3:6], displacement)) urel = -np.dot(cga, urel) urel += np.average(uext, axis=1) if add_rotation: urel -= algebra.cross3(rot_vel_g, np.dot(cga, displacement) - centre_rot_g) dir_urel = algebra.unit_vector(urel) return urel, dir_urel def local_stability_axes(dir_urel, dir_chord): """ Rotates the body axes onto stability axes. This rotation is equivalent to the projection of a vector in S onto B. The stability axes are defined as: * ``x_s``: parallel to the free stream * ``z_s``: perpendicular to the free stream and part of the plane formed by the local chord and the vertical body axis ``z_b``. * ``y_s``: completes the set Args: dir_urel (np.array): Unit vector in the direction of the free stream velocity expressed in B frame. dir_chord (np.array): Unit vector in the direction of the local chord expressed in B frame. Returns: np.array: Rotation matrix from B to S, equivalent to the projection matrix :math:`C^{BS}` that projects a vector from S onto B. """ xs = dir_urel zb = np.array([0, 0, 1.]) zs = algebra.cross3(algebra.cross3(dir_chord, zb), dir_urel) ys = -algebra.cross3(xs, zs) return algebra.triad2rotation(xs, ys, zs) def span_chord(i_node_surf, zeta): """ Retrieve the local span and local chord Args: i_node_surf (int): Node index in aerodynamic surface zeta (np.array): Aerodynamic surface coordinates ``(3 x n_chord x m_span)`` Returns: tuple: ``dir_span``, ``span``, ``dir_chord``, ``chord`` """ N = zeta.shape[2] - 1 # spanwise vertices in surface (-1 for index) # Deal with the extremes if i_node_surf == 0: node_p = 1 node_m = 0 elif i_node_surf == N: node_p = N node_m = N - 1 else: node_p = i_node_surf + 1 node_m = i_node_surf - 1 # Define the span and the span direction dir_span = 0.5 * (zeta[:, 0, node_p] - zeta[:, 0, node_m]) span = np.linalg.norm(dir_span) dir_span = algebra.unit_vector(dir_span) # Define the chord and the chord direction dir_chord = zeta[:, -1, i_node_surf] - zeta[:, 0, i_node_surf] chord = np.linalg.norm(dir_chord) dir_chord = algebra.unit_vector(dir_chord) return dir_span, span, dir_chord, chord def find_aerodynamic_solver_settings(settings): """ Retrieves the settings of the first aerodynamic solver used in the solution ``flow``. For coupled solvers, the aerodynamic solver is found in the aero solver settings. The StaticTrim solver can either contain a coupled or aero solver in its solver settings (making it into a possible 3-level Matryoshka). Args: settings (dict): SHARPy settings (usually found in ``data.settings`` ) Returns: tuple: Aerodynamic solver settings """ flow = settings['SHARPy']['flow'] for solver_name in ['StaticUvlm', 'StaticCoupled', 'StaticTrim', 'DynamicCoupled', 'StepUvlm']: if solver_name in flow: aero_solver_settings = settings[solver_name] if solver_name == 'StaticTrim': aero_solver_settings = aero_solver_settings['solver_settings']['aero_solver_settings'] elif 'aero_solver' in settings[solver_name].keys(): aero_solver_settings = aero_solver_settings['aero_solver_settings'] return aero_solver_settings raise KeyError("ERROR: Aerodynamic solver not found.") def find_velocity_generator(settings): """ Retrieves the name and settings of the fluid velocity generator in the first aerodynamic solver used in the solution ``flow``. Args: settings (dict): SHARPy settings (usually found in ``data.settings`` ) Returns: tuple: velocity generator name and velocity generator settings """ aero_solver_settings = find_aerodynamic_solver_settings(settings) vel_gen_name = aero_solver_settings['velocity_field_generator'] vel_gen_settings = aero_solver_settings['velocity_field_input'] return vel_gen_name, vel_gen_settings ================================================ FILE: sharpy/aero/utils/uvlmlib.py ================================================ from sharpy.utils.sharpydir import SharpyDir import sharpy.utils.ctypes_utils as ct_utils import ctypes as ct from ctypes import * import numpy as np from sharpy.utils.constants import vortex_radius_def try: UvlmLib = ct_utils.import_ctypes_lib(SharpyDir + '/UVLM', 'libuvlm') except OSError: UvlmLib = ct_utils.import_ctypes_lib(SharpyDir + '/lib/UVLM/lib', 'libuvlm') # TODO: Combine VMOpts and UVMOpts (Class + inheritance)? class VMopts(ct.Structure): """ctypes definition for VMopts class struct VMopts { bool ImageMethod; unsigned int Mstar; bool Steady; bool horseshoe; bool KJMeth; bool NewAIC; double DelTime; bool Rollup; bool only_lifting; bool only_nonlifting; unsigned int NumCores; unsigned int NumSurfaces; unsigned int NumSurfacesNonlifting; double vortex_radius; double vortex_radius_wake_ind; bool u_ind_by_sources_for_lifting_forces; uint ignore_first_x_nodes_in_force_calculation; }; """ _fields_ = [("ImageMethod", ct.c_bool), ("Steady", ct.c_bool), ("horseshoe", ct.c_bool), ("KJMeth", ct.c_bool), ("NewAIC", ct.c_bool), ("DelTime", ct.c_double), ("Rollup", ct.c_bool), ("only_lifting", ct.c_bool), ("only_nonlifting", ct.c_bool), ("phantom_wing_test", ct.c_bool), ("NumCores", ct.c_uint), ("NumSurfaces", ct.c_uint), ("NumSurfacesNonlifting", ct.c_uint), ("dt", ct.c_double), ("n_rollup", ct.c_uint), ("rollup_tolerance", ct.c_double), ("rollup_aic_refresh", ct.c_uint), ("iterative_solver", ct.c_bool), ("iterative_tol", ct.c_double), ("iterative_precond", ct.c_bool), ("vortex_radius", ct.c_double), ("vortex_radius_wake_ind", ct.c_double), ("consider_u_ind_by_sources_for_lifting_forces", ct.c_bool), ("ignore_first_x_nodes_in_force_calculation", ct.c_uint)] def __init__(self): ct.Structure.__init__(self) self.ImageMethod = ct.c_bool(False) self.Steady = ct.c_bool(True) self.horseshoe = ct.c_bool(True) self.KJMeth = ct.c_bool(False) # legacy var self.NewAIC = ct.c_bool(False) # legacy var self.DelTime = ct.c_double(1.0) self.Rollup = ct.c_bool(False) self.only_lifting = ct.c_bool(False) self.only_nonlifting = ct.c_bool(False) self.NumCores = ct.c_uint(4) self.NumSurfaces = ct.c_uint(1) self.NumSurfacesNonlifting = ct.c_uint(0) self.dt = ct.c_double(0.01) self.n_rollup = ct.c_uint(0) self.rollup_tolerance = ct.c_double(1e-5) self.rollup_aic_refresh = ct.c_uint(1) self.iterative_solver = ct.c_bool(False) self.iterative_tol = ct.c_double(0) self.iterative_precond = ct.c_bool(False) self.vortex_radius = ct.c_double(vortex_radius_def) self.vortex_radius_wake_ind = ct.c_double(vortex_radius_def) self.phantom_wing_test = ct.c_bool(False) self.consider_u_ind_by_sources_for_lifting_forces = ct.c_bool(False) self.ignore_first_x_nodes_in_force_calculation = ct.c_uint(0) def set_options(self, options, n_surfaces = 0, n_surfaces_nonlifting = 0): self.Steady = ct.c_bool(True) self.NumSurfaces = ct.c_uint(n_surfaces) self.NumSurfacesNonlifting = ct.c_uint(n_surfaces_nonlifting) self.horseshoe = ct.c_bool(options['horseshoe']) self.dt = ct.c_double(options["rollup_dt"]) self.n_rollup = ct.c_uint(options["n_rollup"]) self.rollup_tolerance = ct.c_double(options["rollup_tolerance"]) self.rollup_aic_refresh = ct.c_uint(options['rollup_aic_refresh']) self.NumCores = ct.c_uint(options['num_cores']) self.iterative_solver = ct.c_bool(options['iterative_solver']) self.iterative_tol = ct.c_double(options['iterative_tol']) self.iterative_precond = ct.c_bool(options['iterative_precond']) self.vortex_radius = ct.c_double(options['vortex_radius']) self.vortex_radius_wake_ind = ct.c_double(options['vortex_radius_wake_ind']) self.only_nonlifting = ct.c_bool(options["only_nonlifting"]) self.only_lifting = ct.c_bool(not options["nonlifting_body_interactions"]) self.phantom_wing_test = ct.c_bool(options["phantom_wing_test"]) self.ignore_first_x_nodes_in_force_calculation = ct.c_uint(options["ignore_first_x_nodes_in_force_calculation"]) class UVMopts(ct.Structure): _fields_ = [("dt", ct.c_double), ("NumCores", ct.c_uint), ("NumSurfaces", ct.c_uint), ("NumSurfacesNonlifting", ct.c_uint), ("only_lifting", ct.c_bool), ("only_nonlifting", ct.c_bool), ("phantom_wing_test", ct.c_bool), ("convection_scheme", ct.c_uint), ("ImageMethod", ct.c_bool), ("iterative_solver", ct.c_bool), ("iterative_tol", ct.c_double), ("iterative_precond", ct.c_bool), ("convect_wake", ct.c_bool), ("cfl1", ct.c_bool), ("vortex_radius", ct.c_double), ("vortex_radius_wake_ind", ct.c_double), ("interp_coords", ct.c_uint), ("filter_method", ct.c_uint), ("interp_method", ct.c_uint), ("yaw_slerp", ct.c_double), ("quasi_steady", ct.c_bool), ("num_spanwise_panels_wo_induced_velocity", ct.c_uint), ("consider_u_ind_by_sources_for_lifting_forces", ct.c_bool), ("ignore_first_x_nodes_in_force_calculation", ct.c_uint)] def __init__(self): ct.Structure.__init__(self) self.dt = ct.c_double(0.01) self.NumCores = ct.c_uint(4) self.NumSurfaces = ct.c_uint(1) self.NumSurfacesNonlifting = ct.c_uint(1) self.convection_scheme = ct.c_uint(2) self.ImageMethod = ct.c_bool(False) self.iterative_solver = ct.c_bool(False) self.iterative_tol = ct.c_double(0) self.iterative_precond = ct.c_bool(False) self.convect_wake = ct.c_bool(True) self.cfl1 = ct.c_bool(True) self.vortex_radius = ct.c_double(vortex_radius_def) self.vortex_radius_wake_ind = ct.c_double(vortex_radius_def) self.yaw_slerp = ct.c_double(0.) self.quasi_steady = ct.c_bool(False) self.num_spanwise_panels_wo_induced_velocity = ct.c_uint(0) self.phantom_wing_test = ct.c_bool(False) self.consider_u_ind_by_sources_for_lifting_forces = ct.c_bool(False) self.ignore_first_x_nodes_in_force_calculation = ct.c_uint(0) def set_options(self, options, n_surfaces = 0, n_surfaces_nonlifting = 0, dt = None, convect_wake = False, n_span_panels_wo_u_ind = 0, only_lifting=True): if dt is None: self.dt = ct.c_double(options["dt"]) else: self.dt = ct.c_double(dt) self.NumCores = ct.c_uint(options["num_cores"]) self.NumSurfaces = ct.c_uint(n_surfaces) self.NumSurfacesNonlifting = ct.c_uint(n_surfaces_nonlifting) self.ImageMethod = ct.c_bool(False) self.convection_scheme = ct.c_uint(options["convection_scheme"]) self.iterative_solver = ct.c_bool(options['iterative_solver']) self.iterative_tol = ct.c_double(options['iterative_tol']) self.iterative_precond = ct.c_bool(options['iterative_precond']) self.convect_wake = ct.c_bool(convect_wake) self.cfl1 = ct.c_bool(options['cfl1']) self.vortex_radius = ct.c_double(options['vortex_radius']) self.vortex_radius_wake_ind = ct.c_double(options['vortex_radius_wake_ind']) self.interp_coords = ct.c_uint(options["interp_coords"]) self.filter_method = ct.c_uint(options["filter_method"]) self.interp_method = ct.c_uint(options["interp_method"]) self.yaw_slerp = ct.c_double(options["yaw_slerp"]) self.quasi_steady = ct.c_bool(options['quasi_steady']) self.only_nonlifting = ct.c_bool(options["only_nonlifting"]) self.only_lifting = ct.c_bool(only_lifting) self.phantom_wing_test = ct.c_bool(options["phantom_wing_test"]) self.ignore_first_x_nodes_in_force_calculation = ct.c_uint(options["ignore_first_x_nodes_in_force_calculation"]) self.num_spanwise_panels_wo_induced_velocity = n_span_panels_wo_u_ind class FlightConditions(ct.Structure): _fields_ = [("uinf", ct.c_double), ("uinf_direction", ct.c_double*3), ("rho", ct.c_double), ("c_ref", ct.c_double)] def __init__(self, rho, vec_u_inf): ct.Structure.__init__(self) self.set_flight_conditions(rho, vec_u_inf) def set_flight_conditions(self, rho, vec_u_inf): self.rho = rho self.uinf = np.ctypeslib.as_ctypes(np.linalg.norm(vec_u_inf)) self.uinf_direction = np.ctypeslib.as_ctypes(vec_u_inf/self.uinf) # type for 2d integer matrix t_2int = ct.POINTER(ct.c_int)*2 def vlm_solver(ts_info, options): run_VLM = UvlmLib.run_VLM vmopts = VMopts() vmopts.set_options(options, n_surfaces = ts_info.n_surf) flightconditions = FlightConditions(options['rho'], ts_info.u_ext[0][:, 0, 0]) p_rbm_vel_g = options['rbm_vel_g'].ctypes.data_as(ct.POINTER(ct.c_double)) p_centre_rot_g = options['centre_rot_g'].ctypes.data_as(ct.POINTER(ct.c_double)) ts_info.generate_ctypes_pointers() run_VLM(ct.byref(vmopts), ct.byref(flightconditions), ts_info.ct_p_dimensions, ts_info.ct_p_dimensions_star, ts_info.ct_p_zeta, ts_info.ct_p_zeta_star, ts_info.ct_p_zeta_dot, ts_info.ct_p_u_ext, ts_info.ct_p_gamma, ts_info.ct_p_gamma_star, ts_info.ct_p_forces, p_rbm_vel_g, p_centre_rot_g) ts_info.remove_ctypes_pointers() def vlm_solver_nonlifting_body(ts_info, options): run_linear_source_panel_method = UvlmLib.run_linear_source_panel_method vmopts = VMopts() vmopts.set_options(options, n_surfaces_nonlifting = ts_info.n_surf) flightconditions = FlightConditions(options['rho'], ts_info.u_ext[0][:, 0, 0]) ts_info.generate_ctypes_pointers() run_linear_source_panel_method(ct.byref(vmopts), ct.byref(flightconditions), ts_info.ct_p_dimensions, ts_info.ct_p_zeta, ts_info.ct_p_u_ext, ts_info.ct_p_sigma, ts_info.ct_p_forces, ts_info.ct_p_pressure_coefficients) ts_info.remove_ctypes_pointers() def vlm_solver_lifting_and_nonlifting_bodies(ts_info_lifting, ts_info_nonlifting, options): run_VLM_coupled_with_LSPM = UvlmLib.run_VLM_coupled_with_LSPM vmopts = VMopts() vmopts.set_options(options, n_surfaces = ts_info_lifting.n_surf, n_surfaces_nonlifting = ts_info_nonlifting.n_surf) flightconditions = FlightConditions(options['rho'], ts_info_lifting.u_ext[0][:, 0, 0]) p_rbm_vel_g = options['rbm_vel_g'].ctypes.data_as(ct.POINTER(ct.c_double)) p_centre_rot = options['centre_rot_g'].ctypes.data_as(ct.POINTER(ct.c_double)) ts_info_lifting.generate_ctypes_pointers() ts_info_nonlifting.generate_ctypes_pointers() run_VLM_coupled_with_LSPM(ct.byref(vmopts), ct.byref(flightconditions), ts_info_lifting.ct_p_dimensions, ts_info_lifting.ct_p_dimensions_star, ts_info_lifting.ct_p_zeta, ts_info_lifting.ct_p_zeta_star, ts_info_lifting.ct_p_zeta_dot, ts_info_lifting.ct_p_u_ext, ts_info_lifting.ct_p_gamma, ts_info_lifting.ct_p_gamma_star, ts_info_lifting.ct_p_forces, ts_info_lifting.ct_p_flag_zeta_phantom, ts_info_nonlifting.ct_p_dimensions, ts_info_nonlifting.ct_p_zeta, ts_info_nonlifting.ct_p_u_ext, ts_info_nonlifting.ct_p_sigma, ts_info_nonlifting.ct_p_forces, ts_info_nonlifting.ct_p_pressure_coefficients, p_rbm_vel_g, p_centre_rot) ts_info_lifting.remove_ctypes_pointers() ts_info_nonlifting.remove_ctypes_pointers() def uvlm_solver(i_iter, ts_info, struct_ts_info, options, convect_wake=True, dt=None): p_rbm_vel = get_ctype_pointer_of_rbm_vel_in_G_frame(struct_ts_info.for_vel.copy(), struct_ts_info.cga()) p_centre_rot = options['centre_rot'].ctypes.data_as(ct.POINTER(ct.c_double)) run_UVLM = UvlmLib.run_UVLM uvmopts = UVMopts() uvmopts.set_options(options, n_surfaces = ts_info.n_surf, n_surfaces_nonlifting = 0, dt = dt, convect_wake = convect_wake, n_span_panels_wo_u_ind=0) flightconditions = FlightConditions(options['rho'], ts_info.u_ext[0][:, 0, 0]) i = ct.c_uint(i_iter) ts_info.generate_ctypes_pointers() run_UVLM(ct.byref(uvmopts), ct.byref(flightconditions), ts_info.ct_p_dimensions, ts_info.ct_p_dimensions_star, ct.byref(i), ts_info.ct_p_u_ext, ts_info.ct_p_u_ext_star, ts_info.ct_p_zeta, ts_info.ct_p_zeta_star, ts_info.ct_p_zeta_dot, ts_info.ct_p_gamma, ts_info.ct_p_gamma_star, ts_info.ct_p_dist_to_orig, ts_info.ct_p_normals, ts_info.ct_p_forces, ts_info.ct_p_dynamic_forces, p_rbm_vel, p_centre_rot) ts_info.remove_ctypes_pointers() def uvlm_solver_lifting_and_nonlifting(i_iter, ts_info, ts_info_nonlifting, struct_ts_info, options, convect_wake=True, dt=None): p_rbm_vel = get_ctype_pointer_of_rbm_vel_in_G_frame(struct_ts_info.for_vel.copy(), struct_ts_info.cga()) p_centre_rot = options['centre_rot'].ctypes.data_as(ct.POINTER(ct.c_double)) uvmopts = UVMopts() uvmopts.set_options(options, n_surfaces = ts_info.n_surf, n_surfaces_nonlifting = ts_info_nonlifting.n_surf, dt = dt, convect_wake = convect_wake, n_span_panels_wo_u_ind=4, only_lifting=False) run_UVLM = UvlmLib.run_UVLM_coupled_with_LSPM flightconditions = FlightConditions(options['rho'], ts_info.u_ext[0][:, 0, 0]) i = ct.c_uint(i_iter) ts_info.generate_ctypes_pointers() ts_info_nonlifting.generate_ctypes_pointers() run_UVLM(ct.byref(uvmopts), ct.byref(flightconditions), ts_info.ct_p_dimensions, ts_info.ct_p_dimensions_star, ct.byref(i), ts_info.ct_p_u_ext, ts_info.ct_p_u_ext_star, ts_info.ct_p_zeta, ts_info.ct_p_zeta_star, ts_info.ct_p_zeta_dot, ts_info.ct_p_gamma, ts_info.ct_p_gamma_star, ts_info.ct_p_dist_to_orig, ts_info.ct_p_normals, ts_info.ct_p_forces, ts_info.ct_p_dynamic_forces, ts_info.ct_p_flag_zeta_phantom, ts_info_nonlifting.ct_p_dimensions, ts_info_nonlifting.ct_p_zeta, ts_info_nonlifting.ct_p_u_ext, ts_info_nonlifting.ct_p_sigma, ts_info_nonlifting.ct_p_forces, ts_info_nonlifting.ct_p_pressure_coefficients, p_rbm_vel, p_centre_rot) ts_info.remove_ctypes_pointers() ts_info_nonlifting.remove_ctypes_pointers() def uvlm_calculate_unsteady_forces(ts_info, struct_ts_info, options, convect_wake=True, dt=None): calculate_unsteady_forces = UvlmLib.calculate_unsteady_forces uvmopts = UVMopts() if dt is None: uvmopts.dt = ct.c_double(options["dt"]) else: uvmopts.dt = ct.c_double(dt) uvmopts.NumCores = ct.c_uint(options["num_cores"]) uvmopts.NumSurfaces = ct.c_uint(ts_info.n_surf) uvmopts.ImageMethod = ct.c_bool(False) uvmopts.convection_scheme = ct.c_uint(options["convection_scheme"]) uvmopts.iterative_solver = ct.c_bool(options['iterative_solver']) uvmopts.iterative_tol = ct.c_double(options['iterative_tol']) uvmopts.iterative_precond = ct.c_bool(options['iterative_precond']) uvmopts.convect_wake = ct.c_bool(convect_wake) uvmopts.vortex_radius = ct.c_double(options['vortex_radius']) flightconditions = FlightConditions(options['rho'], ts_info.u_ext[0][:, 0, 0]) p_rbm_vel = get_ctype_pointer_of_rbm_vel_in_G_frame(struct_ts_info.for_vel.copy(), struct_ts_info.cga()) for i_surf in range(ts_info.n_surf): ts_info.dynamic_forces[i_surf].fill(0.0) ts_info.generate_ctypes_pointers() calculate_unsteady_forces(ct.byref(uvmopts), ct.byref(flightconditions), ts_info.ct_p_dimensions, ts_info.ct_p_dimensions_star, ts_info.ct_p_zeta, ts_info.ct_p_zeta_star, p_rbm_vel, ts_info.ct_p_gamma, ts_info.ct_p_gamma_star, ts_info.ct_p_gamma_dot, ts_info.ct_p_normals, ts_info.ct_p_dynamic_forces) ts_info.remove_ctypes_pointers() def uvlm_calculate_incidence_angle(ts_info, struct_ts_info): calculate_incidence_angle = UvlmLib.UVLM_check_incidence_angle p_rbm_vel = get_ctype_pointer_of_rbm_vel_in_G_frame(struct_ts_info.for_vel.copy(), struct_ts_info.cga()) n_surf = ct.c_uint(ts_info.n_surf) ts_info.generate_ctypes_pointers() calculate_incidence_angle(ct.byref(n_surf), ts_info.ct_p_dimensions, ts_info.ct_p_u_ext, ts_info.ct_p_zeta, ts_info.ct_p_zeta_dot, ts_info.ct_p_normals, p_rbm_vel, ts_info.postproc_cell['incidence_angle_ct_pointer']) ts_info.remove_ctypes_pointers() def uvlm_calculate_total_induced_velocity_at_points(ts_info, target_triads, vortex_radius, for_pos=np.zeros((6)), ncores=ct.c_uint(1)): """ uvlm_calculate_total_induced_velocity_at_points Caller to the UVLM library to compute the induced velocity of all the surfaces and wakes at a list of points Args: ts_info (AeroTimeStepInfo): Time step information target_triads (np.array): Point coordinates, size=(npoints, 3) vortex_radius (float): Vortex radius threshold below which do not compute induced velocity uind (np.array): Induced velocity Returns: uind (np.array): Induced velocity, size=(npoints, 3) """ calculate_uind_at_points = UvlmLib.total_induced_velocity_at_points uvmopts = UVMopts() uvmopts.NumSurfaces = ct.c_uint(ts_info.n_surf) uvmopts.ImageMethod = ct.c_bool(False) uvmopts.NumCores = ct.c_uint(ncores) uvmopts.vortex_radius = ct.c_double(vortex_radius) npoints = target_triads.shape[0] uind = np.zeros((npoints, 3), dtype=ct.c_double) if type(target_triads[0,0]) == ct.c_double: aux_target_triads = target_triads else: aux_target_triads = target_triads.astype(dtype=ct.c_double) p_target_triads = ((ct.POINTER(ct.c_double))(* [np.ctypeslib.as_ctypes(aux_target_triads.reshape(-1))])) p_uind = ((ct.POINTER(ct.c_double))(* [np.ctypeslib.as_ctypes(uind.reshape(-1))])) # make a copy of ts info and add for_pos to zeta and zeta_star ts_info_copy = ts_info.copy() for i_surf in range(ts_info_copy.n_surf): # zeta for iM in range(ts_info_copy.zeta[i_surf].shape[1]): for iN in range(ts_info_copy.zeta[i_surf].shape[2]): ts_info_copy.zeta[i_surf][:, iM, iN] += for_pos[0:3] # zeta_star for iM in range(ts_info_copy.zeta_star[i_surf].shape[1]): for iN in range(ts_info_copy.zeta_star[i_surf].shape[2]): ts_info_copy.zeta_star[i_surf][:, iM, iN] += for_pos[0:3] ts_info_copy.generate_ctypes_pointers() calculate_uind_at_points(ct.byref(uvmopts), ts_info_copy.ct_p_dimensions, ts_info_copy.ct_p_dimensions_star, ts_info_copy.ct_p_zeta, ts_info_copy.ct_p_zeta_star, ts_info_copy.ct_p_gamma, ts_info_copy.ct_p_gamma_star, p_target_triads, p_uind, ct.c_uint(npoints)) ts_info_copy.remove_ctypes_pointers() del p_uind del p_target_triads return uind def biot_panel_cpp(zeta_point, zeta_panel, vortex_radius, gamma=1.0): """ Linear UVLM function Returns the induced velocity at a point ``zeta_point`` due to a panel located at ``zeta_panel`` with circulation ``gamma``. Args: zeta_point (np.ndarray): Coordinates of the point with size ``(3,)``. zeta_panel (np.ndarray): Panel coordinates with size ``(4, 3)``. gamma (float): Panel circulation. Returns: np.ndarray: Induced velocity at point """ assert zeta_point.flags['C_CONTIGUOUS'] and zeta_panel.flags['C_CONTIGUOUS'], \ 'Input not C contiguous' if type(vortex_radius) is ct.c_double: vortex_radius_float = vortex_radius.value else: vortex_radius_float = vortex_radius velP = np.zeros((3,), order='C') UvlmLib.call_biot_panel( velP.ctypes.data_as(ct.POINTER(ct.c_double)), zeta_point.ctypes.data_as(ct.POINTER(ct.c_double)), zeta_panel.ctypes.data_as(ct.POINTER(ct.c_double)), ct.byref(ct.c_double(gamma)), ct.byref(ct.c_double(vortex_radius_float))) return velP def eval_panel_cpp(zeta_point, zeta_panel, vortex_radius, gamma_pan=1.0): """ Linear UVLM function Returns tuple: The derivative of the induced velocity with respect to point ``P`` and panel vertices ``ZetaP``. Warnings: Function may fail if zeta_point is not stored contiguously. Eg: The following will fail zeta_point=Mat[:,2,5] eval_panel_cpp(zeta_point,zeta_panel, vortex_radius, gamma_pan=1.0) but zeta_point=Mat[:,2,5].copy() eval_panel_cpp(zeta_point,zeta_panel, vortex_radius, gamma_pan=1.0) will not. """ assert zeta_point.flags['C_CONTIGUOUS'] and zeta_panel.flags['C_CONTIGUOUS'], \ 'Input not C contiguous' der_point = np.zeros((3, 3), order='C') der_vertices = np.zeros((4, 3, 3), order='C') if type(vortex_radius) is ct.c_double: vortex_radius_float = vortex_radius.value else: vortex_radius_float = vortex_radius UvlmLib.call_der_biot_panel( der_point.ctypes.data_as(ct.POINTER(ct.c_double)), der_vertices.ctypes.data_as(ct.POINTER(ct.c_double)), zeta_point.ctypes.data_as(ct.POINTER(ct.c_double)), zeta_panel.ctypes.data_as(ct.POINTER(ct.c_double)), ct.byref(ct.c_double(gamma_pan)), ct.byref(ct.c_double(vortex_radius_float))) return der_point, der_vertices def get_induced_velocity_cpp(maps, zeta, gamma, zeta_target, vortex_radius): """ Linear UVLM function used in bound surfaces Computes induced velocity at a point zeta_target. Args: maps (sharpy.linear.src.surface.AeroGridSurface): instance of bound surface zeta (np.ndarray): Coordinates of panel gamma (float): Panel circulation strength zeta_target (np.ndarray): Coordinates of target point Returns: np.ndarray: Induced velocity by panel at target point """ call_ind_vel = UvlmLib.call_ind_vel assert zeta_target.flags['C_CONTIGUOUS'], "Input not C contiguous" M, N = maps.M, maps.N uind_target = np.zeros((3,), order='C') if type(vortex_radius) is ct.c_double: vortex_radius_float = vortex_radius.value else: vortex_radius_float = vortex_radius call_ind_vel( uind_target.ctypes.data_as(ct.POINTER(ct.c_double)), zeta_target.ctypes.data_as(ct.POINTER(ct.c_double)), zeta.ctypes.data_as(ct.POINTER(ct.c_double)), gamma.ctypes.data_as(ct.POINTER(ct.c_double)), ct.byref(ct.c_int(M)), ct.byref(ct.c_int(N)), ct.byref(ct.c_double(vortex_radius_float))) return uind_target def get_aic3_cpp(maps, zeta, zeta_target, vortex_radius): """ Linear UVLM function used in bound surfaces Produces influence coefficient matrix to calculate the induced velocity at a target point. The aic3 matrix has shape (3,K) Args: maps (sharpy.linear.src.surface.AeroGridSurface): instance of linear bound surface zeta (np.ndarray): Coordinates of panel zeta_target (np.ndarray): Coordinates of target point Returns: np.ndarray: Aerodynamic influence coefficient """ assert zeta_target.flags['C_CONTIGUOUS'], "Input not C contiguous" K = maps.K aic3 = np.zeros((3, K), order='C') if type(vortex_radius) is ct.c_double: vortex_radius_float = vortex_radius.value else: vortex_radius_float = vortex_radius UvlmLib.call_aic3( aic3.ctypes.data_as(ct.POINTER(ct.c_double)), zeta_target.ctypes.data_as(ct.POINTER(ct.c_double)), zeta.ctypes.data_as(ct.POINTER(ct.c_double)), ct.byref(ct.c_int(maps.M)), ct.byref(ct.c_int(maps.N)), ct.byref(ct.c_double(vortex_radius_float))) return aic3 def dvinddzeta_cpp(zetac, surf_in, is_bound, vortex_radius, M_in_bound=None): """ Linear UVLM function used in the assembly of the linear system Produces derivatives of induced velocity by surf_in w.r.t. the zetac point. Derivatives are divided into those associated to the movement of zetac, and to the movement of the surf_in vertices (DerVert). If surf_in is bound (is_bound==True), the circulation over the TE due to the wake is not included in the input. If surf_in is a wake (is_bound==False), derivatives w.r.t. collocation points are computed ad the TE contribution on ``der_vert``. In this case, the chordwise paneling Min_bound of the associated input is required so as to calculate Kzeta and correctly allocate the derivative matrix. Returns: tuple: output derivatives are: - der_coll: 3 x 3 matrix - der_vert: 3 x 3*Kzeta (if surf_in is a wake, Kzeta is that of the bound) Warning: zetac must be contiguously stored! """ M_in, N_in = surf_in.maps.M, surf_in.maps.N Kzeta_in = surf_in.maps.Kzeta shape_zeta_in = (3, M_in + 1, N_in + 1) # allocate matrices der_coll = np.zeros((3, 3), order='C') if is_bound: M_in_bound = M_in Kzeta_in_bound = (M_in_bound + 1) * (N_in + 1) der_vert = np.zeros((3, 3 * Kzeta_in_bound)) if type(vortex_radius) is ct.c_double: vortex_radius_float = vortex_radius.value else: vortex_radius_float = vortex_radius UvlmLib.call_dvinddzeta( der_coll.ctypes.data_as(ct.POINTER(ct.c_double)), der_vert.ctypes.data_as(ct.POINTER(ct.c_double)), zetac.ctypes.data_as(ct.POINTER(ct.c_double)), surf_in.zeta.ctypes.data_as(ct.POINTER(ct.c_double)), surf_in.gamma.ctypes.data_as(ct.POINTER(ct.c_double)), ct.byref(ct.c_int(M_in)), ct.byref(ct.c_int(N_in)), ct.byref(ct.c_bool(is_bound)), ct.byref(ct.c_int(M_in_bound)), ct.byref(ct.c_double(vortex_radius_float)) ) return der_coll, der_vert def get_ctype_pointer_of_rbm_vel_in_G_frame(rbm_vel, cga): rbm_vel[0:3] = np.dot(cga, rbm_vel[0:3]) rbm_vel[3:6] = np.dot(cga, rbm_vel[3:6]) p_rbm_vel = rbm_vel.ctypes.data_as(ct.POINTER(ct.c_double)) return p_rbm_vel ================================================ FILE: sharpy/cases/__init__.py ================================================ ================================================ FILE: sharpy/cases/coupled/WindTurbine/__init__.py ================================================ ================================================ FILE: sharpy/cases/coupled/WindTurbine/generate_rotor.py ================================================ import numpy as np import os import sharpy.utils.generate_cases as gc import sharpy.cases.templates.template_wt as template_wt from sharpy.utils.constants import deg2rad import sharpy.utils.sharpydir as sharpydir ###################################################################### ########################### PARAMETERS ############################# ###################################################################### # Case case = 'rotor' route = os.path.dirname(os.path.realpath(__file__)) + '/' # Geometry discretization chord_panels = np.array([8], dtype=int) revs_in_wake = 1 # Operation rotation_velocity = 1.366190 pitch_deg = 0. #degrees # Wind WSP = 11.4 air_density = 1.225 # Simulation dphi = 4.*deg2rad revs_to_simulate = 5 ###################################################################### ########################## GENERATE WT ############################# ###################################################################### dt = dphi/rotation_velocity # time_steps = int(revs_to_simulate*2.*np.pi/dphi) time_steps = 2 # For the test cases mstar = int(revs_in_wake*2.*np.pi/dphi) op_params = {'rotation_velocity': rotation_velocity, 'pitch_deg': pitch_deg, 'wsp': WSP, 'dt': dt} geom_params = {'chord_panels':chord_panels, 'tol_remove_points': 1e-8, 'n_points_camber': 100, 'm_distribution': 'uniform'} excel_description = {'excel_file_name': os.path.abspath(sharpydir.SharpyDir + '/docs/source/content/example_notebooks/source/type02_db_NREL5MW_v02.xlsx'), 'excel_sheet_parameters': 'parameters', 'excel_sheet_structural_blade': 'structural_blade', 'excel_sheet_discretization_blade': 'discretization_blade', 'excel_sheet_aero_blade': 'aero_blade', 'excel_sheet_airfoil_info': 'airfoil_info', 'excel_sheet_airfoil_chord': 'airfoil_coord'} options = {'camber_effect_on_twist': False, 'user_defined_m_distribution_type': None, 'include_polars': False} rotor = template_wt.rotor_from_excel_type03(op_params, geom_params, excel_description, options) ###################################################################### ###################### DEFINE SIMULATION ########################### ###################################################################### SimInfo = gc.SimulationInformation() SimInfo.set_default_values() SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'BeamPlot', 'AerogridPlot', 'DynamicCoupled', 'SaveData'] SimInfo.solvers['SHARPy']['case'] = case SimInfo.solvers['SHARPy']['route'] = route SimInfo.solvers['SHARPy']['write_log'] = True SimInfo.set_variable_all_dicts('dt', dt) SimInfo.set_variable_all_dicts('rho', air_density) SimInfo.solvers['SteadyVelocityField']['u_inf'] = WSP SimInfo.solvers['SteadyVelocityField']['u_inf_direction'] = np.array([0., 0., 1.]) SimInfo.set_variable_all_dicts('velocity_field_input', SimInfo.solvers['SteadyVelocityField']) SimInfo.solvers['BeamLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['mstar'] = mstar SimInfo.solvers['AerogridLoader']['freestream_dir'] = np.array([0.,0.,0.]) SimInfo.solvers['AerogridLoader']['wake_shape_generator'] = 'HelicoidalWake' SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] = {'u_inf': WSP, 'u_inf_direction': SimInfo.solvers['SteadyVelocityField']['u_inf_direction'], 'rotation_velocity': rotation_velocity*np.array([0., 0., 1.]), 'dt': dt, 'dphi1': dphi, 'ndphi1': mstar, 'r': 1., 'dphimax': 10*deg2rad} SimInfo.solvers['NonLinearStatic']['max_iterations'] = 200 SimInfo.solvers['NonLinearStatic']['num_load_steps'] = 1 SimInfo.solvers['NonLinearStatic']['min_delta'] = 1e-5 SimInfo.solvers['StaticUvlm']['horseshoe'] = False SimInfo.solvers['StaticUvlm']['num_cores'] = 8 SimInfo.solvers['StaticUvlm']['n_rollup'] = 0 SimInfo.solvers['StaticUvlm']['rollup_dt'] = dt SimInfo.solvers['StaticUvlm']['rollup_aic_refresh'] = 1 SimInfo.solvers['StaticUvlm']['rollup_tolerance'] = 1e-8 SimInfo.solvers['StaticUvlm']['velocity_field_generator'] = 'SteadyVelocityField' SimInfo.solvers['StaticUvlm']['velocity_field_input'] = SimInfo.solvers['SteadyVelocityField'] SimInfo.solvers['StaticCoupled']['structural_solver'] = 'NonLinearStatic' SimInfo.solvers['StaticCoupled']['structural_solver_settings'] = SimInfo.solvers['NonLinearStatic'] SimInfo.solvers['StaticCoupled']['aero_solver'] = 'StaticUvlm' SimInfo.solvers['StaticCoupled']['aero_solver_settings'] = SimInfo.solvers['StaticUvlm'] SimInfo.solvers['StaticCoupled']['tolerance'] = 1e-6 SimInfo.solvers['StaticCoupled']['n_load_steps'] = 0 SimInfo.solvers['StaticCoupled']['relaxation_factor'] = 0. SimInfo.solvers['StepUvlm']['convection_scheme'] = 2 SimInfo.solvers['StepUvlm']['num_cores'] = 8 SimInfo.solvers['WriteVariablesTime']['FoR_variables'] = ['total_forces',] SimInfo.solvers['WriteVariablesTime']['FoR_number'] = [0,] SimInfo.solvers['DynamicCoupled']['structural_solver'] = 'RigidDynamicPrescribedStep' SimInfo.solvers['DynamicCoupled']['structural_solver_settings'] = SimInfo.solvers['RigidDynamicPrescribedStep'] SimInfo.solvers['DynamicCoupled']['aero_solver'] = 'StepUvlm' SimInfo.solvers['DynamicCoupled']['aero_solver_settings'] = SimInfo.solvers['StepUvlm'] SimInfo.solvers['DynamicCoupled']['postprocessors'] = ['BeamPlot', 'AerogridPlot', 'WriteVariablesTime', 'Cleanup'] SimInfo.solvers['DynamicCoupled']['postprocessors_settings'] = {'BeamPlot': SimInfo.solvers['BeamPlot'], 'AerogridPlot': SimInfo.solvers['AerogridPlot'], 'WriteVariablesTime': SimInfo.solvers['WriteVariablesTime'], 'Cleanup': SimInfo.solvers['Cleanup']} SimInfo.solvers['DynamicCoupled']['minimum_steps'] = 0 SimInfo.define_num_steps(time_steps) # Define dynamic simulation SimInfo.with_forced_vel = True SimInfo.for_vel = np.zeros((time_steps,6), dtype=float) SimInfo.for_vel[:,5] = rotation_velocity SimInfo.for_acc = np.zeros((time_steps,6), dtype=float) SimInfo.with_dynamic_forces = True SimInfo.dynamic_forces = np.zeros((time_steps,rotor.StructuralInformation.num_node,6), dtype=float) ###################################################################### ####################### GENERATE FILES ############################# ###################################################################### gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) rotor.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(time_steps) ================================================ FILE: sharpy/cases/coupled/X-HALE/generate_xhale.py ================================================ #!/usr/env python # X-HALE model for SHARPy. # Version 1.0 # See IFASD 2019 paper by: del Carre, A and Teixeira, P and Palacios, R and Cesnik, C. E. S. # # Alfonso del Carre # June 2019 # ============================================================================ import h5py as h5 import numpy as np import os import sharpy.utils.algebra as algebra route = os.path.dirname(os.path.realpath(__file__)) + '/' cases = [ '0', # 15%, 15 chords, lateral ] data = dict() data['0'] = { 'gust_length': 15, # in chords 'gust_intensity': 0.15, # in % of uinf 'gust_shape': 'lateral 1-cos', # '1-cos' for vertical gust } horseshoe = 'off' for case in cases: vertical_tail = True if vertical_tail: case_name = 'xhale_ifasd' + '_' + case print('Generating xhale with vertical Ctail') else: case_name = 'xhale_ifasd' + '_' + case print('Generating xhale with horizontal Ctail') flow = [ 'BeamLoader', 'AerogridLoader', # 'StaticTrim', # uncomment for longitudinal static trim 'StaticCoupled', # static coupled solver with GECB and UVLM 'BeamLoads', # beam loads and strains computation for static 'BeamPlot', # beam structure and data output for static 'AerogridPlot', # aero grid output for static 'DynamicCoupled', # dynamic coupled solver. See end of file: # settings['DynamicCoupled']['postprocessors'] # for the corresponding 'BeamLoads', 'BeamPlot' # and 'AerogridPlot' settings and calls. ] u_inf = 14 # free stream vel [SI] rho = 1.225 # density [SI] chord = 0.2 # main wing chord length for gust dimensioning. # the value used for the geometry generation is # given in the input/*.xslx files if vertical_tail: alpha = 2.4744791522743887*np.pi/180 # angle of attack [rad] beta = 0.*np.pi/180 # sideslip angle [rad] cs_deflection = 1.186515244051093*np.pi/180 # elevators deflection [rad] aileron_deflection = 0*0.039011*np.pi/180 # aileron deflection [rad] thrustC = 0.21033486522175712 # baseline thrust [N] differential = 0 # thrust of right side: T_R = thrustC*(1 + differential) # thrust of left side: T_L = thrustC*(1 - differential) roll = 0 # initial/static roll angle [rad] in_structural_twist = 5*np.pi/180 else: # NOTE: These values are not the correct trim. Run StaticTrim with your # current discretisation alpha = 2.4744791522743887*np.pi/180 # angle of attack [rad] beta = 0.*np.pi/180 # sideslip angle [rad] cs_deflection = 1.186515244051093*np.pi/180 # elevators deflection [rad] aileron_deflection = 0*0.039011*np.pi/180 # aileron deflection [rad] thrustC = 0.21033486522175712 # baseline thrust [N] differential = 0 # thrust of right side: T_R = thrustC*(1 + differential) # thrust of left side: T_L = thrustC*(1 - differential) roll = 0 # initial/static roll angle [rad] in_structural_twist = 5*np.pi/180 gravity = 'on' gravity_value = 9.807 # stiffness multiplier sigma = 1 # shear stiffness multipliers ga_mult = 0.1 # spatial offset [m] for the gust. (if == 1, gust 1 m in front of reference # point [0, 0, 0]. space_offset = 1. try: gust_intensity = data[case]['gust_intensity'] gust_length = data[case]['gust_length']*chord gust_shape = data[case]['gust_shape'] except KeyError: gust_intensity = 0 gust_length = 0 gust_shape = '1-cos' # number of load substeps in the static coupled solver. n_step = 1 # relaxation factor for the static coupled solver # static relaxation factor \in [0, 1). 0 == no relaxation static_relaxation_factor = 0.5 # Dynamic relaxation parameters. Relaxation is linearly varied between # initial and final relaxation factor in relaxation_steps initial_relaxation_factor = 0.4 final_relaxation_factor = 0.9 relaxation_steps = 15 # nonlinear beam tolerance. tolerance = 1e-6 # FSI iteration tolerance fsi_tolerance = 1e-6 # wake length when not running horseshoe wake_length = 4 # meters # geometrical data span_section = 1.0 dihedral_outer = 10*np.pi/180 length_centre_tail = 1.106 length_outer_tail = 0.65 span_tail = 0.24 span_ctail_L = 0.145 span_ctail_R = 0.24 span_fin = 0.184 span_vfin = 0.15 n_sections = 3 # DISCRETISATION # spatial discretisation # chordwise discretisation m = 8 # main wing m_tail = 3 # tails m_fin = 4 # fins and pods # number of structural elements in inner and outer sections. # note that you will have 2*n_elem spanwise aero panels n_elem_section = 4 # structural elements in dihedral section n_elem_section_dihedral = 8 # elements in central tail boom n_elem_centre_tail = 1 # elements in outer tail booms n_elem_outer_tail = 1 # elements in tails (per semi span) n_elem_tail = 1 # elements in fins and pods n_elem_fin = 1 n_elem_main = int((n_sections-1)*n_elem_section + n_elem_section_dihedral) # number of aero surfaces. To understand the logic of this, check SHARPy's # documentation n_surfaces = 20 # temporal discretisation # seconds of simulation physical_time = 10 # factor multiplying the theoretical timestep # (dt = chord/m/u_inf*tstep_factor) tstep_factor = 1 dt = chord/m/u_inf*tstep_factor n_tstep = round(physical_time/dt) print('n_tstep: ', n_tstep) # if horseshoe wake (only for static) is 'on' # we only need one chordwise wake panel if horseshoe == 'on': mstar = 1 else: # else, we put as many as we need to reach wake_length in steady conditions mstar = int(wake_length/(u_inf*dt)) print('mstar = ', mstar) # beam processing # don't modify this n_node_elem = 3 span_main = n_sections*span_section # total elements, nodes... calculation # total number of elements n_elem = 0 n_elem += n_elem_main n_elem += n_elem_main n_elem += n_elem_centre_tail n_elem += n_elem_tail n_elem += n_elem_tail n_elem += n_elem_fin n_elem += n_elem_outer_tail n_elem += n_elem_tail n_elem += n_elem_tail n_elem += n_elem_fin n_elem += n_elem_outer_tail n_elem += n_elem_tail n_elem += n_elem_tail n_elem += n_elem_fin n_elem += n_elem_outer_tail n_elem += n_elem_tail n_elem += n_elem_tail n_elem += n_elem_outer_tail n_elem += n_elem_tail n_elem += n_elem_tail n_elem += n_elem_fin n_elem += n_elem_fin n_elem += n_elem_fin n_elem += n_elem_fin n_elem += n_elem_fin # number of nodes per part n_node_section = n_elem_section*(n_node_elem - 1) + 1 n_node_section_dihedral = n_elem_section_dihedral*(n_node_elem - 1) + 1 n_node_main = n_elem_main*(n_node_elem - 1) + 1 n_node_centre_tail = n_elem_centre_tail*(n_node_elem - 1) + 1 n_node_tail = n_elem_tail*(n_node_elem - 1) + 1 n_node_outer_tail = n_elem_outer_tail*(n_node_elem - 1) + 1 n_node_fin = n_elem_fin*(n_node_elem - 1) + 1 # total number of nodes n_node = 0 n_node += n_node_main + n_node_main - 1 n_node += n_node_centre_tail - 1 n_node += n_node_tail - 1 n_node += n_node_tail - 1 n_node += n_node_fin - 1 n_node += n_node_outer_tail - 1 n_node += n_node_tail - 1 n_node += n_node_tail - 1 n_node += n_node_fin - 1 n_node += n_node_outer_tail - 1 n_node += n_node_tail - 1 n_node += n_node_tail - 1 n_node += n_node_fin - 1 n_node += n_node_outer_tail - 1 n_node += n_node_tail - 1 n_node += n_node_tail - 1 n_node += n_node_outer_tail - 1 n_node += n_node_tail - 1 n_node += n_node_tail - 1 n_node += n_node_fin - 1 n_node += n_node_fin - 1 n_node += n_node_fin - 1 n_node += n_node_fin - 1 n_node += n_node_fin - 1 # stiffness and mass matrices # if you add custom stiffness/mass matrices, make sure to update this # number n_stiffness = 12 n_mass = 17 # PLACEHOLDERS # beam x = np.zeros((n_node, )) y = np.zeros((n_node, )) z = np.zeros((n_node, )) stiffness_db = np.zeros((n_stiffness, 6, 6)) mass_db = np.zeros((n_mass, 6, 6)) beam_number = np.zeros((n_elem, ), dtype=int) num_node_elements = np.zeros((n_elem, ), dtype=int) + 3 frame_of_reference_delta = np.zeros((n_elem, n_node_elem, 3)) structural_twist = np.zeros((n_elem, n_node_elem)) conn = np.zeros((n_elem, n_node_elem), dtype=int) elem_stiffness = np.zeros((n_elem, ), dtype=int) elem_mass = np.zeros((n_elem, ), dtype=int) boundary_conditions = np.zeros((n_node, ), dtype=int) app_forces = np.zeros((n_node, 6)) n_lumped_mass = 0 lumped_mass_nodes = None lumped_mass = None lumped_mass_inertia = None lumped_mass_position = None end_nodesL = np.zeros((n_sections,), dtype=int) end_nodesR = np.zeros((n_sections,), dtype=int) end_elementsL = np.zeros((n_sections,), dtype=int) end_elementsR = np.zeros((n_sections,), dtype=int) end_tails_nodesL = np.zeros((2, ), dtype=int) end_tails_elementsL = np.zeros((2, ), dtype=int) end_tails_nodesR = np.zeros((2, ), dtype=int) end_tails_elementsR = np.zeros((2, ), dtype=int) end_of_centre_tail_node = 0 end_of_centre_tail_elem = 0 end_tip_tail_nodeC = np.zeros((2, ), dtype=int) end_tip_tail_elemC = np.zeros((2, ), dtype=int) tail_beam_numbersR = np.zeros((2, 3)) # 0=centre spar, 1=R tail, 2=L tail tail_beam_numbersL = np.zeros((2, 3)) # 0=centre spar, 1=R tail, 2=L tail tail_beam_numbersC = np.zeros((3, )) fin_beam_numberC = 0 fin_beam_numberL = 0 fin_beam_numberR = 0 vfin_beam_numberC = 0 vfin_beam_numberL = 0 vfin_beam_numberR = 0 fin_beam_numberLL = 0 fin_beam_numberRR = 0 # aero airfoil_distribution = np.zeros((n_elem, n_node_elem), dtype=int) surface_distribution = np.zeros((n_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces, ), dtype=int) # chordwise panel distribution. I'd leave there, but if you want, # you can try '1-cos' m_distribution = 'uniform' aero_node = np.zeros((n_node,), dtype=bool) twist = np.zeros((n_elem, n_node_elem)) chord = np.zeros((n_elem, n_node_elem,)) elastic_axis = np.zeros((n_elem, n_node_elem,)) thrust_nodes = np.zeros((5,), dtype=int) # FUNCTIONS------------------------------------------------------------- def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) dyn_file_name = route + '/' + case_name + '.dyn.h5' if os.path.isfile(dyn_file_name): os.remove(dyn_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def read_beam_data(filename=route + '/inputs/beam_properties.xlsx'): """ Function reading the xlsx file with beam properties, including stiffness and distributed mass. Data is then processed to obtain the 6x6 mass matrices. """ import pandas as pd import sharpy.utils.model_utils as model_utils # mass mass_sheet = pd.read_excel(filename, sheet_name='mass', header=1, skip_rows=1, index_col=0) # remove units mass_sheet = mass_sheet.drop(['[-]']) mass_data = dict() for index, row in mass_sheet.iterrows(): mass_data[index] = dict() mass_data[index]['mass'] = mass_sheet['mass'][index] mass_data[index]['inertia'] = np.zeros((3, 3)) mass_data[index]['inertia'][0, 0] = mass_sheet['ixx'][index] mass_data[index]['inertia'][1, 1] = mass_sheet['iyy'][index] mass_data[index]['inertia'][2, 2] = mass_sheet['izz'][index] mass_data[index]['inertia'][1, 2] = mass_sheet['iyz'][index] mass_data[index]['inertia'][2, 1] = mass_sheet['iyz'][index] mass_data[index]['xcg'] = np.zeros((3,)) mass_data[index]['xcg'][0] = mass_sheet['xcg'][index] mass_data[index]['xcg'][1] = mass_sheet['ycg'][index] mass_data[index]['xcg'][2] = mass_sheet['zcg'][index] mass_data[index]['full_matrix'] = ( model_utils.mass_matrix_generator(mass_data[index]['mass'], mass_data[index]['xcg'], mass_data[index]['inertia'])) # stiffness stiff_sheet = pd.read_excel(filename, sheet_name='stiffness', header=1, skip_rows=1, index_col=0) # remove units stiff_sheet = stiff_sheet.drop(['[-]']) stiff_data = dict() for index, row in stiff_sheet.iterrows(): stiff_data[index] = np.zeros((6, 6)) stiff_data[index][0, 0] = stiff_sheet['ea'][index] stiff_data[index][1, 1] = stiff_sheet['gay'][index]*ga_mult stiff_data[index][2, 2] = stiff_sheet['gaz'][index]*ga_mult stiff_data[index][3, 3] = stiff_sheet['gj'][index] stiff_data[index][4, 4] = stiff_sheet['eiy'][index] stiff_data[index][5, 5] = stiff_sheet['eiz'][index] stiff_data[index][0, 4] = stiff_sheet['k13'][index] # cross- stiff_data[index][4, 0] = stiff_sheet['k13'][index] # stiffness stiff_data[index][0, 5] = stiff_sheet['k14'][index] stiff_data[index][5, 0] = stiff_sheet['k14'][index] # 1-axial stiff_data[index][4, 5] = stiff_sheet['k34'][index] # 3-OOP stiff_data[index][5, 4] = stiff_sheet['k34'][index] # 4-IP return mass_data, stiff_data def read_lumped_mass_data(filename=route + '/inputs/lumped_mass.xlsx'): import pandas as pd xl = pd.ExcelFile(filename) sheets = {sheet_name: xl.parse(sheet_name, header=0, skiprows=(0, 2)) for sheet_name in xl.sheet_names} lumped_mass_data = dict() n_lumped = 0 for sheet, val in sheets.items(): if sheet == 'Notes': continue lumped_mass_data[sheet] = list() for row in range(len(val)): n_lumped += 1 lumped_mass_data[sheet].append(dict()) lumped_mass_data[sheet][row]['mass'] = 0.0 lumped_mass_data[sheet][row]['inertia'] = np.zeros((3, 3)) lumped_mass_data[sheet][row]['xcg'] = np.zeros((3,)) lumped_mass_data[sheet][row]['mass'] = val['Mass'][row] lumped_mass_data[sheet][row]['inertia'][0, 0] = val['Ixx'][row] lumped_mass_data[sheet][row]['inertia'][1, 1] = val['Iyy'][row] lumped_mass_data[sheet][row]['inertia'][2, 2] = val['Izz'][row] lumped_mass_data[sheet][row]['inertia'][0, 1] = val['Ixy'][row] lumped_mass_data[sheet][row]['inertia'][1, 0] = val['Ixy'][row] lumped_mass_data[sheet][row]['inertia'][0, 2] = val['Ixz'][row] lumped_mass_data[sheet][row]['inertia'][2, 0] = val['Ixz'][row] lumped_mass_data[sheet][row]['inertia'][1, 2] = val['Iyz'][row] lumped_mass_data[sheet][row]['inertia'][2, 1] = val['Iyz'][row] # location of centre of gravity for lumped mass is given in # material frame of reference, which depends on the element and # node you are clamping it to. Check the documentation for # a full explanation, but as a rule of thumb: check the # frame_of_reference_delta vector parameter in the beam # definition. It usually points towards "y" in material FoR, and # "x" is always tangent to the beam, pointing towards increasing # node numbers. # This is valid as long as structural_twist is 0. If # it is not, you need to take into account that the material # FoR will be rotated a structrual_twist angle around # the material "x" axis. lumped_mass_data[sheet][row]['xcg'][0] = val['xcg'][row] lumped_mass_data[sheet][row]['xcg'][1] = val['ycg'][row] lumped_mass_data[sheet][row]['xcg'][2] = val['zcg'][row] lumped_mass_data['n_lumped_mass'] = n_lumped return lumped_mass_data def generate_fem(): global end_of_centre_tail_node, end_of_centre_tail_elem global fin_beam_numberC, fin_beam_numberL, fin_beam_numberR global vfin_beam_numberC, vfin_beam_numberL, vfin_beam_numberR global fin_beam_numberLL, fin_beam_numberRR global residual_forces, residual_moments mass_data, stiff_data = read_beam_data() lumped_mass_data = read_lumped_mass_data() # process lumped mass n_lumped_mass = lumped_mass_data['n_lumped_mass'] lumped_mass_nodes = np.zeros((n_lumped_mass, ), dtype=int) lumped_mass = np.zeros((n_lumped_mass, )) lumped_mass_inertia = np.zeros((n_lumped_mass, 3, 3)) lumped_mass_position = np.zeros((n_lumped_mass, 3)) lumped_mass_indices = dict() i_lm = 0 for i, k in enumerate(lumped_mass_data.keys()): if k == 'n_lumped_mass': continue lumped_mass_indices[k] = list() for j in range(len(lumped_mass_data[k])): lumped_mass[i_lm] = lumped_mass_data[k][j]['mass'] lumped_mass_inertia[i_lm] = lumped_mass_data[k][j]['inertia'] lumped_mass_position[i_lm] = lumped_mass_data[k][j]['xcg'] lumped_mass_indices[k].append(i_lm) i_lm += 1 # process distributed mass mass_db[0, ...] = mass_data['Linboard']['full_matrix'] mass_db[1, ...] = mass_data['Loutboard']['full_matrix'] mass_db[2, ...] = mass_data['Ldihedral']['full_matrix'] mass_db[3, ...] = mass_data['Rinboard']['full_matrix'] mass_db[4, ...] = mass_data['Routboard']['full_matrix'] mass_db[5, ...] = mass_data['Rdihedral']['full_matrix'] mass_db[6, ...] = mass_data['boom']['full_matrix'] mass_db[7, ...] = mass_data['tailL']['full_matrix'] mass_db[8, ...] = mass_data['tailR']['full_matrix'] mass_db[9, ...] = mass_data['Cfin']['full_matrix'] mass_db[10, ...] = mass_data['Lfin']['full_matrix'] mass_db[11, ...] = mass_data['Rfin']['full_matrix'] mass_db[12, ...] = mass_data['LLfin']['full_matrix'] mass_db[13, ...] = mass_data['RRfin']['full_matrix'] mass_db[14, ...] = mass_data['Cvfin']['full_matrix'] mass_db[15, ...] = mass_data['Lvfin']['full_matrix'] mass_db[16, ...] = mass_data['Rvfin']['full_matrix'] # process stiffness data stiffness_db[0, ...] = sigma*stiff_data['Linboard'] stiffness_db[1, ...] = sigma*stiff_data['Loutboard'] stiffness_db[2, ...] = sigma*stiff_data['Ldihedral'] stiffness_db[3, ...] = sigma*stiff_data['Rinboard'] stiffness_db[4, ...] = sigma*stiff_data['Routboard'] stiffness_db[5, ...] = sigma*stiff_data['Rdihedral'] stiffness_db[6, ...] = sigma*stiff_data['boom'] stiffness_db[7, ...] = sigma*stiff_data['tailL'] stiffness_db[8, ...] = sigma*stiff_data['tailR'] stiffness_db[9, ...] = sigma*stiff_data['Cfin'] stiffness_db[10, ...] = sigma*stiff_data['Lfin'] stiffness_db[11, ...] = sigma*stiff_data['Rfin'] # this matrix is useful when using symmetrical data for the mass, while # your local frames of reference are not. # For example, the right wing has a material "y" axis pointing fore, # while the left points aft due to "x" pointing in opposite directions. rotation_mat = algebra.rotation3d_z(np.pi) # I use this matrix to convert from the canonical material FoR to the one with # structural twist considered rotation_mat_x = algebra.rotation3d_x(-in_structural_twist) we = 0 wn = 0 # SECTION 0R # add the lumped mass of the pods lumped_mass_id = 'centre_pod' for i in range(len(lumped_mass_indices[lumped_mass_id])): lumped_mass_nodes[lumped_mass_indices[lumped_mass_id][i]] = 0 lumped_mass_position[lumped_mass_indices[lumped_mass_id][i]] = np.dot(rotation_mat_x, np.dot(np.eye(3), lumped_mass_position[lumped_mass_indices[lumped_mass_id][i]]) ) # add thrust as applied force # it is necessary to rotate it in order to have a horizontal force, # as "y_materal" points 5 deg above. app_forces[0, 0:3] = thrustC*np.array([0.0, np.cos(in_structural_twist), -np.sin(in_structural_twist)]) thrust_nodes[0] = 0 beam_number[we:we + n_elem_section] = 0 y[wn:wn + n_node_section] = np.linspace(0.0, span_section, n_node_section) structural_twist[we:we + n_elem_section, :] = in_structural_twist for ielem in range(n_elem_section): # connectivities. Order is [0, 2, 1] conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_section] = 3 elem_mass[we:we + n_elem_section] = 3 boundary_conditions[0] = 1 # we: working_element # wn: working_node we += n_elem_section wn += n_node_section # keeping track of the node and element of very end of section to then # map the aero more easily. end_nodesR[0] = wn - 1 end_elementsR[0] = we - 1 # SECTION 1R # add the lumped mass of the pods lumped_mass_id = 'R_inboard_pod' for i in range(len(lumped_mass_indices[lumped_mass_id])): lumped_mass_nodes[lumped_mass_indices[lumped_mass_id][i]] = wn - 1 lumped_mass_position[lumped_mass_indices[lumped_mass_id][i]] = np.dot(rotation_mat_x, np.dot(np.eye(3), lumped_mass_position[lumped_mass_indices[lumped_mass_id][i]]) ) app_forces[wn-1, 0:3] = thrustC*np.array([0.0, np.cos(in_structural_twist), -np.sin(in_structural_twist)]) thrust_nodes[1] = wn - 1 beam_number[we:we + n_elem_section] = 1 y[wn:wn + n_node_section - 1] = y[wn - 1] + np.linspace(0.0, span_section, n_node_section)[1:] structural_twist[we:we + n_elem_section, :] = in_structural_twist for ielem in range(n_elem_section): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_section] = 4 elem_mass[we:we + n_elem_section] = 4 we += n_elem_section wn += n_node_section - 1 end_nodesR[1] = wn - 1 end_elementsR[1] = we - 1 # SECTION 2R # add the lumped mass of the pods lumped_mass_id = 'R_outboard_pod' for i in range(len(lumped_mass_indices[lumped_mass_id])): lumped_mass_nodes[lumped_mass_indices[lumped_mass_id][i]] = wn - 1 lumped_mass_position[lumped_mass_indices[lumped_mass_id][i]] = np.dot(rotation_mat_x, np.dot(np.eye(3), lumped_mass_position[lumped_mass_indices[lumped_mass_id][i]]) ) app_forces[wn-1, 0:3] = thrustC*(1 + differential)*np.array([0.0, np.cos(in_structural_twist), -np.sin(in_structural_twist)]) thrust_nodes[2] = wn - 1 beam_number[we:we + n_elem_section_dihedral] = 2 structural_twist[we:we + n_elem_section_dihedral, :] = in_structural_twist y[wn:wn + n_node_section_dihedral - 1] = y[wn - 1] + np.linspace(0.0, np.cos(dihedral_outer)*span_section, n_node_section_dihedral)[1:] z[wn:wn + n_node_section_dihedral - 1] = z[wn - 1] + np.linspace(0.0, np.sin(dihedral_outer)*span_section, n_node_section_dihedral)[1:] for ielem in range(n_elem_section_dihedral): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_section_dihedral] = 5 elem_mass[we:we + n_elem_section_dihedral] = 5 boundary_conditions[wn + n_node_section_dihedral - 1 - 1] = -1 we += n_elem_section_dihedral wn += n_node_section_dihedral - 1 end_nodesR[2] = wn - 1 end_elementsR[2] = we - 1 # SECTION 0L beam_number[we:we + n_elem_section] = 3 structural_twist[we:we + n_elem_section, :] = -in_structural_twist y[wn:wn + n_node_section - 1] = np.linspace(0.0, -span_section, n_node_section)[1:] for ielem in range(n_elem_section): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = 0 elem_stiffness[we:we + n_elem_section] = 0 elem_mass[we:we + n_elem_section] = 0 we += n_elem_section wn += n_node_section - 1 end_nodesL[0] = wn - 1 end_elementsL[0] = we - 1 # SECTION 1L # add the lumped mass of the pods lumped_mass_id = 'L_inboard_pod' for i in range(len(lumped_mass_indices[lumped_mass_id])): lumped_mass_nodes[lumped_mass_indices[lumped_mass_id][i]] = wn - 1 lumped_mass_position[lumped_mass_indices[lumped_mass_id][i]] = np.dot(rotation_mat_x.T, np.dot(rotation_mat, lumped_mass_position[lumped_mass_indices[lumped_mass_id][i]]) ) lumped_mass_position[lumped_mass_indices[lumped_mass_id][i]][0] *= -1 app_forces[wn-1, 0:3] = thrustC*np.array([0.0, -np.cos(in_structural_twist), -np.sin(in_structural_twist)]) thrust_nodes[3] = wn - 1 beam_number[we:we + n_elem_section] = 4 structural_twist[we:we + n_elem_section, :] = -in_structural_twist y[wn:wn + n_node_section - 1] = y[wn - 1] + np.linspace(0.0, -span_section, n_node_section)[1:] for ielem in range(n_elem_section): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_section] = 1 elem_mass[we:we + n_elem_section] = 1 we += n_elem_section wn += n_node_section - 1 end_nodesL[1] = wn - 1 end_elementsL[1] = we - 1 # SECTION 2L # add the lumped mass of the pods lumped_mass_id = 'L_outboard_pod' for i in range(len(lumped_mass_indices[lumped_mass_id])): lumped_mass_nodes[lumped_mass_indices[lumped_mass_id][i]] = wn - 1 lumped_mass_position[lumped_mass_indices[lumped_mass_id][i]] = np.dot(rotation_mat_x.T, np.dot(rotation_mat, lumped_mass_position[lumped_mass_indices[lumped_mass_id][i]])) lumped_mass_position[lumped_mass_indices[lumped_mass_id][i]][0] *= -1 app_forces[wn-1, 0:3] = thrustC*(1 - differential)*np.array([0.0, -np.cos(in_structural_twist), -np.sin(in_structural_twist)]) thrust_nodes[4] = wn - 1 beam_number[we:we + n_elem_section_dihedral] = 5 structural_twist[we:we + n_elem_section_dihedral, :] = -in_structural_twist y[wn:wn + n_node_section_dihedral - 1] = y[wn - 1] + np.linspace(0.0, -np.cos(dihedral_outer)*span_section, n_node_section_dihedral)[1:] z[wn:wn + n_node_section_dihedral - 1] = z[wn - 1] + np.linspace(0.0, np.sin(dihedral_outer)*span_section, n_node_section_dihedral)[1:] for ielem in range(n_elem_section_dihedral): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_section_dihedral] = 2 elem_mass[we:we + n_elem_section_dihedral] = 2 boundary_conditions[wn + n_node_section_dihedral - 1 - 1] = -1 we += n_elem_section_dihedral wn += n_node_section_dihedral - 1 end_nodesL[2] = wn - 1 end_elementsL[2] = we - 1 # centre tail beam_number[we:we + n_elem_centre_tail] = 6 tail_beam_numbersC[0] = 6 x[wn:wn + n_node_centre_tail - 1] = np.linspace(0.0, length_centre_tail, n_node_centre_tail)[1:] for ielem in range(n_elem_centre_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [0.0, 1.0, 0.0] conn[we, 0] = 0 elem_stiffness[we:we + n_elem_centre_tail] = 6 elem_mass[we:we + n_elem_centre_tail] = 6 we += n_elem_centre_tail wn += n_node_centre_tail - 1 end_of_centre_tail_node = wn - 1 end_of_centre_tail_elem = we if vertical_tail: beam_number[we:we + n_elem_tail] = 7 tail_beam_numbersC[1] = 7 x[wn:wn + n_node_tail - 1] = x[wn - 1] y[wn:wn + n_node_tail - 1] = y[wn - 1] z[wn:wn + n_node_tail - 1] = z[wn - 1] + np.linspace(0.0, span_ctail_R, n_node_tail)[1:] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_tail] = 8 elem_mass[we:we + n_elem_tail] = 8 boundary_conditions[wn + n_node_tail - 1 - 1] = -1 end_tip_tail_nodeC[0] = wn + n_node_tail - 1 - 1 end_tip_tail_elemC[0] = we + n_elem_tail - 1 we += n_elem_tail wn += n_node_tail - 1 beam_number[we:we + n_elem_tail] = 8 tail_beam_numbersC[2] = 8 x[wn:wn + n_node_tail - 1] = x[end_of_centre_tail_node] y[wn:wn + n_node_tail - 1] = y[wn - 1] z[wn:wn + n_node_tail - 1] = z[end_of_centre_tail_node] + np.linspace(0.0, -span_ctail_L, n_node_tail)[1:] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = end_of_centre_tail_node elem_stiffness[we:we + n_elem_tail] = 7 elem_mass[we:we + n_elem_tail] = 7 boundary_conditions[wn + n_node_tail - 1 -1] = -1 end_tip_tail_nodeC[1] = wn + n_node_tail - 1 - 1 end_tip_tail_elemC[1] = we + n_elem_tail - 1 we += n_elem_tail wn += n_node_tail - 1 else: beam_number[we:we + n_elem_tail] = 7 tail_beam_numbersC[1] = 7 x[wn:wn + n_node_tail - 1] = x[wn - 1] y[wn:wn + n_node_tail - 1] = y[wn - 1] + np.linspace(0.0, span_ctail_R, n_node_tail)[1:] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_tail] = 8 elem_mass[we:we + n_elem_tail] = 8 boundary_conditions[wn + n_node_tail - 1 - 1] = -1 end_tip_tail_nodeC[0] = wn + n_node_tail - 1 - 1 end_tip_tail_elemC[0] = we + n_elem_tail - 1 we += n_elem_tail wn += n_node_tail - 1 beam_number[we:we + n_elem_tail] = 8 tail_beam_numbersC[2] = 8 x[wn:wn + n_node_tail - 1] = x[end_of_centre_tail_node] y[wn:wn + n_node_tail - 1] = y[end_of_centre_tail_node] + np.linspace(0.0, -span_ctail_L, n_node_tail)[1:] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = end_of_centre_tail_node elem_stiffness[we:we + n_elem_tail] = 7 elem_mass[we:we + n_elem_tail] = 7 boundary_conditions[wn + n_node_tail - 1 -1] = -1 end_tip_tail_nodeC[1] = wn + n_node_tail - 1 - 1 end_tip_tail_elemC[1] = we + n_elem_tail - 1 we += n_elem_tail wn += n_node_tail - 1 # outer tail 0R beam_number[we:we + n_elem_outer_tail] = 9 tail_beam_numbersR[0,0] = 9 x[wn:wn + n_node_outer_tail - 1] = np.linspace(0.0, length_outer_tail, n_node_outer_tail)[1:] y[wn:wn + n_node_outer_tail - 1] = y[end_nodesR[0]] for ielem in range(n_elem_outer_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [0.0, 1.0, 0.0] conn[we, 0] = end_nodesR[0] elem_stiffness[we:we + n_elem_outer_tail] = 6 elem_mass[we:we + n_elem_outer_tail] = 6 we += n_elem_outer_tail wn += n_node_outer_tail - 1 end_tails_nodesR[0] = wn - 1 end_tails_elementsR[0] = we - 1 beam_number[we:we + n_elem_tail] = 10 tail_beam_numbersR[0,1] = 10 x[wn:wn + n_node_tail - 1] = x[end_tails_nodesR[0]] y[wn:wn + n_node_tail - 1] = y[end_tails_nodesR[0]] + np.linspace(0.0, span_tail, n_node_tail)[1:] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_tail] = 8 elem_mass[we:we + n_elem_tail] = 8 boundary_conditions[wn + n_node_tail - 1 -1] = -1 we += n_elem_tail wn += n_node_tail - 1 beam_number[we:we + n_elem_tail] = 11 tail_beam_numbersR[0,2] = 11 x[wn:wn + n_node_tail - 1] = x[end_tails_nodesR[0]] y[wn:wn + n_node_tail - 1] = y[end_tails_nodesR[0]] + np.linspace(0.0, -span_tail, n_node_tail)[1:] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = end_tails_nodesR[0] elem_stiffness[we:we + n_elem_tail] = 7 elem_mass[we:we + n_elem_tail] = 7 boundary_conditions[wn + n_node_tail - 1 -1] = -1 we += n_elem_tail wn += n_node_tail - 1 # outer tail 1R beam_number[we:we + n_elem_outer_tail] = 12 tail_beam_numbersR[1,0] = 12 x[wn:wn + n_node_outer_tail - 1] = np.linspace(0.0, length_outer_tail, n_node_outer_tail)[1:] y[wn:wn + n_node_outer_tail - 1] = y[end_nodesR[1]] for ielem in range(n_elem_outer_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [0.0, 1.0, 0.0] conn[we, 0] = end_nodesR[1] elem_stiffness[we:we + n_elem_outer_tail] = 6 elem_mass[we:we + n_elem_outer_tail] = 6 we += n_elem_outer_tail wn += n_node_outer_tail - 1 end_tails_nodesR[1] = wn - 1 end_tails_elementsR[1] = we - 1 beam_number[we:we + n_elem_tail] = 13 tail_beam_numbersR[1,1] = 13 x[wn:wn + n_node_tail - 1] = x[end_tails_nodesR[1]] y[wn:wn + n_node_tail - 1] = y[end_tails_nodesR[1]] + np.linspace(0.0, span_tail, n_node_tail)[1:] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_tail] = 8 elem_mass[we:we + n_elem_tail] = 8 boundary_conditions[wn + n_node_tail - 1 -1] = -1 we += n_elem_tail wn += n_node_tail - 1 beam_number[we:we + n_elem_tail] = 14 tail_beam_numbersR[1,2] = 14 x[wn:wn + n_node_tail - 1] = x[end_tails_nodesR[1]] y[wn:wn + n_node_tail - 1] = y[end_tails_nodesR[1]] + np.linspace(0.0, -span_tail, n_node_tail)[1:] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = end_tails_nodesR[1] elem_stiffness[we:we + n_elem_tail] = 7 elem_mass[we:we + n_elem_tail] = 7 boundary_conditions[wn + n_node_tail - 1 -1] = -1 we += n_elem_tail wn += n_node_tail - 1 # outer tail 0L beam_number[we:we + n_elem_outer_tail] = 15 tail_beam_numbersL[0,0] = 15 x[wn:wn + n_node_outer_tail - 1] = np.linspace(0.0, length_outer_tail, n_node_outer_tail)[1:] y[wn:wn + n_node_outer_tail - 1] = y[end_nodesL[0]] for ielem in range(n_elem_outer_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [0.0, 1.0, 0.0] conn[we, 0] = end_nodesL[0] elem_stiffness[we:we + n_elem_outer_tail] = 6 elem_mass[we:we + n_elem_outer_tail] = 6 we += n_elem_outer_tail wn += n_node_outer_tail - 1 end_tails_nodesL[0] = wn - 1 end_tails_elementsL[0] = we - 1 beam_number[we:we + n_elem_tail] = 16 tail_beam_numbersL[0,1] = 16 x[wn:wn + n_node_tail - 1] = x[end_tails_nodesL[0]] y[wn:wn + n_node_tail - 1] = y[end_tails_nodesL[0]] + np.linspace(0.0, span_tail, n_node_tail)[1:] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_tails_nodesL[0] elem_stiffness[we:we + n_elem_tail] = 8 elem_mass[we:we + n_elem_tail] = 8 boundary_conditions[wn + n_node_tail - 1 - 1] = -1 we += n_elem_tail wn += n_node_tail - 1 beam_number[we:we + n_elem_tail] = 17 tail_beam_numbersL[0,2] = 17 x[wn:wn + n_node_tail - 1] = x[end_tails_nodesL[0]] y[wn:wn + n_node_tail - 1] = y[end_tails_nodesL[0]] + np.linspace(0.0, -span_tail, n_node_tail)[1:] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = end_tails_nodesL[0] elem_stiffness[we:we + n_elem_tail] = 7 elem_mass[we:we + n_elem_tail] = 7 boundary_conditions[wn + n_node_tail - 1 - 1] = -1 we += n_elem_tail wn += n_node_tail - 1 # outer tail 1L beam_number[we:we + n_elem_outer_tail] = 18 tail_beam_numbersL[1,0] = 18 x[wn:wn + n_node_outer_tail - 1] = np.linspace(0.0, length_outer_tail, n_node_outer_tail)[1:] y[wn:wn + n_node_outer_tail - 1] = y[end_nodesL[1]] for ielem in range(n_elem_outer_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [0.0, 1.0, 0.0] conn[we, 0] = end_nodesL[1] elem_stiffness[we:we + n_elem_outer_tail] = 6 elem_mass[we:we + n_elem_outer_tail] = 6 we += n_elem_outer_tail wn += n_node_outer_tail - 1 end_tails_nodesL[1] = wn - 1 end_tails_elementsL[1] = we - 1 beam_number[we:we + n_elem_tail] = 19 tail_beam_numbersL[1,1] = 19 x[wn:wn + n_node_tail - 1] = x[end_tails_nodesL[1]] y[wn:wn + n_node_tail - 1] = y[end_tails_nodesL[1]] + np.linspace(0.0, span_tail, n_node_tail)[1:] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_tail] = 8 elem_mass[we:we + n_elem_tail] = 8 boundary_conditions[wn + n_node_tail - 1 -1] = -1 we += n_elem_tail wn += n_node_tail - 1 beam_number[we:we + n_elem_tail] = 20 tail_beam_numbersL[1,2] = 20 x[wn:wn + n_node_tail - 1] = x[end_tails_nodesL[1]] y[wn:wn + n_node_tail - 1] = y[end_tails_nodesL[1]] + np.linspace(0.0, -span_tail, n_node_tail)[1:] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = end_tails_nodesL[1] elem_stiffness[we:we + n_elem_tail] = 7 elem_mass[we:we + n_elem_tail] = 7 boundary_conditions[wn + n_node_tail - 1 -1] = -1 we += n_elem_tail wn += n_node_tail - 1 # vertical fins (pods) # centre one beam_number[we:we + n_elem_fin] = 21 fin_beam_numberC = 21 x[wn:wn + n_node_fin - 1] = x[0] y[wn:wn + n_node_fin - 1] = y[0] z[wn:wn + n_node_fin - 1] = z[0] + np.linspace(0.0, -span_fin, n_node_fin)[1:] for ielem in range(n_elem_fin): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = 0 elem_stiffness[we:we + n_elem_fin] = 9 elem_mass[we:we + n_elem_fin] = 9 boundary_conditions[wn + n_node_fin - 1 - 1] = -1 we += n_elem_fin wn += n_node_fin - 1 # left one beam_number[we:we + n_elem_fin] = 22 fin_beam_numberL = 22 x[wn:wn + n_node_fin - 1] = x[end_nodesL[0]] y[wn:wn + n_node_fin - 1] = y[end_nodesL[0]] z[wn:wn + n_node_fin - 1] = z[end_nodesL[0]] + np.linspace(0.0, -span_fin, n_node_fin)[1:] for ielem in range(n_elem_fin): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_nodesL[0] elem_stiffness[we:we + n_elem_fin] = 10 elem_mass[we:we + n_elem_fin] = 10 boundary_conditions[wn + n_node_fin - 1 - 1] = -1 we += n_elem_fin wn += n_node_fin - 1 # right one beam_number[we:we + n_elem_fin] = 23 fin_beam_numberR = 23 x[wn:wn + n_node_fin - 1] = x[end_nodesR[0]] y[wn:wn + n_node_fin - 1] = y[end_nodesR[0]] z[wn:wn + n_node_fin - 1] = z[end_nodesR[0]] + np.linspace(0.0, -span_fin, n_node_fin)[1:] for ielem in range(n_elem_fin): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_nodesR[0] elem_stiffness[we:we + n_elem_fin] = 11 elem_mass[we:we + n_elem_fin] = 11 boundary_conditions[wn + n_node_fin - 1 - 1] = -1 we += n_elem_fin wn += n_node_fin - 1 # left outer one beam_number[we:we + n_elem_fin] = 24 fin_beam_numberLL = 24 x[wn:wn + n_node_fin - 1] = x[end_nodesL[1]] y[wn:wn + n_node_fin - 1] = y[end_nodesL[1]] z[wn:wn + n_node_fin - 1] = z[end_nodesL[1]] + np.linspace(0.0, -span_fin, n_node_fin)[1:] for ielem in range(n_elem_fin): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_nodesL[1] elem_stiffness[we:we + n_elem_fin] = 10 elem_mass[we:we + n_elem_fin] = 12 boundary_conditions[wn + n_node_fin - 1 - 1] = -1 we += n_elem_fin wn += n_node_fin - 1 # right outer one beam_number[we:we + n_elem_fin] = 25 fin_beam_numberRR = 25 x[wn:wn + n_node_fin - 1] = x[end_nodesR[1]] y[wn:wn + n_node_fin - 1] = y[end_nodesR[1]] z[wn:wn + n_node_fin - 1] = z[end_nodesR[1]] + np.linspace(0.0, -span_fin, n_node_fin)[1:] for ielem in range(n_elem_fin): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_nodesR[1] elem_stiffness[we:we + n_elem_fin] = 11 elem_mass[we:we + n_elem_fin] = 13 boundary_conditions[wn + n_node_fin - 1 - 1] = -1 we += n_elem_fin wn += n_node_fin - 1 # vertical fins # centre one beam_number[we:we + n_elem_fin] = 26 vfin_beam_numberC = 26 x[wn:wn + n_node_fin - 1] = x[end_of_centre_tail_node] y[wn:wn + n_node_fin - 1] = y[end_of_centre_tail_node] z[wn:wn + n_node_fin - 1] = z[end_of_centre_tail_node] + np.linspace(0.0, -span_vfin, n_node_fin)[1:] for ielem in range(n_elem_fin): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_of_centre_tail_node elem_stiffness[we:we + n_elem_fin] = 9 elem_mass[we:we + n_elem_fin] = 14 boundary_conditions[wn + n_node_fin - 1 - 1] = -1 we += n_elem_fin wn += n_node_fin - 1 # left one beam_number[we:we + n_elem_fin] = 27 vfin_beam_numberL = 27 x[wn:wn + n_node_fin - 1] = x[end_tails_nodesL[0]] y[wn:wn + n_node_fin - 1] = y[end_tails_nodesL[0]] z[wn:wn + n_node_fin - 1] = z[end_tails_nodesL[0]] + np.linspace(0.0, -span_vfin, n_node_fin)[1:] for ielem in range(n_elem_fin): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_tails_nodesL[0] elem_stiffness[we:we + n_elem_fin] = 10 elem_mass[we:we + n_elem_fin] = 15 boundary_conditions[wn + n_node_fin - 1 - 1] = -1 we += n_elem_fin wn += n_node_fin - 1 # right one beam_number[we:we + n_elem_fin] = 28 vfin_beam_numberR = 28 x[wn:wn + n_node_fin - 1] = x[end_tails_nodesR[0]] y[wn:wn + n_node_fin - 1] = y[end_tails_nodesR[0]] z[wn:wn + n_node_fin - 1] = z[end_tails_nodesR[0]] + np.linspace(0.0, -span_vfin, n_node_fin)[1:] for ielem in range(n_elem_fin): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + np.array([0, 2, 1])) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_tails_nodesR[0] elem_stiffness[we:we + n_elem_fin] = 11 elem_mass[we:we + n_elem_fin] = 16 boundary_conditions[wn + n_node_fin - 1 - 1] = -1 we += n_elem_fin wn += n_node_fin - 1 # this output to the HDF5 file that is read by 'BeamLoader'. # if you want to see what's inside, check HDFView with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=n_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=n_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=n_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness_db) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass_db) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) lumped_mass_nodes_handle = h5file.create_dataset( 'lumped_mass_nodes', data=lumped_mass_nodes) lumped_mass_handle = h5file.create_dataset( 'lumped_mass', data=lumped_mass) lumped_mass_inertia_handle = h5file.create_dataset( 'lumped_mass_inertia', data=lumped_mass_inertia) lumped_mass_position_handle = h5file.create_dataset( 'lumped_mass_position', data=lumped_mass_position) def read_aero_data(filename=route + '/inputs/aero_properties.xlsx'): import pandas as pd xl = pd.ExcelFile(filename) sheets = {sheet_name: xl.parse(sheet_name, header=0, index_col=0) for sheet_name in xl.sheet_names} aero_data = dict() for sheet, val in sheets.items(): aero_data[sheet] = dict() for item in val['value'].items(): aero_data[sheet][item[0]] = item[1] return aero_data def generate_aero_file(): global x, y, z global fin_beam_numberC, fin_beam_numberL, fin_beam_numberR global vfin_beam_numberC, vfin_beam_numberL, vfin_beam_numberR global fin_beam_numberLL, fin_beam_numberRR aero_data = read_aero_data() # control surfaces n_control_surfaces = 3 control_surface = np.zeros((n_elem, n_node_elem), dtype=int) - 1 control_surface_type = np.zeros((n_control_surfaces, ), dtype=int) control_surface_deflection = np.zeros((n_control_surfaces, )) control_surface_chord = np.zeros((n_control_surfaces, ), dtype=int) control_surface_hinge_coord = np.zeros((n_control_surfaces, ), dtype=float) # control surface type is 0 if static, 1 if dynamic control_surface_type[0] = 0 control_surface_deflection[0] = cs_deflection # chord is given as number of panels, not length, so it has to be integer control_surface_chord[0] = m_tail # location of the hinge wrt e.axis when surface_chord == m_surface control_surface_hinge_coord[0] = 0 control_surface_type[1] = 0 control_surface_deflection[1] = aileron_deflection control_surface_chord[1] = int(m*0.25) control_surface_hinge_coord[1] = 0 control_surface_type[2] = 0 control_surface_deflection[2] = -aileron_deflection control_surface_chord[2] = int(m*0.25) control_surface_hinge_coord[2] = 0 # right wing (surface 0, beams 0, 1, 2, 3) type = 'inboard' main_chord = aero_data[type]['chord'] initial_node = 0 final_node = end_nodesR[-1] initial_elem = 0 final_elem = end_elementsR[-1] i_surf = 0 airfoil_distribution[initial_elem: final_elem + 1, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] surface_distribution[initial_elem: final_elem + 1] = i_surf surface_m[i_surf] = m aero_node[initial_node:final_node + 1] = True node_counter = 0 for i_elem in range(initial_elem, final_elem + 1): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 for i_elem in range(end_elementsR[1] + 1, end_elementsR[2] + 1): if i_elem == 0 or i_elem == end_elementsR[2]: continue control_surface[i_elem, :] = 1 # left wing (surface 1, beams 4, 5, 6, 7) initial_node = end_nodesR[-1] + 1 final_node = end_nodesL[-1] initial_elem = end_elementsR[-1] + 1 final_elem = end_elementsL[-1] i_surf += 1 airfoil_distribution[initial_elem: final_elem + 1, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] surface_distribution[initial_elem: final_elem + 1] = i_surf surface_m[i_surf] = m aero_node[initial_node:final_node + 1] = True node_counter = 0 for i_elem in range(initial_elem, final_elem + 1): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 for i_elem in range(end_elementsL[1] + 1, end_elementsL[2] + 1): if i_elem == 0 or i_elem == end_elementsL[2]: continue control_surface[i_elem, :] = 2 # centre tail type = 'Ctail' ctail_chord = aero_data[type]['chord'] ctail_m = m_tail elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == tail_beam_numbersC[1]] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = ctail_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == tail_beam_numbersC[2]] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = ctail_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 # 0R tail type = '0Rtail' rtail_chord = aero_data[type]['chord'] tail_m = max(3, int(m*rtail_chord/main_chord)) tail_m = m_tail elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == tail_beam_numbersR[0,1]] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = tail_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 control_surface[i_elem, i_local_node] = 0 elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == tail_beam_numbersR[0,2]] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = tail_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 control_surface[i_elem, i_local_node] = 0 # 1R tail type = '0Rtail' elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == tail_beam_numbersR[1,1]] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = tail_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 control_surface[i_elem, i_local_node] = 0 elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == tail_beam_numbersR[1,2]] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = tail_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 control_surface[i_elem, i_local_node] = 0 # 0L tail type = '0Ltail' elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == tail_beam_numbersL[0,1]] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = tail_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 control_surface[i_elem, i_local_node] = 0 elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == tail_beam_numbersL[0,2]] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = tail_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 control_surface[i_elem, i_local_node] = 0 # 1L tail type = '0Ltail' elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == tail_beam_numbersL[1,1]] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = tail_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 control_surface[i_elem, i_local_node] = 0 elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == tail_beam_numbersL[1,2]] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = tail_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 control_surface[i_elem, i_local_node] = 0 type = 'Cfin' cfin_chord = aero_data[type]['chord'] cfin_m = m_fin elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == fin_beam_numberC] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = cfin_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 type = 'Lfin' lfin_chord = aero_data[type]['chord'] fin_m = m_fin elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == fin_beam_numberL] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = fin_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 type = 'Rfin' elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == fin_beam_numberR] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = fin_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 type = 'LLfin' lfin_chord = aero_data[type]['chord'] fin_m = m_fin elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == fin_beam_numberLL] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = fin_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 type = 'RRfin' elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == fin_beam_numberRR] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = fin_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 type = 'Cvfin' cfin_chord = aero_data[type]['chord'] cfin_m = m_fin elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == vfin_beam_numberC] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = cfin_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 type = 'Lvfin' lfin_chord = aero_data[type]['chord'] fin_m = m_fin elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == vfin_beam_numberL] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = fin_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 type = 'Rvfin' elements = np.linspace(0, n_elem - 1, n_elem, dtype=int)[beam_number == vfin_beam_numberR] i_surf += 1 for i_elem in elements: for i_node in range(n_node_elem): airfoil_distribution[i_elem, :] = aero_data['airfoil_indices'][aero_data[type]['airfoil']] aero_node[conn[i_elem, i_node]] = True surface_distribution[elements] = i_surf surface_m[i_surf] = fin_m node_counter = 0 for i_elem in elements: for i_local_node in [0, 1, 2]: if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = aero_data[type]['chord'] elastic_axis[i_elem, i_local_node] = aero_data[type]['elastic_axis'] twist[i_elem, i_local_node] = -aero_data[type]['twist']*np.pi/180 with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil emx07_main = airfoils_group.create_dataset('0', data=load_airfoil(route + '/inputs/EMX-07_camber.txt')) flat_tail = airfoils_group.create_dataset('1', data=np.column_stack( generate_naca_camber(P=0, M=0))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input .attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) control_surface_input = h5file.create_dataset('control_surface', data=control_surface) control_surface_deflection_input = h5file.create_dataset('control_surface_deflection', data=control_surface_deflection) control_surface_chord_input = h5file.create_dataset('control_surface_chord', data=control_surface_chord) control_surface_types_input = h5file.create_dataset('control_surface_type', data=control_surface_type) control_surface_hinge_coord_input = h5file.create_dataset('control_surface_hinge_coord', data=control_surface_hinge_coord) def load_airfoil(filename): data = np.loadtxt(filename, skiprows=1) return data def generate_naca_camber(M=0, P=0): """ https://en.wikipedia.org/wiki/NACA_airfoil """ mm = M*1e-2 p = P*1e-1 def naca(x, mm, p): if x < 1e-6: return 0.0 elif x < p: return mm/(p*p)*(2*p*x - x*x) elif x > p and x < 1+1e-6: return mm/((1-p)*(1-p))*(1 - 2*p + 2*p*x - x*x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, mm, p) for x in x_vec]) return x_vec, y_vec def generate_solver_file(): """ Main file generator. This text file contains the location of the case files, the case name and all the solvers to be run together with their settings. """ file_name = route + '/' + case_name + '.sharpy' # the extension # of the file is # not important settings = dict() settings['SHARPy'] = {'case': case_name, 'route': route, 'flow': flow, 'write_screen': 'on', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} settings['BeamLoader'] = {'unsteady': 'on', # it's ok to leave it # 'on' for static # initial orientation of the aircraft, # this is where the trim conditions come 'orientation': algebra.euler2quat(np.array([roll, alpha, beta]))} settings['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'mstar': mstar, # freestream_dir only used for wake init 'freestream_dir': ['1', '0', '0']} settings['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 350, 'num_load_steps': 1, 'delta_curved': 1e-1, 'min_delta': tolerance, 'gravity_on': gravity, 'gravity': gravity_value} settings['StaticUvlm2'] = {'print_info': 'on', 'horseshoe': horseshoe, 'num_cores': 1, 'n_rollup': 0, 'rollup_dt': dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho} settings['StaticCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': settings['NonLinearStatic'], 'aero_solver': 'StaticUvlm', 'aero_solver_settings': settings['StaticUvlm2'], 'max_iter': 100, 'n_load_steps': n_step, 'tolerance': fsi_tolerance, 'relaxation_factor': static_relaxation_factor} settings['StaticUvlm'] = {'print_info': 'on', 'horseshoe': horseshoe, 'num_cores': 1, 'n_rollup': 0, 'rollup_dt': dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho} # this trim solver is much slower, but supports lateral trim. # it is based on scipy.optimize settings['Trim'] = {'solver': 'StaticCoupled', 'solver_settings': settings['StaticCoupled'], 'initial_alpha': alpha, 'initial_beta': beta, 'initial_roll': roll, 'cs_indices': 0, 'initial_cs_deflection': cs_deflection, 'initial_thrust': [], 'thrust_nodes': [], 'refine_solution': 'on', 'special_case': {'case_name': 'differential_thrust', 'initial_base_thrust': thrustC, 'initial_differential_parameter': differential, 'base_thrust_nodes': [thrust_nodes[0]], 'negative_thrust_nodes': [thrust_nodes[2]], 'positive_thrust_nodes': [thrust_nodes[1]]}} settings['NonLinearDynamicCoupledStep'] = {'print_info': 'off', 'max_iterations': 500, 'delta_curved': 1e-1, 'min_delta': tolerance, # newmark damping parameter. # it affects higher freq # mostly. 'newmark_damp': 1e-3, 'gravity_on': gravity, 'gravity': gravity_value, 'num_steps': n_tstep, 'balancing': 'off', 'dt': dt, 'initial_velocity_direction': np.array([-1.0, 0.0, 0.0]), 'initial_velocity': u_inf} settings['StaticTrim'] = {'solver': 'StaticCoupled', 'solver_settings': settings['StaticCoupled'], 'initial_alpha': alpha, 'initial_deflection': cs_deflection, 'initial_thrust': thrustC, 'thrust_nodes': thrust_nodes} settings['StepUvlm'] = {'print_info': 'off', 'num_cores': 6, 'convection_scheme': 2, 'gamma_dot_filtering': 6, # this is where the gust is input. 'velocity_field_generator': 'GustVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0], 'gust_shape': gust_shape, 'gust_parameters': { 'gust_length': gust_length, 'gust_intensity': gust_intensity * u_inf}, 'offset': -space_offset}, 'rho': rho, 'n_time_steps': n_tstep, 'dt': dt} settings['DynamicCoupled'] = {'structural_solver': 'NonLinearDynamicCoupledStep', 'structural_solver_settings': settings['NonLinearDynamicCoupledStep'], 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'fsi_substeps': 100, 'fsi_tolerance': fsi_tolerance, 'relaxation_factor': initial_relaxation_factor, 'final_relaxation_factor': final_relaxation_factor, 'minimum_steps': 1, 'relaxation_steps': relaxation_steps, 'n_time_steps': n_tstep, 'dt': dt, 'include_unsteady_force_contribution': 'on', 'cleanup_previous_solution': 'on', # the postprocessors in this list are # called every time step. Some of them # are the same you call in static simulations # and you wrote at the beginning of the # file 'postprocessors': [ # output some variables by text 'WriteVariablesTime', # remove old timesteps # already output to save RAM 'Cleanup', # calculate internal # beam loads and strains 'BeamLoads', 'BeamPlot', 'AerogridPlot', # saves a copy of self.data # (the state of the simulation) # to restart later if needed. # careful, because if you don't # add Cleanup and don't pay attention # to the 'frequency' and # 'keep' parameters, you might # fill you HD for long simulations 'CreateSnapshot', ], 'postprocessors_settings': {'BeamLoads': {}, # you need to specify the dict even if empty 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}, 'AerogridPlot': { 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, 'CreateSnapshot': {# every how many tsteps if should create a snapshot 'frequency': 100, # how many old snapshots should it keep? # the rest will be discarded. 'keep': 5}, 'Cleanup': {# keep only the last 2000 tsteps. # it is good to keep a relatively large number # in order to have a good noise filter # for gamma_dot 'remaining_steps': 2000 }, 'WriteVariablesTime': { # output a text file with # quat: orientation of the aircraft in quaternion # for_pos: FoR position in inertial FoR # for_vel: the derivative of the previous # for_acc: the derivative of the previous 'FoR_variables': ['quat', 'for_pos', 'for_vel', 'for_acc'], 'cleanup_old_solution': 'on', 'delimiter': ', ', } } } settings['Modal'] = {'print_info': 'on', 'use_undamped_modes': 'on', 'NumLambda': 100, 'write_modes_vtk': 'on', 'print_matrices': 'on', 'continuous_eigenvalues': 'off', 'dt': dt, 'plot_eigenvalues': 'on'} settings['BeamPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on'} settings['AerogridPlot'] = {'include_rbm': 'on', 'include_forward_motion': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0, 'u_inf': u_inf, 'dt': dt} settings['BeamLoads'] = dict() import configobj config = configobj.ConfigObj() config.filename = file_name for k, v in settings.items(): config[k] = v config.write() clean_test_files() generate_fem() generate_aero_file() generate_solver_file() ================================================ FILE: sharpy/cases/coupled/X-HALE/inputs/EMX-07_camber.txt ================================================ 0.0 0.0 0.3527358E-06 0.5961561E-03 0.2736395E-02 0.1136788E-02 0.1092258E-01 0.4241672E-02 0.2445874E-01 0.7896996E-02 0.4321506E-01 0.1164179E-01 0.6697159E-01 0.1542114E-01 0.9546839E-01 0.1902119E-01 0.1284056 0.2208612E-01 0.1654034 0.2422109E-01 0.2060818 0.2527606E-01 0.2499709 0.2526599E-01 0.2966008 0.2410581E-01 0.3454613 0.2209060E-01 0.3960123 0.1946039E-01 0.4477137 0.1645021E-01 0.4999755 0.1325003E-01 0.5522375 0.1003985E-01 0.6039397 0.6979703E-02 0.6544919 0.4224549E-02 0.7033541 0.1969342E-02 0.7499865 0.3292383E-03 0.7938786 -0.8407012E-03 0.8345606 -0.1650622E-02 0.8715624 -0.2095578E-02 0.9045041 -0.2160548E-02 0.9330056 -0.1880469E-02 0.9567668 -0.1405372E-02 0.9755279 -0.8752758E-03 0.9890686 -0.4201971E-03 0.9972590 -0.1101422E-03 0.9999992 0.5238689E-09 1.0 0.0 ================================================ FILE: sharpy/cases/coupled/__init__.py ================================================ ================================================ FILE: sharpy/cases/coupled/hinged_controlled_wing/__init__.py ================================================ ================================================ FILE: sharpy/cases/coupled/hinged_controlled_wing/generate_hinged_controlled_wing.py ================================================ import h5py as h5 import numpy as np import configparser import os import sharpy.utils.algebra as algebra import sharpy.utils.generate_cases as gc case_name = 'hinged_controlled_wing' route = os.path.dirname(os.path.realpath(__file__)) + '/' # m = 16 gives flutter with 165 m_main = 4 amplitude = 5*np.pi/180 period = 3 dt_factor = 1. n_structural_substeps = 1 # flight conditions u_inf = 10 rho = 1.225 alpha = 0.5 beta = 0 c_ref = 1 b_ref = 16 sweep = 0*np.pi/180. sigma = 1 wake_length = 10 # chords alpha_rad = alpha*np.pi/180 pitch_file = route + 'pitch.csv' gains = -np.array([0.9, 6.0, 0.75]) # main geometry data main_span = 10 main_chord = 2. main_ea = 0. main_cg = 0.3 main_sigma = 1 main_airfoil_P = 0 main_airfoil_M = 0 n_surfaces = 1 dt = main_chord/m_main/u_inf*dt_factor num_steps = int(10./dt) alpha_hist = np.linspace(0, num_steps*dt, num_steps) alpha_hist = amplitude*np.sin(2.0*np.pi*alpha_hist/period) np.savetxt(pitch_file, alpha_hist) # discretisation data num_elem_main = 5 num_node_elem = 3 num_elem = num_elem_main num_node_main = num_elem_main*(num_node_elem - 1) + 1 num_node = num_node_main def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) dyn_file_name = route + '/' + case_name + '.dyn.h5' if os.path.isfile(dyn_file_name): os.remove(dyn_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def generate_dyn_file(): global dt global num_steps global route global case_name global num_elem global num_node_elem global num_node global amplitude global period dynamic_forces_time = None with_dynamic_forces = False with_forced_vel = False if with_dynamic_forces: f1 = 100 dynamic_forces = np.zeros((num_node, 6)) app_node = [int(num_node_main - 1), int(num_node_main)] dynamic_forces[app_node, 2] = f1 force_time = np.zeros((num_steps, )) limit = round(0.05/dt) force_time[50:61] = 1 dynamic_forces_time = np.zeros((num_steps, num_node, 6)) for it in range(num_steps): dynamic_forces_time[it, :, :] = force_time[it]*dynamic_forces forced_for_vel = None if with_forced_vel: forced_for_vel = np.zeros((num_steps, 6)) forced_for_acc = np.zeros((num_steps, 6)) for it in range(num_steps): # if dt*it < period: forced_for_vel[it, 2] = 2*np.pi/period*amplitude*np.sin(2*np.pi*dt*it/period) forced_for_acc[it, 2] = (2*np.pi/period)**2*amplitude*np.cos(2*np.pi*dt*it/period) # forced_for_vel[it, 2] = 2*np.pi/period*np.pi/180*amplitude*np.cos(2*np.pi*dt*it/period) with h5.File(route + '/' + case_name + '.dyn.h5', 'a') as h5file: if with_dynamic_forces: h5file.create_dataset( 'dynamic_forces', data=dynamic_forces_time) if with_forced_vel: h5file.create_dataset( 'for_vel', data=forced_for_vel) h5file.create_dataset( 'for_acc', data=forced_for_acc) h5file.create_dataset( 'num_steps', data=num_steps) def generate_fem_file(): # placeholders # coordinates global x, y, z global sigma x = np.zeros((num_node, )) y = np.zeros((num_node, )) z = np.zeros((num_node, )) # struct twist structural_twist = np.zeros((num_elem, 3)) # beam number beam_number = np.zeros((num_elem, ), dtype=int) # frame of reference delta frame_of_reference_delta = np.zeros((num_elem, num_node_elem, 3)) # connectivities conn = np.zeros((num_elem, num_node_elem), dtype=int) # stiffness num_stiffness = 1 ea = 1e5 ga = 1e5 gj = 0.987581e6 eiy = 9.77221e6 eiz = 9.77221e8 base_stiffness = sigma*np.diag([ea, ga, ga, gj, eiy, eiz]) stiffness = np.zeros((num_stiffness, 6, 6)) stiffness[0, :, :] = main_sigma*base_stiffness elem_stiffness = np.zeros((num_elem,), dtype=int) # mass num_mass = 1 m_base = 35.71 j_base = 8.64 import sharpy.utils.algebra as algebra # m_chi_cg = algebra.skew(m_base*np.array([0., -(main_ea - main_cg), 0.])) m_chi_cg = algebra.skew(m_base*np.array([0., (main_ea - main_cg), 0.])) base_mass = np.diag([m_base, m_base, m_base, j_base, 0.1*j_base, 0.1*j_base]) base_mass[0:3, 3:6] = -m_chi_cg base_mass[3:6, 0:3] = m_chi_cg mass = np.zeros((num_mass, 6, 6)) mass[0, :, :] = base_mass elem_mass = np.zeros((num_elem,), dtype=int) # boundary conditions boundary_conditions = np.zeros((num_node, ), dtype=int) boundary_conditions[0] = 1 # applied forces # n_app_forces = 2 # node_app_forces = np.zeros((n_app_forces,), dtype=int) app_forces = np.zeros((num_node, 6)) spacing_param = 4 # right wing (beam 0) -------------------------------------------------------------- working_elem = 0 working_node = 0 beam_number[working_elem:working_elem + num_elem_main] = 0 domain = np.linspace(0, 1.0, num_node_main) # 16 - (np.geomspace(20, 4, 10) - 4) x[working_node:working_node + num_node_main] = np.sin(sweep)*(main_span - (np.geomspace(main_span + spacing_param, 0 + spacing_param, num_node_main) - spacing_param)) y[working_node:working_node + num_node_main] = np.abs(np.cos(sweep)*(main_span - (np.geomspace(main_span + spacing_param, 0 + spacing_param, num_node_main) - spacing_param))) y[0] = 0 # y[working_node:working_node + num_node_main] = np.cos(sweep)*np.linspace(0.0, main_span, num_node_main) # x[working_node:working_node + num_node_main] = np.sin(sweep)*np.linspace(0.0, main_span, num_node_main) for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[0] = 1 boundary_conditions[working_node + num_node_main - 1] = -1 working_elem += num_elem_main working_node += num_node_main # # left wing (beam 1) -------------------------------------------------------------- # beam_number[working_elem:working_elem + num_elem_main] = 1 # domain = np.linspace(0.0, 1.0, num_node_main) # tempy = np.linspace(0.0, main_span, num_node_main) # # x[working_node:working_node + num_node_main - 1] = -np.sin(sweep)*tempy[0:-1] # # y[working_node:working_node + num_node_main - 1] = np.cos(sweep)*tempy[0:-1] # x[working_node:working_node + num_node_main - 1] = -np.sin(sweep)*(main_span - (np.geomspace(0 + spacing_param, # main_span + spacing_param, # num_node_main)[:-1] # - spacing_param)) # y[working_node:working_node + num_node_main - 1] = -np.abs(np.cos(sweep)*(main_span - (np.geomspace(0 + spacing_param, # main_span + spacing_param, # num_node_main)[:-1] # - spacing_param))) # for ielem in range(num_elem_main): # for inode in range(num_node_elem): # frame_of_reference_delta[working_elem + ielem, inode, :] = [1, 0, 0] # # connectivity # for ielem in range(num_elem_main): # conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + # [0, 2, 1]) # conn[working_elem , 0] = 0 # elem_stiffness[working_elem:working_elem + num_elem_main] = 0 # elem_mass[working_elem:working_elem + num_elem_main] = 0 # boundary_conditions[working_node + num_node_main - 1 - 1] = -1 # working_elem += num_elem_main # working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=num_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=num_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=num_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) body_number_handle = h5file.create_dataset( 'body_number', data=np.zeros((num_elem, ), dtype=int)) # node_app_forces_handle = h5file.create_dataset( # 'node_app_forces', data=node_app_forces) def generate_aero_file(): global x, y, z n_control_surfaces = 1 control_surface = np.zeros((num_elem, num_node_elem), dtype=int) control_surface_type = np.zeros((n_control_surfaces,), dtype=int) control_surface_deflection = np.zeros((n_control_surfaces,)) control_surface_chord = np.zeros((n_control_surfaces, ), dtype=int) control_surface_hinge_coord = np.zeros((n_control_surfaces, )) control_surface_type[0] = 2 control_surface_deflection[0] = 0.0 control_surface_chord[0] = 1 airfoil_distribution = np.zeros((num_elem, num_node_elem), dtype=int) surface_distribution = np.zeros((num_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces, ), dtype=int) m_distribution = 'uniform' aero_node = np.zeros((num_node,), dtype=bool) twist = np.zeros((num_elem, 3)) chord = np.zeros((num_elem, 3)) elastic_axis = np.zeros((num_elem, 3,)) working_elem = 0 working_node = 0 # right wing (surface 0, beam 0) i_surf = 0 chord[:] = main_chord elastic_axis[:] = main_ea airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main] = True # chord[working_node:working_node + num_node_main] = main_chord # elastic_axis[working_node:working_node + num_node_main] = main_ea working_elem += num_elem_main working_node += num_node_main # left wing (surface 1, beam 1) # i_surf = 1 # airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 # # airfoil_distribution[working_node:working_node + num_node_main - 1] = 0 # surface_distribution[working_elem:working_elem + num_elem_main] = i_surf # surface_m[i_surf] = m_main # aero_node[working_node:working_node + num_node_main - 1] = True # # chord[working_node:working_node + num_node_main - 1] = main_chord # # elastic_axis[working_node:working_node + num_node_main - 1] = main_ea # working_elem += num_elem_main # working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( generate_naca_camber(P=main_airfoil_P, M=main_airfoil_M))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input .attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) control_surface_input = h5file.create_dataset('control_surface', data = control_surface) control_surface_input = h5file.create_dataset('control_surface_deflection', data = control_surface_deflection) control_surface_input = h5file.create_dataset('control_surface_chord', data = control_surface_chord) control_surface_input = h5file.create_dataset('control_surface_hinge_coord', data = control_surface_hinge_coord) control_surface_input = h5file.create_dataset('control_surface_type', data = control_surface_type) def generate_naca_camber(M=0, P=0): m = M*1e-2 p = P*1e-1 def naca(x, m, p): if x < 1e-6: return 0.0 elif x < p: return m/(p*p)*(2*p*x - x*x) elif x > p and x < 1+1e-6: return m/((1-p)*(1-p))*(1 - 2*p + 2*p*x - x*x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, m, p) for x in x_vec]) return x_vec, y_vec def generate_multibody_file(): LC2 = gc.LagrangeConstraint() LC2.behaviour = 'hinge_FoR' LC2.rot_axis_AFoR = np.array([0.0, 1.0, 0.0]) LC2.body_FoR = 0 # LC2.node_number = int(0) LC = [] LC.append(LC2) MB1 = gc.BodyInformation() MB1.body_number = 0 MB1.FoR_position = np.zeros((6,)) MB1.FoR_velocity = np.zeros((6,)) MB1.FoR_acceleration = np.zeros((6,)) MB1.FoR_movement = 'free' MB1.quat = np.array([1.0, 0.0, 0.0, 0.0]) MB = [] MB.append(MB1) gc.clean_test_files(route, case_name) gc.generate_multibody_file(LC, MB, route, case_name) def generate_solver_file(horseshoe=False): file_name = route + '/' + case_name + '.sharpy' # config = configparser.ConfigParser() import configobj config = configobj.ConfigObj() config.filename = file_name config['SHARPy'] = {'case': case_name, 'route': route, 'flow': ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'DynamicCoupled', # 'AerogridPlot', # 'BeamPlot', ], 'write_screen': 'on', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} config['BeamLoader'] = {'unsteady': 'on', 'orientation': algebra.euler2quat(np.array([0.0, alpha_rad, beta*np.pi/180]))} config['StaticCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-5, 'min_delta': 1e-13, 'gravity_on': 'on', 'gravity': 9.754}, 'aero_solver': 'StaticUvlm', 'aero_solver_settings': {'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': main_chord/m_main/u_inf, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho}, 'max_iter': 80, 'n_load_steps': 1, 'tolerance': 1e-5, 'relaxation_factor': 0.0} config['DynamicCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearDynamicMultibody', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 550, 'num_load_steps': 1, 'delta_curved': 1e-1, 'min_delta': 1e-4, 'newmark_damp': 5e-3, 'gravity_on': 'on', 'gravity': 9.754, 'num_steps': num_steps, 'dt': dt, 'relax_factor_lm': 0.2, 'time_integrator': 'NewmarkBeta', 'time_integrator_settings': {'dt': dt, 'newmark_damp': 5e-3, 'num_LM_eq': 5}}, 'aero_solver': 'StepUvlm', 'aero_solver_settings': {'print_info': 'off', 'num_cores': 4, 'convection_scheme': 2, 'velocity_field_generator': 'GustVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0], 'gust_shape': '1-cos', 'gust_parameters': { 'gust_length': 4.0 * main_chord, 'gust_intensity': 0.1 * u_inf}, 'offset': 1.0, 'relative_motion': 'on'}, 'rho': rho, 'n_time_steps': num_steps, 'dt': dt}, 'controller_id': {'controller_tip': 'ControlSurfacePidController'}, 'controller_settings': {'controller_tip': {'P': gains[0], 'I': gains[1], 'D': gains[2], 'dt': dt, 'input_type': 'pitch', 'controller_log_route': './output/' + case_name + '/', 'controlled_surfaces': 0, 'time_history_input_file': 'pitch.csv'}}, 'postprocessors': ['BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}, 'AerogridPlot': { 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}}, 'fsi_substeps': 100, # 'fsi_substeps': 1, 'fsi_tolerance': 1e-5, 'relaxation_factor': 0.3, 'dynamic_relaxation': 'off', 'minimum_steps': 1, 'relaxation_steps': 25, 'structural_substeps': n_structural_substeps, 'n_time_steps': num_steps, 'dt': dt} config['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'mstar': 1, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': ['1', '0', '0'], 'dt': dt}} if not horseshoe: config['AerogridLoader']['mstar'] = int(m_main*wake_length) config['AerogridPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0 } config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': case_name + '_aeroforces.csv', 'screen_output': 'on', 'unsteady': 'off' } config['BeamPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on'} config.write() clean_test_files() generate_multibody_file() generate_fem_file() generate_dyn_file() generate_solver_file(horseshoe=False) generate_aero_file() ================================================ FILE: sharpy/cases/coupled/hinged_controlled_wing/generate_hinged_roll_controlled_wing.py ================================================ import h5py as h5 import numpy as np import configparser import os import sharpy.utils.algebra as algebra import sharpy.utils.generate_cases as gc case_name = 'hinged_roll_controlled_wing' route = os.path.dirname(os.path.realpath(__file__)) + '/' # m = 16 gives flutter with 165 m_main = 6 amplitude = 0*np.pi/180 period = 5 dt_factor = 1. n_structural_substeps = 10 # flight conditions u_inf = 10 rho = 1.225 alpha = 0 beta = 0 c_ref = 1 b_ref = 16 sweep = 0*np.pi/180. sigma = 1 wake_length = 10 # chords lambda_control_surface = 0.5 alpha_rad = alpha*np.pi/180 roll_file = route + 'roll.csv' gains = -np.array([45, 50.0, 1.5]) # main geometry data main_span = 10 main_chord = 2. main_ea = 0.4 main_cg = 0.3 main_sigma = 1 main_airfoil_P = 0 main_airfoil_M = 0 n_surfaces = 1 dt = main_chord/m_main/u_inf*dt_factor num_steps = int(30./dt) roll_hist = np.linspace(0, num_steps*dt, num_steps) roll_hist = amplitude*np.sin(2.0*np.pi*roll_hist/period) np.savetxt(roll_file, roll_hist) # discretisation data num_elem_main = 5 num_elem_cs = round(lambda_control_surface*num_elem_main) num_node_elem = 3 num_elem = num_elem_main num_node_main = num_elem_main*(num_node_elem - 1) + 1 num_node = num_node_main def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) dyn_file_name = route + '/' + case_name + '.dyn.h5' if os.path.isfile(dyn_file_name): os.remove(dyn_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def generate_dyn_file(): global dt global num_steps global route global case_name global num_elem global num_node_elem global num_node global amplitude global period dynamic_forces_time = None with_dynamic_forces = False with_forced_vel = False if with_dynamic_forces: f1 = 100 dynamic_forces = np.zeros((num_node, 6)) app_node = [int(num_node_main - 1), int(num_node_main)] dynamic_forces[app_node, 2] = f1 force_time = np.zeros((num_steps, )) limit = round(0.05/dt) force_time[50:61] = 1 dynamic_forces_time = np.zeros((num_steps, num_node, 6)) for it in range(num_steps): dynamic_forces_time[it, :, :] = force_time[it]*dynamic_forces forced_for_vel = None if with_forced_vel: forced_for_vel = np.zeros((num_steps, 6)) forced_for_acc = np.zeros((num_steps, 6)) for it in range(num_steps): # if dt*it < period: forced_for_vel[it, 2] = 2*np.pi/period*amplitude*np.sin(2*np.pi*dt*it/period) forced_for_acc[it, 2] = (2*np.pi/period)**2*amplitude*np.cos(2*np.pi*dt*it/period) # forced_for_vel[it, 2] = 2*np.pi/period*np.pi/180*amplitude*np.cos(2*np.pi*dt*it/period) with h5.File(route + '/' + case_name + '.dyn.h5', 'a') as h5file: if with_dynamic_forces: h5file.create_dataset( 'dynamic_forces', data=dynamic_forces_time) if with_forced_vel: h5file.create_dataset( 'for_vel', data=forced_for_vel) h5file.create_dataset( 'for_acc', data=forced_for_acc) h5file.create_dataset( 'num_steps', data=num_steps) def generate_fem_file(): # placeholders # coordinates global x, y, z global sigma x = np.zeros((num_node, )) y = np.zeros((num_node, )) z = np.zeros((num_node, )) # struct twist structural_twist = np.zeros((num_elem, 3)) # beam number beam_number = np.zeros((num_elem, ), dtype=int) # frame of reference delta frame_of_reference_delta = np.zeros((num_elem, num_node_elem, 3)) # connectivities conn = np.zeros((num_elem, num_node_elem), dtype=int) # stiffness num_stiffness = 1 ea = 1e5 ga = 1e5 gj = 0.987581e6 eiy = 9.77221e6 eiz = 9.77221e8 base_stiffness = sigma*np.diag([ea, ga, ga, gj, eiy, eiz]) stiffness = np.zeros((num_stiffness, 6, 6)) stiffness[0, :, :] = main_sigma*base_stiffness elem_stiffness = np.zeros((num_elem,), dtype=int) # mass num_mass = 1 m_base = 1 j_base = 0.5 import sharpy.utils.algebra as algebra # m_chi_cg = algebra.skew(m_base*np.array([0., -(main_ea - main_cg), 0.])) m_chi_cg = algebra.skew(m_base*np.array([0., (main_ea - main_cg), 0.])) base_mass = np.diag([m_base, m_base, m_base, j_base, 0.5*j_base, 0.5*j_base]) base_mass[0:3, 3:6] = -m_chi_cg base_mass[3:6, 0:3] = m_chi_cg mass = np.zeros((num_mass, 6, 6)) mass[0, :, :] = base_mass elem_mass = np.zeros((num_elem,), dtype=int) # boundary conditions boundary_conditions = np.zeros((num_node, ), dtype=int) boundary_conditions[0] = 1 # applied forces # n_app_forces = 2 # node_app_forces = np.zeros((n_app_forces,), dtype=int) app_forces = np.zeros((num_node, 6)) spacing_param = 10 # right wing (beam 0) -------------------------------------------------------------- working_elem = 0 working_node = 0 beam_number[working_elem:working_elem + num_elem_main] = 0 domain = np.linspace(0, 1.0, num_node_main) # 16 - (np.geomspace(20, 4, 10) - 4) x[working_node:working_node + num_node_main] = np.sin(sweep)*(main_span - (np.geomspace(main_span + spacing_param, 0 + spacing_param, num_node_main) - spacing_param)) y[working_node:working_node + num_node_main] = np.abs(np.cos(sweep)*(main_span - (np.geomspace(main_span + spacing_param, 0 + spacing_param, num_node_main) - spacing_param))) y[0] = 0 # y[working_node:working_node + num_node_main] = np.cos(sweep)*np.linspace(0.0, main_span, num_node_main) # x[working_node:working_node + num_node_main] = np.sin(sweep)*np.linspace(0.0, main_span, num_node_main) for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[0] = 1 boundary_conditions[working_node + num_node_main - 1] = -1 working_elem += num_elem_main working_node += num_node_main # # left wing (beam 1) -------------------------------------------------------------- # beam_number[working_elem:working_elem + num_elem_main] = 1 # domain = np.linspace(0.0, 1.0, num_node_main) # tempy = np.linspace(0.0, main_span, num_node_main) # # x[working_node:working_node + num_node_main - 1] = -np.sin(sweep)*tempy[0:-1] # # y[working_node:working_node + num_node_main - 1] = np.cos(sweep)*tempy[0:-1] # x[working_node:working_node + num_node_main - 1] = -np.sin(sweep)*(main_span - (np.geomspace(0 + spacing_param, # main_span + spacing_param, # num_node_main)[:-1] # - spacing_param)) # y[working_node:working_node + num_node_main - 1] = -np.abs(np.cos(sweep)*(main_span - (np.geomspace(0 + spacing_param, # main_span + spacing_param, # num_node_main)[:-1] # - spacing_param))) # for ielem in range(num_elem_main): # for inode in range(num_node_elem): # frame_of_reference_delta[working_elem + ielem, inode, :] = [1, 0, 0] # # connectivity # for ielem in range(num_elem_main): # conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + # [0, 2, 1]) # conn[working_elem , 0] = 0 # elem_stiffness[working_elem:working_elem + num_elem_main] = 0 # elem_mass[working_elem:working_elem + num_elem_main] = 0 # boundary_conditions[working_node + num_node_main - 1 - 1] = -1 # working_elem += num_elem_main # working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=num_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=num_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=num_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) body_number_handle = h5file.create_dataset( 'body_number', data=np.zeros((num_elem, ), dtype=int)) # node_app_forces_handle = h5file.create_dataset( # 'node_app_forces', data=node_app_forces) def generate_aero_file(): global x, y, z n_control_surfaces = 1 control_surface = np.zeros((num_elem, num_node_elem), dtype=int) - 1 control_surface_type = np.zeros((n_control_surfaces,), dtype=int) control_surface_deflection = np.zeros((n_control_surfaces,)) control_surface_chord = np.zeros((n_control_surfaces, ), dtype=int) control_surface_hinge_coord = np.zeros((n_control_surfaces, )) control_surface[-num_elem_cs:, :] = 0 control_surface_type[0] = 2 control_surface_deflection[0] = 0.0 control_surface_chord[0] = int(0.35*m_main) airfoil_distribution = np.zeros((num_elem, num_node_elem), dtype=int) surface_distribution = np.zeros((num_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces, ), dtype=int) m_distribution = 'uniform' aero_node = np.zeros((num_node,), dtype=bool) twist = np.zeros((num_elem, 3)) chord = np.zeros((num_elem, 3)) elastic_axis = np.zeros((num_elem, 3,)) working_elem = 0 working_node = 0 # right wing (surface 0, beam 0) i_surf = 0 chord[:] = main_chord elastic_axis[:] = main_ea airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main] = True # chord[working_node:working_node + num_node_main] = main_chord # elastic_axis[working_node:working_node + num_node_main] = main_ea working_elem += num_elem_main working_node += num_node_main # left wing (surface 1, beam 1) # i_surf = 1 # airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 # # airfoil_distribution[working_node:working_node + num_node_main - 1] = 0 # surface_distribution[working_elem:working_elem + num_elem_main] = i_surf # surface_m[i_surf] = m_main # aero_node[working_node:working_node + num_node_main - 1] = True # # chord[working_node:working_node + num_node_main - 1] = main_chord # # elastic_axis[working_node:working_node + num_node_main - 1] = main_ea # working_elem += num_elem_main # working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( generate_naca_camber(P=main_airfoil_P, M=main_airfoil_M))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input .attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) control_surface_input = h5file.create_dataset('control_surface', data = control_surface) control_surface_input = h5file.create_dataset('control_surface_deflection', data = control_surface_deflection) control_surface_input = h5file.create_dataset('control_surface_chord', data = control_surface_chord) control_surface_input = h5file.create_dataset('control_surface_hinge_coord', data = control_surface_hinge_coord) control_surface_input = h5file.create_dataset('control_surface_type', data = control_surface_type) def generate_naca_camber(M=0, P=0): m = M*1e-2 p = P*1e-1 def naca(x, m, p): if x < 1e-6: return 0.0 elif x < p: return m/(p*p)*(2*p*x - x*x) elif x > p and x < 1+1e-6: return m/((1-p)*(1-p))*(1 - 2*p + 2*p*x - x*x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, m, p) for x in x_vec]) return x_vec, y_vec def generate_multibody_file(): LC2 = gc.LagrangeConstraint() LC2.behaviour = 'hinge_FoR' LC2.rot_axis_AFoR = np.array([1.0, 0.0, 0.0]) LC2.body_FoR = 0 # LC2.node_number = int(0) LC = [] LC.append(LC2) MB1 = gc.BodyInformation() MB1.body_number = 0 MB1.FoR_position = np.zeros((6,)) MB1.FoR_velocity = np.zeros((6,)) MB1.FoR_acceleration = np.zeros((6,)) MB1.FoR_movement = 'free' MB1.quat = np.array([1.0, 0.0, 0.0, 0.0]) MB = [] MB.append(MB1) gc.clean_test_files(route, case_name) gc.generate_multibody_file(LC, MB, route, case_name) def generate_solver_file(horseshoe=False): file_name = route + '/' + case_name + '.sharpy' # config = configparser.ConfigParser() import configobj config = configobj.ConfigObj() config.filename = file_name config['SHARPy'] = {'case': case_name, 'route': route, 'flow': ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'DynamicCoupled', # 'AerogridPlot', # 'BeamPlot', ], 'write_screen': 'on', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} config['BeamLoader'] = {'unsteady': 'on', 'orientation': algebra.euler2quat(np.array([0.0, alpha_rad, beta*np.pi/180]))} config['StaticCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-5, 'min_delta': 1e-13, 'gravity_on': 'on', 'gravity': 9.754}, 'aero_solver': 'StaticUvlm', 'aero_solver_settings': {'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho}, 'max_iter': 80, 'n_load_steps': 1, 'tolerance': 1e-5, 'relaxation_factor': 0.0} config['DynamicCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearDynamicMultibody', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 550, 'num_load_steps': 1, 'delta_curved': 1e-1, 'min_delta': 1e-6, 'newmark_damp': 1e-2, 'gravity_on': 'on', 'gravity': 9.754, 'num_steps': num_steps, 'time_integrator': 'NewmarkBeta', 'time_integrator_settings': {'dt': dt, 'newmark_damp': 5e-3, 'num_LM_eq': 5}, }, 'aero_solver': 'StepUvlm', 'aero_solver_settings': {'print_info': 'off', 'num_cores': 4, 'convection_scheme': 3, 'gamma_dot_filtering': 9, 'velocity_field_generator': 'GustVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0], 'gust_shape': '1-cos', 'gust_parameters': { 'gust_length': 10.0 * main_chord, 'gust_intensity': 0.15 * u_inf}, 'offset': 20.0, 'relative_motion': 'on'}, 'rho': rho, 'n_time_steps': num_steps, 'dt': dt}, 'controller_id': {'controller_tip': 'ControlSurfacePidController'}, 'controller_settings': {'controller_tip': {'P': gains[0], 'I': gains[1], 'D': gains[2], 'dt': dt, 'input_type': 'roll', 'controller_log_route': './output/' + case_name + '/', 'controlled_surfaces': 0, 'time_history_input_file': 'roll.csv'}}, 'postprocessors': ['BeamLoads', 'BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'BeamLoads': {}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}, 'AerogridPlot': { 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}}, 'fsi_substeps': 100, # 'fsi_substeps': 1, 'fsi_tolerance': 1e-5, 'relaxation_factor': 0.2, 'dynamic_relaxation': 'off', 'minimum_steps': 1, 'relaxation_steps': 25, 'include_unsteady_force_contribution': 'off', 'steps_without_unsteady_force': 10, 'pseudosteps_ramp_unsteady_force': 6, 'structural_substeps': n_structural_substeps, 'n_time_steps': num_steps, 'dt': dt, 'structural_substeps': 0} if horseshoe is True: config['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'mstar': 1, 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': { 'u_inf': u_inf, 'u_inf_direction': [1., 0., 0.], 'dt': dt, }, 'freestream_dir': ['1', '0', '0']} else: config['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': { 'u_inf': u_inf, 'u_inf_direction': [1., 0., 0.], 'dt': dt, }, 'mstar': int(m_main * wake_length), 'freestream_dir': ['1', '0', '0']} config['AerogridPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0 } config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': case_name + '_aeroforces.csv', 'screen_output': 'on', 'unsteady': 'off' } config['BeamPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on'} config.write() clean_test_files() generate_multibody_file() generate_fem_file() generate_dyn_file() generate_solver_file(horseshoe=False) generate_aero_file() ================================================ FILE: sharpy/cases/coupled/linear_horten/__init__.py ================================================ ================================================ FILE: sharpy/cases/coupled/multibody_takeoff/__init__.py ================================================ ================================================ FILE: sharpy/cases/coupled/multibody_takeoff/generate_hale.py ================================================ #! /usr/bin/env python3 import h5py as h5 import numpy as np import os import sharpy.utils.algebra as algebra case_name = 'simple_HALE_cato' route = os.path.dirname(os.path.realpath(__file__)) + '/' # EXECUTION flow = ['BeamLoader', 'AerogridLoader', # 'NonLinearStatic', # 'StaticUvlm', # 'StaticTrim', 'StaticCoupled', 'BeamLoads', 'AerogridPlot', 'BeamPlot', 'DynamicCoupled', ] # if free_flight is False, the motion of the centre of the wing is prescribed. free_flight = True # FLIGHT CONDITIONS # the simulation is set such that the aircraft flies at a u_inf velocity while # the air is calm. u_inf = 10 rho = 1.225 # trim sigma = 1.5 alpha = 4.281820969436041*np.pi/180 beta = 0 roll = 0 gravity = 'on' cs_deflection = -1.588626664555141*np.pi/180 rudder_static_deflection = 0.0 rudder_step = 0.0*np.pi/180 thrust = 8.482569525210431 sigma = 100 lambda_dihedral = 20*np.pi/180 # gust settings gust_intensity = 0.00 gust_length = 1*u_inf gust_offset = 0.5*u_inf # numerics n_step = 1 relaxation_factor = 0.5 tolerance = 1e-7 fsi_tolerance = 1e-6 num_cores = 2 # MODEL GEOMETRY # beam span_main = 16.0 lambda_main = 0.25 ea_main = 0.3 ea = 1e7 ga = 1e7 gj = 1e4 eiy = 2e4 eiz = 4e6 m_bar_main = 0.75 j_bar_main = 0.075 length_fuselage = 10 offset_fuselage = 0 sigma_fuselage = 100 m_bar_fuselage = 0.2 j_bar_fuselage = 0.08 span_tail = 2.5 ea_tail = 0.5 fin_height = 2.5 ea_fin = 0.5 sigma_tail = 100 m_bar_tail = 0.3 j_bar_tail = 0.08 # lumped masses n_lumped_mass = 1 lumped_mass_nodes = np.zeros((n_lumped_mass, ), dtype=int) lumped_mass = np.zeros((n_lumped_mass, )) lumped_mass[0] = 50 lumped_mass_inertia = np.zeros((n_lumped_mass, 3, 3)) lumped_mass_position = np.zeros((n_lumped_mass, 3)) # aero chord_main = 1.0 chord_tail = 0.5 chord_fin = 0.5 # DISCRETISATION # spatial discretisation # chordiwse panels m = 8 # spanwise elements n_elem_multiplier = 2 n_elem_main = int(4*n_elem_multiplier) n_elem_tail = int(2*n_elem_multiplier) n_elem_fin = int(2*n_elem_multiplier) n_elem_fuselage = int(2*n_elem_multiplier) n_surfaces = 5 # temporal discretisation physical_time = 30 tstep_factor = 1. dt = 1.0/m/u_inf*tstep_factor n_tstep = round(physical_time/dt) # END OF INPUT----------------------------------------------------------------- # beam processing n_node_elem = 3 span_main1 = (1.0 - lambda_main)*span_main span_main2 = lambda_main*span_main n_elem_main1 = round(n_elem_main*(1 - lambda_main)) n_elem_main2 = n_elem_main - n_elem_main1 # total number of elements n_elem = 0 n_elem += n_elem_main1 + n_elem_main1 n_elem += n_elem_main2 + n_elem_main2 n_elem += n_elem_fuselage n_elem += n_elem_fin n_elem += n_elem_tail + n_elem_tail # number of nodes per part n_node_main1 = n_elem_main1*(n_node_elem - 1) + 1 n_node_main2 = n_elem_main2*(n_node_elem - 1) + 1 n_node_main = n_node_main1 + n_node_main2 - 1 n_node_fuselage = n_elem_fuselage*(n_node_elem - 1) + 1 n_node_fin = n_elem_fin*(n_node_elem - 1) + 1 n_node_tail = n_elem_tail*(n_node_elem - 1) + 1 # total number of nodes n_node = 0 n_node += n_node_main1 + n_node_main1 - 1 n_node += n_node_main2 - 1 + n_node_main2 - 1 n_node += n_node_fuselage - 1 n_node += n_node_fin - 1 n_node += n_node_tail - 1 n_node += n_node_tail - 1 # stiffness and mass matrices n_stiffness = 3 base_stiffness_main = sigma*np.diag([ea, ga, ga, gj, eiy, eiz]) base_stiffness_fuselage = base_stiffness_main.copy()*sigma_fuselage base_stiffness_fuselage[4, 4] = base_stiffness_fuselage[5, 5] base_stiffness_tail = base_stiffness_main.copy()*sigma_tail base_stiffness_tail[4, 4] = base_stiffness_tail[5, 5] n_mass = 3 base_mass_main = np.diag([m_bar_main, m_bar_main, m_bar_main, j_bar_main, 0.5*j_bar_main, 0.5*j_bar_main]) base_mass_fuselage = np.diag([m_bar_fuselage, m_bar_fuselage, m_bar_fuselage, j_bar_fuselage, j_bar_fuselage*0.5, j_bar_fuselage*0.5]) base_mass_tail = np.diag([m_bar_tail, m_bar_tail, m_bar_tail, j_bar_tail, j_bar_tail*0.5, j_bar_tail*0.5]) # PLACEHOLDERS # beam x = np.zeros((n_node, )) y = np.zeros((n_node, )) z = np.zeros((n_node, )) beam_number = np.zeros((n_elem, ), dtype=int) frame_of_reference_delta = np.zeros((n_elem, n_node_elem, 3)) structural_twist = np.zeros((n_elem, 3)) conn = np.zeros((n_elem, n_node_elem), dtype=int) stiffness = np.zeros((n_stiffness, 6, 6)) elem_stiffness = np.zeros((n_elem, ), dtype=int) mass = np.zeros((n_mass, 6, 6)) elem_mass = np.zeros((n_elem, ), dtype=int) boundary_conditions = np.zeros((n_node, ), dtype=int) app_forces = np.zeros((n_node, 6)) # aero airfoil_distribution = np.zeros((n_elem, n_node_elem), dtype=int) surface_distribution = np.zeros((n_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces, ), dtype=int) m_distribution = 'uniform' aero_node = np.zeros((n_node,), dtype=bool) twist = np.zeros((n_elem, n_node_elem)) sweep = np.zeros((n_elem, n_node_elem)) chord = np.zeros((n_elem, n_node_elem,)) elastic_axis = np.zeros((n_elem, n_node_elem,)) # FUNCTIONS------------------------------------------------------------- def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) dyn_file_name = route + '/' + case_name + '.dyn.h5' if os.path.isfile(dyn_file_name): os.remove(dyn_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def generate_dyn_file(): global dt global n_tstep global route global case_name global num_elem global num_node_elem global num_node global amplitude global period dynamic_forces_time = None with_dynamic_forces = False with_forced_vel = False if with_dynamic_forces: f1 = 100 dynamic_forces = np.zeros((num_node, 6)) app_node = [int(num_node_main - 1), int(num_node_main)] dynamic_forces[app_node, 2] = f1 force_time = np.zeros((n_tstep, )) limit = round(0.05/dt) force_time[50:61] = 1 dynamic_forces_time = np.zeros((n_tstep, num_node, 6)) for it in range(n_tstep): dynamic_forces_time[it, :, :] = force_time[it]*dynamic_forces forced_for_vel = None if with_forced_vel: forced_for_vel = np.zeros((n_tstep, 6)) forced_for_acc = np.zeros((n_tstep, 6)) for it in range(n_tstep): # if dt*it < period: # forced_for_vel[it, 2] = 2*np.pi/period*amplitude*np.sin(2*np.pi*dt*it/period) # forced_for_acc[it, 2] = (2*np.pi/period)**2*amplitude*np.cos(2*np.pi*dt*it/period) forced_for_vel[it, 3] = 2*np.pi/period*amplitude*np.sin(2*np.pi*dt*it/period) forced_for_acc[it, 3] = (2*np.pi/period)**2*amplitude*np.cos(2*np.pi*dt*it/period) if with_dynamic_forces or with_forced_vel: with h5.File(route + '/' + case_name + '.dyn.h5', 'a') as h5file: if with_dynamic_forces: h5file.create_dataset( 'dynamic_forces', data=dynamic_forces_time) if with_forced_vel: h5file.create_dataset( 'for_vel', data=forced_for_vel) h5file.create_dataset( 'for_acc', data=forced_for_acc) h5file.create_dataset( 'num_steps', data=n_tstep) def generate_fem(): stiffness[0, ...] = base_stiffness_main stiffness[1, ...] = base_stiffness_fuselage stiffness[2, ...] = base_stiffness_tail mass[0, ...] = base_mass_main mass[1, ...] = base_mass_fuselage mass[2, ...] = base_mass_tail we = 0 wn = 0 # inner right wing beam_number[we:we + n_elem_main1] = 0 y[wn:wn + n_node_main1] = np.linspace(0.0, span_main1, n_node_main1) for ielem in range(n_elem_main1): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main1] = 0 elem_mass[we:we + n_elem_main1] = 0 boundary_conditions[0] = 1 # remember this is in B FoR app_forces[0] = [0, thrust, 0, 0, 0, 0] we += n_elem_main1 wn += n_node_main1 # outer right wing beam_number[we:we + n_elem_main1] = 0 y[wn:wn + n_node_main2 - 1] = y[wn - 1] + np.linspace(0.0, np.cos(lambda_dihedral)*span_main2, n_node_main2)[1:] z[wn:wn + n_node_main2 - 1] = z[wn - 1] + np.linspace(0.0, np.sin(lambda_dihedral)*span_main2, n_node_main2)[1:] for ielem in range(n_elem_main2): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main2] = 0 elem_mass[we:we + n_elem_main2] = 0 boundary_conditions[wn + n_node_main2 - 2] = -1 we += n_elem_main2 wn += n_node_main2 - 1 # inner left wing beam_number[we:we + n_elem_main1 - 1] = 1 y[wn:wn + n_node_main1 - 1] = np.linspace(0.0, -span_main1, n_node_main1)[1:] for ielem in range(n_elem_main1): conn[we + ielem, :] = ((np.ones((3, ))*(we+ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = 0 elem_stiffness[we:we + n_elem_main1] = 0 elem_mass[we:we + n_elem_main1] = 0 we += n_elem_main1 wn += n_node_main1 - 1 # outer left wing beam_number[we:we + n_elem_main2] = 1 y[wn:wn + n_node_main2 - 1] = y[wn - 1] + np.linspace(0.0, -np.cos(lambda_dihedral)*span_main2, n_node_main2)[1:] z[wn:wn + n_node_main2 - 1] = z[wn - 1] + np.linspace(0.0, np.sin(lambda_dihedral)*span_main2, n_node_main2)[1:] for ielem in range(n_elem_main2): conn[we + ielem, :] = ((np.ones((3, ))*(we+ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main2] = 0 elem_mass[we:we + n_elem_main2] = 0 boundary_conditions[wn + n_node_main2 - 2] = -1 we += n_elem_main2 wn += n_node_main2 - 1 # fuselage beam_number[we:we + n_elem_fuselage] = 2 x[wn:wn + n_node_fuselage - 1] = np.linspace(0.0, length_fuselage, n_node_fuselage)[1:] z[wn:wn + n_node_fuselage - 1] = np.linspace(0.0, offset_fuselage, n_node_fuselage)[1:] for ielem in range(n_elem_fuselage): conn[we + ielem, :] = ((np.ones((3,))*(we + ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [0.0, 1.0, 0.0] conn[we, 0] = 0 elem_stiffness[we:we + n_elem_fuselage] = 1 elem_mass[we:we + n_elem_fuselage] = 1 we += n_elem_fuselage wn += n_node_fuselage - 1 global end_of_fuselage_node end_of_fuselage_node = wn - 1 # fin beam_number[we:we + n_elem_fin] = 3 x[wn:wn + n_node_fin - 1] = x[end_of_fuselage_node] z[wn:wn + n_node_fin - 1] = z[end_of_fuselage_node] + np.linspace(0.0, fin_height, n_node_fin)[1:] for ielem in range(n_elem_fin): conn[we + ielem, :] = ((np.ones((3,))*(we + ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_of_fuselage_node elem_stiffness[we:we + n_elem_fin] = 2 elem_mass[we:we + n_elem_fin] = 2 we += n_elem_fin wn += n_node_fin - 1 end_of_fin_node = wn - 1 # right tail beam_number[we:we + n_elem_tail] = 4 x[wn:wn + n_node_tail - 1] = x[end_of_fin_node] y[wn:wn + n_node_tail - 1] = np.linspace(0.0, span_tail, n_node_tail)[1:] z[wn:wn + n_node_tail - 1] = z[end_of_fin_node] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_of_fin_node elem_stiffness[we:we + n_elem_tail] = 2 elem_mass[we:we + n_elem_tail] = 2 boundary_conditions[wn + n_node_tail - 2] = -1 we += n_elem_tail wn += n_node_tail - 1 # left tail beam_number[we:we + n_elem_tail] = 5 x[wn:wn + n_node_tail - 1] = x[end_of_fin_node] y[wn:wn + n_node_tail - 1] = np.linspace(0.0, -span_tail, n_node_tail)[1:] z[wn:wn + n_node_tail - 1] = z[end_of_fin_node] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3, ))*(we + ielem)*(n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = end_of_fin_node elem_stiffness[we:we + n_elem_tail] = 2 elem_mass[we:we + n_elem_tail] = 2 boundary_conditions[wn + n_node_tail - 2] = -1 we += n_elem_tail wn += n_node_tail - 1 with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=n_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=n_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=n_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) lumped_mass_nodes_handle = h5file.create_dataset( 'lumped_mass_nodes', data=lumped_mass_nodes) lumped_mass_handle = h5file.create_dataset( 'lumped_mass', data=lumped_mass) lumped_mass_inertia_handle = h5file.create_dataset( 'lumped_mass_inertia', data=lumped_mass_inertia) lumped_mass_position_handle = h5file.create_dataset( 'lumped_mass_position', data=lumped_mass_position) def generate_aero_file(): global x, y, z # control surfaces n_control_surfaces = 2 control_surface = np.zeros((n_elem, n_node_elem), dtype=int) - 1 control_surface_type = np.zeros((n_control_surfaces, ), dtype=int) control_surface_deflection = np.zeros((n_control_surfaces, )) control_surface_chord = np.zeros((n_control_surfaces, ), dtype=int) control_surface_hinge_coord = np.zeros((n_control_surfaces, ), dtype=float) # control surface type 0 = static # control surface type 1 = dynamic control_surface_type[0] = 0 control_surface_deflection[0] = cs_deflection control_surface_chord[0] = m control_surface_hinge_coord[0] = -0.25 # nondimensional wrt elastic axis (+ towards the trailing edge) control_surface_type[1] = 0 control_surface_deflection[1] = rudder_static_deflection control_surface_chord[1] = 1 control_surface_hinge_coord[1] = -0. # nondimensional wrt elastic axis (+ towards the trailing edge) we = 0 wn = 0 # right wing (surface 0, beam 0) i_surf = 0 airfoil_distribution[we:we + n_elem_main, :] = 0 surface_distribution[we:we + n_elem_main] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_main] = True temp_chord = np.linspace(chord_main, chord_main, n_node_main) temp_sweep = np.linspace(0.0, 0*np.pi/180, n_node_main) node_counter = 0 for i_elem in range(we, we + n_elem_main): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = temp_chord[node_counter] elastic_axis[i_elem, i_local_node] = ea_main sweep[i_elem, i_local_node] = temp_sweep[node_counter] we += n_elem_main wn += n_node_main # left wing (surface 1, beam 1) i_surf = 1 airfoil_distribution[we:we + n_elem_main, :] = 0 # airfoil_distribution[wn:wn + n_node_main - 1] = 0 surface_distribution[we:we + n_elem_main] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_main - 1] = True # chord[wn:wn + num_node_main - 1] = np.linspace(main_chord, main_tip_chord, num_node_main)[1:] # chord[wn:wn + num_node_main - 1] = main_chord # elastic_axis[wn:wn + num_node_main - 1] = main_ea temp_chord = np.linspace(chord_main, chord_main, n_node_main) node_counter = 0 for i_elem in range(we, we + n_elem_main): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = temp_chord[node_counter] elastic_axis[i_elem, i_local_node] = ea_main sweep[i_elem, i_local_node] = -temp_sweep[node_counter] we += n_elem_main wn += n_node_main - 1 we += n_elem_fuselage wn += n_node_fuselage - 1 - 1 # # # fin (surface 2, beam 3) i_surf = 2 airfoil_distribution[we:we + n_elem_fin, :] = 1 # airfoil_distribution[wn:wn + n_node_fin] = 0 surface_distribution[we:we + n_elem_fin] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_fin] = True # chord[wn:wn + num_node_fin] = fin_chord for i_elem in range(we, we + n_elem_fin): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_fin elastic_axis[i_elem, i_local_node] = ea_fin control_surface[i_elem, i_local_node] = 1 # twist[end_of_fuselage_node] = 0 # twist[wn:] = 0 # elastic_axis[wn:wn + num_node_main] = fin_ea we += n_elem_fin wn += n_node_fin - 1 # # # # right tail (surface 3, beam 4) i_surf = 3 airfoil_distribution[we:we + n_elem_tail, :] = 2 # airfoil_distribution[wn:wn + n_node_tail] = 0 surface_distribution[we:we + n_elem_tail] = i_surf surface_m[i_surf] = m # XXX not very elegant aero_node[wn:] = True # chord[wn:wn + num_node_tail] = tail_chord # elastic_axis[wn:wn + num_node_main] = tail_ea for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): twist[i_elem, i_local_node] = -0 for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_tail elastic_axis[i_elem, i_local_node] = ea_tail control_surface[i_elem, i_local_node] = 0 we += n_elem_tail wn += n_node_tail # # # left tail (surface 4, beam 5) i_surf = 4 airfoil_distribution[we:we + n_elem_tail, :] = 2 # airfoil_distribution[wn:wn + n_node_tail - 1] = 0 surface_distribution[we:we + n_elem_tail] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_tail - 1] = True # chord[wn:wn + num_node_tail] = tail_chord # elastic_axis[wn:wn + num_node_main] = tail_ea # twist[we:we + num_elem_tail] = -tail_twist for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): twist[i_elem, i_local_node] = -0 for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_tail elastic_axis[i_elem, i_local_node] = ea_tail control_surface[i_elem, i_local_node] = 0 we += n_elem_tail wn += n_node_tail with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( generate_naca_camber(P=0, M=0))) naca_airfoil_tail = airfoils_group.create_dataset('1', data=np.column_stack( generate_naca_camber(P=0, M=0))) naca_airfoil_fin = airfoils_group.create_dataset('2', data=np.column_stack( generate_naca_camber(P=0, M=0))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input .attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # sweep sweep_input = h5file.create_dataset('sweep', data=sweep) dim_attr = sweep_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) control_surface_input = h5file.create_dataset('control_surface', data=control_surface) control_surface_deflection_input = h5file.create_dataset('control_surface_deflection', data=control_surface_deflection) control_surface_chord_input = h5file.create_dataset('control_surface_chord', data=control_surface_chord) control_surface_hinge_coord_input = h5file.create_dataset('control_surface_hinge_coord', data=control_surface_hinge_coord) control_surface_types_input = h5file.create_dataset('control_surface_type', data=control_surface_type) def generate_naca_camber(M=0, P=0): mm = M*1e-2 p = P*1e-1 def naca(x, mm, p): if x < 1e-6: return 0.0 elif x < p: return mm/(p*p)*(2*p*x - x*x) elif x > p and x < 1+1e-6: return mm/((1-p)*(1-p))*(1 - 2*p + 2*p*x - x*x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, mm, p) for x in x_vec]) return x_vec, y_vec def generate_solver_file(): file_name = route + '/' + case_name + '.sharpy' settings = dict() settings['SHARPy'] = {'case': case_name, 'route': route, 'flow': flow, 'write_screen': 'on', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} settings['BeamLoader'] = {'unsteady': 'on', 'orientation': algebra.euler2quat(np.array([roll, alpha, beta]))} settings['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'mstar': int(20 / tstep_factor), 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': { 'u_inf': u_inf, 'u_inf_direction': [1., 0., 0.], 'dt': dt, }, 'control_surface_deflection': ['', ''], 'control_surface_deflection_generator_settings': {'0': {}, '1': {}}} settings['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-1, 'min_delta': tolerance, 'gravity_on': gravity, 'gravity': 9.81} settings['StaticUvlm'] = {'print_info': 'on', 'horseshoe': 'off', 'num_cores': num_cores, 'n_rollup': 0, 'rollup_dt': dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho} settings['StaticCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': settings['NonLinearStatic'], 'aero_solver': 'StaticUvlm', 'aero_solver_settings': settings['StaticUvlm'], 'max_iter': 100, 'n_load_steps': n_step, 'tolerance': fsi_tolerance, 'relaxation_factor': relaxation_factor} settings['StaticTrim'] = {'solver': 'StaticCoupled', 'solver_settings': settings['StaticCoupled'], 'initial_alpha': alpha, 'initial_deflection': cs_deflection, 'initial_thrust': thrust} settings['NonLinearDynamicCoupledStep'] = {'print_info': 'off', 'max_iterations': 950, 'delta_curved': 1e-1, 'min_delta': tolerance, 'newmark_damp': 5e-3, 'gravity_on': gravity, 'gravity': 9.81, 'num_steps': n_tstep, 'dt': dt, 'initial_velocity': u_inf} settings['NonLinearDynamicPrescribedStep'] = {'print_info': 'off', 'max_iterations': 950, 'delta_curved': 1e-1, 'min_delta': tolerance, 'newmark_damp': 5e-3, 'gravity_on': gravity, 'gravity': 9.81, 'num_steps': n_tstep, 'dt': dt, 'initial_velocity': u_inf} relative_motion = 'off' settings['StepUvlm'] = {'print_info': 'off', 'num_cores': num_cores, 'convection_scheme': 2, 'gamma_dot_filtering': 6, 'velocity_field_generator': 'GustVelocityField', 'velocity_field_input': {'u_inf': 0, 'u_inf_direction': [1., 0, 0], 'gust_shape': '1-cos', 'gust_parameters': { 'gust_length': gust_length, 'gust_intensity': gust_intensity * u_inf}, 'offset': gust_offset, 'relative_motion': relative_motion}, 'rho': rho, 'n_time_steps': n_tstep, 'dt': dt} solver = 'NonLinearDynamicCoupledStep' settings['DynamicCoupled'] = {'structural_solver': solver, 'structural_solver_settings': settings[solver], 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'fsi_substeps': 200, 'fsi_tolerance': fsi_tolerance, 'relaxation_factor': relaxation_factor, 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': 0.5, 'n_time_steps': n_tstep, 'dt': dt, 'include_unsteady_force_contribution': 'on', 'postprocessors': ['BeamLoads', 'BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'BeamLoads': {'csv_output': 'off'}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}, 'AerogridPlot': { 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, }} settings['BeamLoads'] = {'csv_output': 'off'} settings['BeamPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on'} settings['AerogridPlot'] = {'include_rbm': 'on', 'include_forward_motion': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0, 'u_inf': u_inf, 'dt': dt} import configobj config = configobj.ConfigObj() config.filename = file_name for k, v in settings.items(): config[k] = v config.write() clean_test_files() generate_fem() generate_aero_file() generate_solver_file() generate_dyn_file() ================================================ FILE: sharpy/cases/coupled/simple_HALE/__init__.py ================================================ ================================================ FILE: sharpy/cases/coupled/simple_HALE/generate_hale.py ================================================ #! /usr/bin/env python3 import h5py as h5 import numpy as np import os import sharpy.utils.algebra as algebra case_name = 'simple_HALE' route = os.path.dirname(os.path.realpath(__file__)) + '/' # EXECUTION flow = ['BeamLoader', 'AerogridLoader', # 'NonLinearStatic', # 'StaticUvlm', 'StaticTrim', # 'StaticCoupled', 'BeamLoads', 'AerogridPlot', 'BeamPlot', 'DynamicCoupled', # 'Modal', # 'LinearAssember', # 'AsymptoticStability', ] # if free_flight is False, the motion of the centre of the wing is prescribed. free_flight = True if not free_flight: case_name += '_prescribed' amplitude = 0 * np.pi / 180 period = 3 case_name += '_amp_' + str(amplitude).replace('.', '') + '_period_' + str(period) # FLIGHT CONDITIONS # the simulation is set such that the aircraft flies at a u_inf velocity while # the air is calm. u_inf = 10 rho = 1.225 # trim sigma = 1.5 alpha = 4.31 * np.pi / 180 beta = 0 roll = 0 gravity = 'on' cs_deflection = -2.08 * np.pi / 180 rudder_static_deflection = 0.0 rudder_step = 0.0 * np.pi / 180 thrust = 6.16 sigma = 1.5 lambda_dihedral = 20 * np.pi / 180 # gust settings gust_intensity = 0.20 gust_length = 1 * u_inf gust_offset = 0.5 * u_inf # numerics n_step = 5 structural_relaxation_factor = 0.6 relaxation_factor = 0.35 tolerance = 1e-6 fsi_tolerance = 1e-4 num_cores = 2 # MODEL GEOMETRY # beam span_main = 16.0 lambda_main = 0.25 ea_main = 0.3 ea = 1e7 ga = 1e5 gj = 1e4 eiy = 2e4 eiz = 4e6 m_bar_main = 0.75 j_bar_main = 0.075 length_fuselage = 10 offset_fuselage = 0 sigma_fuselage = 10 m_bar_fuselage = 0.2 j_bar_fuselage = 0.08 span_tail = 2.5 ea_tail = 0.5 fin_height = 2.5 ea_fin = 0.5 sigma_tail = 100 m_bar_tail = 0.3 j_bar_tail = 0.08 # lumped masses n_lumped_mass = 1 lumped_mass_nodes = np.zeros((n_lumped_mass,), dtype=int) lumped_mass = np.zeros((n_lumped_mass,)) lumped_mass[0] = 50 lumped_mass_inertia = np.zeros((n_lumped_mass, 3, 3)) lumped_mass_position = np.zeros((n_lumped_mass, 3)) # aero chord_main = 1.0 chord_tail = 0.5 chord_fin = 0.5 # DISCRETISATION # spatial discretisation # chordiwse panels m = 4 # spanwise elements n_elem_multiplier = 2 n_elem_main = int(4 * n_elem_multiplier) n_elem_tail = int(2 * n_elem_multiplier) n_elem_fin = int(2 * n_elem_multiplier) n_elem_fuselage = int(2 * n_elem_multiplier) n_surfaces = 5 # temporal discretisation physical_time = 30 tstep_factor = 1. dt = 1.0 / m / u_inf * tstep_factor n_tstep = round(physical_time / dt) # END OF INPUT----------------------------------------------------------------- # beam processing n_node_elem = 3 span_main1 = (1.0 - lambda_main) * span_main span_main2 = lambda_main * span_main n_elem_main1 = round(n_elem_main * (1 - lambda_main)) n_elem_main2 = n_elem_main - n_elem_main1 # total number of elements n_elem = 0 n_elem += n_elem_main1 + n_elem_main1 n_elem += n_elem_main2 + n_elem_main2 n_elem += n_elem_fuselage n_elem += n_elem_fin n_elem += n_elem_tail + n_elem_tail # number of nodes per part n_node_main1 = n_elem_main1 * (n_node_elem - 1) + 1 n_node_main2 = n_elem_main2 * (n_node_elem - 1) + 1 n_node_main = n_node_main1 + n_node_main2 - 1 n_node_fuselage = n_elem_fuselage * (n_node_elem - 1) + 1 n_node_fin = n_elem_fin * (n_node_elem - 1) + 1 n_node_tail = n_elem_tail * (n_node_elem - 1) + 1 # total number of nodes n_node = 0 n_node += n_node_main1 + n_node_main1 - 1 n_node += n_node_main2 - 1 + n_node_main2 - 1 n_node += n_node_fuselage - 1 n_node += n_node_fin - 1 n_node += n_node_tail - 1 n_node += n_node_tail - 1 # stiffness and mass matrices n_stiffness = 3 base_stiffness_main = sigma * np.diag([ea, ga, ga, gj, eiy, eiz]) base_stiffness_fuselage = base_stiffness_main.copy() * sigma_fuselage base_stiffness_fuselage[4, 4] = base_stiffness_fuselage[5, 5] base_stiffness_tail = base_stiffness_main.copy() * sigma_tail base_stiffness_tail[4, 4] = base_stiffness_tail[5, 5] n_mass = 3 base_mass_main = np.diag([m_bar_main, m_bar_main, m_bar_main, j_bar_main, 0.5 * j_bar_main, 0.5 * j_bar_main]) base_mass_fuselage = np.diag([m_bar_fuselage, m_bar_fuselage, m_bar_fuselage, j_bar_fuselage, j_bar_fuselage * 0.5, j_bar_fuselage * 0.5]) base_mass_tail = np.diag([m_bar_tail, m_bar_tail, m_bar_tail, j_bar_tail, j_bar_tail * 0.5, j_bar_tail * 0.5]) # PLACEHOLDERS # beam x = np.zeros((n_node,)) y = np.zeros((n_node,)) z = np.zeros((n_node,)) beam_number = np.zeros((n_elem,), dtype=int) frame_of_reference_delta = np.zeros((n_elem, n_node_elem, 3)) structural_twist = np.zeros((n_elem, 3)) conn = np.zeros((n_elem, n_node_elem), dtype=int) stiffness = np.zeros((n_stiffness, 6, 6)) elem_stiffness = np.zeros((n_elem,), dtype=int) mass = np.zeros((n_mass, 6, 6)) elem_mass = np.zeros((n_elem,), dtype=int) boundary_conditions = np.zeros((n_node,), dtype=int) app_forces = np.zeros((n_node, 6)) # aero airfoil_distribution = np.zeros((n_elem, n_node_elem), dtype=int) surface_distribution = np.zeros((n_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces,), dtype=int) m_distribution = 'uniform' aero_node = np.zeros((n_node,), dtype=bool) twist = np.zeros((n_elem, n_node_elem)) sweep = np.zeros((n_elem, n_node_elem)) chord = np.zeros((n_elem, n_node_elem,)) elastic_axis = np.zeros((n_elem, n_node_elem,)) # FUNCTIONS------------------------------------------------------------- def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) dyn_file_name = route + '/' + case_name + '.dyn.h5' if os.path.isfile(dyn_file_name): os.remove(dyn_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def generate_dyn_file(): global dt global n_tstep global route global case_name global num_elem global num_node_elem global num_node global amplitude global period global free_flight dynamic_forces_time = None with_dynamic_forces = False with_forced_vel = False if not free_flight: with_forced_vel = True if with_dynamic_forces: f1 = 100 dynamic_forces = np.zeros((num_node, 6)) app_node = [int(num_node_main - 1), int(num_node_main)] dynamic_forces[app_node, 2] = f1 force_time = np.zeros((n_tstep,)) limit = round(0.05 / dt) force_time[50:61] = 1 dynamic_forces_time = np.zeros((n_tstep, num_node, 6)) for it in range(n_tstep): dynamic_forces_time[it, :, :] = force_time[it] * dynamic_forces forced_for_vel = None if with_forced_vel: forced_for_vel = np.zeros((n_tstep, 6)) forced_for_acc = np.zeros((n_tstep, 6)) for it in range(n_tstep): # if dt*it < period: # forced_for_vel[it, 2] = 2*np.pi/period*amplitude*np.sin(2*np.pi*dt*it/period) # forced_for_acc[it, 2] = (2*np.pi/period)**2*amplitude*np.cos(2*np.pi*dt*it/period) forced_for_vel[it, 3] = 2 * np.pi / period * amplitude * np.sin(2 * np.pi * dt * it / period) forced_for_acc[it, 3] = (2 * np.pi / period) ** 2 * amplitude * np.cos(2 * np.pi * dt * it / period) if with_dynamic_forces or with_forced_vel: with h5.File(route + '/' + case_name + '.dyn.h5', 'a') as h5file: if with_dynamic_forces: h5file.create_dataset( 'dynamic_forces', data=dynamic_forces_time) if with_forced_vel: h5file.create_dataset( 'for_vel', data=forced_for_vel) h5file.create_dataset( 'for_acc', data=forced_for_acc) h5file.create_dataset( 'num_steps', data=n_tstep) def generate_fem(): stiffness[0, ...] = base_stiffness_main stiffness[1, ...] = base_stiffness_fuselage stiffness[2, ...] = base_stiffness_tail mass[0, ...] = base_mass_main mass[1, ...] = base_mass_fuselage mass[2, ...] = base_mass_tail we = 0 wn = 0 # inner right wing beam_number[we:we + n_elem_main1] = 0 y[wn:wn + n_node_main1] = np.linspace(0.0, span_main1, n_node_main1) for ielem in range(n_elem_main1): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main1] = 0 elem_mass[we:we + n_elem_main1] = 0 boundary_conditions[0] = 1 # remember this is in B FoR app_forces[0] = [0, thrust, 0, 0, 0, 0] we += n_elem_main1 wn += n_node_main1 # outer right wing beam_number[we:we + n_elem_main1] = 0 y[wn:wn + n_node_main2 - 1] = y[wn - 1] + np.linspace(0.0, np.cos(lambda_dihedral) * span_main2, n_node_main2)[1:] z[wn:wn + n_node_main2 - 1] = z[wn - 1] + np.linspace(0.0, np.sin(lambda_dihedral) * span_main2, n_node_main2)[1:] for ielem in range(n_elem_main2): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main2] = 0 elem_mass[we:we + n_elem_main2] = 0 boundary_conditions[wn + n_node_main2 - 2] = -1 we += n_elem_main2 wn += n_node_main2 - 1 # inner left wing beam_number[we:we + n_elem_main1 - 1] = 1 y[wn:wn + n_node_main1 - 1] = np.linspace(0.0, -span_main1, n_node_main1)[1:] for ielem in range(n_elem_main1): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = 0 elem_stiffness[we:we + n_elem_main1] = 0 elem_mass[we:we + n_elem_main1] = 0 we += n_elem_main1 wn += n_node_main1 - 1 # outer left wing beam_number[we:we + n_elem_main2] = 1 y[wn:wn + n_node_main2 - 1] = y[wn - 1] + np.linspace(0.0, -np.cos(lambda_dihedral) * span_main2, n_node_main2)[1:] z[wn:wn + n_node_main2 - 1] = z[wn - 1] + np.linspace(0.0, np.sin(lambda_dihedral) * span_main2, n_node_main2)[1:] for ielem in range(n_elem_main2): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main2] = 0 elem_mass[we:we + n_elem_main2] = 0 boundary_conditions[wn + n_node_main2 - 2] = -1 we += n_elem_main2 wn += n_node_main2 - 1 # fuselage beam_number[we:we + n_elem_fuselage] = 2 x[wn:wn + n_node_fuselage - 1] = np.linspace(0.0, length_fuselage, n_node_fuselage)[1:] z[wn:wn + n_node_fuselage - 1] = np.linspace(0.0, offset_fuselage, n_node_fuselage)[1:] for ielem in range(n_elem_fuselage): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [0.0, 1.0, 0.0] conn[we, 0] = 0 elem_stiffness[we:we + n_elem_fuselage] = 1 elem_mass[we:we + n_elem_fuselage] = 1 we += n_elem_fuselage wn += n_node_fuselage - 1 global end_of_fuselage_node end_of_fuselage_node = wn - 1 # fin beam_number[we:we + n_elem_fin] = 3 x[wn:wn + n_node_fin - 1] = x[end_of_fuselage_node] z[wn:wn + n_node_fin - 1] = z[end_of_fuselage_node] + np.linspace(0.0, fin_height, n_node_fin)[1:] for ielem in range(n_elem_fin): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_of_fuselage_node elem_stiffness[we:we + n_elem_fin] = 2 elem_mass[we:we + n_elem_fin] = 2 we += n_elem_fin wn += n_node_fin - 1 end_of_fin_node = wn - 1 # right tail beam_number[we:we + n_elem_tail] = 4 x[wn:wn + n_node_tail - 1] = x[end_of_fin_node] y[wn:wn + n_node_tail - 1] = np.linspace(0.0, span_tail, n_node_tail)[1:] z[wn:wn + n_node_tail - 1] = z[end_of_fin_node] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_of_fin_node elem_stiffness[we:we + n_elem_tail] = 2 elem_mass[we:we + n_elem_tail] = 2 boundary_conditions[wn + n_node_tail - 2] = -1 we += n_elem_tail wn += n_node_tail - 1 # left tail beam_number[we:we + n_elem_tail] = 5 x[wn:wn + n_node_tail - 1] = x[end_of_fin_node] y[wn:wn + n_node_tail - 1] = np.linspace(0.0, -span_tail, n_node_tail)[1:] z[wn:wn + n_node_tail - 1] = z[end_of_fin_node] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = end_of_fin_node elem_stiffness[we:we + n_elem_tail] = 2 elem_mass[we:we + n_elem_tail] = 2 boundary_conditions[wn + n_node_tail - 2] = -1 we += n_elem_tail wn += n_node_tail - 1 with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=n_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=n_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=n_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) lumped_mass_nodes_handle = h5file.create_dataset( 'lumped_mass_nodes', data=lumped_mass_nodes) lumped_mass_handle = h5file.create_dataset( 'lumped_mass', data=lumped_mass) lumped_mass_inertia_handle = h5file.create_dataset( 'lumped_mass_inertia', data=lumped_mass_inertia) lumped_mass_position_handle = h5file.create_dataset( 'lumped_mass_position', data=lumped_mass_position) def generate_aero_file(): global x, y, z # control surfaces n_control_surfaces = 2 control_surface = np.zeros((n_elem, n_node_elem), dtype=int) - 1 control_surface_type = np.zeros((n_control_surfaces,), dtype=int) control_surface_deflection = np.zeros((n_control_surfaces,)) control_surface_chord = np.zeros((n_control_surfaces,), dtype=int) control_surface_hinge_coord = np.zeros((n_control_surfaces,), dtype=float) # control surface type 0 = static # control surface type 1 = dynamic control_surface_type[0] = 0 control_surface_deflection[0] = cs_deflection control_surface_chord[0] = m control_surface_hinge_coord[0] = -0.25 # nondimensional wrt elastic axis (+ towards the trailing edge) control_surface_type[1] = 0 control_surface_deflection[1] = rudder_static_deflection control_surface_chord[1] = 1 control_surface_hinge_coord[1] = -0. # nondimensional wrt elastic axis (+ towards the trailing edge) we = 0 wn = 0 # right wing (surface 0, beam 0) i_surf = 0 airfoil_distribution[we:we + n_elem_main, :] = 0 surface_distribution[we:we + n_elem_main] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_main] = True temp_chord = np.linspace(chord_main, chord_main, n_node_main) temp_sweep = np.linspace(0.0, 0 * np.pi / 180, n_node_main) node_counter = 0 for i_elem in range(we, we + n_elem_main): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = temp_chord[node_counter] elastic_axis[i_elem, i_local_node] = ea_main sweep[i_elem, i_local_node] = temp_sweep[node_counter] we += n_elem_main wn += n_node_main # left wing (surface 1, beam 1) i_surf = 1 airfoil_distribution[we:we + n_elem_main, :] = 0 # airfoil_distribution[wn:wn + n_node_main - 1] = 0 surface_distribution[we:we + n_elem_main] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_main - 1] = True # chord[wn:wn + num_node_main - 1] = np.linspace(main_chord, main_tip_chord, num_node_main)[1:] # chord[wn:wn + num_node_main - 1] = main_chord # elastic_axis[wn:wn + num_node_main - 1] = main_ea temp_chord = np.linspace(chord_main, chord_main, n_node_main) node_counter = 0 for i_elem in range(we, we + n_elem_main): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = temp_chord[node_counter] elastic_axis[i_elem, i_local_node] = ea_main sweep[i_elem, i_local_node] = -temp_sweep[node_counter] we += n_elem_main wn += n_node_main - 1 we += n_elem_fuselage wn += n_node_fuselage - 1 - 1 # # # fin (surface 2, beam 3) i_surf = 2 airfoil_distribution[we:we + n_elem_fin, :] = 1 # airfoil_distribution[wn:wn + n_node_fin] = 0 surface_distribution[we:we + n_elem_fin] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_fin] = True # chord[wn:wn + num_node_fin] = fin_chord for i_elem in range(we, we + n_elem_fin): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_fin elastic_axis[i_elem, i_local_node] = ea_fin control_surface[i_elem, i_local_node] = 1 # twist[end_of_fuselage_node] = 0 # twist[wn:] = 0 # elastic_axis[wn:wn + num_node_main] = fin_ea we += n_elem_fin wn += n_node_fin - 1 # # # # right tail (surface 3, beam 4) i_surf = 3 airfoil_distribution[we:we + n_elem_tail, :] = 2 # airfoil_distribution[wn:wn + n_node_tail] = 0 surface_distribution[we:we + n_elem_tail] = i_surf surface_m[i_surf] = m # XXX not very elegant aero_node[wn:] = True # chord[wn:wn + num_node_tail] = tail_chord # elastic_axis[wn:wn + num_node_main] = tail_ea for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): twist[i_elem, i_local_node] = -0 for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_tail elastic_axis[i_elem, i_local_node] = ea_tail control_surface[i_elem, i_local_node] = 0 we += n_elem_tail wn += n_node_tail # # # left tail (surface 4, beam 5) i_surf = 4 airfoil_distribution[we:we + n_elem_tail, :] = 2 # airfoil_distribution[wn:wn + n_node_tail - 1] = 0 surface_distribution[we:we + n_elem_tail] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_tail - 1] = True # chord[wn:wn + num_node_tail] = tail_chord # elastic_axis[wn:wn + num_node_main] = tail_ea # twist[we:we + num_elem_tail] = -tail_twist for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): twist[i_elem, i_local_node] = -0 for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_tail elastic_axis[i_elem, i_local_node] = ea_tail control_surface[i_elem, i_local_node] = 0 we += n_elem_tail wn += n_node_tail with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( generate_naca_camber(P=0, M=0))) naca_airfoil_tail = airfoils_group.create_dataset('1', data=np.column_stack( generate_naca_camber(P=0, M=0))) naca_airfoil_fin = airfoils_group.create_dataset('2', data=np.column_stack( generate_naca_camber(P=0, M=0))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input.attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # sweep sweep_input = h5file.create_dataset('sweep', data=sweep) dim_attr = sweep_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) control_surface_input = h5file.create_dataset('control_surface', data=control_surface) control_surface_deflection_input = h5file.create_dataset('control_surface_deflection', data=control_surface_deflection) control_surface_chord_input = h5file.create_dataset('control_surface_chord', data=control_surface_chord) control_surface_hinge_coord_input = h5file.create_dataset('control_surface_hinge_coord', data=control_surface_hinge_coord) control_surface_types_input = h5file.create_dataset('control_surface_type', data=control_surface_type) def generate_naca_camber(M=0, P=0): mm = M * 1e-2 p = P * 1e-1 def naca(x, mm, p): if x < 1e-6: return 0.0 elif x < p: return mm / (p * p) * (2 * p * x - x * x) elif x > p and x < 1 + 1e-6: return mm / ((1 - p) * (1 - p)) * (1 - 2 * p + 2 * p * x - x * x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, mm, p) for x in x_vec]) return x_vec, y_vec def generate_solver_file(): file_name = route + '/' + case_name + '.sharpy' settings = dict() settings['SHARPy'] = {'case': case_name, 'route': route, 'flow': flow, 'write_screen': 'on', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} settings['BeamLoader'] = {'unsteady': 'on', 'orientation': algebra.euler2quat(np.array([roll, alpha, beta]))} settings['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'mstar': int(20 / tstep_factor), 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': ['1', '0', '0'], 'dt': dt}} settings['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-1, 'min_delta': tolerance, 'gravity_on': gravity, 'gravity': 9.81} settings['StaticUvlm'] = {'print_info': 'on', 'horseshoe': 'off', 'num_cores': num_cores, 'n_rollup': 0, 'rollup_dt': dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho} settings['StaticCoupled'] = {'print_info': 'off', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': settings['NonLinearStatic'], 'aero_solver': 'StaticUvlm', 'aero_solver_settings': settings['StaticUvlm'], 'max_iter': 100, 'n_load_steps': n_step, 'tolerance': fsi_tolerance, 'relaxation_factor': structural_relaxation_factor} settings['StaticTrim'] = {'solver': 'StaticCoupled', 'solver_settings': settings['StaticCoupled'], 'initial_alpha': alpha, 'initial_deflection': cs_deflection, 'initial_thrust': thrust} settings['NonLinearDynamicCoupledStep'] = {'print_info': 'off', 'max_iterations': 950, 'delta_curved': 1e-1, 'min_delta': tolerance, 'newmark_damp': 5e-3, 'gravity_on': gravity, 'gravity': 9.81, 'num_steps': n_tstep, 'dt': dt, 'initial_velocity': u_inf} settings['NonLinearDynamicPrescribedStep'] = {'print_info': 'off', 'max_iterations': 950, 'delta_curved': 1e-1, 'min_delta': tolerance, 'newmark_damp': 5e-3, 'gravity_on': gravity, 'gravity': 9.81, 'num_steps': n_tstep, 'dt': dt, 'initial_velocity': u_inf * int(free_flight)} relative_motion = 'off' if not free_flight: relative_motion = 'on' settings['StepUvlm'] = {'print_info': 'off', 'num_cores': num_cores, 'convection_scheme': 2, 'gamma_dot_filtering': 6, 'velocity_field_generator': 'GustVelocityField', 'velocity_field_input': {'u_inf': int(not free_flight) * u_inf, 'u_inf_direction': [1., 0, 0], 'gust_shape': '1-cos', 'gust_parameters': {'gust_length': gust_length, 'gust_intensity': gust_intensity * u_inf}, 'offset': gust_offset, 'relative_motion': relative_motion}, 'rho': rho, 'n_time_steps': n_tstep, 'dt': dt} if free_flight: solver = 'NonLinearDynamicCoupledStep' else: solver = 'NonLinearDynamicPrescribedStep' settings['DynamicCoupled'] = {'structural_solver': solver, 'structural_solver_settings': settings[solver], 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'fsi_substeps': 200, 'fsi_tolerance': fsi_tolerance, 'relaxation_factor': relaxation_factor, 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': 0.5, 'n_time_steps': n_tstep, 'dt': dt, 'include_unsteady_force_contribution': 'on', 'postprocessors': ['BeamLoads', 'BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'BeamLoads': {'csv_output': 'off'}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}, 'AerogridPlot': { 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, }} settings['BeamLoads'] = {'csv_output': 'off'} settings['BeamPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on'} settings['AerogridPlot'] = {'include_rbm': 'on', 'include_forward_motion': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0, 'u_inf': u_inf, 'dt': dt} settings['Modal'] = {'print_info': True, 'use_undamped_modes': True, 'NumLambda': 30, 'rigid_body_modes': True, 'write_modes_vtk': 'on', 'print_matrices': 'on', 'continuous_eigenvalues': 'off', 'dt': dt, 'plot_eigenvalues': False} settings['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', 'linear_system_settings': { 'beam_settings': {'modal_projection': False, 'inout_coords': 'nodes', 'discrete_time': True, 'newmark_damp': 0.05, 'discr_method': 'newmark', 'dt': dt, 'proj_modes': 'undamped', 'use_euler': 'off', 'num_modes': 40, 'print_info': 'on', 'gravity': 'on', 'remove_dofs': []}, 'aero_settings': {'dt': dt, 'integr_order': 2, 'density': rho, 'remove_predictor': False, 'use_sparse': True, 'remove_inputs': ['u_gust']} }} settings['AsymptoticStability'] = {'print_info': 'on', 'modes_to_plot': [], 'display_root_locus': 'off', 'frequency_cutoff': 0, 'export_eigenvalues': 'off', 'num_evals': 40} import configobj config = configobj.ConfigObj() config.filename = file_name for k, v in settings.items(): config[k] = v config.write() clean_test_files() generate_fem() generate_aero_file() generate_solver_file() generate_dyn_file() ================================================ FILE: sharpy/cases/hangar/__init__.py ================================================ ================================================ FILE: sharpy/cases/hangar/horten_wing.py ================================================ """ Horten Wing Class Generator N Goizueta Nov 18 """ import numpy as np import os import h5py as h5 import sharpy.utils.geo_utils as geo_utils import sharpy.utils.algebra as algebra import configobj import scipy.linalg as sclalg class HortenWing: """ Horten Wing Class Generator A ``HortenWing`` class contains the basic geometry and properties of a simplified Horten wing, as described by Richards (2016) This class allows the user to quickly obtain SHARPy cases for the purposes of parametric analyses. """ def __init__(self, M, N, Mstarfactor, u_inf, rho=1.225, alpha_deg=0., beta_deg=0., roll_deg=0., cs_deflection_deg=0., thrust=5., physical_time=10, case_name_format=2, case_remarks=None, case_route='./cases/', case_name='horten'): # Discretisation self.M = M self.N = N self.Mstarfactor = Mstarfactor self.n_node_elem = 3 self.n_elem_wing = N self.n_elem_fuselage = 1 self.n_surfaces = 4 # Case admin if case_name_format == 0: self.case_name = case_name + '_u_inf%.4d_a%.4d' % (int(u_inf*100), int(alpha_deg * 100)) elif case_name_format == 1: self.case_name = case_name + '_u_inf%.4d' % int(u_inf*100) elif case_name_format == 2: self.case_name = case_name else: self.case_name = case_name + '_u_inf%.4d_%s' % (int(u_inf*100), case_remarks) self.case_route = os.path.abspath(case_route + self.case_name + '/') self.config = None # Flight conditions self.u_inf = u_inf self.rho = rho self.alpha = alpha_deg * np.pi / 180 self.roll = roll_deg * np.pi / 180 self.beta = beta_deg * np.pi / 180 self.cs_deflection = cs_deflection_deg * np.pi / 180 self.thrust = thrust # Compute number of nodes n_node = 0 self.n_node_wing = self.n_elem_wing * (self.n_node_elem - 1) self.n_node_fuselage = self.n_elem_fuselage * self.n_node_elem n_node += 2 * self.n_node_fuselage - 1 + 2 * self.n_node_wing self.n_node = n_node # Compute number of elements self.n_elem = 2 * (self.n_elem_wing + self.n_elem_fuselage) # Wing geometry self.span = 20.0 # [m] self.sweep_LE = 20 * np.pi / 180 # [rad] Leading Edge Sweep # self.c_root = 1.0 # [m] Root chord - Richards self.c_root = 1.5819 # [m] Root chord - Mardanpour 2014 self.taper_ratio = 0.17 # Mardanpour 2014 # self.taper_ratio = 0.25 # Richards self.thrust_nodes = [self.n_node_fuselage - 1, self.n_node_fuselage + self.n_node_wing + 1] self.loc_cg = 0.45 # CG position wrt to LE (from sectional analysis) # EA is the reference in NATASHA - defined with respect to the midchord. SHARPy is wrt to LE and as a pct of # local chord self.main_ea_root = 0.5 + 0.15*0.0254 / self.c_root self.main_ea_tip = 0.5 + 0.21*0.0254 / (self.c_root*self.taper_ratio) # FUSELAGE GEOMETRY # self.fuselage_width = 1. self.fuselage_width = 0.8248 self.c_fuselage = 84*0.0254 # WASH OUT self.washout_root = 0*np.pi/180 self.washout_tip = -2 * np.pi / 180 # Horseshoe wake self.horseshoe = False self.wake_type = 2 self.dt_factor = 1 self.dt = 1 / self.M / self.u_inf * self.dt_factor # Dynamics self.physical_time = physical_time self.n_tstep = int(physical_time/self.dt) self.gust_intensity = 0.05 # Numerics self.tolerance = 1e-12 self.fsi_tolerance = 1e-10 self.relaxation_factor = 0.2 # H5 Variables initialisation as class attributes # coordinates self.x = np.zeros((n_node,)) self.y = np.zeros((n_node,)) self.z = np.zeros((n_node,)) # beam number self.beam_number = np.zeros(self.n_elem, dtype=int) # frame of reference delta self.frame_of_reference_delta = np.zeros((self.n_elem, self.n_node_elem, 3)) # connectivity of beams self.connectivities = np.zeros((self.n_elem, self.n_node_elem), dtype=int) # stiffness self.n_stiffness = self.n_elem_wing + self.n_elem_fuselage self.base_stiffness = np.zeros((self.n_stiffness, 6, 6)) self.elem_stiffness = np.zeros((self.n_elem,), dtype=int) # mass self.n_mass = self.n_elem_wing * 2 // 2 self.base_mass = np.zeros((self.n_mass, 6, 6)) self.elem_mass = np.zeros(self.n_elem, dtype=int) # boundary conditions self.boundary_conditions = np.zeros((n_node,), dtype=int) # applied forces self.app_forces = np.zeros((n_node, 6)) self.n_lumped_mass = 3 self.lumped_mass_nodes = np.zeros((self.n_lumped_mass), dtype=int) self.lumped_mass = np.zeros(self.n_lumped_mass) self.lumped_mass_inertia = np.zeros((self.n_lumped_mass, 3, 3)) self.lumped_mass_position = np.zeros((self.n_lumped_mass, 3)) # Aerodynamic properties # H5 AERO FILE VARIABLES INITIALISATION # airfoil distribution self.airfoil_distribution = np.zeros((self.n_elem, self.n_node_elem), dtype=int) # surface distribution self.surface_distribution = np.zeros((self.n_elem,), dtype=int) - 1 self.surface_m = np.zeros((self.n_surfaces,), dtype=int) self.m_distribution = 'uniform' # aerodynamic nodes boolean self.aero_nodes = np.zeros((self.n_node,), dtype=bool) # aero twist self.twist = np.zeros((self.n_elem, self.n_node_elem)) # chord self.chord = np.zeros((self.n_elem, self.n_node_elem)) # elastic axis self.elastic_axis = np.zeros((self.n_elem, self.n_node_elem)) # control surfaces attributes initialisation self.n_control_surfaces = 1 self.control_surface = np.zeros((self.n_elem, self.n_node_elem), dtype=int) - 1 self.control_surface_type = np.zeros((self.n_control_surfaces,), dtype=int) self.control_surface_deflection = np.zeros((self.n_control_surfaces,)) self.control_surface_chord = np.zeros((self.n_control_surfaces,), dtype=int) self.control_surface_hinge_coord = np.zeros((self.n_control_surfaces,), dtype=float) self.settings = dict() def initialise(self): if not os.path.exists(self.case_route): os.makedirs(self.case_route) # Compute number of nodes n_node = 0 self.n_node_wing = self.n_elem_wing * (self.n_node_elem - 1) self.n_node_fuselage = self.n_elem_fuselage * self.n_node_elem n_node += 2 * self.n_node_fuselage - 1 + 2 * self.n_node_wing self.n_node = n_node # Compute number of elements self.n_elem = 2 * (self.n_elem_wing + self.n_elem_fuselage) self.dt = 1 / self.M / self.u_inf * self.dt_factor # H5 Variables initialisation as class attributes # coordinates self.x = np.zeros((n_node,)) self.y = np.zeros((n_node,)) self.z = np.zeros((n_node,)) # beam number self.beam_number = np.zeros(self.n_elem, dtype=int) # frame of reference delta self.frame_of_reference_delta = np.zeros((self.n_elem, self.n_node_elem, 3)) # connectivity of beams self.connectivities = np.zeros((self.n_elem, self.n_node_elem), dtype=int) # stiffness self.n_stiffness = self.n_elem_wing + self.n_elem_fuselage self.base_stiffness = np.zeros((self.n_stiffness, 6, 6)) self.elem_stiffness = np.zeros((self.n_elem,), dtype=int) # mass self.base_mass = np.zeros((self.n_mass, 6, 6)) self.elem_mass = np.zeros(self.n_elem, dtype=int) # boundary conditions self.boundary_conditions = np.zeros((n_node,), dtype=int) # applied forces self.app_forces = np.zeros((n_node, 6)) self.n_lumped_mass = 3 self.lumped_mass_nodes = np.zeros((self.n_lumped_mass), dtype=int) self.lumped_mass = np.zeros(self.n_lumped_mass) self.lumped_mass_inertia = np.zeros((self.n_lumped_mass, 3, 3)) self.lumped_mass_position = np.zeros((self.n_lumped_mass, 3)) # Aerodynamic properties # H5 AERO FILE VARIABLES INITIALISATION # airfoil distribution self.airfoil_distribution = np.zeros((self.n_elem, self.n_node_elem), dtype=int) # surface distribution self.surface_distribution = np.zeros((self.n_elem,), dtype=int) - 1 self.surface_m = np.zeros((self.n_surfaces,), dtype=int) self.m_distribution = 'uniform' # aerodynamic nodes boolean self.aero_nodes = np.zeros((self.n_node,), dtype=bool) # aero twist self.twist = np.zeros((self.n_elem, self.n_node_elem)) # chord self.chord = np.zeros((self.n_elem, self.n_node_elem)) # elastic axis self.elastic_axis = np.zeros((self.n_elem, self.n_node_elem)) # control surfaces attributes initialisation self.n_control_surfaces = 1 self.control_surface = np.zeros((self.n_elem, self.n_node_elem), dtype=int) - 1 self.control_surface_type = np.zeros((self.n_control_surfaces,), dtype=int) self.control_surface_deflection = np.zeros((self.n_control_surfaces,)) self.control_surface_chord = np.zeros((self.n_control_surfaces,), dtype=int) self.control_surface_hinge_coord = np.zeros((self.n_control_surfaces,), dtype=float) self.settings = dict() def dynamic_control_surface(self, *delta): """ Generate dynamic control surface input files Args: delta (list): list of numpy arrays containing deflection time history Returns: """ i = 0 for cs in delta: self.control_surface_type[i] = 1 np.savetxt(self.case_route + '/' + self.case_name + '.input.txt', cs) i += 1 def planform_area(self): S_fus = 0.5 * (self.c_fuselage + self.c_root) * self.fuselage_width S_wing = 0.5 * (self.c_root + self.c_root*self.taper_ratio) * self.span / 2 return 2*S_fus + 2*S_wing def update_mass_stiffness(self, sigma=1.): r""" The mass and stiffness matrices are computed. Both vary over the span of the wing, hence a dictionary is created that acts as a database of the different properties along the wing. The variation of the stiffness is cubic along the span: .. math:: \mathbf{K} = \mathbf{K}_0\bar{c}^3 where :math:`\mathbf{K}_0 is the stiffness of the wing root section and :math:`\bar{c}` is the ratio between the local chord and the root chord. The variation of the sectional mass is quadratic along the span: .. math:: \mu = \mu_0\,\bar{c}^2 where :math:`\mu` is the mass per unit span and the zero subscript denotes the root value. The sectional inertia is varied linearly along the span, based on the information by Mardanpour 2013. Three lumped masses are included with the following properties (Richards, 2013) ===== ===== ================= ========= =============================== ============ No Node Relative Position Mass [kg] Inertia [kg m^2] Description ===== ===== ================= ========= =============================== ============ ``0`` ``2`` ``[0,0,0]`` ``5.24`` ``[0.29547, 0.29322, 0.29547]`` Right Engine ``1`` ``S`` ``[0,0,0]`` ``5.24`` ``[0.29547, 0.29322, 0.29547]`` Left Engine ``2`` ``0`` ``[0,0,0]`` ``15.29`` ``[0.5, 1.0, 1.0]*15.29`` Fuselage ===== ===== ================= ========= =============================== ============ Args: sigma (float): stiffening factor Returns: """ n_elem_fuselage = self.n_elem_fuselage n_elem_wing = self.n_elem_wing n_node_wing = self.n_node_wing n_node_fuselage = self.n_node_fuselage c_root = self.c_root taper_ratio = self.taper_ratio # Local chord to root chord initialisation c_bar_temp = np.linspace(c_root, taper_ratio * c_root, n_elem_wing) # Structural properties at the wing root section from Richards 2016 ea = 1e6 ga = 1e6 gj = 4.24e5 eiy = 3.84e5 eiz = 2.46e7 # Sectional mass from Richards 2016 mu_0 = 9.761 # Bending inertia properties from Mardanpour 2013 j_root = 0.303 j_tip = 0.2e-2 * 4.448 / 9.81 # Number of stiffnesses used n_stiffness = self.n_stiffness # Initialise the stiffness database base_stiffness = self.base_stiffness # Wing root section stiffness properties # stiffness_root = sigma * np.diag([ea, ga, ga, gj, eiy, eiz]) stiffness_root = np.array([[3.23624595e+09, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [0.00000000e+00, 1.00000000e+14, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [0.00000000e+00, 0.00000000e+00, 1.00000000e+14, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 8.04987046e+07, -1.69971789e+05, 5.69905411e+07], [0.00000000e+00, 0.00000000e+00, 0.00000000e+00, -1.69971789e+05, 5.03651190e+07, -6.70649560e+06], [0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 5.69905411e+07, -6.70649560e+06, 2.24864852e+09]]) * sigma stiffness_tip = np.array([[5.86034256e+07, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [0.00000000e+00, 1.00000000e+15, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [0.00000000e+00, 0.00000000e+00, 1.00000000e+15, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 2.93227100e+04, -2.11280834e+03, 2.52782426e+04], [0.00000000e+00, 0.00000000e+00, 0.00000000e+00, -2.11280834e+03, 1.44883639e+05, -2.52913470e+04], [0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 2.52782426e+04, -2.52913470e+04, 3.02592902e+05]]) * sigma stiffness_root = np.diag(np.diag(stiffness_root)) stiffness_tip = np.diag(np.diag(stiffness_tip)) # Rigid fuselage sigma_fuselage = 1000 base_stiffness[0, :, :] = sigma_fuselage * stiffness_root # Cubic variation of the stiffness along the span - Richards # for stiff_entry in range(1, n_stiffness): # base_stiffness[stiff_entry, :, :] = stiffness_root * c_bar_temp[stiff_entry - 1] ** 3 # Linear variation between root an tip as per Mardanpour 2014 alpha = np.linspace(0, 1, self.n_elem_wing) for i_elem in range(0, self.n_elem_wing): base_stiffness[i_elem + 1, :, :] = stiffness_root*(1-alpha[i_elem]) + stiffness_tip*alpha[i_elem] # Mass variation along the span n_mass = self.n_mass sigma_mass = 1 # Initialise database base_mass = self.base_mass mu_root = 2.784472 * sigma_mass chi_root_right = np.array([0, -5.29, 0.594]) * 0.0254 * 0 chi_root_left = np.array([0, +5.29, 0.594]) * 0.0254 * 0 m_root = np.eye(3) * mu_root j_root = np.array([[0.30378797, 0., 0.], [0., 0.0122422, 0.], [0., 0., 0.30016065]]) j_root = np.diag([0.1, 0.1, 0.2]) mass_root_right = sclalg.block_diag(np.diag([mu_root, mu_root, mu_root]), j_root) mass_root_left = sclalg.block_diag(np.diag([mu_root, mu_root, mu_root]), j_root) mass_root_right[:3, -3:] = -mu_root * algebra.skew(chi_root_right) mass_root_right[-3:, :3] = mu_root * algebra.skew(chi_root_right) mass_root_left[:3, -3:] = -mu_root * algebra.skew(chi_root_left) mass_root_left[-3:, :3] = mu_root * algebra.skew(chi_root_left) mu_tip = 0.284084 * sigma_mass chi_tip_right = np.array([0, -1.644, +0.563]) * 0.0254 * 0 chi_tip_left = np.array([0, +1.644, +0.563]) * 0.0254 * 0 j_tip = np.array([[9.06829766e-04, 0.00000000e+00, 0.00000000e+00], [0.00000000e+00, 6.34780836e-05, 0.00000000e+00], [0.00000000e+00, 0.00000000e+00, 8.16146789e-04]]) j_tip = np.diag([0.1, 0.1, 0.2]) mass_tip_right = sclalg.block_diag(np.diag([mu_tip, mu_tip, mu_tip]), j_tip) mass_tip_left = sclalg.block_diag(np.diag([mu_tip, mu_tip, mu_tip]), j_tip) mass_tip_right[:3, -3:] += -algebra.skew(chi_tip_right) * mu_tip mass_tip_right[-3:, :3] += algebra.skew(chi_tip_right) * mu_tip mass_tip_left[:3, -3:] += -algebra.skew(chi_tip_right) * mu_tip mass_tip_left[-3:, :3] += algebra.skew(chi_tip_right) * mu_tip mass_tip_left = mass_tip_right mass_root_left = mass_root_right for i_elem in range(self.n_elem_wing): base_mass[i_elem, :, :] = mass_root_right*(1-alpha[i_elem]) + mass_tip_right*alpha[i_elem] # for i_elem in range(self.n_elem_wing, 2*self.n_elem_wing): # base_mass[i_elem, :, :] = mass_root_left*(1-alpha[i_elem-self.n_elem_wing]) + mass_tip_left*alpha[i_elem-self.n_elem_wing] # Quadratic variation in the mass per unit span - Richards # mu_temp = mu_0 * np.ones_like(c_bar_temp) # j_temp = np.linspace(j_root, j_tip, n_elem_wing) # for mass_entry in range(n_mass): # base_mass[mass_entry, :, :] = np.diag([ # mu_temp[mass_entry], # mu_temp[mass_entry], # mu_temp[mass_entry], # j_temp[mass_entry], # j_temp[mass_entry], # j_temp[mass_entry] # ]) # Lumped mass initialisation # 0 - Right engine # 1 - Left engine # 2 - Fuselage lumped_mass_nodes = self.lumped_mass_nodes lumped_mass = self.lumped_mass lumped_mass_inertia = self.lumped_mass_inertia lumped_mass_position = self.lumped_mass_position # Lumped masses nodal position lumped_mass_nodes[0] = 2 lumped_mass_nodes[1] = n_node_fuselage + n_node_wing + 1 lumped_mass_nodes[2] = 0 # Lumped mass value from Richards 2013 # lumped_mass[0:2] = 51.445 / 9.81 # lumped_mass[2] = 150 / 9.81 # Lumped masses from Mardanpour # Pilot mass lumped_mass[2] = 16.06 lumped_mass_position[2] = np.array([0., -0.254, -0.254]) # Engine mass lumped_mass[0:2] = 0.535 lumped_mass_inertia[0, :, :] = np.diag([0.02994352, 0.02994352, 0.02994352]) lumped_mass_inertia[1, :, :] = np.diag([0.02994352, 0.02994352, 0.02994352]) # Lumped mass inertia # lumped_mass_inertia[0, :, :] = np.diag([0.29547, 0.29322, 0.29547]) # lumped_mass_inertia[1, :, :] = np.diag([0.29547, 0.29322, 0.29547]) # lumped_mass_inertia[2, :, :] = np.diag([0.5, 1, 1]) * lumped_mass[2] # Define class attributes self.lumped_mass = lumped_mass * 0 self.lumped_mass_nodes = lumped_mass_nodes * 0 self.lumped_mass_inertia = lumped_mass_inertia * 0 self.lumped_mass_position = lumped_mass_position * 0 self.base_stiffness = base_stiffness self.base_mass = base_mass def update_fem_prop(self): """ Computes the FEM properties prior to analysis such as the connectivity matrix, coordinates, etc Returns: """ # Obtain class attributes n_node_elem = self.n_node_elem n_elem = self.n_elem n_elem_wing = self.n_elem_wing n_elem_fuselage = self.n_elem_fuselage n_node = self.n_node n_node_wing = self.n_node_wing n_node_fuselage = self.n_node_fuselage fuselage_width = self.fuselage_width thrust = self.thrust thrust_nodes = self.thrust_nodes span = self.span sweep_LE = self.sweep_LE # mass and stiffness matrices stiffness = self.base_stiffness mass = self.base_mass n_stiffness = stiffness.shape[0] n_mass = mass.shape[0] # H5 FEM FILE VARIABLES INITIALISATION # coordinates x = np.zeros((n_node,)) y = np.zeros((n_node,)) z = np.zeros((n_node,)) # twist structural_twist = np.zeros_like(x) # beam number beam_number = np.zeros(n_elem, dtype=int) # frame of reference delta frame_of_reference_delta = np.zeros((n_elem, n_node_elem, 3)) # connectivity of beams conn = np.zeros((n_elem, n_node_elem), dtype=int) # stiffness stiffness = np.zeros((n_stiffness, 6, 6)) elem_stiffness = np.zeros((n_elem,), dtype=int) # mass mass = np.zeros((n_mass, 6, 6)) elem_mass = np.zeros(n_elem, dtype=int) # boundary conditions boundary_conditions = np.zeros((n_node,), dtype=int) # applied forces app_forces = np.zeros((n_node, 6)) # assemble connectivites # worked elements we = 0 # worked nodes wn = 0 # RIGHT RIGID FUSELAGE beam_number[we:we + 1] = 0 # coordinates x[wn:wn + n_node_fuselage] = 0 y[wn:wn + n_node_fuselage] = np.linspace(0, fuselage_width / 2, n_node_fuselage) # connectivities elem_mass[0] = 0 conn[we, :] = [0, 2, 1] # frame of reference change frame_of_reference_delta[0, 0, :] = [-1.0, 0.0, 0.0] frame_of_reference_delta[0, 1, :] = [-1.0, 0.0, 0.0] frame_of_reference_delta[0, 2, :] = [-1.0, 0.0, 0.0] # element stiffness elem_stiffness[0] = 0 elem_mass[0] = 0 # boundary conditions boundary_conditions[0] = 1 # applied forces - engine 1 app_forces[thrust_nodes[0]] = [0, thrust, 0, 0, 0, 0] # updated worked nodes and elements we += n_elem_fuselage wn += n_node_fuselage # RIGHT WING beam_number[we:we + n_elem_wing] = 1 # y coordinate (positive right) y[wn:wn + n_node_wing] = np.linspace(fuselage_width / 2, span / 2, n_node_wing + 1)[1:] x[wn:wn + n_node_wing] = 0 + (y[wn:wn + n_node_wing] - fuselage_width / 2) * np.tan(sweep_LE) # connectivities for ielem in range(n_elem_wing): conn[we + ielem, :] = (np.ones(n_node_elem) * (we + ielem) * (n_node_elem - 1) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_mass[we + ielem] = ielem elem_stiffness[we + ielem] = ielem + 1 # element stiffness and mass # elem_stiffness[we:we+n_elem_wing] = 0 # elem_mass[we:we+n_elem_wing] = 0 # boundary conditions of free end boundary_conditions[wn + n_node_wing - 1] = -1 # update worked elements and nodes we += n_elem_wing wn += n_node_wing # LEFT FUSELAGE beam_number[we:we + n_elem_fuselage] = 2 # coordinates y[wn:wn + n_node_fuselage - 1] = np.linspace(0, -fuselage_width / 2, n_node_fuselage)[1:] x[wn:wn + n_node_fuselage - 1] = 0 # connectivity conn[we, :] = [0, wn + 1, wn] # frame of reference delta for ielem in range(n_elem_fuselage): for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] # element stiffness and mass elem_stiffness[we:we + n_elem_fuselage] = 0 elem_mass[we:we + n_elem_fuselage] = self.n_elem_wing # applied forces - engine 2 app_forces[thrust_nodes[1]] = [0, -thrust, 0, 0, 0, 0] # update worked elements and nodes we += n_elem_fuselage wn += n_node_fuselage - 1 # LEFT WING # coordinates beam_number[we:we + n_elem_wing] = 3 y[wn:wn + n_node_wing] = np.linspace(-fuselage_width / 2, -span / 2, n_node_wing + 1)[1:] x[wn:wn + n_node_wing] = 0 + -1 * (y[wn:wn + n_node_wing] + fuselage_width / 2) * np.tan(sweep_LE) # left wing connectivities for ielem in range(n_elem_wing): conn[we + ielem, :] = np.ones(n_node_elem) * (we + ielem) * (n_node_elem - 1) + [0, 2, 1] for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] elem_mass[we + ielem] = ielem + self.n_elem_wing elem_stiffness[we + ielem] = ielem + 1 # element stiffness and mass # elem_stiffness[we:we+n_node_wing] = 0 # boundary conditions at the free end boundary_conditions[wn + n_node_wing - 1] = -1 # update worked elements and nodes we += n_elem_wing wn += n_node_wing # set attributes self.x = x self.y = y self.z = z self.connectivities = conn self.elem_stiffness = elem_stiffness self.elem_mass = elem_mass self.frame_of_reference_delta = frame_of_reference_delta self.boundary_conditions = boundary_conditions self.beam_number = beam_number self.app_forces = app_forces def generate_fem_file(self): """ Generates the ``.fem.h5`` folder containing the structural information of the problem The file is written to ``self.case_route / self.case_name .fem.h5`` """ with h5.File(self.case_route + '/' + self.case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((self.x, self.y, self.z))) connectivities = h5file.create_dataset('connectivities', data=self.connectivities) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=self.n_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=self.n_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=self.n_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=self.base_stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=self.elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=self.base_mass) mass_handle = h5file.create_dataset( 'elem_mass', data=self.elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=self.frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=np.zeros((self.n_elem, self.n_node_elem))) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=self.boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=self.beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=self.app_forces) lumped_mass_nodes_handle = h5file.create_dataset( 'lumped_mass_nodes', data=self.lumped_mass_nodes) lumped_mass_handle = h5file.create_dataset( 'lumped_mass', data=self.lumped_mass) lumped_mass_inertia_handle = h5file.create_dataset( 'lumped_mass_inertia', data=self.lumped_mass_inertia) lumped_mass_position_handle = h5file.create_dataset( 'lumped_mass_position', data=self.lumped_mass_position) def create_linear_simulation(self, delta_e=None, delta_dot=None): Kpanels = self.M * (self.n_node - 1) Kvertices = (self.M + 1) * self.n_node Kpanels_wake = Kpanels * self.Mstarfactor n_states_aero = 3 * Kpanels + Kpanels_wake # n_inputs_aero = 2 * 3 * Kvertices n_states_struct = 2*(6 * (self.n_node - 1) + 9) n_inputs_struct = n_states_struct // 2 x0 = np.zeros((n_states_aero + n_states_struct)) # x0[-7] = 0.05 # x0[-4:] = (algebra.euler2quat([ -5*np.pi/180, 0, 0])) u = np.zeros((self.n_tstep, n_states_struct + n_inputs_struct + 2 * self.n_control_surfaces)) # u[0:3, -7] = -1000 if delta_e is not None: u[:, n_states_struct:n_states_struct+self.n_control_surfaces] = delta_e u[:, n_states_struct + self.n_control_surfaces:n_states_struct+self.n_control_surfaces + self.n_control_surfaces] = delta_dot # u[10:15, -8] = 100 self.generate_linear_sim_files(x0, u) def generate_linear_sim_files(self, x0, input_vec): if not os.path.exists(self.case_route): os.makedirs(self.case_route) with h5.File(self.case_route + '/' + self.case_name + '.lininput.h5', 'a') as h5file: x0 = h5file.create_dataset( 'x0', data=x0) u = h5file.create_dataset( 'u', data=input_vec) def clean_test_files(self): """ Clears previously generated files """ case_name = self.case_name route = self.case_route # FEM fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) # Dynamics File dyn_file_name = route + '/' + case_name + '.dyn.h5' if os.path.isfile(dyn_file_name): os.remove(dyn_file_name) # Aerodynamics File aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) # Solver file solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) # Flight conditions file flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) # Linear inputs file lin_file_name = self.case_route + '/' + self.case_name + '.lininput.h5' if os.path.isfile(lin_file_name): os.remove(lin_file_name) # if os.path.isdir(route): # os.system('rm -r %s' %route) def update_aero_properties(self): """ Updates the aerodynamic properties of the horten wing """ # Retrieve attributes n_elem = self.n_elem n_node_elem = self.n_node_elem n_node_wing = self.n_node_wing n_node_fuselage = self.n_node_fuselage n_elem_fuselage = self.n_elem_fuselage n_elem_wing = self.n_elem_wing c_root = self.c_root taper_ratio = self.taper_ratio washout_root = self.washout_root washout_tip = self.washout_tip n_control_surfaces = self.n_control_surfaces cs_deflection = self.cs_deflection m = self.M main_ea_root = self.main_ea_root main_ea_tip = self.main_ea_tip airfoil_distribution = self.airfoil_distribution chord = self.chord surface_distribution = self.surface_distribution surface_m = self.surface_m aero_nodes = self.aero_nodes elastic_axis = self.elastic_axis twist = self.twist control_surface = self.control_surface control_surface_type = self.control_surface_type control_surface_deflection = self.control_surface_deflection control_surface_chord = self.control_surface_chord control_surface_hinge_coord = self.control_surface_hinge_coord self.dt = 1 / self.M / self.u_inf # control surface type: 0 = static # control surface type: 1 = dynamic control_surface_type[0] = self.control_surface_type[0] control_surface_deflection[0] = cs_deflection control_surface_chord[0] = 2 # m control_surface_hinge_coord[0] = 0.25 # RIGHT FUSELAGE (Surface 0, Beam 0) we = 0 wn = 0 i_surf = 0 airfoil_distribution[we:we + n_elem_fuselage] = 0 surface_distribution[we:we + n_elem_fuselage] = i_surf surface_m[i_surf] = m aero_nodes[wn:wn + n_node_fuselage] = True temp_chord = np.linspace(self.c_fuselage, self.c_root, self.n_node_fuselage) temp_washout = washout_root # apply chord and elastic axis at each node node_counter = 0 for ielem in range(we, we + n_elem_fuselage): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[ielem, i_local_node] = temp_chord[node_counter] elastic_axis[ielem, i_local_node] = main_ea_root twist[ielem, i_local_node] = -temp_washout we += n_elem_fuselage wn += n_node_fuselage # RIGHT WING (Surface 1, Beam 1) # surface_id i_surf = 1 airfoil_distribution[we:we + n_elem_wing, :] = 0 surface_distribution[we:we + n_elem_wing] = i_surf surface_m[i_surf] = m # specify aerodynamic characteristics of wing nodes aero_nodes[wn:wn + n_node_wing - 1] = True # linear taper initialisation temp_chord = np.linspace(c_root, taper_ratio * c_root, n_node_wing + 1) # linear wash out initialisation temp_washout = np.linspace(washout_root, washout_tip, n_node_wing + 1) # elastic axis variation temp_ea = np.linspace(main_ea_root, main_ea_tip, n_node_wing + 1) # apply chord and elastic axis at each node node_counter = 0 for ielem in range(we, we + n_elem_wing): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[ielem, i_local_node] = temp_chord[node_counter] elastic_axis[ielem, i_local_node] = temp_ea[node_counter] twist[ielem, i_local_node] = -temp_washout[node_counter] if ielem >= round(((we + n_elem_wing) / 2)): control_surface[ielem, i_local_node] = 0 # update working element and node we += n_elem_wing wn += n_node_wing - 1 # LEFT FUSELAGE (Surface 2, Beam 2) i_surf = 2 airfoil_distribution[we:we + n_elem_fuselage] = 0 surface_distribution[we:we + n_elem_fuselage] = i_surf surface_m[i_surf] = m aero_nodes[wn:wn + n_node_fuselage] = True temp_chord = np.linspace(self.c_fuselage, self.c_root, self.n_node_fuselage) temp_washout = washout_root # apply chord and elastic axis at each node node_counter = 0 for ielem in range(we, we + n_elem_fuselage): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[ielem, i_local_node] = temp_chord[node_counter] elastic_axis[ielem, i_local_node] = main_ea_root twist[ielem, i_local_node] = -temp_washout we += n_elem_fuselage wn += n_node_fuselage # LEFT WING (Surface 3, Beam 3) i_surf = 3 airfoil_distribution[we:we + n_elem_wing, :] = 0 surface_distribution[we: we + n_elem_wing] = i_surf surface_m[i_surf] = m # linear taper initialisation temp_chord = np.linspace(c_root, taper_ratio * c_root, n_node_wing + 1) # linear wash out initialisation temp_washout = np.linspace(washout_root, washout_tip, n_node_wing + 1) # specify aerodynamic characterisics of wing nodes aero_nodes[wn:wn + n_node_wing] = True # linear taper initialisation # apply chord and elastic axis at each node node_counter = 0 for ielem in range(we, we + n_elem_wing): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[ielem, i_local_node] = temp_chord[node_counter] elastic_axis[ielem, i_local_node] = temp_ea[node_counter] twist[ielem, i_local_node] = -temp_washout[node_counter] if ielem >= round((we + n_elem_wing / 2)): control_surface[ielem, i_local_node] = 0 # update working element and node we += n_elem_wing wn += n_node_wing # end node is the middle node mid_chord = np.array(chord[:, 1], copy=True) chord[:, 1] = chord[:, 2] chord[:, 2] = mid_chord mid_ea = np.array(elastic_axis[:, 1], copy=True) elastic_axis[:, 1] = elastic_axis[:, 2] elastic_axis[:, 2] = mid_ea # Update aerodynamic attributes of class self.chord = chord self.twist = twist self.aero_nodes = aero_nodes self.elastic_axis = elastic_axis self.control_surface = control_surface def generate_aero_file(self, route=None, case_name=None): """ Generates the ``.aero.h5`` file with the aerodynamic properties of the wing Args: route (str): route to write case file. If None is specified the default will be used case_name (str): name of file. If None is specified the default will be used """ if not route: route = self.case_route if not case_name: case_name = self.case_name if not os.path.isdir(self.case_route): os.makedirs(self.case_route) chord = self.chord twist = self.twist airfoil_distribution = self.airfoil_distribution surface_distribution = self.surface_distribution surface_m = self.surface_m m_distribution = self.m_distribution aero_nodes = self.aero_nodes elastic_axis = self.elastic_axis control_surface = self.control_surface control_surface_deflection = self.control_surface_deflection control_surface_chord = self.control_surface_chord control_surface_hinge_coord = self.control_surface_hinge_coord control_surface_type = self.control_surface_type control_surface_deflection[0] = self.cs_deflection with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( geo_utils.generate_naca_camber(P=0, M=0))) naca_airfoil_tail = airfoils_group.create_dataset('1', data=np.column_stack( geo_utils.generate_naca_camber(P=0, M=0))) naca_airfoil_fin = airfoils_group.create_dataset('2', data=np.column_stack( geo_utils.generate_naca_camber(P=0, M=0))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input.attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_nodes) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) control_surface_input = h5file.create_dataset('control_surface', data=control_surface) control_surface_deflection_input = h5file.create_dataset('control_surface_deflection', data=control_surface_deflection) control_surface_chord_input = h5file.create_dataset('control_surface_chord', data=control_surface_chord) control_surface_hinge_coord_input = h5file.create_dataset('control_surface_hinge_coord', data=control_surface_hinge_coord) control_surface_types_input = h5file.create_dataset('control_surface_type', data=control_surface_type) def set_default_config_dict(self, route=None, case_name=None): """ Generates default solver configuration file Returns: """ if not route: route = self.case_route if not case_name: case_name = self.case_name u_inf = self.u_inf rho = self.rho dt = self.dt tolerance = self.tolerance alpha = self.alpha beta = self.beta thrust = self.thrust thrust_nodes = self.thrust_nodes cs_deflection = self.cs_deflection fsi_tolerance = self.fsi_tolerance n_tstep = self.n_tstep gust_intensity = self.gust_intensity relaxation_factor = self.relaxation_factor file_name = route + '/' + case_name + '.sharpy' settings = dict() settings['SHARPy'] = {'case': case_name, 'route': route, 'flow': ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'Modal', 'AerogridPlot', 'BeamPlot', 'SaveData'], 'write_screen': 'on', 'write_log': 'on', 'log_folder': route + '/output/' + case_name + '/', 'log_file': case_name + '.log'} settings['BeamLoader'] = {'unsteady': 'off', 'orientation': algebra.euler2quat(np.array([self.roll, self.alpha, self.beta]))} settings['StaticUvlm'] = {'print_info': 'on', 'horseshoe': self.horseshoe, 'num_cores': 4, 'n_rollup': 1, 'rollup_dt': dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho} settings['StaticCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 200, 'num_load_steps': 1, 'delta_curved': 1e-5, 'min_delta': tolerance, 'gravity_on': 'on', 'gravity': 9.81}, 'aero_solver': 'StaticUvlm', 'aero_solver_settings': {'print_info': 'on', 'horseshoe': self.horseshoe, 'num_cores': 4, 'n_rollup': int(0), 'rollup_dt': dt, #self.c_root / self.M / self.u_inf, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho}, 'max_iter': 200, 'n_load_steps': 1, 'tolerance': tolerance, 'relaxation_factor': 0.2} if self.horseshoe is True: settings['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 1, 'freestream_dir': ['1', '0', '0'], 'control_surface_deflection': ['']} else: settings['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': int(self.M * self.Mstarfactor), 'freestream_dir': ['1', '0', '0'], 'control_surface_deflection': ['']} settings['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-8, 'min_delta': tolerance, 'gravity_on': True, 'gravity': 9.81} settings['StaticTrim'] = {'solver': 'StaticCoupled', 'solver_settings': settings['StaticCoupled'], 'thrust_nodes': thrust_nodes, 'initial_alpha': alpha, 'initial_deflection': cs_deflection, 'initial_thrust': thrust, 'max_iter': 200, 'fz_tolerance': 1e-2, 'fx_tolerance': 1e-2, 'm_tolerance': 1e-2} settings['Trim'] = {'solver': 'StaticCoupled', 'solver_settings': settings['StaticCoupled'], 'initial_alpha': alpha, 'initial_beta': beta, 'cs_indices': [0], 'initial_cs_deflection': [cs_deflection], 'thrust_nodes': thrust_nodes, 'initial_thrust': [thrust, thrust]} settings['NonLinearDynamicCoupledStep'] = {'print_info': 'off', 'initial_velocity_direction': [-1., 0., 0.], 'max_iterations': 950, 'delta_curved': 1e-6, 'min_delta': tolerance, 'newmark_damp': 5e-3, 'gravity_on': True, 'gravity': 9.81, 'num_steps': n_tstep, 'dt': dt, 'initial_velocity': u_inf * 1} settings['NonLinearDynamicPrescribedStep'] = {'print_info': 'on', 'initial_velocity_direction': [-1., 0., 0.], 'max_iterations': 950, 'delta_curved': 1e-6, 'min_delta': self.tolerance, 'newmark_damp': 5e-3, 'gravity_on': True, 'gravity': 9.81, 'num_steps': self.n_tstep, 'dt': self.dt} settings['StepLinearUVLM'] = {'dt': self.dt, 'integr_order': 1, 'remove_predictor': True, 'use_sparse': True, 'velocity_field_generator': 'GustVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0., 0.], 'gust_shape': '1-cos', 'gust_length': 1., 'gust_intensity': self.gust_intensity * u_inf, 'offset': 30., 'span': self.span}} settings['StepUvlm'] = {'print_info': 'on', 'horseshoe': self.horseshoe, 'num_cores': 4, 'n_rollup': 1, 'convection_scheme': self.wake_type, 'rollup_dt': dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'GustVelocityField', 'velocity_field_input': {'u_inf': u_inf * 0, 'u_inf_direction': [1., 0, 0], 'gust_shape': '1-cos', 'gust_length': 5., 'gust_intensity': gust_intensity * u_inf, 'offset': 15.0, 'span': self.span, 'relative_motion': True}, # 'velocity_field_generator': 'SteadyVelocityField', # 'velocity_field_input': {'u_inf': u_inf*1, # 'u_inf_direction': [1., 0., 0.]}, 'rho': rho, 'n_time_steps': n_tstep, 'dt': dt, 'gamma_dot_filtering': 3} settings['DynamicCoupled'] = {'print_info': 'on', # 'structural_substeps': 1, # 'dynamic_relaxation': 'on', # 'clean_up_previous_solution': 'on', 'structural_solver': 'NonLinearDynamicCoupledStep', 'structural_solver_settings': settings['NonLinearDynamicCoupledStep'], 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'fsi_substeps': 200, 'fsi_tolerance': fsi_tolerance, 'relaxation_factor': relaxation_factor, 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': 0.5, 'n_time_steps': n_tstep, 'dt': dt, 'include_unsteady_force_contribution': 'off', 'postprocessors': ['BeamLoads', 'StallCheck', 'BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'BeamLoads': {'csv_output': 'off'}, 'StallCheck': {'output_degrees': True, 'stall_angles': { '0': [-12 * np.pi / 180, 6 * np.pi / 180], '1': [-12 * np.pi / 180, 6 * np.pi / 180], '2': [-12 * np.pi / 180, 6 * np.pi / 180]}}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}, 'AerogridPlot': { 'u_inf': u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, # 'WriteVariablesTime': { # # 'delimeter': ',', # # 'structure_nodes': [0], # # 'structure_variables': ['Z'] # # settings['WriteVariablesTime'] = {'delimiter': ' ', # 'FoR_variables': ['GFoR_pos', 'GFoR_vel', 'GFoR_acc'], # 'FoR_number': [], # 'structure_variables': ['AFoR_steady_forces', 'AFoR_unsteady_forces','AFoR_position'], # 'structure_nodes': [0,-1], # 'aero_panels_variables': ['gamma', 'gamma_dot'], # 'aero_panels_isurf': [0,1,2], # 'aero_panels_im': [1,1,1], # 'aero_panels_in': [-2,-2,-2], # 'aero_nodes_variables': ['GFoR_steady_force', 'GFoR_unsteady_force'], # 'aero_nodes_isurf': [0,1,2], # 'aero_nodes_im': [1,1,1], # 'aero_nodes_in': [-2,-2,-2] # }}} }} settings['Modal'] = {'print_info': True, 'use_undamped_modes': True, 'NumLambda': 30, 'rigid_body_modes': True, 'write_modes_vtk': 'on', 'print_matrices': 'on', 'save_data': 'on', 'continuous_eigenvalues': 'off', 'dt': dt, 'plot_eigenvalues': False} settings['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', 'linear_system_settings': { 'beam_settings': {'modal_projection': False, 'inout_coords': 'nodes', 'discrete_time': True, 'newmark_damp': 0.5, 'discr_method': 'newmark', 'dt': dt, 'proj_modes': 'undamped', 'use_euler': 'off', 'num_modes': 40, 'print_info': 'on', 'gravity': 'on', 'remove_dofs': []}, 'aero_settings': {'dt': dt, 'integr_order': 2, 'density': rho, 'remove_predictor': False, 'use_sparse': True, 'rigid_body_motion': True, 'use_euler': False, 'remove_inputs': ['u_gust']}, 'rigid_body_motion': True}} settings['AsymptoticStability'] = {'sys_id': 'LinearAeroelastic', 'print_info': 'on', 'display_root_locus':'off', 'frequency_cutoff': 0, 'export_eigenvalues': 'on', 'num_evals':100} settings['LinDynamicSim'] = {'dt': dt, 'n_tsteps': self.n_tstep, 'sys_id': 'LinearAeroelastic', 'postprocessors': ['BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'AerogridPlot': { 'u_inf': u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}}} settings['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0, 'u_inf': u_inf } settings['AeroForcesCalculator'] = {'write_text_file': 'off', 'text_file_name': case_name + '_aeroforces.csv', 'screen_output': 'on', 'unsteady': 'off', 'coefficients': True, 'q_ref': 0.5 * rho * u_inf ** 2, 'S_ref': self.planform_area() } settings['BeamPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on', 'include_FoR': 'on'} settings['BeamLoads'] = {'csv_output': 'off'} settings['SaveData'] = {'save_aero': 'on', 'save_structure': 'on', 'save_linear': 'off'} settings['StabilityDerivatives'] = {'u_inf': self.u_inf, 'S_ref': 12.809, 'b_ref': self.span, 'c_ref': 0.719} config = configobj.ConfigObj() config.filename = file_name for k, v in settings.items(): config[k] = v config.write() self.settings = settings self.config = config if __name__=='__main__': ws = HortenWing(M=4, N=11, Mstarfactor=5, u_inf=28, rho=1.225, alpha_deg=4) ================================================ FILE: sharpy/cases/hangar/richards_wing.py ================================================ """ Simple Horten Wing as used by Richards. Baseline and simplified models Richards, P. W., Yao, Y., Herd, R. A., Hodges, D. H., & Mardanpour, P. (2016). Effect of Inertial and Constitutive Properties on Body-Freedom Flutter for Flying Wings. Journal of Aircraft. https://doi.org/10.2514/1.C033435 """ import numpy as np from sharpy.cases.hangar.horten_wing import HortenWing import sharpy.utils.algebra as algebra class Baseline(HortenWing): def set_properties(self): # Wing geometry self.span = 20.0 # [m] self.sweep_LE = 20 * np.pi / 180 # [rad] Leading Edge Sweep self.c_root = 1.0 # [m] Root chord - Richards self.taper_ratio = 0.25 # Richards self.thrust_nodes = [self.n_node_fuselage - 1, self.n_node_fuselage + self.n_node_wing + 1] self.loc_cg = 0.45 # CG position wrt to LE (from sectional analysis) # EA is the reference in NATASHA - defined with respect to the midchord. SHARPy is wrt to LE and as a pct of # local chord self.main_ea_root = 0.33 self.main_ea_tip = 0.33 self.n_mass = 2 * self.n_elem_wing # FUSELAGE GEOMETRY self.fuselage_width = 1.65/2 self.c_fuselage = self.c_root # WASH OUT self.washout_root = 0*np.pi/180 self.washout_tip = -2 * np.pi / 180 # Horseshoe wake self.horseshoe = False self.wake_type = 2 self.dt_factor = 1 self.dt = 1 / self.M / self.u_inf * self.dt_factor # Dynamics self.n_tstep = int(self.physical_time/self.dt) self.gust_intensity = 0.1 # Numerics self.tolerance = 1e-12 self.fsi_tolerance = 1e-10 self.relaxation_factor = 0.2 def update_mass_stiffness(self, sigma=1., sigma_mass=1., payload=0): """ Sets the mass and stiffness properties of the default wing Returns: """ n_elem_fuselage = self.n_elem_fuselage n_elem_wing = self.n_elem_wing n_node_wing = self.n_node_wing n_node_fuselage = self.n_node_fuselage c_root = self.c_root taper_ratio = self.taper_ratio # Local chord to root chord initialisation c_bar_temp = np.linspace(c_root, taper_ratio * c_root, n_elem_wing) # Structural properties at the wing root section from Richards 2016 ea = 1e6 ga = 1e6 gj = 4.24e5 eiy = 3.84e5 eiz = 2.46e7 root_i_beam = IBeam() root_i_beam.build(c_root) root_i_beam.rotation_axes = np.array([0, self.main_ea_root-0.25, 0]) root_airfoil = Airfoil() root_airfoil.build(c_root) root_i_beam.rotation_axes = np.array([0, self.main_ea_root-0.25, 0]) mu_0 = root_i_beam.mass + root_airfoil.mass j_xx = root_i_beam.ixx + root_airfoil.ixx j_yy = root_i_beam.iyy + root_airfoil.iyy j_zz = root_i_beam.izz + root_airfoil.izz # Number of stiffnesses used n_stiffness = self.n_stiffness # Initialise the stiffness database base_stiffness = self.base_stiffness stiffness_root = sigma * np.diag([ea, ga, ga, gj, eiy, eiz]) stiffness_tip = taper_ratio ** 2 * stiffness_root # Assume a linear variation in the stiffness. Richards et al. use VABS on the linearly tapered wing to find the # spanwise properties alpha = np.linspace(0, 1, self.n_elem_wing) for i_elem in range(0, self.n_elem_wing): base_stiffness[i_elem + 1, :, :] = stiffness_root*(1-alpha[i_elem]**2) + stiffness_tip*alpha[i_elem]**2 base_stiffness[0] = base_stiffness[1] # Mass variation along the span # Right wing centre of mass - wrt to 0.25c cm = (root_airfoil.centre_mass * root_airfoil.mass + root_i_beam.centre_mass * root_i_beam.mass) \ / np.sum(root_airfoil.mass + root_i_beam.mass) cg = np.array([0, -(cm[0] + 0.25 * self.c_root - self.main_ea_root), 0]) * 1 n_mass = self.n_mass # sigma_mass = 1.25 # Initialise database base_mass = self.base_mass mass_root_right = np.diag([mu_0, mu_0, mu_0, j_xx, j_yy, j_zz]) * sigma_mass mass_root_right[:3, -3:] = -algebra.skew(cg) * mu_0 mass_root_right[-3:, :3] = algebra.skew(cg) * mu_0 mass_root_left = np.diag([mu_0, mu_0, mu_0, j_xx, j_yy, j_zz]) * sigma_mass mass_root_left[:3, -3:] = -algebra.skew(-cg) * mu_0 mass_root_left[-3:, :3] = algebra.skew(-cg) * mu_0 mass_tip_right = taper_ratio * mass_root_right mass_tip_left = taper_ratio * mass_root_left ixx_dummy = [] iyy_dummy = [] izz_dummy = [] for i_elem in range(self.n_elem_wing): # Create full cross section c_bar = self.c_root * ((1-alpha[i_elem]) + self.taper_ratio * alpha[i_elem]) x_section = WingCrossSection(c_bar) # print(i_elem) # print('Section Mass: %.2f ' %x_section.mass) # print('Linear Mass: %.2f' % (mu_0 * (1-alpha[i_elem]) + mu_0 * self.taper_ratio * alpha[i_elem])) # print('Section Ixx: %.4f' % x_section.ixx) # print('Section Iyy: %.4f' % x_section.iyy) # print('Section Izz: %.4f' % x_section.izz) # print('Linear Ixx: %.2f' % (j_xx * (1-alpha[i_elem]) + j_xx * self.taper_ratio * alpha[i_elem])) # base_mass[i_elem, :, :] = mass_root_right*(1-alpha[i_elem]) + mass_tip_right*alpha[i_elem] # base_mass[i_elem + self.n_elem_wing + self.n_elem_fuselage - 1] = mass_root_left*(1-alpha[i_elem]) + mass_tip_left*alpha[i_elem] base_mass[i_elem, :, :] = np.diag([x_section.mass, x_section.mass, x_section.mass, x_section.ixx, x_section.iyy, x_section.izz]) cg = np.array([0, -(x_section.centre_mass[0] + (0.25 - self.main_ea_root) * c_bar / self.c_root), 0]) * 1 base_mass[i_elem, :3, -3:] = -algebra.skew(cg) * x_section.mass base_mass[i_elem, -3:, :3] = algebra.skew(cg) * x_section.mass base_mass[i_elem + self.n_elem_wing + self.n_elem_fuselage - 1, :, :] = np.diag([x_section.mass, x_section.mass, x_section.mass, x_section.ixx, x_section.iyy, x_section.izz]) cg = np.array([0, -(x_section.centre_mass[0] + (0.25 - self.main_ea_root) * c_bar / self.c_root), 0]) * 1 base_mass[i_elem + self.n_elem_wing + self.n_elem_fuselage - 1, :3, -3:] = -algebra.skew(-cg) * x_section.mass base_mass[i_elem + self.n_elem_wing + self.n_elem_fuselage - 1, -3:, :3] = algebra.skew(-cg) * x_section.mass ixx_dummy.append(x_section.ixx) iyy_dummy.append(x_section.iyy) izz_dummy.append(x_section.izz) # for item in x_section.items: # plt.plot(item.y, item.z) # plt.scatter(x_section.centre_mass[0], x_section.centre_mass[1]) # plt.show() # print(x_section.centre_mass) # print(cg) # plt.plot(range(self.n_elem_wing), ixx_dummy) # plt.plot(range(self.n_elem_wing), iyy_dummy) # plt.plot(range(self.n_elem_wing), izz_dummy) # plt.show() # Lumped mass initialisation lumped_mass_nodes = self.lumped_mass_nodes lumped_mass = self.lumped_mass lumped_mass_inertia = self.lumped_mass_inertia lumped_mass_position = self.lumped_mass_position # Lumped masses nodal position # 0 - Right engine # 1 - Left engine # 2 - Fuselage lumped_mass_nodes[0] = 2 lumped_mass_nodes[1] = n_node_fuselage + n_node_wing + 1 lumped_mass_nodes[2] = 0 # Lumped mass value from Richards 2013 lumped_mass[0:2] = 51.445 / 9.81 lumped_mass[2] = 150 / 9.81 + payload # lumped_mass_position[2] = [0, 0, -10.] # Lumped mass inertia lumped_mass_inertia[0, :, :] = np.diag([0.29547, 0.29322, 0.29547]) lumped_mass_inertia[1, :, :] = np.diag([0.29547, 0.29322, 0.29547]) lumped_mass_inertia[2, :, :] = np.diag([0.5, 1, 1]) * lumped_mass[2] # Define class attributes self.lumped_mass = lumped_mass * 1 self.lumped_mass_nodes = lumped_mass_nodes * 1 self.lumped_mass_inertia = lumped_mass_inertia * 1 self.lumped_mass_position = lumped_mass_position * 1 self.base_stiffness = base_stiffness self.base_mass = base_mass class CrossSection(object): def __init__(self): self.rho = 2770 self.rotation_axes = np.array([0, 0.33-0.25, 0]) self.y = np.ndarray((2,)) self.z = np.ndarray((2,)) self.t = np.ndarray((2,)) @property def mass(self): """ Mass of the I beam per unit length """ return np.sum(self.t * self.elem_length) * self.rho @property def ixx(self): ixx_ = np.sum(self.elem_length * self.t * self.rho * (self.elem_cm_y ** 2 + self.elem_cm_z ** 2)) return ixx_ + self.mass * (self.centre_mass[0] - self.rotation_axes[1]) ** 2 @property def elem_length(self): elem_length = np.sqrt(np.diff(self.y) ** 2 + np.diff(self.z) ** 2) return elem_length @property def elem_cm_y(self): elem_cm_y_ = np.ndarray((self.n_elem, )) elem_cm_y_[:] = 0.5 * (self.y[:-1] + self.y[1:]) return elem_cm_y_ @property def elem_cm_z(self): elem_cm_z_ = np.ndarray((self.n_elem, )) elem_cm_z_[:] = 0.5 * (self.z[:-1] + self.z[1:]) return elem_cm_z_ @property def centre_mass(self): y_cm = np.sum(self.elem_cm_y * self.elem_length) / np.sum(self.elem_length) z_cm = np.sum(self.elem_cm_z * self.elem_length) / np.sum(self.elem_length) return np.array([y_cm, z_cm]) @property def iyy(self): x_dom = np.linspace(-0.5, 0.5, 100) x_cg = 0.5 * (x_dom[:-1].copy() + x_dom[1:].copy()) dx = np.diff(x_dom)[0] iyy_ = 0 for elem in range(len(self.elem_length)): z_cg = np.ones_like(x_cg) * self.elem_cm_z[elem] iyy_ += np.sum(self.elem_length[elem] * self.t[elem] * dx * self.rho * (x_cg ** 2 + z_cg ** 2)) return iyy_ #np.sum(self.elem_length * self.t * self.rho * 1 * self.elem_cm_z ** 2) @property def izz(self): x_dom = np.linspace(-0.5, 0.5, 100) x_cg = 0.5 * (x_dom[:-1].copy() + x_dom[1:].copy()) dx = np.diff(x_dom)[0] iyy_ = 0 izz_ = 0 for elem in range(len(self.elem_length)): y_cg = np.ones_like(x_cg) * self.elem_cm_y[elem] izz_ += np.sum(self.elem_length[elem] * self.t[elem] * dx * self.rho * (x_cg ** 2 + y_cg ** 2)) return izz_ #np.sum(self.elem_length * self.t * self.rho * 1 * self.elem_cm_y ** 2) @property def n_node(self): return self.y.shape[0] @property def n_elem(self): return self.n_node - 1 def build(self, y, z, t): self.y = y self.z = z self.t = t class IBeam(CrossSection): def build(self, c_root): t_skin = 0.127e-2 t_c = 0.12 w_I = 10e-2 * c_root # Width of the Ibeam self.rho = 2770 self.y = np.ndarray((self.n_node, )) self.z = np.ndarray((self.n_node, )) self.t = np.ndarray((self.n_node, )) z_max = t_c * c_root y = np.array([-w_I/2, w_I/2, 0, 0, -w_I/2, w_I/2]) z = np.array([z_max/2, z_max/2, z_max/2, -z_max/2, -z_max/2, -z_max/2]) t = np.array([t_skin, 0, t_skin, 0, t_skin]) self.y = y self.z = z self.t = t class Airfoil(CrossSection): def build(self, c_root): t_c = 0.12 t_skin = 0.127e-2 * 1.5 y_dom = np.linspace(0, c_root, 100) y = np.concatenate((y_dom, y_dom[:-1][::-1])) z_dom = 5 * t_c * (0.2969 * np.sqrt(y_dom/c_root) - 0.1260 * y_dom/c_root - 0.3516 * (y_dom/c_root) ** 2 + 0.2843 * (y_dom/c_root) ** 3 - 0.1015 * (y_dom/c_root) ** 4) * c_root z = np.concatenate((z_dom, -z_dom[:-1][::-1])) self.y = y - 0.25 * c_root self.z = z self.t = t_skin * np.ones(self.n_elem) class WingCrossSection: def __init__(self, chord): self.chord = chord self.items = list() self.items.append(Airfoil()) self.items.append(IBeam()) for item in self.items: item.build(chord) @property def mass(self): return np.sum([item.mass for item in self.items]) @property def centre_mass(self): y = np.sum([item.mass * item.centre_mass[0] for item in self.items]) / self.mass z = np.sum([item.mass * item.centre_mass[1] for item in self.items]) / self.mass return np.array([y, z]) @property def ixx(self): return np.sum([item.ixx for item in self.items]) @property def iyy(self): return np.sum([item.iyy for item in self.items]) @property def izz(self): return np.sum([item.izz for item in self.items]) if __name__ == '__main__': ws = Baseline(M=4, N=11, Mstarfactor=5, u_inf=28, rho=1.225, alpha_deg=4) # ws.clean_test_files() ws.update_mass_stiffness() ws.update_fem_prop() # ws.generate_fem_file() ws.update_aero_properties() # ws.generate_aero_file() # ws.set_default_config_dict() ================================================ FILE: sharpy/cases/hangar/swept_flying_wing.py ================================================ """ Generic Swept Flying Wing Class Generator N Goizueta Nov 18 """ import numpy as np import os import h5py as h5 import sharpy.utils.geo_utils as geo_utils import sharpy.utils.algebra as algebra import configobj import scipy.linalg as sclalg class SweptWing: """ Generic Swept Flying Wing Wing Class Generator A ``HortenWing`` class contains the basic geometry and properties of a simplified swept flying wing, as described by Simpson. This class allows the user to quickly obtain SHARPy cases for the purposes of parametric analyses. """ def __init__(self, M, N, Mstarfactor, u_inf, rho=1.225, alpha_deg=0., beta_deg=0., cs_deflection_deg=0., thrust=5., physical_time=10, case_name_format=0, case_remarks=None, case_route='./cases/', case_name='swf'): # Discretisation self.M = M self.N = N self.Mstarfactor = Mstarfactor self.n_node_elem = 3 self.n_elem_wing = 11 self.n_elem_fuselage = 0 self.n_surfaces = 4 # Case admin if case_name_format == 0: self.case_name = case_name + '_u_inf%.4d_a%.4d' % (int(u_inf), int(alpha_deg * 100)) elif case_name_format == 1: self.case_name = case_name + '_u_inf%.4d' % int(u_inf*100) elif case_name_format == 2: self.case_name = case_name else: self.case_name = case_name + '_u_inf%.4d_%s' % (int(u_inf), case_remarks) self.case_route = os.path.abspath(case_route + self.case_name + '/') self.config = None # Flight conditions self.u_inf = u_inf self.rho = rho self.alpha = alpha_deg * np.pi / 180 self.beta = beta_deg * np.pi / 180 self.cs_deflection = cs_deflection_deg * np.pi / 180 self.thrust = thrust # Compute number of nodes n_node = 0 self.n_node_wing = self.n_elem_wing * (self.n_node_elem - 1) self.n_node_fuselage = self.n_elem_fuselage * self.n_node_elem n_node += 2 * self.n_node_wing + 1 self.n_node = n_node # Compute number of elements self.n_elem = 2 * (self.n_elem_wing + self.n_elem_fuselage) # Wing geometry self.span = 8 # [m] self.sweep_LE = 20 * np.pi / 180 # [rad] Leading Edge Sweep self.c_root = 1.15 # [m] Root chord self.taper_ratio = 0.65 * self.c_root self.thrust_nodes = [self.n_node_fuselage + 2, self.n_node_fuselage + self.n_node_wing + 2] # self.thrust_nodes = [1] self.loc_cg = 0.45 # CG position wrt to LE (from sectional analysis) self.ea_offset_root = 0.13 # from Mardanpour self.ea_offset_tip = -1.644 * 0.0254 self.main_ea_root = 0.25 self.main_ea_tip = 0.25 # FUSELAGE GEOMETRY self.fuselage_width = 0 # self.fuselage_width = 0.8248 # self.c_fuselage = 84*0.0254 # WASH OUT self.washout_root = 0*np.pi/180 self.washout_tip = -2 * np.pi / 180 # Horseshoe wake self.horseshoe = False self.wake_type = 2 self.dt_factor = 1 self.dt = 1 / self.M / self.u_inf * self.dt_factor # Dynamics self.n_tstep = int(physical_time/self.dt) self.gust_intensity = 0.1 # Numerics self.tolerance = 1e-12 self.fsi_tolerance = 1e-10 self.relaxation_factor = 0.2 # H5 Variables initialisation as class attributes # coordinates self.x = np.zeros((n_node,)) self.y = np.zeros((n_node,)) self.z = np.zeros((n_node,)) # beam number self.beam_number = np.zeros(self.n_elem, dtype=int) # frame of reference delta self.frame_of_reference_delta = np.zeros((self.n_elem, self.n_node_elem, 3)) # connectivity of beams self.connectivities = np.zeros((self.n_elem, self.n_node_elem), dtype=int) # stiffness self.n_stiffness = self.n_elem_wing + self.n_elem_fuselage self.base_stiffness = np.zeros((self.n_stiffness, 6, 6)) self.elem_stiffness = np.zeros((self.n_elem,), dtype=int) # mass self.n_mass = self.n_elem_wing self.base_mass = np.zeros((self.n_mass, 6, 6)) self.elem_mass = np.zeros(self.n_elem, dtype=int) # boundary conditions self.boundary_conditions = np.zeros((n_node,), dtype=int) # applied forces self.app_forces = np.zeros((n_node, 6)) self.n_lumped_mass = 3 self.lumped_mass_nodes = np.zeros((self.n_lumped_mass), dtype=int) self.lumped_mass = np.zeros(self.n_lumped_mass) self.lumped_mass_inertia = np.zeros((self.n_lumped_mass, 3, 3)) self.lumped_mass_position = np.zeros((self.n_lumped_mass, 3)) # Aerodynamic properties # H5 AERO FILE VARIABLES INITIALISATION # airfoil distribution self.airfoil_distribution = np.zeros((self.n_elem, self.n_node_elem), dtype=int) # surface distribution self.surface_distribution = np.zeros((self.n_elem,), dtype=int) - 1 self.surface_m = np.zeros((self.n_surfaces,), dtype=int) self.m_distribution = 'uniform' # aerodynamic nodes boolean self.aero_nodes = np.zeros((self.n_node,), dtype=bool) # aero twist self.twist = np.zeros((self.n_elem, self.n_node_elem)) # chord self.chord = np.zeros((self.n_elem, self.n_node_elem)) # elastic axis self.elastic_axis = np.zeros((self.n_elem, self.n_node_elem)) # control surfaces attributes initialisation self.n_control_surfaces = 1 self.control_surface = np.zeros((self.n_elem, self.n_node_elem), dtype=int) - 1 self.control_surface_type = np.zeros((self.n_control_surfaces,), dtype=int) self.control_surface_deflection = np.zeros((self.n_control_surfaces,)) self.control_surface_chord = np.zeros((self.n_control_surfaces,), dtype=int) self.control_surface_hinge_coord = np.zeros((self.n_control_surfaces,), dtype=float) self.settings = dict() def update_mass_stiffness(self, sigma=1.): r""" The mass and stiffness matrices are computed. Both vary over the span of the wing, hence a dictionary is created that acts as a database of the different properties along the wing. The variation of the stiffness is cubic along the span: .. math:: \mathbf{K} = \mathbf{K}_0\bar{c}^3 where :math:`\mathbf{K}_0 is the stiffness of the wing root section and :math:`\bar{c}` is the ratio between the local chord and the root chord. The variation of the sectional mass is quadratic along the span: .. math:: \mu = \mu_0\,\bar{c}^2 where :math:`\mu` is the mass per unit span and the zero subscript denotes the root value. The sectional inertia is varied linearly along the span, based on the information by Mardanpour 2013. Three lumped masses are included with the following properties (Richards, 2013) ===== ===== ================= ========= =============================== ============ No Node Relative Position Mass [kg] Inertia [kg m^2] Description ===== ===== ================= ========= =============================== ============ ``0`` ``2`` ``[0,0,0]`` ``5.24`` ``[0.29547, 0.29322, 0.29547]`` Right Engine ``1`` ``S`` ``[0,0,0]`` ``5.24`` ``[0.29547, 0.29322, 0.29547]`` Left Engine ``2`` ``0`` ``[0,0,0]`` ``15.29`` ``[0.5, 1.0, 1.0]*15.29`` Fuselage ===== ===== ================= ========= =============================== ============ Args: sigma (float): stiffening factor Returns: """ n_elem_fuselage = self.n_elem_fuselage n_elem_wing = self.n_elem_wing n_node_wing = self.n_node_wing n_node_fuselage = self.n_node_fuselage c_root = self.c_root taper_ratio = self.taper_ratio # Local chord to root chord initialisation c_bar_temp = np.linspace(c_root, taper_ratio * c_root, n_elem_wing) # Structural properties at the wing root section from Richards 2016 ea = 1e7 ga = 1e9 gj = 7.5e4 eiy = 4.5e4 eiz = 2.4e6 ea_t = 1e7 ga_t = 1e9 gj_t = 3e4 eiy_t = 2e4 eiz_t = 1e6 # Sectional mass from Richards 2016 mu_r = 15 mu_t = 2 j_r = 0.5 j_t = 0.1 # Number of stiffnesses used n_stiffness = self.n_stiffness # Initialise the stiffness database base_stiffness = self.base_stiffness # Wing root section stiffness properties stiffness_root = sigma * np.diag([ea, ga, ga, gj, eiy, eiz]) stiffness_tip = sigma * np.diag([ea_t, ga_t, ga_t, gj_t, eiy_t, eiz_t]) # Linear variation between root and tip alpha = np.linspace(0, 1, self.n_elem_wing) for i_elem in range(0, self.n_elem_wing): base_stiffness[i_elem, :, :] = stiffness_root*(1-alpha[i_elem]) + stiffness_tip*alpha[i_elem] # Mass variation along the span n_mass = self.n_mass sigma_mass = 1 # Initialise database base_mass = self.base_mass chi_root = np.array([0, 5.29, 0*-0.594]) * 0 m_root = np.eye(3) * mu_r j_root = np.array([[j_r, 0., 0.], [0., j_r, 0.], [0., 0., 2*j_r]]) mass_root = sclalg.block_diag(m_root, j_root) mass_root[:3, -3:] = -mu_r * algebra.skew(chi_root) mass_root[-3:, :3] = mu_r * algebra.skew(chi_root) chi_tip = chi_root # np.array([0, -1.644, -0.563]) j_tip = np.array([[j_t, 0.00000000e+00, 0.00000000e+00], [0.00000000e+00, j_t, 0.00000000e+00], [0.00000000e+00, 0.00000000e+00, 2*j_t]]) mass_tip = sclalg.block_diag(np.diag([mu_t, mu_t, mu_t]), j_tip) mass_tip[:3, -3:] += -algebra.skew(chi_tip) * mu_t mass_tip[-3:, :3] += -algebra.skew(chi_tip) * mu_t for i_elem in range(self.n_elem_wing): base_mass[i_elem, :, :] = mass_root*(1-alpha[i_elem]) + mass_tip*alpha[i_elem] # Lumped mass initialisation # 0 - Right engine # 1 - Left engine lumped_mass_nodes = self.lumped_mass_nodes lumped_mass = self.lumped_mass lumped_mass_inertia = self.lumped_mass_inertia lumped_mass_position = self.lumped_mass_position # Lumped masses nodal position lumped_mass_nodes[0] = 2 lumped_mass_nodes[1] = n_node_fuselage + n_node_wing + 2 # Engine mass lumped_mass[0:2] = 40 lumped_mass_inertia[0, :, :] = np.diag([5, 5, 5]) lumped_mass_inertia[1, :, :] = np.diag([5, 5, 5]) lumped_mass_position[0] = np.array([0, -0.1, 0]) lumped_mass_position[1] = np.array([0, 0.1, 0]) # Define class attributes self.lumped_mass = lumped_mass self.lumped_mass_nodes = lumped_mass_nodes self.lumped_mass_inertia = lumped_mass_inertia self.lumped_mass_position = lumped_mass_position self.base_stiffness = base_stiffness self.base_mass = base_mass def update_fem_prop(self): """ Computes the FEM properties prior to analysis such as the connectivity matrix, coordinates, etc Returns: """ # Obtain class attributes n_node_elem = self.n_node_elem n_elem = self.n_elem n_elem_wing = self.n_elem_wing n_elem_fuselage = self.n_elem_fuselage n_node = self.n_node n_node_wing = self.n_node_wing n_node_fuselage = self.n_node_fuselage fuselage_width = self.fuselage_width thrust = self.thrust thrust_nodes = self.thrust_nodes span = self.span sweep_LE = self.sweep_LE # mass and stiffness matrices stiffness = self.base_stiffness mass = self.base_mass n_stiffness = stiffness.shape[0] n_mass = mass.shape[0] # H5 FEM FILE VARIABLES INITIALISATION # coordinates x = np.zeros((n_node,)) y = np.zeros((n_node,)) z = np.zeros((n_node,)) # twist structural_twist = np.zeros_like(x) # beam number beam_number = np.zeros(n_elem, dtype=int) # frame of reference delta frame_of_reference_delta = np.zeros((n_elem, n_node_elem, 3)) # connectivity of beams conn = np.zeros((n_elem, n_node_elem), dtype=int) # stiffness stiffness = np.zeros((n_stiffness, 6, 6)) elem_stiffness = np.zeros((n_elem,), dtype=int) # mass mass = np.zeros((n_mass, 6, 6)) elem_mass = np.zeros(n_elem, dtype=int) # boundary conditions boundary_conditions = np.zeros((n_node,), dtype=int) # applied forces app_forces = np.zeros((n_node, 6)) # assemble connectivites # worked elements we = 0 # worked nodes wn = 0 # # RIGHT RIGID FUSELAGE # beam_number[we:we + 1] = 0 # # coordinates # x[wn:wn + n_node_fuselage] = 0 # y[wn:wn + n_node_fuselage] = np.linspace(0, fuselage_width / 2, n_node_fuselage) # # # connectivities # elem_mass[0] = 0 # conn[we, :] = [0, 2, 1] # # # frame of reference change # frame_of_reference_delta[0, 0, :] = [-1.0, 0.0, 0.0] # frame_of_reference_delta[0, 1, :] = [-1.0, 0.0, 0.0] # frame_of_reference_delta[0, 2, :] = [-1.0, 0.0, 0.0] # # # element stiffness # elem_stiffness[0] = 0 # elem_mass[0] = 0 # # # boundary conditions # boundary_conditions[0] = 1 # # # applied forces - engine 1 # app_forces[thrust_nodes[0]] = [0, thrust, 0, # 0, 0, 0] # # # updated worked nodes and elements # we += n_elem_fuselage # wn += n_node_fuselage # RIGHT WING beam_number[we:we + n_elem_wing] = 0 # y coordinate (positive right) y[wn:wn + n_node_wing + 1] = np.linspace(0, span / 2, n_node_wing + 1) x[wn:wn + n_node_wing + 1] = y[wn:wn + n_node_wing + 1] * np.tan(sweep_LE) # connectivities for ielem in range(n_elem_wing): conn[we + ielem, :] = (np.ones(n_node_elem) * (we + ielem) * (n_node_elem - 1) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_mass[we + ielem] = ielem elem_stiffness[we + ielem] = ielem # element stiffness and mass # elem_stiffness[we:we+n_elem_wing] = 0 # elem_mass[we:we+n_elem_wing] = 0 # boundary conditions of free end boundary_conditions[wn + n_node_wing - 1] = -1 # Clamped end boundary_conditions[0] = 1 # applied forces - engine 1 app_forces[thrust_nodes[0]] = [-thrust * np.sin(sweep_LE), thrust * np.cos(sweep_LE), 0, 0, 0, 0] # update worked elements and nodes we += n_elem_wing wn += n_node_wing + 1 # # LEFT FUSELAGE # beam_number[we:we + n_elem_fuselage] = 2 # # coordinates # y[wn:wn + n_node_fuselage - 1] = np.linspace(0, # -fuselage_width / 2, # n_node_fuselage)[1:] # x[wn:wn + n_node_fuselage - 1] = 0 # # # connectivity # conn[we, :] = [0, wn + 1, wn] # # # frame of reference delta # for ielem in range(n_elem_fuselage): # for inode in range(n_node_elem): # frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] # # # element stiffness and mass # elem_stiffness[we:we + n_elem_fuselage] = 0 # elem_mass[we:we + n_elem_fuselage] = 0 # # # applied forces - engine 2 # app_forces[thrust_nodes[1]] = [0, -thrust, 0, # 0, 0, 0] # # # update worked elements and nodes # we += n_elem_fuselage # wn += n_node_fuselage - 1 # LEFT WING # coordinates beam_number[we:we + n_elem_wing] = 1 y[wn:wn + n_node_wing] = np.linspace(0, -span / 2, n_node_wing + 1)[1:] x[wn:wn + n_node_wing] = -1 * y[wn:wn + n_node_wing] * np.tan(sweep_LE) # left wing connectivities for ielem in range(n_elem_wing): conn[we + ielem, :] = np.ones(n_node_elem) * (we + ielem) * (n_node_elem - 1) + [0, 2, 1] for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] elem_mass[we + ielem] = ielem elem_stiffness[we + ielem] = ielem # element stiffness and mass # elem_stiffness[we:we+n_node_wing] = 0 conn[we, 0] = 0 # boundary conditions at the free end boundary_conditions[wn + n_node_wing - 1] = -1 # applied forces - engine 2 app_forces[thrust_nodes[1]] = [-thrust * np.sin(sweep_LE), -thrust * np.cos(sweep_LE), 0, 0, 0, 0] # update worked elements and nodes we += n_elem_wing wn += n_node_wing # set attributes self.x = x self.y = y self.z = z self.connectivities = conn self.elem_stiffness = elem_stiffness self.elem_mass = elem_mass self.frame_of_reference_delta = frame_of_reference_delta self.boundary_conditions = boundary_conditions self.beam_number = beam_number self.app_forces = app_forces def generate_fem_file(self): """ Generates the ``.fem.h5`` folder containing the structural information of the problem The file is written to ``self.case_route / self.case_name .fem.h5`` """ if not os.path.exists(self.case_route): os.makedirs(self.case_route) with h5.File(self.case_route + '/' + self.case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((self.x, self.y, self.z))) connectivities = h5file.create_dataset('connectivities', data=self.connectivities) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=self.n_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=self.n_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=self.n_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=self.base_stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=self.elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=self.base_mass) mass_handle = h5file.create_dataset( 'elem_mass', data=self.elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=self.frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=np.zeros((self.n_elem, self.n_node_elem))) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=self.boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=self.beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=self.app_forces) lumped_mass_nodes_handle = h5file.create_dataset( 'lumped_mass_nodes', data=self.lumped_mass_nodes) lumped_mass_handle = h5file.create_dataset( 'lumped_mass', data=self.lumped_mass) lumped_mass_inertia_handle = h5file.create_dataset( 'lumped_mass_inertia', data=self.lumped_mass_inertia) lumped_mass_position_handle = h5file.create_dataset( 'lumped_mass_position', data=self.lumped_mass_position) def clean_test_files(self): """ Clears previously generated files """ case_name = self.case_name route = self.case_route # FEM fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) # Dynamics File dyn_file_name = route + '/' + case_name + '.dyn.h5' if os.path.isfile(dyn_file_name): os.remove(dyn_file_name) # Aerodynamics File aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) # Solver file solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) # Flight conditions file flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) # if os.path.isdir(route): # os.system('rm -r %s' %route) def update_aero_properties(self): """ Updates the aerodynamic properties of the horten wing """ # Retrieve attributes n_elem = self.n_elem n_node_elem = self.n_node_elem n_node_wing = self.n_node_wing n_node_fuselage = self.n_node_fuselage n_elem_fuselage = self.n_elem_fuselage n_elem_wing = self.n_elem_wing c_root = self.c_root taper_ratio = self.taper_ratio washout_root = self.washout_root washout_tip = self.washout_tip n_control_surfaces = self.n_control_surfaces cs_deflection = self.cs_deflection m = self.M main_ea_root = self.main_ea_root main_ea_tip = self.main_ea_tip airfoil_distribution = self.airfoil_distribution chord = self.chord surface_distribution = self.surface_distribution surface_m = self.surface_m aero_nodes = self.aero_nodes elastic_axis = self.elastic_axis twist = self.twist control_surface = self.control_surface control_surface_type = self.control_surface_type control_surface_deflection = self.control_surface_deflection control_surface_chord = self.control_surface_chord control_surface_hinge_coord = self.control_surface_hinge_coord self.dt = 1 / self.M / self.u_inf # control surface type: 0 = static # control surface type: 1 = dynamic control_surface_type[0] = 0 control_surface_deflection[0] = cs_deflection control_surface_chord[0] = 2 # m control_surface_hinge_coord[0] = 0.25 # # RIGHT FUSELAGE (Surface 0, Beam 0) we = 0 wn = 0 # # i_surf = 0 # airfoil_distribution[we:we + n_elem_fuselage] = 0 # surface_distribution[we:we + n_elem_fuselage] = i_surf # surface_m[i_surf] = m # # aero_nodes[wn:wn + n_node_fuselage] = True # # temp_chord = np.linspace(self.c_fuselage, self.c_root, self.n_node_fuselage) # temp_washout = washout_root # # # apply chord and elastic axis at each node # node_counter = 0 # for ielem in range(we, we + n_elem_fuselage): # for i_local_node in range(n_node_elem): # if not i_local_node == 0: # node_counter += 1 # chord[ielem, i_local_node] = temp_chord[node_counter] # elastic_axis[ielem, i_local_node] = main_ea_root # twist[ielem, i_local_node] = -temp_washout # # we += n_elem_fuselage # wn += n_node_fuselage # RIGHT WING (Surface 1, Beam 1) # surface_id i_surf = 0 airfoil_distribution[we:we + n_elem_wing, :] = 0 surface_distribution[we:we + n_elem_wing] = i_surf surface_m[i_surf] = m # specify aerodynamic characteristics of wing nodes aero_nodes[wn:wn + n_node_wing + 1] = True # linear taper initialisation temp_chord = np.linspace(c_root, taper_ratio * c_root, n_node_wing + 1) # linear wash out initialisation temp_washout = np.linspace(washout_root, washout_tip, n_node_wing + 1) # elastic axis variation temp_ea = np.linspace(main_ea_root, main_ea_tip, n_node_wing + 1) # apply chord and elastic axis at each node node_counter = 0 for ielem in range(we, we + n_elem_wing): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[ielem, i_local_node] = temp_chord[node_counter] elastic_axis[ielem, i_local_node] = temp_ea[node_counter] twist[ielem, i_local_node] = -temp_washout[node_counter] if ielem >= round((we + n_elem_wing // 2)): control_surface[ielem, i_local_node] = 0 # update working element and node we += n_elem_wing wn += n_node_wing + 1 # # LEFT FUSELAGE (Surface 2, Beam 2) # i_surf = 2 # airfoil_distribution[we:we + n_elem_fuselage] = 0 # surface_distribution[we:we + n_elem_fuselage] = i_surf # surface_m[i_surf] = m # # aero_nodes[wn:wn + n_node_fuselage] = True # # temp_chord = np.linspace(self.c_fuselage, self.c_root, self.n_node_fuselage) # temp_washout = washout_root # # # apply chord and elastic axis at each node # node_counter = 0 # for ielem in range(we, we + n_elem_fuselage): # for i_local_node in range(n_node_elem): # if not i_local_node == 0: # node_counter += 1 # chord[ielem, i_local_node] = temp_chord[node_counter] # elastic_axis[ielem, i_local_node] = main_ea_root # twist[ielem, i_local_node] = -temp_washout # # we += n_elem_fuselage # wn += n_node_fuselage # LEFT WING (Surface 3, Beam 3) i_surf = 1 airfoil_distribution[we:we + n_elem_wing, :] = 0 surface_distribution[we: we + n_elem_wing] = i_surf surface_m[i_surf] = m # linear taper initialisation temp_chord = np.linspace(c_root, taper_ratio * c_root, n_node_wing + 1) # linear wash out initialisation temp_washout = np.linspace(washout_root, washout_tip, n_node_wing + 1) # specify aerodynamic characterisics of wing nodes aero_nodes[wn:wn + n_node_wing] = True # linear taper initialisation # apply chord and elastic axis at each node node_counter = 0 for ielem in range(we, we + n_elem_wing): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[ielem, i_local_node] = temp_chord[node_counter] elastic_axis[ielem, i_local_node] = temp_ea[node_counter] twist[ielem, i_local_node] = -temp_washout[node_counter] if ielem >= round((we + n_elem_wing // 2)): control_surface[ielem, i_local_node] = 0 # update working element and node we += n_elem_wing wn += n_node_wing # end node is the middle node mid_chord = np.array(chord[:, 1], copy=True) chord[:, 1] = chord[:, 2] chord[:, 2] = mid_chord mid_ea = np.array(elastic_axis[:, 1], copy=True) elastic_axis[:, 1] = elastic_axis[:, 2] elastic_axis[:, 2] = mid_ea # Update aerodynamic attributes of class self.chord = chord self.twist = twist self.aero_nodes = aero_nodes self.elastic_axis = elastic_axis self.control_surface = control_surface def generate_aero_file(self, route=None, case_name=None): """ Generates the ``.aero.h5`` file with the aerodynamic properties of the wing Args: route (str): route to write case file. If None is specified the default will be used case_name (str): name of file. If None is specified the default will be used """ if not route: route = self.case_route if not case_name: case_name = self.case_name if not os.path.isdir(self.case_route): os.makedirs(self.case_route) chord = self.chord twist = self.twist airfoil_distribution = self.airfoil_distribution surface_distribution = self.surface_distribution surface_m = self.surface_m m_distribution = self.m_distribution aero_nodes = self.aero_nodes elastic_axis = self.elastic_axis control_surface = self.control_surface control_surface_deflection = self.control_surface_deflection control_surface_chord = self.control_surface_chord control_surface_hinge_coord = self.control_surface_hinge_coord control_surface_type = self.control_surface_type control_surface_deflection[0] = self.cs_deflection with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( geo_utils.generate_naca_camber(P=0, M=0))) naca_airfoil_tail = airfoils_group.create_dataset('1', data=np.column_stack( geo_utils.generate_naca_camber(P=0, M=0))) naca_airfoil_fin = airfoils_group.create_dataset('2', data=np.column_stack( geo_utils.generate_naca_camber(P=0, M=0))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input.attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_nodes) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) control_surface_input = h5file.create_dataset('control_surface', data=control_surface) control_surface_deflection_input = h5file.create_dataset('control_surface_deflection', data=control_surface_deflection) control_surface_chord_input = h5file.create_dataset('control_surface_chord', data=control_surface_chord) control_surface_hinge_coord_input = h5file.create_dataset('control_surface_hinge_coord', data=control_surface_hinge_coord) control_surface_types_input = h5file.create_dataset('control_surface_type', data=control_surface_type) def set_default_config_dict(self, route=None, case_name=None): """ Generates default solver configuration file Returns: """ if not route: route = self.case_route if not case_name: case_name = self.case_name u_inf = self.u_inf rho = self.rho dt = self.dt tolerance = self.tolerance alpha = self.alpha beta = self.beta thrust = self.thrust thrust_nodes = self.thrust_nodes cs_deflection = self.cs_deflection fsi_tolerance = self.fsi_tolerance n_tstep = self.n_tstep gust_intensity = self.gust_intensity relaxation_factor = self.relaxation_factor file_name = route + '/' + case_name + '.sharpy' settings = dict() settings['SHARPy'] = {'case': case_name, 'route': route, 'flow': ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'Modal', 'AerogridPlot', 'BeamPlot', 'SaveData'], 'write_screen': 'on', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} settings['BeamLoader'] = {'unsteady': 'off', 'orientation': algebra.euler2quat(np.array([0.0, self.alpha, self.beta]))} settings['StaticUvlm'] = {'print_info': 'on', 'horseshoe': self.horseshoe, 'num_cores': 4, 'n_rollup': 1, 'rollup_dt': dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho} settings['StaticCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 200, 'num_load_steps': 1, 'delta_curved': 1e-5, 'min_delta': tolerance, 'gravity_on': 'on', 'gravity': 9.81}, 'aero_solver': 'StaticUvlm', 'aero_solver_settings': {'print_info': 'on', 'horseshoe': self.horseshoe, 'num_cores': 4, 'n_rollup': int(1), 'rollup_dt': dt, #self.c_root / self.M / self.u_inf, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho}, 'max_iter': 200, 'n_load_steps': 1, 'tolerance': tolerance, 'relaxation_factor': 0.2} if self.horseshoe is True: settings['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 1, 'freestream_dir': ['1', '0', '0'], 'control_surface_deflection': ['']} else: settings['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': int(self.M * self.Mstarfactor), 'freestream_dir': ['1', '0', '0'], 'control_surface_deflection': ['']} settings['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-8, 'min_delta': tolerance, 'gravity_on': True, 'gravity': 9.81} settings['StaticTrim'] = {'solver': 'StaticCoupled', 'solver_settings': settings['StaticCoupled'], 'thrust_nodes': thrust_nodes, 'initial_alpha': alpha, 'initial_deflection': cs_deflection, 'initial_thrust': thrust, 'max_iter': 200, 'fz_tolerance': 1e-2, 'fx_tolerance': 1e-2, 'm_tolerance': 1e-2} settings['Trim'] = {'solver': 'StaticCoupled', 'solver_settings': settings['StaticCoupled'], 'initial_alpha': alpha, 'initial_beta': beta, 'cs_indices': [0], 'initial_cs_deflection': [cs_deflection], 'thrust_nodes': thrust_nodes, 'initial_thrust': [thrust, thrust]} settings['NonLinearDynamicCoupledStep'] = {'print_info': 'off', 'initial_velocity_direction': [-1., 0., 0.], 'max_iterations': 950, 'delta_curved': 1e-6, 'min_delta': tolerance, 'newmark_damp': 5e-3, 'gravity_on': True, 'gravity': 9.81, 'num_steps': n_tstep, 'dt': dt, 'initial_velocity': u_inf * 0} settings['NonLinearDynamicPrescribedStep'] = {'print_info': 'off', 'initial_velocity_direction': [-1., 0., 0.], 'max_iterations': 950, 'delta_curved': 1e-6, 'min_delta': self.tolerance, 'newmark_damp': 5e-3, 'gravity_on': True, 'gravity': 9.81, 'num_steps': self.n_tstep, 'dt': self.dt} settings['StepLinearUVLM'] = {'dt': self.dt, 'integr_order': 1, 'remove_predictor': True, 'use_sparse': True, 'velocity_field_generator': 'GustVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0., 0.], 'gust_shape': '1-cos', 'gust_length': 1., 'gust_intensity': self.gust_intensity * u_inf, 'offset': 30., 'span': self.span}} settings['StepUvlm'] = {'print_info': 'off', 'horseshoe': self.horseshoe, 'num_cores': 4, 'n_rollup': 100, 'convection_scheme': self.wake_type, 'rollup_dt': dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'GustVelocityField', 'velocity_field_input': {'u_inf': u_inf * 1, 'u_inf_direction': [1., 0, 0], 'gust_shape': '1-cos', 'gust_length': 1., 'gust_intensity': gust_intensity * u_inf, 'offset': 30.0, 'span': self.span}, # 'velocity_field_generator': 'SteadyVelocityField', # 'velocity_field_input': {'u_inf': u_inf*1, # 'u_inf_direction': [1., 0., 0.]}, 'rho': rho, 'n_time_steps': n_tstep, 'dt': dt, 'gamma_dot_filtering': 3} settings['DynamicCoupled'] = {'print_info': 'on', 'structural_substeps': 1, 'dynamic_relaxation': 'on', 'clean_up_previous_solution': 'on', 'structural_solver': 'NonLinearDynamicCoupledStep', 'structural_solver_settings': settings['NonLinearDynamicCoupledStep'], 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'fsi_substeps': 200, 'fsi_tolerance': fsi_tolerance, 'relaxation_factor': relaxation_factor, 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': 0.0, 'n_time_steps': n_tstep, 'dt': dt, 'include_unsteady_force_contribution': 'off', 'postprocessors': ['BeamLoads', 'StallCheck', 'BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'BeamLoads': {'csv_output': 'off'}, 'StallCheck': {'output_degrees': True, 'stall_angles': { '0': [-12 * np.pi / 180, 6 * np.pi / 180], '1': [-12 * np.pi / 180, 6 * np.pi / 180], '2': [-12 * np.pi / 180, 6 * np.pi / 180]}}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}, 'AerogridPlot': { 'u_inf': u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, # 'WriteVariablesTime': { # # 'delimeter': ',', # # 'structure_nodes': [0], # # 'structure_variables': ['Z'] # # settings['WriteVariablesTime'] = {'delimiter': ' ', # 'FoR_variables': ['GFoR_pos', 'GFoR_vel', 'GFoR_acc'], # 'FoR_number': [], # 'structure_variables': ['AFoR_steady_forces', 'AFoR_unsteady_forces','AFoR_position'], # 'structure_nodes': [0,-1], # 'aero_panels_variables': ['gamma', 'gamma_dot'], # 'aero_panels_isurf': [0,1,2], # 'aero_panels_im': [1,1,1], # 'aero_panels_in': [-2,-2,-2], # 'aero_nodes_variables': ['GFoR_steady_force', 'GFoR_unsteady_force'], # 'aero_nodes_isurf': [0,1,2], # 'aero_nodes_im': [1,1,1], # 'aero_nodes_in': [-2,-2,-2] # }}} }} settings['Modal'] = {'print_info': True, 'use_undamped_modes': True, 'NumLambda': 20, 'rigid_body_modes': True, 'write_modes_vtk': 'on', 'print_matrices': 'on', 'save_data': 'on', 'continuous_eigenvalues': 'off', 'dt': dt, 'plot_eigenvalues': False} settings['LinearAssembler'] = {'flow': ['LinearAeroelastic'], 'LinearAeroelastic': { 'beam_settings': {'modal_projection': False, 'inout_coords': 'nodes', 'discrete_time': True, 'newmark_damp': 0.015, 'discr_method': 'newmark', 'dt': dt, 'proj_modes': 'undamped', 'use_euler': 'off', 'num_modes': 40, 'print_info': 'on', 'gravity': 'on', 'remove_dofs': []}, 'aero_settings': {'dt': dt, 'integr_order': 2, 'density': rho, 'remove_predictor': False, 'use_sparse': True, 'rigid_body_motion': True, 'use_euler': False, 'remove_inputs': []}, 'rigid_body_motion': True}} settings['AsymptoticStability'] = {'sys_id': 'LinearAeroelastic', 'print_info': 'on', 'frequency_cutoff': 0, 'export_eigenvalues': 'on'} settings['AerogridPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0, 'u_inf': u_inf } settings['AeroForcesCalculator'] = {'write_text_file': 'off', 'text_file_name': case_name + '_aeroforces.csv', 'screen_output': 'on', 'unsteady': 'off' } settings['BeamPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on'} settings['BeamLoads'] = {'csv_output': 'off'} settings['SaveData'] = {} config = configobj.ConfigObj() config.filename = file_name for k, v in settings.items(): config[k] = v config.write() self.settings = settings self.config = config if __name__=='__main__': import sharpy.sharpy_main ws = SweptWing(M=6, N=11, Mstarfactor=1, u_inf=22, rho=1.225, alpha_deg=0, cs_deflection_deg=-10, thrust=5, case_name_format=1) ws.horseshoe = True ws.clean_test_files() ws.update_mass_stiffness() ws.update_fem_prop() ws.update_aero_properties() ws.generate_fem_file() ws.generate_aero_file() ws.set_default_config_dict() ws.config['SHARPy']['flow'] = ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'AeroForcesCalculator', # 'StaticTrim', # 'AeroForcesCalculator', 'Modal', 'LinearAssembler', 'AsymptoticStability', # 'BeamPlot', # 'AerogridPlot'] ] ws.config.write() sharpy.sharpy_main.main(['', ws.config['SHARPy']['route'] + '/' + ws.config['SHARPy']['case'] + '.sharpy']) ================================================ FILE: sharpy/cases/templates/Ttail.py ================================================ ''' Templates to build T-tail models S. Maraniello, Oct 2018 classes: - Ttail_3beam allows to generate general T-tail models with 3-beam. - Ttail_canonical: builds the canonical test case as per Murua et al., Prog. Aerosp. Sci., 71 (2014) 54-84 ''' import h5py as h5 import numpy as np import configobj import os from IPython import embed import sharpy.utils.algebra as algebra import sharpy.utils.geo_utils as geo_utils class Ttail_3beams(): ''' Produces a relatively general geometry of a 3 beams T-tail. By default, quadratic beam elements are used. Three beams, sharing one node, are used to model the system. VTP and HPT can be tapered, but they have the same chord at the intersection. The time-step is automatically defined based on the HTP root chord, the panelling M and the incoming flow speed. Input: - M: chordwise panelling of surfaces (same for HTP and VTP) - Nv: vertical panelling of VTP. Nv must be divisible by 2. - Nh: span-wise panelling of HTP. Nh refers to the full number of panels on the HTP (left and right) and must be divisible by 4. - Mstar_fact: wake length to chord ratio - u_inf: wind-speed - rho: air density - chord_htp_root: chord length at the htp-vtp interspection - chord_htp_tip: tip chord of the HPT (at the root, vtp and htp) - chord_vtp_root: chord at the vtp base. - span_htp: total span of the HTP (left and right) - height_vtp: vertical distance between first and last node of the vtp. Note that, in case of sweep angle, this is not the VTP length. - alpha_htp_deg=0.0: HTP incidence in degrees, positive if upward. Note that if non-zero, both UVLM grid and structural properties are rotated. - sweep_htp_deg=0.0: sweep angle, positive if backward - sweep_vtp_deg=0.0: sweep angle, positive if backward - main_ea: chord-normalised distance of elastic axis from leading edge. Must be the same for HTP and VTP - alpha_inf_deg: angle of attach of incoming flow [deg]. This is obtained rotating the local frame A in which the geometry is defined if rotate_frame_A is True, otherwise the incoming flow is tilted. - beta_inf_deg: sideslip angle of incoming flow [deg]. This is obtained rotating the local frame A in which the geometry is defined if rotate_frame_A is True, otherwise the incoming flow is tilted. - rotate_frame_A: determines whether the angles alpha_inf_deg and beta_inf_deg are obtained through rotation of the T-tail or rotation of the incoming flow. Usage: - generate class instance: ws=Ttail_3beams(...) - manually define mass/stiffness properties in update_mass_stiff method - def ws.update_mass_stiff: ... - run: ws.clean_test_files() ws.update_derived_params() ws.generate_fem_file() ws.generate_aero_file() ws.set_default_config_dict() Warning: - cambred aerofoils not implemented in the model - as the mass/structural properties are not generated by this class, to produce a *fem.h5 input file one needs to manually define the update_mass_stiff method. (see Ttail_canonical) ''' def __init__(self, M, # chordwise panelling Nv, # panelling VTP Nh, # panelling HTP (total, not half) Mstar_fact, u_inf, # flight cond rho, # size chord_htp_root, chord_htp_tip, chord_vtp_root, span_htp, height_vtp, # elastic axis main_ea, # from LE. # angles alpha_htp_deg=0.0, # positive if upward sweep_htp_deg=0.0, # positive if backward sweep_vtp_deg=0.0, # positive if backward # others alpha_inf_deg=0.0, beta_inf_deg=0.0, rotate_frame_A=True, route='.', # saving case_name='Ttail'): ### parametrisation assert Nv%2 != 1,\ 'vertical panelling of VTP must be divisible by 2 (using 3-noded FEs!)' assert Nh%4 != 1,\ 'spanwise panelling of full HTP must be divisible by 4 (using 3-noded FEs!)' # chordwise self.M=M # vtp self.Nv=Nv # htp self.Nh=Nh self.Nh_semi=Nh//2 # wake self.Mstar_fact=Mstar_fact # wake chord-wise panel factor # beam elements self.n_surfaces=3 self.num_nodes_elem=3 self.num_elem_vtp=self.Nv//2 self.num_elem_htpL=self.Nh_semi//2 # port self.num_elem_htpR=self.Nh_semi//2 # starboard self.num_elem_tot=self.num_elem_vtp+self.num_elem_htpL+self.num_elem_htpR self.num_nodes_vtp =2*self.num_elem_vtp +1 self.num_nodes_htpL=2*self.num_elem_htpL+1 self.num_nodes_htpR=2*self.num_elem_htpR+1 self.num_nodes_tot=2*self.num_elem_vtp+2*self.num_elem_htpL+2*self.num_elem_htpR+1 ### store input self.u_inf=u_inf # flight cond self.rho=rho self.chord_htp_root=chord_htp_root self.chord_htp_tip=chord_htp_tip self.chord_vtp_root=chord_vtp_root self.span_htp=span_htp self.height_vtp=height_vtp self.main_ea=main_ea self.alpha_htp_deg=alpha_htp_deg self.sweep_htp_deg=sweep_htp_deg self.sweep_vtp_deg=sweep_vtp_deg # FoR A orientation self.alpha_inf_deg=alpha_inf_deg self.beta_inf_deg=beta_inf_deg self.rotate_frame_A=rotate_frame_A if self.rotate_frame_A: self.quat=algebra.euler2quat( np.pi/180.*np.array([0.0,alpha_inf_deg,beta_inf_deg])) self.u_inf_direction=np.array([1.,0.,0.]) else: self.quat=algebra.euler2quat(np.array([0.,0.,0.])) self.u_inf_direction=np.dot(algebra.euler2rot( np.pi/180.*np.array([0.0,alpha_inf_deg,beta_inf_deg])), np.array([1.,0.,0.])) # time-step self.dt=self.chord_htp_root/self.M/self.u_inf self.route=route + '/' self.case_name=case_name # # Aerofoil shape: root and tip # self.root_airfoil_P = 0 # self.root_airfoil_M = 0 # self.tip_airfoil_P = 0 # self.tip_airfoil_M = 0 def update_fem_prop(self): ''' Produce FEM connectivity, coordinates, mapping and BCs. Elements are numbered globally starting from: - vtp: bottom to top - htpL: tip to middle - htpR: middle to tip The node at which VTP and HPT intersect is number (global) 2*num_elem_vtp+1 ''' num_nodes_elem=self.num_nodes_elem num_elem_vtp=self.num_elem_vtp num_elem_htpL=self.num_elem_htpL num_elem_htpR=self.num_elem_htpR num_elem_tot=self.num_elem_tot num_nodes_vtp =self.num_nodes_vtp num_nodes_htpL=self.num_nodes_htpL num_nodes_htpR=self.num_nodes_htpR num_nodes_tot=self.num_nodes_tot ### beam number # used by both fem and aero (surface_number) # for each element (global number) allocate beam # vtp: 0 # htpL: 1 # htpR: 2 beam_number=np.zeros((num_elem_tot),dtype=np.int) beam_number[ :num_elem_vtp]=0 # vtp beam_number[num_elem_vtp:num_elem_vtp+num_elem_htpL]=1 # htp L beam_number[-num_elem_htpR:]=2 # htp R ### Connectivity # surface: for each surface, specity the elements global no. # global: for each element, specify the local-global node number. conn_loc=np.array([0, 2, 1],dtype=int) conn_surf_vtp=[ee for ee in range(num_elem_vtp)] conn_surf_htpL=[num_elem_vtp+ee for ee in range(num_elem_htpL)] conn_surf_htpR=[num_elem_vtp+num_elem_htpL+ee for ee in range(num_elem_htpR)] conn_glob=np.zeros((num_elem_tot,num_nodes_elem),dtype=int) # add vtp node_here=0 for ee in conn_surf_vtp: conn_glob[ee,:]=node_here+conn_loc node_here+=2 node_intersection=node_here # add htp left node_here+=1 node_free_htpL=node_here for ee in conn_surf_htpL:#range(num_elem_htpL): conn_glob[ee,:]=node_here+conn_loc node_here+=2 conn_glob[num_elem_vtp+num_elem_htpL-1,1]=node_intersection node_here-=1 # add htp right for ee in conn_surf_htpR: conn_glob[ee,:]=node_here+conn_loc node_here+=2 conn_glob[num_elem_vtp+num_elem_htpL,0]=node_intersection node_free_htpR=node_here ### Nodal coordinates sweep_htp=np.pi/180.*self.sweep_htp_deg sweep_vtp=np.pi/180.*self.sweep_vtp_deg xv = np.zeros((num_nodes_tot,)) yv = np.zeros((num_nodes_tot,)) zv = np.zeros((num_nodes_tot,)) # vtp nnvec=range(num_nodes_vtp) zv[nnvec]=np.linspace(0,self.height_vtp,num_nodes_vtp) xv[nnvec]=zv[nnvec]*np.tan( sweep_vtp ) # increment all other nodes xv[node_intersection:]=xv[node_intersection] zv[node_intersection:]=zv[node_intersection] # htpL nnvec=[1+node_intersection+nn for nn in range(num_nodes_htpL)] nnvec[-1]=node_intersection yv[nnvec]=np.linspace(-.5*self.span_htp,0.,num_nodes_htpL) xv[nnvec]-=yv[nnvec]*np.tan(sweep_htp) # htpR nnvec=[node_free_htpR-nn for nn in range(num_nodes_htpR)][::-1] nnvec[0]=node_intersection yv[nnvec]=np.linspace(0.,.5*self.span_htp,num_nodes_htpR) xv[nnvec]+=yv[nnvec]*np.tan(sweep_htp) ### boundary conditions boundary_conditions=np.zeros((num_nodes_tot,), dtype=int) boundary_conditions[0]=1 # clamp boundary_conditions[node_free_htpL]=-1 # free-end htpL boundary_conditions[node_free_htpR]=-1 # free end htpR ### Define yB, where yB points to the LE. # account for HTP incidence frame_of_reference_delta = np.zeros((num_elem_tot, num_nodes_elem, 3)) for ielem in range(num_elem_tot): for inode in range(num_nodes_elem): frame_of_reference_delta[ielem, inode, :] = [-1, 0, 0] self.frame_of_reference_delta=frame_of_reference_delta self.boundary_conditions=boundary_conditions self.beam_number=beam_number self.conn_loc=conn_loc self.conn_surf_vtp=conn_surf_vtp self.conn_surf_htpL=conn_surf_htpL self.conn_surf_htpR=conn_surf_htpR self.conn_glob=conn_glob self.x=xv self.y=yv self.z=zv def update_aero_prop(self): assert hasattr(self,'conn_glob'),\ 'Run "update_derived_params" before generating files' num_nodes_elem=self.num_nodes_elem num_elem_vtp=self.num_elem_vtp num_elem_htpL=self.num_elem_htpL num_elem_htpR=self.num_elem_htpR num_elem_tot=self.num_elem_tot num_nodes_vtp =self.num_nodes_vtp num_nodes_htpL=self.num_nodes_htpL num_nodes_htpR=self.num_nodes_htpR num_nodes_tot=self.num_nodes_tot ### Generate aerofoil profiles. # only flat plate airfoil_distribution=np.zeros((num_elem_tot,3),dtype=np.int) Airfoils_surf=[] Airfoils_surf.append(np.column_stack(geo_utils.generate_naca_camber(0,2))) # # vtp # # Airfoils_surf.append(geo_utils.generate_naca_camber(0,2)) # # Na=1 # for ee in self.conn_surf_vtp: # for nn_loc in [0,2]: # node_glob=self.conn_glob[ee,nn_loc] # eta=xxx # Airfoils_surf.append( # np.column_stack( # geo_utils.interpolate_naca_camber( # eta, # 0,self.root_airfoil_P, # self.tip_airfoil_M,self.tip_airfoil_P))) ### Define aerodynamic nodes aero_node=np.ones((num_nodes_tot,),dtype=bool) ### Define chord-wise panelling surface_m=self.M*np.ones((3,),dtype=int) ### Define chord-length, sweep, twist chord=np.zeros((num_elem_tot, 3)) twist=np.zeros((num_elem_tot, 3)) sweep=np.zeros((num_elem_tot, 3)) # vtp nn_count=0 for ee in self.conn_surf_vtp: for nn in [0,1,2]: nn_loc=self.conn_loc[nn] eta=np.float(nn_count+nn)/(num_nodes_vtp-1) chord[ee,nn_loc]=(1.-eta)*self.chord_vtp_root+eta*self.chord_htp_root sweep[ee,nn_loc]=eta*np.pi/180.*(-self.alpha_htp_deg) nn_count+=2 # htpL twist[self.conn_surf_htpL,:]=-np.pi/180.*self.alpha_htp_deg nn_count=0 for ee in self.conn_surf_htpL: for nn in [0,1,2]: nn_loc=self.conn_loc[nn] eta=np.float(nn_count+nn)/(num_nodes_htpL-1) chord[ee,nn_loc]=(1.-eta)*self.chord_htp_tip+eta*self.chord_htp_root nn_count+=1 # htpR twist[self.conn_surf_htpR,:]=-np.pi/180.*self.alpha_htp_deg nn_count=0 for ee in self.conn_surf_htpR: for nn in [0,1,2]: nn_loc=self.conn_loc[nn] eta=np.float(nn_count+nn)/(num_nodes_htpR-1) chord[ee,nn_loc]=(1.-eta)*self.chord_htp_root+eta*self.chord_htp_tip nn_count+=1 ### Define chord elastic axis position elastic_axis=self.main_ea*np.ones((num_elem_tot, 3,)) ### store self.Airfoils_surf=Airfoils_surf self.airfoil_distribution=airfoil_distribution self.aero_node=aero_node self.surface_m=surface_m self.twist=twist self.sweep=sweep self.chord=chord self.elastic_axis=elastic_axis def update_mass_stiff(self): ''' This method can be substituted to produce different wing configs ''' # uniform mass/stiffness on HTP/VTP ea,ga=1e7,1e7 gj, eiy,eiz=1e6,2e5,5e6 self.stiffness=np.zeros((1, 6, 6)) self.stiffness[0]=np.diag([ea, ga, ga, gj, eiy, eiz]) self.mass=np.zeros((1, 6, 6)) self.mass[0, :, :]=np.diag([1., 1., 1., .1, .1, .1]) self.elem_stiffness=np.zeros((self.num_elem_tot,), dtype=int) self.elem_mass=np.zeros((self.num_elem_tot,), dtype=int) def update_derived_params(self): # FEM connectivity, coords definition and mapping self.update_fem_prop() # Mass/stiffness properties self.update_mass_stiff() # Aero props self.update_aero_prop() def generate_fem_file(self): assert hasattr(self,'conn_glob'),\ 'Run "update_derived_params" before generating files' with h5.File(self.route+'/'+self.case_name+'.fem.h5','a') as h5file: coordinates = h5file.create_dataset( 'coordinates',data=np.column_stack((self.x, self.y, self.z))) conectivities = h5file.create_dataset( 'connectivities', data=self.conn_glob) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=self.num_nodes_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=self.num_nodes_tot) num_elem_handle = h5file.create_dataset( 'num_elem', data=self.num_elem_tot) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=self.stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=self.elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=self.mass) mass_handle = h5file.create_dataset( 'elem_mass', data=self.elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=self.frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=np.zeros((self.num_elem_tot,3))) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=self.boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=self.beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=np.zeros((self.num_nodes_tot,6))) def set_default_config_dict(self): if self.rotate_frame_A: alpha_aero=np.pi/180.*self.alpha_inf_deg beta_aero=np.pi/180.*self.beta_inf_deg else: alpha_aero=0. beta_aero=0. str_u_inf_direction=[str(self.u_inf_direction[cc]) for cc in range(3)] config=configobj.ConfigObj() config.filename=self.route+'/'+self.case_name+'.sharpy' config['SHARPy']={ 'flow':['BeamLoader', 'AerogridLoader', 'StaticCoupled', #'StaticUvlm', 'AerogridPlot', 'BeamPlot', 'SaveData'], 'case': self.case_name, 'route': self.route, 'write_screen': 'off', 'write_log': 'on', 'log_folder': self.route+'/output/', 'log_file': self.case_name+'.log'} config['BeamLoader']={ 'unsteady': 'off', 'orientation': self.quat} config['AerogridLoader']={ 'unsteady': 'off', 'aligned_grid': 'on', 'mstar': self.Mstar_fact*self.M, 'freestream_dir':str_u_inf_direction } config['StaticUvlm']={ 'rho': self.rho, 'velocity_field_generator':'SteadyVelocityField', 'velocity_field_input':{ 'u_inf': self.u_inf, 'u_inf_direction':self.u_inf_direction}, 'rollup_dt': self.dt, 'print_info': 'on', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup' : 0, 'rollup_aic_refresh': 0, 'rollup_tolerance': 1e-4} config['StaticCoupled']={ 'print_info': 'on', 'max_iter': 50, 'n_load_steps': 1, 'tolerance': 1e-6, 'relaxation_factor': 0., 'aero_solver': 'StaticUvlm', 'aero_solver_settings':{ 'rho': self.rho, 'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': self.dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': self.u_inf, 'u_inf_direction': str_u_inf_direction}}, # 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 4, 'delta_curved': 1e-5, 'min_delta': 1e-5, 'gravity_on': 'on', 'gravity': 9.754, 'orientation': self.quat},} config['LinearUvlm'] = { 'dt': self.dt, 'integr_order': 2, 'density': self.rho, 'remove_predictor': True, 'use_sparse': True, 'ScalingDict':{'length': 1., 'speed': 1., 'density':1.}} config['AerogridPlot']={'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0} config['AeroForcesCalculator']={'write_text_file': 'on', 'text_file_name': self.case_name+'_aeroforces.csv', 'screen_output': 'on', 'unsteady': 'off'} config['BeamPlot']={'include_rbm': 'off', 'include_applied_forces': 'on'} config['SaveData'] = {} config['Modal'] = {'NumLambda': 60, 'print_matrices': 'off', 'write_modes_vtk': True, 'use_undamped_modes': True} config.write() self.config=config def generate_aero_file(self): with h5.File(self.route+'/'+self.case_name+'.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil for aa in range(len(self.Airfoils_surf)): airfoils_group.create_dataset('%d'%aa,data=self.Airfoils_surf[aa]) chord_input = h5file.create_dataset('chord', data=self.chord) dim_attr = chord_input.attrs['units'] = 'm' twist_input = h5file.create_dataset('twist', data=self.twist) dim_attr=twist_input.attrs['units']='rad' sweep_input = h5file.create_dataset('sweep', data=self.sweep) dim_attr=sweep_input.attrs['units']='rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset( 'airfoil_distribution', data=self.airfoil_distribution) surface_distribution_input = h5file.create_dataset( 'surface_distribution', data=self.beam_number) surface_m_input = h5file.create_dataset( 'surface_m', data=self.surface_m) m_distribution_input = h5file.create_dataset( 'm_distribution', data='uniform'.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset( 'aero_node', data=self.aero_node) elastic_axis_input = h5file.create_dataset( 'elastic_axis', data=self.elastic_axis) def clean_test_files(self): fem_file_name = self.route+'/'+self.case_name+'.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) aero_file_name = self.route+'/'+self.case_name+'.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = self.route+'/'+self.case_name+'.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = self.route+'/'+self.case_name+'.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) class Ttail_canonical(Ttail_3beams): ''' Produces a model of the Canonical T-tail test case proposed by Murua et al., Prog. Aerosp. Sci., 71 (2014) 54-84 starting from the Ttail_3beam class. ''' def __init__(self, M,Nv,Nh,Mstar_fact, u_inf,rho, alpha_htp_deg, kv,kh, alpha_inf_deg=0., beta_inf_deg=0., rotate_frame_A=True, route='.',case_name='Ttail_can'): super().__init__( M=M, Nv=Nv, Nh=Nh, Mstar_fact=Mstar_fact, u_inf=u_inf, rho=rho, # size chord_htp_root=2., chord_htp_tip=2., chord_vtp_root=2., span_htp=4.*2., height_vtp=6., # ea main_ea=.25, # angles alpha_htp_deg=alpha_htp_deg, sweep_htp_deg=0.0, sweep_vtp_deg=0.0, # flow alpha_inf_deg=alpha_inf_deg, beta_inf_deg=beta_inf_deg, rotate_frame_A=rotate_frame_A, # other route=route, case_name=case_name) self.kv=kv self.kh=kh self.main_cg=.35 def update_mass_stiff(self): ''' This method can be substituted to produce different wing configs. Remind: the delta_frame_of_reference is chosen such that the B FoR axis are: - xb: along the wing span - yb: pointing towards the leading edge - zb: accordingly ''' ### mass matrix # identical for vtp/htp m_unit = 35. j_tors = 8. pos_cg_b=np.array([0.,self.chord_vtp_root*(self.main_cg-self.main_ea), 0.]) m_chi_cg=algebra.skew(m_unit*pos_cg_b) self.mass=np.zeros((2, 6, 6)) self.mass[0, :, :]=np.diag([ m_unit, m_unit, m_unit, j_tors, .1*j_tors, .9*j_tors]) self.mass[0,:3,3:]=+m_chi_cg self.mass[0,3:,:3]=-m_chi_cg self.elem_mass=np.zeros((self.num_elem_tot,), dtype=int) ### stiffness ea,ga=1e9,1e9 # vtp Kvtp=np.diag([ea, ga, ga, 1e6*self.kv, 1e7, 1e9]) # htp Khtp=np.diag([ea, ga, ga, 1e7*self.kh, 1e7*self.kh, 1e9]) self.stiffness=np.zeros((2, 6, 6)) self.stiffness[0,:,:]=Kvtp self.stiffness[1,:,:]=Khtp self.elem_stiffness=np.zeros((self.num_elem_tot,), dtype=int) self.elem_stiffness[self.conn_surf_vtp] =0 self.elem_stiffness[self.conn_surf_htpL]=1 self.elem_stiffness[self.conn_surf_htpR]=1 if __name__=='__main__': import os os.system('mkdir -p %s' %'./test' ) ws=Ttail_3beams(M=4, Nv=6, # panelling VTP Nh=8, # panelling HTP (total, not half) Mstar_fact=7., u_inf=30., # flight cond rho=1.225, # size chord_htp_root=2., chord_htp_tip=2., chord_vtp_root=2., span_htp=4.*2., height_vtp=6., # ea main_ea=.25, # angles alpha_htp_deg=5.0, # positive if upward sweep_htp_deg=60.0, # positive if backward sweep_vtp_deg=30.0, # positive if backward # alpha_inf_deg=0.0, beta_inf_deg=0.0, rotate_frame_A=True, route='./test/', case_name='Ttail') ws.clean_test_files() ws.update_derived_params() ws.generate_fem_file() ws.generate_aero_file() ws.set_default_config_dict() wc=Ttail_canonical( M=4,Nv=6,Nh=8,Mstar_fact=6, u_inf=30.,rho=1.225, alpha_htp_deg=2., kv=1.,kh=10., alpha_inf_deg=0., beta_inf_deg=0., rotate_frame_A=True, route='./test/',case_name='Tcanonical') wc.clean_test_files() wc.update_derived_params() wc.generate_fem_file() wc.generate_aero_file() wc.set_default_config_dict() ================================================ FILE: sharpy/cases/templates/__init__.py ================================================ ================================================ FILE: sharpy/cases/templates/flying_wings.py ================================================ ''' Templates to build flying wing models S. Maraniello, Jul 2018 classes: - FlyingWing: generate a flying wing model from a reduced set of input. The built in method 'update_mass_stiff' can be re-defined by the user to enter more complex inertial/stiffness properties - Smith(FlyingWing): generate HALE wing model - Goland(FlyingWing): generate Goland wing model ''' import warnings import h5py as h5 import numpy as np import configobj import os # from IPython import embed import sharpy.utils.algebra as algebra import sharpy.utils.geo_utils as geo_utils np.set_printoptions(linewidth=120) class FlyingWing(): ''' Flying wing template. - discretisation, and basic geometry/flight conditions can be passed in input - stiffness/mass properties must defined within the "update_mass_stiffness" method. - other aeroelastic params are attached as attributes to the class. - the method update_config_dict provides default setting for solver. These can be modified after an instance of the FlyingWing class is created. Args: M,N: chord/span-wise discretisations Mstar_fact: wake u_inf: flow speed alpha: FoR A pitch angle [deg] rho: density b_ref: geometry main_chord: main chord aspect_ratio: roll=0.: FoR A roll angle [deg] (see RollNodes flag) beta=0@ FoR A side angle [deg] sweep=0: sweep angle [deg] n_surfaces=2: physical_time=2: route='.': saving route case_name='flying_wing': RollNodes=False : If true, the wing nodes are rolled insted of the FoR A Usage: ws=flying_wings.FlyingWing(*args) ws.clean_test_files() ws.update_derived_params() ws.generate_fem_file() ws.generate_aero_file() ws.set_default_config_dict() ws.config['SHARPy']= bla bla bla ws.config.write() ''' def __init__(self, M, N, Mstar_fact, u_inf, alpha, rho, b_ref, main_chord, aspect_ratio, roll=0., yaw=0., beta=0, sweep=0, n_surfaces=2, physical_time=2, route='.', case_name='flying_wing', RollNodes=False, polar_file=None): ### parametrisation assert n_surfaces < 3, "use 1 or 2 surfaces only!" assert N % 2 != 1, \ 'UVLM spanwise panels must be even when using 3-noded FEs!' self.M = M # chord-wise panels self.N = N # total spanwise panels (over all surfaces) self.Mstar_fact = Mstar_fact # wake chord-wise panel factor self.n_surfaces = n_surfaces self.num_node_elem = 3 ### store input self.u_inf = u_inf # flight cond self.rho = rho self.alpha = alpha # angles self.beta = beta self.roll = roll self.yaw = yaw self.sweep = sweep self.b_ref = b_ref # geometry self.main_chord = main_chord self.aspect_ratio = aspect_ratio self.route = route + '/' self.case_name = case_name self.RollNodes = RollNodes # Verify that the route exists and create directory if necessary try: os.makedirs(self.route) except FileExistsError: pass ### other params self.u_inf_direction = np.array([1., np.sin(beta * np.pi / 180), 0.]) self.gravity_on = True # aeroelasticity self.sigma = 1 self.main_ea = 0.2 self.main_cg = 0.5 self.c_ref = 1. # ref. chord # Aerofoil shape: root and tip self.root_airfoil_P = 0 self.root_airfoil_M = 0 self.tip_airfoil_P = 0 self.tip_airfoil_M = 0 self.polars = None # list of polar for each airfoil if polar_file is not None: self.load_polar(polar_file) # Numerics for dynamic simulations self.dt_factor = 1 self.n_tstep = None self.physical_time = physical_time self.horseshoe = False self.fsi_tolerance = 1e-10 self.relaxation_factor = 0.2 self.gust_intensity = 0.01 self.gust_length = 5 self.tolerance = 1e-6 n_lumped_mass = 1 self.lumped_mass = np.zeros((n_lumped_mass)) self.lumped_mass_position = np.zeros((n_lumped_mass, 3)) self.lumped_mass_inertia = np.zeros((n_lumped_mass, 3, 3)) self.lumped_mass_nodes = np.zeros((n_lumped_mass), dtype=int) # Control surface initialisation self.n_control_surfaces = 0 self.control_surface = np.zeros((N + 1, 3), dtype=int) - 1 self.control_surface_type = np.zeros((self.n_control_surfaces), dtype=int) self.control_surface_deflection = np.zeros((self.n_control_surfaces,)) self.control_surface_chord = np.array([M//2], dtype=int) self.control_surface_hinge_coord = np.zeros_like(self.control_surface_type, dtype=float) def settings_to_config(self, settings): file_name = self.route + '/' + self.case_name + '.sharpy' config = configobj.ConfigObj() config.filename = file_name for k, v in settings.items(): config[k] = v config.write() return file_name def update_mass_stiff(self): '''This method can be substituted to produce different wing configs''' # uniform mass/stiffness ea, ga = 1e7, 1e7 gj, eiy, eiz = 1e6, 2e5, 5e6 base_stiffness = np.diag([ea, ga, ga, gj, eiy, eiz]) self.stiffness = np.zeros((1, 6, 6)) self.stiffness[0] = self.sigma * base_stiffness self.mass = np.zeros((1, 6, 6)) self.mass[0, :, :] = np.diag([1., 1., 1., .1, .1, .1]) self.elem_stiffness = np.zeros((self.num_elem_tot,), dtype=int) self.elem_mass = np.zeros((self.num_elem_tot,), dtype=int) n_lumped_mass = 1 self.lumped_mass = np.zeros((n_lumped_mass)) self.lumped_mass_position = np.zeros((n_lumped_mass, 3)) self.lumped_mass_inertia = np.zeros((n_lumped_mass, 3, 3)) self.lumped_mass_nodes = np.zeros((n_lumped_mass), dtype=int) self.lumped_mass[0] = 5. self.lumped_mass_position[0] = np.array([0, 0.25, 0]) def load_polar(self, file): """ Read polar from Airfoil Tools polar txt file Args: file (str): """ polar_raw_data = np.loadtxt(file, skiprows=12) self.polars = np.column_stack((polar_raw_data[:, 0] * np.pi / 180, # aoa polar_raw_data[:, 1], # cl polar_raw_data[:, 2], # cd polar_raw_data[:, 4])) #cm def update_derived_params(self): ### Derived # time-step self.dt = self.main_chord / self.M / self.u_inf * self.dt_factor self.n_tstep = int(np.round(self.physical_time / self.dt)) # angles self.alpha_rad = self.alpha * np.pi / 180. self.roll_rad = self.roll * np.pi / 180. self.beta_rad = self.beta * np.pi / 180. self.sweep_rad = self.sweep * np.pi / 180. self.yaw_rad = self.yaw * np.pi / 180. # FoR A orientation # if self.RollNodes: self.quat = algebra.euler2quat(np.array([self.roll_rad, self.alpha_rad, self.yaw_rad])) # else: # if np.abs(self.roll)>1e-3: # warnings.warn( # 'FoR A quaternion will be built with inverted '+\ # 'roll angle sign to compensate bug in algebra.euler2quat') # self.quat=algebra.euler2quat(np.array([-self.roll_rad,self.alpha_rad,self.beta_rad])) # geometry self.wing_span = self.aspect_ratio * self.main_chord # /np.cos(self.sweep_rad) self.S = self.wing_span * self.main_chord # discretisation assert self.n_surfaces < 3, "use 1 or 2 surfaces only!" assert self.N % 2 != 1, \ 'UVLM spanwise panels must be even when using 3-noded FEs!' self.num_elem_tot = self.N // 2 assert self.num_elem_tot % self.n_surfaces != 1, \ "Can't distribute equally FEM elements over surfaces" self.num_elem_surf = self.num_elem_tot // self.n_surfaces self.num_node_surf = self.N // self.n_surfaces + 1 self.num_node_tot = self.N + 1 self.control_surface = np.zeros((self.num_elem_tot, self.num_node_elem), dtype=int) - 1 # FEM connectivity, coords definition and mapping self.update_fem_prop() # Mass/stiffness properties self.update_mass_stiff() # Aero props self.update_aero_prop() def update_fem_prop(self): ''' Produce FEM connectivity, coordinates, mapping and BCs''' n_surfaces = self.n_surfaces num_node_elem = self.num_node_elem num_node_surf = self.num_node_surf num_node_tot = self.num_node_tot num_elem_surf = self.num_elem_surf num_elem_tot = self.num_elem_tot sweep_rad = self.sweep_rad half_span = 0.5 * self.wing_span #### Connectivity and nodal coordinates # Warning: the elements direction determines the xB axis direction. # Hence, to avoid accidentally rotating aerofoil profiles, if a wing is # defined through multiple surfaces, nodes should be oriented in the # same direction. # generate connectivity. Mid node at the end of array. conn_loc = np.array([0, 2, 1], dtype=int) conn_surf = np.zeros((num_elem_surf, num_node_elem), dtype=int) conn_glob = np.zeros((num_elem_tot, num_node_elem), dtype=int) # connectivity surface 01 for ielem in range(num_elem_surf): conn_surf[ielem, :] = conn_loc + ielem * (num_node_elem - 1) # global connectivity. Multiple surfaces merge at node 0. conn_glob[:num_elem_surf, :] = conn_surf for ss in range(1, n_surfaces): conn_glob[ss * num_elem_surf:(ss + 1) * num_elem_surf, :] = \ conn_surf + ss * (num_node_surf - 1) + 1 conn_glob[(ss + 1) * num_elem_surf - 1, 1] = 0 ### Nodal coordinates z = np.zeros((num_node_tot,)) if n_surfaces == 1: ### Local coord from half surface x01 = np.sin(sweep_rad) * np.linspace(0., half_span, (num_node_surf + 1) // 2) y01 = np.cos(sweep_rad) * np.linspace(0., half_span, (num_node_surf + 1) // 2) # and mirrow x = np.concatenate([x01[-1:0:-1], x01]) y = np.concatenate([-y01[-1:0:-1], y01]) if n_surfaces == 2: ### Local coord for surface 00 x01 = np.sin(sweep_rad) * np.linspace(0., half_span, num_node_surf) y01 = np.cos(sweep_rad) * np.linspace(0., half_span, num_node_surf) # and mirrow x = np.concatenate([x01, x01[-1:0:-1]]) y = np.concatenate([y01, -y01[-1:0:-1]]) if n_surfaces > 2: raise NameError( 'Geometry not implemented for multiple surfaces! Rotate them.') if self.RollNodes: sr, cr = np.sin(self.roll_rad), np.cos(self.roll_rad) yold = y.copy() y = cr * yold z = sr * yold ### surface/beam to element mapping # beam_number and surface_distribution in fem/aero files surface_number = np.zeros((num_elem_tot,), dtype=int) for ss in range(n_surfaces): surface_number[ss * num_elem_surf:(ss + 1) * num_elem_surf] = ss ##### boundary conditions boundary_conditions = np.zeros((num_node_tot,), dtype=int) if n_surfaces == 1: boundary_conditions[[0, -1]] = -1 # free-ends boundary_conditions[(num_node_surf - 1) // 2] = 1 # mid-clamp if n_surfaces == 2: boundary_conditions[0] = 1 # clamp at root (node 0) boundary_conditions[num_node_surf - 1] = -1 # free surf 00 boundary_conditions[num_node_surf] = -1 # free surf 01 if n_surfaces > 2: raise NameError('BCs not implemented for more than 2 surfaces') ### Define yB, where yB points to the LE. frame_of_reference_delta = np.zeros((num_elem_tot, num_node_elem, 3)) for ielem in range(num_elem_tot): for inode in range(num_node_elem): frame_of_reference_delta[ielem, inode, :] = [-1, 0, 0] self.frame_of_reference_delta = frame_of_reference_delta self.boundary_conditions = boundary_conditions self.conn_glob = conn_glob self.conn_surf = conn_surf self.surface_number = surface_number self.x = x self.y = y self.z = z def update_aero_prop(self): assert hasattr(self, 'conn_glob'), \ 'Run "update_derived_params" before generating files' n_surfaces = self.n_surfaces num_node_surf = self.num_node_surf num_node_tot = self.num_node_tot num_elem_surf = self.num_elem_surf num_elem_tot = self.num_elem_tot ### Generate aerofoil profiles. Only on surf 0. Airfoils_surf = [] if n_surfaces == 2: for inode in range(num_node_surf): eta = inode / num_node_surf Airfoils_surf.append( np.column_stack( geo_utils.interpolate_naca_camber( eta, self.root_airfoil_M, self.root_airfoil_P, self.tip_airfoil_M, self.tip_airfoil_P))) airfoil_distribution_surf = self.conn_surf airfoil_distribution = np.concatenate([airfoil_distribution_surf, airfoil_distribution_surf[::-1, [1, 0, 2]]]) if n_surfaces == 1: num_node_half = (num_node_surf + 1) // 2 for inode in range(num_node_half): eta = inode / num_node_half Airfoils_surf.append( np.column_stack( geo_utils.interpolate_naca_camber( eta, self.root_airfoil_M, self.root_airfoil_P, self.tip_airfoil_M, self.tip_airfoil_P))) airfoil_distribution_surf = self.conn_surf[:num_elem_surf // 2, :] airfoil_distribution = np.concatenate([ airfoil_distribution_surf[::-1, [1, 0, 2]], airfoil_distribution_surf]) self.Airfoils_surf = Airfoils_surf self.airfoil_distribution = airfoil_distribution ### others self.aero_node = np.ones((num_node_tot,), dtype=bool) self.surface_m = self.M * np.ones((n_surfaces,), dtype=int) self.twist = np.zeros((num_elem_tot, 3)) self.chord = self.main_chord * np.ones((num_elem_tot, 3)) self.elastic_axis = self.main_ea * np.ones((num_elem_tot, 3,)) def set_default_config_dict(self): str_u_inf_direction = [str(self.u_inf_direction[cc]) for cc in range(3)] config = configobj.ConfigObj() config.filename = self.route + '/' + self.case_name + '.sharpy' settings = dict() config['SHARPy'] = { 'flow': ['BeamLoader', 'AerogridLoader', # 'StaticUvlm', 'StaticCoupled', 'AerogridPlot', 'BeamPlot', 'SaveData'], 'case': self.case_name, 'route': self.route, 'write_screen': 'on', 'write_log': 'on', 'log_folder': './output/' + self.case_name + '/', 'log_file': self.case_name + '.log'} config['BeamLoader'] = { 'unsteady': 'off', 'orientation': self.quat} config['AerogridLoader'] = { 'unsteady': 'off', 'aligned_grid': 'on', 'mstar': self.Mstar_fact * self.M, 'freestream_dir': str_u_inf_direction } config['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 0, 'delta_curved': 1e-5, 'min_delta': 1e-5, 'gravity_on': self.gravity_on, 'gravity': 9.754, 'orientation': self.quat} config['StaticUvlm'] = { 'rho': self.rho, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': self.u_inf, 'u_inf_direction': self.u_inf_direction}, 'rollup_dt': self.dt, 'print_info': 'on', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_aic_refresh': 0, 'rollup_tolerance': 1e-4} config['StaticCoupled'] = { 'print_info': 'on', 'max_iter': 200, 'n_load_steps': 1, 'tolerance': 1e-10, 'relaxation_factor': 0., 'aero_solver': 'StaticUvlm', 'aero_solver_settings': { 'rho': self.rho, 'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': self.dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': self.u_inf, 'u_inf_direction': str_u_inf_direction}}, # 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 0, 'delta_curved': 1e-1, 'min_delta': 1e-10, 'gravity_on': self.gravity_on, 'gravity': 9.81}} config['LinearUvlm'] = {'dt': self.dt, 'integr_order': 2, 'density': self.rho, 'remove_predictor': True, 'use_sparse': True, 'ScalingDict': {'length': 1., 'speed': 1., 'density': 1.}} settings['StepLinearUVLM'] = {'dt': self.dt, 'solution_method': 'direct', 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': self.u_inf, 'u_inf_direction': self.u_inf_direction}} settings['NonLinearDynamicPrescribedStep'] = {'print_info': 'off', 'max_iterations': 950, 'delta_curved': 1e-1, 'min_delta': self.tolerance*1e3, 'newmark_damp': 5e-3, 'gravity_on': self.gravity_on, 'gravity': 9.81, 'num_steps': self.n_tstep, 'dt': self.dt} settings['StepUvlm'] = {'print_info': 'on', 'horseshoe': self.horseshoe, 'num_cores': 4, 'n_rollup': 100, 'convection_scheme': 0, 'rollup_dt': self.dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, # 'velocity_field_generator': 'TurbSimVelocityField', # 'velocity_field_input': {'turbulent_field': '/2TB/turbsim_fields/TurbSim_wide_long_A_low.h5', # 'offset': [30., 0., -10], # 'u_inf': 0.}, # 'velocity_field_generator': 'GustVelocityField', # 'velocity_field_input': {'u_inf': self.u_inf, # 'u_inf_direction': self.u_inf_direction, # 'gust_shape': 'continuous_sin', # 'gust_length': self.gust_length, # 'gust_intensity': self.gust_intensity * self.u_inf, # 'offset': 15.0, # 'span': self.main_chord * self.aspect_ratio}, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': self.u_inf*1, 'u_inf_direction': [1., 0., 0.]}, 'rho': self.rho, 'n_time_steps': self.n_tstep, 'dt': self.dt, 'gamma_dot_filtering': 3} config['DynamicCoupled'] = {'print_info': 'on', 'structural_substeps': 0, 'dynamic_relaxation': 'on', 'cleanup_previous_solution': 'on', 'structural_solver': 'NonLinearDynamicPrescribedStep', 'structural_solver_settings': settings['NonLinearDynamicPrescribedStep'], 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'fsi_substeps': 200, 'fsi_tolerance': self.fsi_tolerance, 'relaxation_factor': self.relaxation_factor, 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': 0.0, 'n_time_steps': self.n_tstep, 'dt': self.dt, 'include_unsteady_force_contribution': 'off', 'postprocessors': ['BeamLoads', 'StallCheck', 'BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'BeamLoads': {'csv_output': 'off'}, 'StallCheck': {'output_degrees': True, 'stall_angles': { '0': [-12 * np.pi / 180, 6 * np.pi / 180], '1': [-12 * np.pi / 180, 6 * np.pi / 180], '2': [-12 * np.pi / 180, 6 * np.pi / 180]}}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}, 'AerogridPlot': { 'u_inf': self.u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}}} config['DynamicUVLM'] = {'print_info': 'on', 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'n_time_steps': self.n_tstep, 'dt': self.dt, 'include_unsteady_force_contribution': 'on', 'postprocessors': ['AerogridPlot'], 'postprocessors_settings': {'AerogridPlot': {'u_inf': self.u_inf, 'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0}} } config['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0} config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': self.case_name + '_aeroforces.csv', 'screen_output': 'on', 'unsteady': 'off'} config['BeamPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on'} config['SaveData'] = {} config['Modal'] = {'NumLambda': 20, 'rigid_body_modes': 'off', 'print_matrices': 'off', 'save_data': 'off', 'continuous_eigenvalues': 'off', 'dt': 0, 'plot_eigenvalues': False, 'max_rotation_deg': 15., 'max_displacement': 0.15, 'write_modes_vtk': True, 'use_undamped_modes': True} config['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', 'linear_system_settings': { 'beam_settings': {'modal_projection': False, 'inout_coords': 'nodes', 'discrete_time': True, 'newmark_damp': 0.5, 'discr_method': 'newmark', 'dt': self.dt, 'proj_modes': 'undamped', 'use_euler': 'off', 'num_modes': 40, 'print_info': 'on', 'gravity': 'on', 'remove_dofs': []}, 'aero_settings': {'dt': self.dt, 'integr_order': 2, 'density': self.rho, 'remove_predictor': False, 'use_sparse': True, 'rigid_body_motion': False, 'use_euler': False, 'remove_inputs': ['u_gust']}, 'rigid_body_motion': False}} config['AsymptoticStability'] = {'print_info': True, 'velocity_analysis': [30, 180, 151]} config['LinDynamicSim'] = {'dt': self.dt, 'n_tsteps': self.n_tstep, 'sys_id': 'LinearAeroelastic', 'postprocessors': ['BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'AerogridPlot': { 'u_inf': self.u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}}} config['FrequencyResponse'] = {'compute_fom': 'on', 'frequency_unit': 'k', 'frequency_bounds': [0.0001, 1.0], 'quick_plot': 'on'} config.write() self.config = config # print('config dictionary set-up with flow:') # print(config['SHARPy']['flow']) def generate_aero_file(self, airfoil_efficiency=None): with h5.File(self.route + '/' + self.case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil for aa in range(len(self.Airfoils_surf)): airfoils_group.create_dataset('%d' % aa, data=self.Airfoils_surf[aa]) chord_input = h5file.create_dataset('chord', data=self.chord) dim_attr = chord_input.attrs['units'] = 'm' twist_input = h5file.create_dataset('twist', data=self.twist) dim_attr = twist_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset( 'airfoil_distribution', data=self.airfoil_distribution) surface_distribution_input = h5file.create_dataset( 'surface_distribution', data=self.surface_number) surface_m_input = h5file.create_dataset( 'surface_m', data=self.surface_m) m_distribution_input = h5file.create_dataset( 'm_distribution', data='uniform'.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset( 'aero_node', data=self.aero_node) elastic_axis_input = h5file.create_dataset( 'elastic_axis', data=self.elastic_axis) control_surface_input = h5file.create_dataset( 'control_surface', data=self.control_surface) control_surface_type_input = h5file.create_dataset( 'control_surface_type', data=self.control_surface_type) control_surface_deflection_input = h5file.create_dataset( 'control_surface_deflection', data=self.control_surface_deflection) control_surface_chord_input = h5file.create_dataset( 'control_surface_chord', data=self.control_surface_chord) if airfoil_efficiency is not None: a_eff_handle = h5file.create_dataset( 'airfoil_efficiency', data=airfoil_efficiency) if self.control_surface_hinge_coord is not None: cs_hinge = h5file.create_dataset( 'control_surface_hinge_coord', data=self.control_surface_hinge_coord ) if self.polars is not None: polars_group = h5file.create_group('polars') for i_airfoil in range(len(self.Airfoils_surf)): polars_group.create_dataset('{:g}'.format(i_airfoil), data=self.polars) def generate_fem_file(self): assert hasattr(self, 'conn_glob'), \ 'Run "update_derived_params" before generating files' with h5.File(self.route + '/' + self.case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset( 'coordinates', data=np.column_stack((self.x, self.y, self.z))) conectivities = h5file.create_dataset( 'connectivities', data=self.conn_glob) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=self.num_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=self.num_node_tot) num_elem_handle = h5file.create_dataset( 'num_elem', data=self.num_elem_tot) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=self.stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=self.elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=self.mass) mass_handle = h5file.create_dataset( 'elem_mass', data=self.elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=self.frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=np.zeros((self.num_elem_tot, 3))) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=self.boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=self.surface_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=np.zeros((self.num_node_tot, 6))) lumped_mass_handle = h5file.create_dataset( 'lumped_mass', data=self.lumped_mass) lumped_mass_inertia_handle = h5file.create_dataset( 'lumped_mass_inertia', data=self.lumped_mass_inertia) lumped_mass_position_handle = h5file.create_dataset( 'lumped_mass_position', data=self.lumped_mass_position) lumped_mass__nodes_handle = h5file.create_dataset( 'lumped_mass_nodes', data=self.lumped_mass_nodes) def generate_rom_files(self, left_tangent, right_tangent, ro, rc, fo, fc): with h5.File(self.route + '/' + self.case_name + '.rom.h5', 'a') as h5file: lt_handle = h5file.create_dataset('left_tangent', data=left_tangent) rt_handle = h5file.create_dataset('right_tangent', data=right_tangent) ro_h = h5file.create_dataset('ro', data=ro) fo_h = h5file.create_dataset('fo', data=fo) fc_h = h5file.create_dataset('fc', data=fc) rc_h = h5file.create_dataset('rc', data=rc) def clean_test_files(self): fem_file_name = self.route + '/' + self.case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) aero_file_name = self.route + '/' + self.case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = self.route + '/' + self.case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = self.route + '/' + self.case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) lininput_file_name = self.route + '/' + self.case_name + '.lininput.h5' if os.path.isfile(lininput_file_name): os.remove(lininput_file_name) rom_file = self.route + '/' + self.case_name + '.rom.h5' if os.path.isfile(rom_file): os.remove(rom_file) class Smith(FlyingWing): ''' Build Smith HALE wing. This class is nothing but a FlyingWing with pre-defined geometry properties and mass/stiffness data ("update_mass_stiffness" method) ''' def __init__(self, M, N, # chord/span-wise discretisations Mstar_fact, u_inf, # flight cond alpha, rho=0.08891, b_ref=32., # geometry main_chord=1., aspect_ratio=32, roll=0., beta=0., sweep=0., n_surfaces=2, route='.', case_name='smith', RollNodes=False): super().__init__(M=M, N=N, Mstar_fact=Mstar_fact, u_inf=u_inf, alpha=alpha, rho=rho, b_ref=b_ref, main_chord=main_chord, aspect_ratio=aspect_ratio, roll=roll, beta=beta, sweep=sweep, n_surfaces=n_surfaces, route=route, case_name=case_name, RollNodes=RollNodes) self.c_ref = 1. def update_mass_stiff(self): '''This method can be substituted to produce different wing configs''' # uniform mass/stiffness ea, ga = 1e5, 1e5 gj, eiy, eiz = 1e4, 2e4, 5e6 base_stiffness = np.diag([ea, ga, ga, gj, eiy, eiz]) self.stiffness = np.zeros((1, 6, 6)) self.stiffness[0] = self.sigma * base_stiffness self.mass = np.zeros((1, 6, 6)) self.mass[0, :, :] = np.diag([0.75, 0.75, 0.75, .1, .1, .1]) self.elem_stiffness = np.zeros((self.num_elem_tot,), dtype=int) self.elem_mass = np.zeros((self.num_elem_tot,), dtype=int) class Goland(FlyingWing): ''' Build a Goland wing. This class is nothing but a FlyingWing with pre-defined geometry properties and mass/stiffness data ("update_mass_stiffness" method) ''' def __init__(self, M, N, # chord/span-wise discretisations Mstar_fact, u_inf, # flight cond alpha, rho=1.02, b_ref=2. * 6.096, # geometry main_chord=1.8288, aspect_ratio=(2. * 6.096) / 1.8288, roll=0., yaw=0., beta=0., sweep=0., n_surfaces=1, physical_time=2, route='.', case_name='goland', RollNodes=False): super().__init__(M=M, N=N, Mstar_fact=Mstar_fact, u_inf=u_inf, alpha=alpha, rho=rho, b_ref=b_ref, main_chord=main_chord, aspect_ratio=aspect_ratio, roll=roll, beta=beta, yaw=yaw, sweep=sweep, physical_time=physical_time, n_surfaces=n_surfaces, route=route, case_name=case_name, RollNodes=RollNodes) # aeroelasticity parameters self.main_ea = 0.33 self.main_cg = 0.43 self.sigma = 1 # other self.c_ref = 1.8288 def update_mass_stiff(self): ''' This method can be substituted to produce different wing configs. Forthis model, remind that the delta_frame_of_reference is chosen such that the B FoR axis are: - xb: along the wing span - yb: pointing towards the leading edge (i.e. roughly opposite than xa) - zb: upward as za ''' # uniform mass/stiffness # ea,ga=1e7,1e6 ea, ga = 1e9, 1e9 gj = 0.987581e6 eiy = 9.77221e6 eiz = 1e2 * eiy base_stiffness = np.diag([ea, ga, ga, self.sigma * gj, self.sigma * eiy, eiz]) self.stiffness = np.zeros((1, 6, 6)) self.stiffness[0] = base_stiffness m_unit = 35.71 j_tors = 8.64 pos_cg_b = np.array([0., self.c_ref * (self.main_cg - self.main_ea), 0.]) m_chi_cg = algebra.skew(m_unit * pos_cg_b) self.mass = np.zeros((1, 6, 6)) self.mass[0, :, :] = np.diag([m_unit, m_unit, m_unit, j_tors, .1 * j_tors, .9 * j_tors]) self.mass[0, :3, 3:] = m_chi_cg self.mass[0, 3:, :3] = -m_chi_cg self.elem_stiffness = np.zeros((self.num_elem_tot,), dtype=int) self.elem_mass = np.zeros((self.num_elem_tot,), dtype=int) class GolandControlSurface(Goland): def __init__(self, M, N, # chord/span-wise discretisations Mstar_fact, u_inf, # flight cond alpha, cs_deflection=[0,0], n_control_surfaces=2, rho=1.02, b_ref=2. * 6.096, # geometry main_chord=1.8288, pct_flap=0.2, aspect_ratio=(2. * 6.096) / 1.8288, roll=0., yaw=0., beta=0., sweep=0., n_surfaces=1, physical_time=2, cs_type=0, route='.', case_name='goland', RollNodes=False): super().__init__(M=M, N=N, Mstar_fact=Mstar_fact, u_inf=u_inf, alpha=alpha, rho=rho, b_ref=b_ref, main_chord=main_chord, aspect_ratio=aspect_ratio, roll=roll, beta=beta, yaw=yaw, sweep=sweep, physical_time=physical_time, n_surfaces=n_surfaces, route=route, case_name=case_name, RollNodes=RollNodes) # aeroelasticity parameters self.main_ea = 0.33 self.main_cg = 0.43 self.sigma = 1 self.n_control_surfaces = n_control_surfaces self.control_surface_deflection = np.zeros(self.n_control_surfaces, dtype=float) self.control_surface_deflection[:] = np.deg2rad(cs_deflection) self.control_surface_chord = M // 2 * np.ones(self.n_control_surfaces, dtype=int) self.control_surface_type = np.zeros(self.n_control_surfaces, dtype=int) + cs_type self.control_surface_hinge_coord = np.zeros_like(self.control_surface_type, dtype=int) # other self.c_ref = 1.8288 self.pct_flap = pct_flap def update_aero_prop(self): assert hasattr(self, 'conn_glob'), \ 'Run "update_derived_params" before generating files' n_surfaces = self.n_surfaces num_node_surf = self.num_node_surf num_node_tot = self.num_node_tot num_elem_surf = self.num_elem_surf num_elem_tot = self.num_elem_tot pct_flap = self.pct_flap control_surface = self.control_surface ### Generate aerofoil profiles. Only on surf 0. Airfoils_surf = [] if n_surfaces == 2: for inode in range(num_node_surf): eta = inode / num_node_surf Airfoils_surf.append( np.column_stack( geo_utils.interpolate_naca_camber( eta, self.root_airfoil_M, self.root_airfoil_P, self.tip_airfoil_M, self.tip_airfoil_P))) # if inode >= num_node_surf // 2: ws_elem = 0 for i_surf in range(2): # print('Surface' + str(i_surf)) for i_elem in range(num_elem_surf): for i_local_node in range(self.num_node_elem): if i_elem >= int(num_elem_surf *(1- pct_flap)): if i_surf == 0: control_surface[ws_elem + i_elem, i_local_node] = 0 # Right flap else: control_surface[ws_elem + i_elem, i_local_node] = 1 # Left flap ws_elem += num_elem_surf # control_surface[i_elem, i_local_node] = 0 airfoil_distribution_surf = self.conn_surf airfoil_distribution = np.concatenate([airfoil_distribution_surf, airfoil_distribution_surf[::-1, [1, 0, 2]]]) control_surface[-num_elem_surf:] = control_surface[-num_elem_surf:, :][::-1] if n_surfaces == 1: num_node_half = (num_node_surf + 1) // 2 for inode in range(num_node_half): eta = inode / num_node_half Airfoils_surf.append( np.column_stack( geo_utils.interpolate_naca_camber( eta, self.root_airfoil_M, self.root_airfoil_P, self.tip_airfoil_M, self.tip_airfoil_P))) airfoil_distribution_surf = self.conn_surf[:num_elem_surf // 2, :] airfoil_distribution = np.concatenate([ airfoil_distribution_surf[::-1, [1, 0, 2]], airfoil_distribution_surf]) self.Airfoils_surf = Airfoils_surf self.airfoil_distribution = airfoil_distribution ### others self.aero_node = np.ones((num_node_tot,), dtype=bool) self.surface_m = self.M * np.ones((n_surfaces,), dtype=int) self.twist = np.zeros((num_elem_tot, 3)) self.chord = self.main_chord * np.ones((num_elem_tot, 3)) self.elastic_axis = self.main_ea * np.ones((num_elem_tot, 3,)) self.control_surface = control_surface def create_linear_files(self, x0, input_vec): with h5.File(self.route + '/' + self.case_name + '.lininput.h5', 'a') as h5file: x0 = h5file.create_dataset( 'x0', data=x0) u = h5file.create_dataset( 'u', data=input_vec) class QuasiInfinite(FlyingWing): ''' Builds a very high aspect ratio wing, for simulating 2D aerodynamics This class is nothing but a FlyingWing with pre-defined geometry properties and mass/stiffness data ("update_mass_stiffness" method) ''' def __init__(self, M, N, Mstar_fact, u_inf, alpha, aspect_ratio, rho=0.08891, b_ref=32., main_chord=3., roll=0., beta=0., sweep=0., n_surfaces=1, route='.', case_name='qsinf', RollNodes=False, polar_file=None): super().__init__(M=M, N=N, Mstar_fact=Mstar_fact, u_inf=u_inf, alpha=alpha, rho=rho, b_ref=b_ref, main_chord=main_chord, aspect_ratio=aspect_ratio, roll=roll, beta=beta, sweep=sweep, n_surfaces=n_surfaces, route=route, case_name=case_name, RollNodes=RollNodes, polar_file=polar_file) self.c_ref = main_chord self.main_ea = 0.5 self.main_cg = 0.5 def update_mass_stiff(self): '''This method can be substituted to produce different wing configs''' # uniform mass/stiffness ea, ga = 1e9, 1e9 gj = 2e8 eiy = 1e8 eiz = 1e8 base_stiffness = np.diag([ea, ga, ga, gj, eiy, eiz]) self.stiffness = np.zeros((1, 6, 6)) self.stiffness[0] = self.sigma * base_stiffness m_unit = 1. self.mass = np.zeros((1, 6, 6)) self.mass[0, :, :] = np.diag([m_unit, m_unit, m_unit, 1., .5, .5]) self.elem_stiffness = np.zeros((self.num_elem_tot,), dtype=int) self.elem_mass = np.zeros((self.num_elem_tot,), dtype=int) class Pazy(FlyingWing): ''' Build a Pazy wing. The Pazy wing is a highly flexible wing designed and developed at Technion University as an aeroelastic test case. ''' def __init__(self, M, N, # chord/span-wise discretisations Mstar_fact, u_inf, # flight cond alpha, rho=1.225, tip_rod=True, b_ref=2. * 0.55, # geometry main_chord=0.1, aspect_ratio=(2. * 0.55) / 0.1, roll=0., yaw=0., beta=0., sweep=0., n_surfaces=1, physical_time=2, route='.', case_name='pazy', RollNodes=False): super().__init__(M=M, N=N, Mstar_fact=Mstar_fact, u_inf=u_inf, alpha=alpha, rho=rho, b_ref=b_ref, main_chord=main_chord, aspect_ratio=aspect_ratio, roll=roll, beta=beta, yaw=yaw, sweep=sweep, physical_time=physical_time, n_surfaces=n_surfaces, route=route, case_name=case_name, RollNodes=RollNodes) # aeroelasticity parameters # self.main_ea = 0.3 self.main_ea = 0.4475 self.main_cg = 0.4510 self.sigma = 1 # other self.c_ref = main_chord self.tip_rod = tip_rod def update_mass_stiff(self): ''' This method can be substituted to produce different wing configs. For this model, remind that the delta_frame_of_reference is chosen such that the B FoR axis are: - xb: along the wing span - yb: pointing towards the leading edge (i.e. roughly opposite than xa) - zb: upward as za ''' # uniform mass/stiffness # Scaling factors (for debugging): sigma_scale_stiff = 1 sigma_scale_I = 1 # Pulling: ea = 7.12E+06 # In-plane bending: ga_inp = 3.31E+06 ei_inp = 3.11E+03 # Out-of-plane bending: # Original: # ga_oup = -4.16E+03 ei_oup = 4.67E+00 # Adjusted: # Adjusted to become non-negative ga_oup = 1E+06 # Torsion: gj = 7.20E+00 base_stiffness = np.diag([ea, ga_oup, ga_inp, gj, ei_oup, ei_inp]) * sigma_scale_stiff self.stiffness = np.zeros((1, 6, 6)) self.stiffness[0] = base_stiffness m_unit = 5.50E-01 # kg/m # Test cases to confirm properties: # Tests in vacuum, AoA=0 deg, analysed with NonLinearStatic beam solver. Verification displacements taken from # full 3D non-linear analysis in Abaqus. # Bending: # 5 N load at the tip. Expected deflection of about 62.6 mm at the tip (no gravity) # Tip coordinate in beam-fitted coordinates: X = 0.55m, Y = 0.m, Z = 0.m # m_unit = 0.001 # negligible mass distribution to mimic no-gravity FEA analysis # self.lumped_mass[0] = 5.2866 / 9.81 # self.lumped_mass_position[0] = np.array([0, 0, 0]) # self.lumped_mass_nodes[0] = self.N // 2 # self.lumped_mass_inertia[0, :, :] = np.diag([1e-1, 1e-1, 1e-1]) # Alternative bending: # self.app_forces[self.N//2, :] = [0, 0, 5.2866, 0, 0, 0] # Torsion: # Torsion of 0.3275 Nm, difference in tip LE/TE height: 1.130498767 + 1.361232758 = 2.49 mm # self.app_forces[self.N // 2, :] = [0, 0, 0, 0.3275, 0, 0] pos_cg_b = np.array([0., self.c_ref * (self.main_cg - self.main_ea), 0.])*sigma_scale_I m_chi_cg = algebra.skew(m_unit * pos_cg_b) self.mass = np.zeros((1, 6, 6)) Js = 3.03E-04 # kg.m2/m # Mass matrix J components: torsion, out of plane bending, in-plane bending # Torsion: increasing J decreases natural frequency # Bending: increasing J increases natural frequency # Chosen experimentally to match natural frequencies from Abaqus analysis self.mass[0, :, :] = np.diag([m_unit, m_unit, m_unit, Js, 0.5*Js, 12*Js])*sigma_scale_I self.mass[0, :3, 3:] = m_chi_cg self.mass[0, 3:, :3] = -m_chi_cg self.elem_stiffness = np.zeros((self.num_elem_tot,), dtype=int) self.elem_mass = np.zeros((self.num_elem_tot,), dtype=int) if self.tip_rod: n_lumped_mass = 2 self.lumped_mass = np.zeros((n_lumped_mass)) self.lumped_mass_position = np.zeros((n_lumped_mass, 3)) self.lumped_mass_inertia = np.zeros((n_lumped_mass, 3, 3)) self.lumped_mass_nodes = np.zeros((n_lumped_mass), dtype=int) # Lumped mass for approximating the wingtip weight (1): self.lumped_mass[0] = 19.95 / 1E3 # mass in kg # self.lumped_mass[0] = 1 # mass in kg - just to visually check self.lumped_mass_position[0] = np.array([0.005, -0.005, 0]) self.lumped_mass_nodes[0] = self.N // 2 self.lumped_mass_inertia[0, :, :] = np.diag([1.2815E-04, 2.87E-07, 1.17E-04]) # Lumped mass for approximating the wingtip weight (2): self.lumped_mass[1] = 19.95 / 1E3 # mass in kg # self.lumped_mass[1] = 1 # mass in kg - just to visually check self.lumped_mass_position[1] = np.array([-0.005, -0.005, 0]) # positive x now ascending towards root self.lumped_mass_nodes[1] = self.N // 2 + 1 self.lumped_mass_inertia[1, :, :] = np.diag([1.2815E-04, 2.87E-07, 1.17E-04]) class PazyControlSurface(Pazy): def __init__(self, M, N, # chord/span-wise discretisations Mstar_fact, u_inf, # flight cond alpha, cs_deflection=0, n_control_surfaces=2, rho=1.225, tip_rod=True, b_ref= 2. * 0.55, # geometry main_chord= 0.1, pct_flap= 0.2, aspect_ratio= (2. * 0.55) / 0.1, roll=0., yaw=0., beta=0., sweep=0., n_surfaces=1, physical_time=2, route='.', case_name='pazy', RollNodes=False, cs_type=0): super().__init__(M=M, N=N, Mstar_fact=Mstar_fact, u_inf=u_inf, alpha=alpha, rho=rho, tip_rod=tip_rod, b_ref=b_ref, main_chord=main_chord, aspect_ratio=aspect_ratio, roll=roll, beta=beta, yaw=yaw, sweep=sweep, physical_time=physical_time, n_surfaces=n_surfaces, route=route, case_name=case_name, RollNodes=RollNodes) # aeroelasticity parameters # self.main_ea = 0.3 self.main_ea = 0.4475 self.main_cg = 0.4510 self.sigma = 1 self.n_control_surfaces = self.n_surfaces self.control_surface_deflection = np.zeros(self.n_control_surfaces, dtype=float) self.control_surface_deflection[:] = np.deg2rad(cs_deflection) self.control_surface_chord = M // 2 * np.ones(self.n_control_surfaces, dtype=int) self.control_surface_type = np.zeros(self.n_control_surfaces, dtype=int) + cs_type self.control_surface_hinge_coord = np.zeros_like(self.control_surface_type, dtype=int) # other self.c_ref = main_chord self.pct_flap = pct_flap def update_aero_prop(self): assert hasattr(self, 'conn_glob'), \ 'Run "update_derived_params" before generating files' n_surfaces = self.n_surfaces num_node_surf = self.num_node_surf num_node_tot = self.num_node_tot num_elem_surf = self.num_elem_surf num_elem_tot = self.num_elem_tot pct_flap = self.pct_flap control_surface = self.control_surface ### Generate aerofoil profiles. Only on surf 0. Airfoils_surf = [] if n_surfaces == 2: for inode in range(num_node_surf): eta = inode / num_node_surf Airfoils_surf.append( np.column_stack( geo_utils.interpolate_naca_camber( eta, self.root_airfoil_M, self.root_airfoil_P, self.tip_airfoil_M, self.tip_airfoil_P))) # if inode >= num_node_surf // 2: ws_elem = 0 for i_surf in range(2): for i_elem in range(num_elem_surf): for i_local_node in range(self.num_node_elem): if i_elem >= int(num_elem_surf *(1- pct_flap)): if i_surf == 0: control_surface[ws_elem + i_elem, i_local_node] = 0 # Right flap else: control_surface[ws_elem + i_elem, i_local_node] = 1 # Left flap ws_elem += num_elem_surf # control_surface[i_elem, i_local_node] = 0 airfoil_distribution_surf = self.conn_surf airfoil_distribution = np.concatenate([airfoil_distribution_surf, airfoil_distribution_surf[::-1, [1, 0, 2]]]) control_surface[-num_elem_surf:] = control_surface[-num_elem_surf:, :][::-1] if n_surfaces == 1: num_node_half = (num_node_surf + 1) // 2 for inode in range(num_node_half): eta = inode / num_node_half Airfoils_surf.append( np.column_stack( geo_utils.interpolate_naca_camber( eta, self.root_airfoil_M, self.root_airfoil_P, self.tip_airfoil_M, self.tip_airfoil_P))) airfoil_distribution_surf = self.conn_surf[:num_elem_surf // 2, :] airfoil_distribution = np.concatenate([ airfoil_distribution_surf[::-1, [1, 0, 2]], airfoil_distribution_surf]) self.Airfoils_surf = Airfoils_surf self.airfoil_distribution = airfoil_distribution ### others self.aero_node = np.ones((num_node_tot,), dtype=bool) self.surface_m = self.M * np.ones((n_surfaces,), dtype=int) self.twist = np.zeros((num_elem_tot, 3)) self.chord = self.main_chord * np.ones((num_elem_tot, 3)) self.elastic_axis = self.main_ea * np.ones((num_elem_tot, 3,)) self.control_surface = control_surface def create_linear_files(self, x0, input_vec): with h5.File(self.route + '/' + self.case_name + '.lininput.h5', 'a') as h5file: x0 = h5file.create_dataset( 'x0', data=x0) u = h5file.create_dataset( 'u', data=input_vec) if __name__ == '__main__': import os os.system('mkdir -p %s' % './test') ws = Goland(M=4, N=20, Mstar_fact=12, u_inf=25., alpha=2.0, route='./test') ws.clean_test_files() ws.update_derived_params() ws.generate_fem_file() ws.generate_aero_file() ================================================ FILE: sharpy/cases/templates/fuselage_wing_configuration/__init__.py ================================================ ================================================ FILE: sharpy/cases/templates/fuselage_wing_configuration/fuselage_wing_configuration.py ================================================ import configobj from .fwc_structure import FWC_Structure from .fwc_aero import FWC_Aero from .fwc_fuselage import FWC_Fuselage import os import sharpy.sharpy_main class Fuselage_Wing_Configuration: """ Fuselage_Wing_Configuration is a template class to create an aircraft model of a simple wing-fuselage configuration within SHARPy. """ def __init__(self, case_name, case_route, output_route): self.case_name = case_name self.case_route = case_route self.output_route = output_route self.structure = None self.aero = None self.fuselage = None self.settings = None def init_aeroelastic(self, **kwargs): self.clean() self.init_structure(**kwargs) self.init_aero(**kwargs) if not kwargs.get('lifting_only', True): self.init_fuselage(**kwargs) def init_structure(self, **kwargs): self.structure = FWC_Structure(self.case_name, self.case_route, **kwargs) def init_aero(self, **kwargs): self.aero = FWC_Aero(self.structure, self.case_name, self.case_route,**kwargs) def init_fuselage(self, **kwargs): self.fuselage = FWC_Fuselage(self.structure, self.case_name, self.case_route,**kwargs) def generate(self): if not os.path.isdir(self.case_route): os.makedirs(self.case_route) self.structure.generate() self.aero.generate() if self.fuselage is not None: self.fuselage.generate() def create_settings(self, settings): file_name = self.case_route + '/' + self.case_name + '.sharpy' config = configobj.ConfigObj() config.filename = file_name for k, v in settings.items(): config[k] = v config.write() self.settings = settings def clean(self): list_files = ['.fem.h5', '.aero.h5', '.nonlifting_body.h5', '.dyn.h5', '.mb.h5', '.sharpy', '.flightcon.txt'] for file in list_files: path_file = self.case_route + '/' + self.case_name + file if os.path.isfile(path_file): os.remove(path_file) def run(self): sharpy.sharpy_main.main(['', self.case_route + '/' + self.case_name + '.sharpy']) ================================================ FILE: sharpy/cases/templates/fuselage_wing_configuration/fwc_aero.py ================================================ import h5py as h5 import numpy as np class FWC_Aero: """ FWC_Aero contains all attributes to define the aerodynamic grid and makes it accessible for SHARPy. """ def __init__(self, structure, case_name, case_route, **kwargs): """ Key-Word Arguments: - structure: structural object of BFF model """ self.structure = structure self.route = case_route self.case_name = case_name self.ea_wing = kwargs.get('elastic_axis',0.5) self.num_chordwise_panels = kwargs.get('num_chordwise_panels', 4) self.chord_wing = kwargs.get('chord', 1.) self.alpha_zero_deg = kwargs.get('alpha_zero_deg', 0.) self.n_surfaces = 2 self.radius_fuselage = kwargs.get('max_radius', 0.5) self.lifting_only = kwargs.get('lifting_only', True) def generate(self): """ Function to set up all necessary parameter inputs to define the geometry and discretisation of the lifting surfaces. Finally, informations are saved to an .h5 file serving as an input file for SHARPy. """ self.initialize_attributes() self.set_wing_properties() self.set_junction_boundary_conditions() self.write_input_file() def initialize_attributes(self): """ Initilializes all necessary attributes for the aero.h5 input file based on the number of nodes, elements, and surfaces of the model. """ self.airfoil_distribution = np.zeros((self.structure.n_elem, self.structure.n_node_elem), dtype=int) self.surface_distribution = np.zeros((self.structure.n_elem,), dtype=int) self.surface_m = np.zeros((self.n_surfaces, ), dtype=int) + self.num_chordwise_panels self.aero_node = np.zeros((self.structure.n_node,), dtype=bool) self.twist = np.zeros((self.structure.n_elem, self.structure.n_node_elem)) self.sweep = np.zeros_like(self.twist) self.chord = np.zeros_like(self.twist) self.elastic_axis = np.zeros_like(self.twist) self.junction_boundary_condition_aero = np.zeros((1, self.n_surfaces), dtype=int) - 1 def get_y_junction(self): if self.structure.vertical_wing_position == 0: return self.radius_fuselage else: return np.sqrt(self.radius_fuselage**2-self.structure.vertical_wing_position**2) def set_wing_properties(self): """ Sets necessary parameters to define the lifting surfaces of one wing (right). """ if not self.lifting_only: self.aero_node[:self.structure.n_node_right_wing] = abs(self.structure.y[:self.structure.n_node_right_wing]) > self.get_y_junction() - 0.05 self.aero_node[self.structure.n_node_right_wing:self.structure.n_node_wing_total] = self.aero_node[1:self.structure.n_node_right_wing] else: self.aero_node[:self.structure.n_node_wing_total] = True self.chord[:2*self.structure.n_elem_per_wing, :] = self.chord_wing self.elastic_axis[:2*self.structure.n_elem_per_wing, :] = self.ea_wing self.twist[:2*self.structure.n_elem_per_wing, :] = -np.deg2rad(self.alpha_zero_deg) # surf distribution 0 for right and 1 for left wing self.surface_distribution[self.structure.n_elem_per_wing:2*self.structure.n_elem_per_wing] = 1 def set_junction_boundary_conditions(self): """ Sets the boundary conditions for the fuselage-wing junction. These BCs define the partner surface. """ # Right wing (surface 0) has the left wing (surface 1) as a partner surface. self.junction_boundary_condition_aero[0, 0] = 1 # Left wing (surface 1) has the left wing (surface 0) as a partner surface. self.junction_boundary_condition_aero[0, 1] = 0 def write_input_file(self): """ Writes previously defined parameters to an .h5 file which serves later as an input file for SHARPy. """ with h5.File(self.route + '/' + self.case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil airfoils_group.create_dataset('0', data=np.column_stack( self.generate_naca_camber(P=0, M=0) )) h5file.create_dataset('chord', data=self.chord) h5file.create_dataset('twist', data=self.twist) h5file.create_dataset('sweep', data=self.sweep) # airfoil distribution h5file.create_dataset('airfoil_distribution', data=self.airfoil_distribution) h5file.create_dataset('surface_distribution', data=self.surface_distribution) h5file.create_dataset('surface_m', data=self.surface_m) h5file.create_dataset('m_distribution', data='uniform') h5file.create_dataset('aero_node', data=self.aero_node) h5file.create_dataset('elastic_axis', data=self.elastic_axis) h5file.create_dataset('junction_boundary_condition', data=self.junction_boundary_condition_aero) def generate_naca_camber(self,M=0, P=0): """ Generates the camber line coordinates of a specified NACA airfoil. # TODO: needed? """ mm = M*1e-2 p = P*1e-1 def naca(x, mm, p): if x < 1e-6: return 0.0 elif x < p: return mm/(p*p)*(2*p*x - x*x) elif x > p and x < 1+1e-6: return mm/((1-p)*(1-p))*(1 - 2*p + 2*p*x - x*x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, mm, p) for x in x_vec]) return x_vec, y_vec ================================================ FILE: sharpy/cases/templates/fuselage_wing_configuration/fwc_fuselage.py ================================================ #! /usr/bin/env python3 import h5py as h5 import numpy as np import matplotlib.pyplot as plt class FWC_Fuselage: """ FWC_Fuselage contains all attributes to define the nonlifting body grid representing the fuselage shape and makes it accessible for SHARPy. """ def __init__(self, structure, case_name, case_route, **kwargs): """ Key-Word Arguments: - structure: structural object of BFF model """ self.num_radial_panels = kwargs.get('num_radial_panels', 12) self.max_radius = kwargs.get('max_radius') self.fuselage_shape = kwargs.get('fuselage_shape','cylindrical') self.flag_plot_radius = kwargs.get('flag_plot_radius', False) self.structure = structure self.route = case_route self.case_name = case_name self.n_nonlifting_bodies = 1 def generate(self): """ Function to set up all necessary parameter inputs to define the geometry and discretisation of the nonlifting surfaces. Finally, informations are saved to an .h5 file serving as an input file for SHARPy. """ self.initialize_parameters() self.nonlifting_body_node[self.structure.n_node_wing_total:self.structure.n_node_fuselage_tail] = True if self.structure.vertical_wing_position == 0: self.nonlifting_body_node[0] = True self.nonlifting_body_distribution[self.structure.n_elem_per_wing*2:self.structure.n_elem_per_wing*2+self.structure.n_elem_fuselage] = 0 self.nonlifting_body_m[0] = self.num_radial_panels self.get_fuselage_geometry() self.write_fuselage_input_file() def initialize_parameters(self): """ Initilializes all necessary attributes for the nonlifting.h5 input file based on the number of nodes, elements, and surfaces of the nonlifting model. """ self.nonlifting_body_node = np.zeros((self.structure.n_node,), dtype=bool) self.nonlifting_body_distribution = np.zeros((self.structure.n_elem,), dtype=int) - 1 self.nonlifting_body_m = np.zeros((self.n_nonlifting_bodies, ), dtype=int) self.radius = np.zeros((self.structure.n_node,)) def get_fuselage_geometry(self): x_coord_fuselage_sorted = np.sort(self.get_values_at_fuselage_nodes(self.structure.x)) if self.fuselage_shape == 'cylindrical': radius_fuselage = self.create_fuselage_geometry(x_coord_fuselage_sorted.copy(), 0.2*self.structure.fuselage_length, 0.8*self.structure.fuselage_length) elif self.fuselage_shape == 'ellipsoid': radius_fuselage = self.get_radius_ellipsoid(x_coord_fuselage_sorted.copy(), self.structure.fuselage_length/2, self.max_radius) else: raise "ERROR Fuselage shape {} unknown.".format(self.fuselage_shape) if self.structure.vertical_wing_position == 0: self.radius[0] = radius_fuselage[self.structure.idx_junction] self.radius[self.structure.n_node_wing_total:] =np.delete(radius_fuselage, self.structure.idx_junction) else: self.radius[self.structure.n_node_wing_total:self.structure.n_node_fuselage_tail] = radius_fuselage if self.flag_plot_radius: self.plot_fuselage_radius(self.get_values_at_fuselage_nodes(self.structure.x), self.get_values_at_fuselage_nodes(self.radius)) self.plot_fuselage_radius(x_coord_fuselage_sorted, radius_fuselage) def get_values_at_fuselage_nodes(self, array): return array[self.nonlifting_body_node] def plot_fuselage_radius(self, x, radius): plt.scatter(x, radius) plt.grid() plt.xlabel("x, m") plt.ylabel("r, m") plt.gca().set_aspect('equal') plt.show() def write_fuselage_input_file(self): with h5.File(self.route + '/' + self.case_name + '.nonlifting_body.h5', 'a') as h5file: h5file.create_dataset('shape', data='cylindrical') h5file.create_dataset('surface_m', data=self.nonlifting_body_m) h5file.create_dataset('nonlifting_body_node', data=self.nonlifting_body_node) h5file.create_dataset('surface_distribution', data=self.nonlifting_body_distribution) h5file.create_dataset('radius', data=self.radius) def get_radius_ellipsoid(self, x_coordinates, a, b): x_coordinates[:] -= x_coordinates[-1] - a y_coordinates = b*np.sqrt(1-(x_coordinates/a)**2) return y_coordinates def find_index_of_closest_entry(self, array_values, target_value): return np.argmin(np.abs(array_values - target_value)) def create_fuselage_geometry(self, x_coord_fuselage, x_nose_end, x_tail_start): array_radius = np.zeros_like(x_coord_fuselage) # start with nose at zero to get indices of nose and tail correct x_coord_fuselage[:] -= x_coord_fuselage[0] idx_cylinder_start = self.find_index_of_closest_entry(x_coord_fuselage, x_nose_end) idx_cylinder_end = self.find_index_of_closest_entry(x_coord_fuselage, x_tail_start) # set constant radius of cylinder array_radius[idx_cylinder_start:idx_cylinder_end] = self.max_radius # get ellipsoidal nose and tail shape array_radius[:idx_cylinder_start+1] = self.get_nose_shape(x_coord_fuselage[:idx_cylinder_start+1], self.max_radius) array_radius[idx_cylinder_end-1:] = np.flip(self.get_nose_shape(x_coord_fuselage[idx_cylinder_end-1:], self.max_radius)) return array_radius def get_nose_shape(self, x_coord, radius): n_nodes = len(x_coord) x_coord[:] -= x_coord[-1] x_coord = np.concatenate((x_coord, np.flip(x_coord[:-1]))) radius = self.get_radius_ellipsoid(x_coord, x_coord[0], radius) return radius[:n_nodes] ================================================ FILE: sharpy/cases/templates/fuselage_wing_configuration/fwc_get_settings.py ================================================ import numpy as np import sharpy.utils.algebra as algebra def define_simulation_settings(flow, model, alpha_deg, u_inf, dt=1, rho = 1.225, lifting_only=True, nonlifting_only=False, phantom_test=False, horseshoe=False, **kwargs): gravity = kwargs.get('gravity',True) nonlifting_body_interactions = not lifting_only and not nonlifting_only wake_length = kwargs.get('wake_length', 10) # Other parameters if horseshoe: mstar = 1 else: mstar = wake_length*model.aero.num_chordwise_panels # numerics n_step = kwargs.get('n_step', 5) structural_relaxation_factor = kwargs.get('structural_relaxation_factor', 0.6) tolerance = kwargs.get('tolerance', 1e-6) fsi_tolerance = kwargs.get('fsi_tolerance', 1e-6) num_cores = kwargs.get('num_cores',2) if not lifting_only: nonlifting_body_interactions = True else: nonlifting_body_interactions = False settings = {} settings['SHARPy'] = {'case': model.case_name, 'route': model.case_route, 'flow': flow, 'write_screen': 'off', 'write_log': 'on', 'log_folder': model.output_route, 'log_file': model.case_name + '.log'} settings['BeamLoader'] = {'unsteady': 'on', 'orientation': algebra.euler2quat(np.array([0., np.deg2rad(alpha_deg), 0.]))} settings['BeamLoads'] = {} settings['LiftDistribution'] = {'coefficients': True} settings['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-1, 'min_delta': tolerance, 'gravity_on': gravity, 'gravity': 9.81} settings['StaticUvlm'] = {'print_info': 'on', 'horseshoe': horseshoe, 'num_cores': num_cores, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho, 'nonlifting_body_interactions': nonlifting_body_interactions, 'only_nonlifting': nonlifting_only, 'phantom_wing_test': phantom_test, 'ignore_first_x_nodes_in_force_calculation': kwargs.get('ignore_first_x_nodes_in_force_calculation', 0), } settings['StaticCoupled'] = {'print_info': 'off', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': settings['NonLinearStatic'], 'aero_solver': 'StaticUvlm', 'aero_solver_settings': settings['StaticUvlm'], 'max_iter': 100, 'n_load_steps': n_step, 'tolerance': fsi_tolerance, 'relaxation_factor': structural_relaxation_factor, 'nonlifting_body_interactions': nonlifting_body_interactions} settings['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'mstar': mstar, #int(20/tstep_factor), 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': { 'u_inf': u_inf, 'u_inf_direction': [1., 0., 0.], 'dt': dt, }, } if 'WriteVariablesTime' in flow: settings['WriteVariablesTime'] = {'cleanup_old_solution': True} if kwargs.get('writeCpVariables', False): settings['WriteVariablesTime']['nonlifting_nodes_variables'] = ['pressure_coefficients'] settings['WriteVariablesTime']['nonlifting_nodes_isurf'] = np.zeros((model.structure.n_node_fuselage,)) settings['WriteVariablesTime']['nonlifting_nodes_im'] = np.zeros((model.structure.n_node_fuselage)) settings['WriteVariablesTime']['nonlifting_nodes_in'] = list(range(model.structure.n_node_fuselage)) if kwargs.get('writeWingPosVariables', False): settings['WriteVariablesTime']['structure_variables'] = ['pos'] settings['WriteVariablesTime']['structure_nodes'] = list(range(model.structure.n_node-2)) settings['NonliftingbodygridLoader'] = {} settings['AeroForcesCalculator'] = {'coefficients': False} settings['AerogridPlot'] = {'plot_nonlifting_surfaces': nonlifting_body_interactions} settings['BeamPlot'] = {} settings['NoStructural'] = {} settings['NonLinearDynamicPrescribedStep'] = {'print_info': 'off', 'max_iterations': 950, 'delta_curved': 1e-1, 'min_delta': 1e-6, 'newmark_damp': 1e-4, 'gravity_on': gravity, 'gravity': 9.81, 'num_steps': kwargs.get('n_tsteps',10), 'dt': dt, } settings['StepUvlm'] = {'print_info': 'on', 'num_cores': 4, 'convection_scheme': 3, 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0., 0.]}, 'rho': rho, 'n_time_steps': kwargs.get('n_tsteps',10), 'dt': dt, 'phantom_wing_test': phantom_test, 'nonlifting_body_interactions': not lifting_only, 'gamma_dot_filtering': 3, 'ignore_first_x_nodes_in_force_calculation': kwargs.get('ignore_first_x_nodes_in_force_calculation', 0),} dynamic_structural_solver = kwargs.get('structural_solver','NonLinearDynamicPrescribedStep') settings['DynamicCoupled'] = {'structural_solver': dynamic_structural_solver, 'structural_solver_settings': settings[dynamic_structural_solver], 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'fsi_substeps': kwargs.get('fsi_substeps', 200), 'fsi_tolerance': fsi_tolerance, 'relaxation_factor': kwargs.get('relaxation_factor',0.1), 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': kwargs.get('final_relaxation_factor', 0.05), 'n_time_steps': kwargs.get('n_tsteps',10), 'dt': dt, 'nonlifting_body_interactions': not lifting_only, 'include_unsteady_force_contribution': kwargs.get('unsteady_force_distribution', True), 'postprocessors': ['BeamLoads'], 'postprocessors_settings': { 'BeamLoads': {'csv_output': 'off'}, }, } return settings ================================================ FILE: sharpy/cases/templates/fuselage_wing_configuration/fwc_structure.py ================================================ import numpy as np import h5py as h5 class FWC_Structure: """ FWC_Structure contains all attributes to define the beam geometriy and discretisation and makes it accessible for SHARPy. """ def __init__(self, case_name, case_route, **kwargs): self.n_elem_multiplier = kwargs.get('n_elem_multiplier', 1) self.route = case_route self.case_name = case_name self.n_node_elem = 3 self.n_surfaces = 2 self.n_material = 2 # wing + fuselage self.half_wingspan = kwargs.get('half_wingspan', 2) self.fuselage_length = kwargs.get('fuselage_length', 10) self.offset_nose_wing_beam = kwargs.get('offset_nose_wing', self.fuselage_length/2) self.vertical_wing_position = kwargs.get('vertical_wing_position', 0.) self.n_elem_per_wing = kwargs.get('n_elem_per_wing', 10) self.n_elem_fuselage = kwargs.get('n_elem_fuselage', 10) self.sigma = kwargs.get('sigma', 10) self.sigma_fuselage = kwargs.get('sigma_fuselage', 100.) self.enforce_uniform_fuselage_discretisation = kwargs.get( 'enforce_uniform_fuselage_discretisation', False ) self.fuselage_discretisation = kwargs.get('fuselage_discretisation', 'uniform') self.thrust = 0. def generate(self): """ Function to set up all necessary parameter inputs to define the geometry and discretisation of the beam. Finally, informations are saved to an .h5 file serving as an input file for SHARPy. """ self.set_element_and_nodes() self.initialize_parameters() self.set_stiffness_and_mass_propoerties() self.set_beam_properties_right_wing() self.mirror_wing_beam() self.app_forces[0] = [0, self.thrust, 0, 0, 0, 0] self.set_beam_properties_fuselage() self.write_input_file() def set_element_and_nodes(self): """ Based on the specified number of elements of the wing and fuselage, the number of nodes of each component and the total number of elements and nodes are defined here. """ self.n_node_fuselage = self.n_elem_fuselage*(self.n_node_elem - 1) self.n_node_right_wing = self.n_elem_per_wing*(self.n_node_elem - 1) + 1 # the left wing beam has one node less than the right one, since they shares the center node self.n_node_left_wing = self.n_node_right_wing - 1 self.n_node_wing_total = self.n_node_right_wing + self.n_node_left_wing self.n_node_fuselage_tail = self.n_node_wing_total + self.n_node_fuselage self.n_node = self.n_node_fuselage + self.n_node_wing_total self.n_elem =self.n_elem_fuselage + 2 * self.n_elem_per_wing # vertical wing offset requires a connection element between wing and fuselage beam if not self.vertical_wing_position == 0: self.n_elem += 1 self.n_node += 1 def initialize_parameters(self): self.x = np.zeros((self.n_node, )) self.y = np.zeros((self.n_node, )) self.z = np.zeros((self.n_node, )) self.frame_of_reference_delta = np.zeros((self.n_elem, self.n_node_elem, 3)) self.conn = np.zeros((self.n_elem, self.n_node_elem), dtype=int) self.beam_number = np.zeros((self.n_elem, ), dtype=int) self.boundary_conditions = np.zeros((self.n_node, ), dtype=int) self.structural_twist = np.zeros((self.n_elem, self.n_node_elem)) self.app_forces = np.zeros((self.n_node, 6)) self.stiffness = np.zeros((self.n_material, 6, 6)) self.mass = np.zeros((self.n_material, 6, 6)) self.elem_stiffness = np.zeros((self.n_elem, ), dtype=int) self.mass = np.zeros((self.n_material, 6, 6)) self.elem_mass = np.zeros((self.n_elem, ), dtype=int) def set_stiffness_and_mass_propoerties(self): # Define aeroelastic properties ea = 1e7 ga = 1e5 gj = 1e4 eiy = 2e4 eiz = 4e6 m_bar_main = 0.75 j_bar_main = 0.075 m_bar_fuselage = 0.3*1.5 j_bar_fuselage = 0.08 base_stiffness_main = self.sigma*np.diag([ea, ga, ga, gj, eiy, eiz]) base_stiffness_fuselage = base_stiffness_main.copy()*self.sigma_fuselage base_stiffness_fuselage[4, 4] = base_stiffness_fuselage[5, 5] self.stiffness[0, ...] = base_stiffness_main self.stiffness[1, ...] = base_stiffness_fuselage self.mass[0, ...] = self.generate_mass_matrix(m_bar_main, j_bar_main) self.mass[1, ...] = self.generate_mass_matrix(m_bar_fuselage, j_bar_fuselage) def generate_mass_matrix(self, m_bar, j_bar): return np.diag([m_bar, m_bar, m_bar, j_bar, 0.5*j_bar, 0.5*j_bar]) def set_beam_properties_right_wing(self): """ Defines all necessary parameters to define the beam including node coordinate, elements with their associated nodes, frame of reference delta, boundary conditions such as reference node and free tips, stiffness and mass property ID for each element, and twist. """ self.y[:self.n_node_right_wing] = np.linspace(0, self.half_wingspan, self.n_node_right_wing) self.z[:self.n_node_right_wing] += self.vertical_wing_position for ielem in range(self.n_elem_per_wing): self.conn[ielem, :] = ((np.ones((3, )) * ielem * (self.n_node_elem - 1)) + [0, 2, 1]) for ilocalnode in range(self.n_node_elem): self.frame_of_reference_delta[ielem, ilocalnode, :] = [-1.0, 0.0, 0.0] self.boundary_conditions[0] = 1 self.boundary_conditions[self.n_node_right_wing-1] = -1 # free tip def mirror_wing_beam(self): """ Mirrors the parameters from the beam representing the right free-flying wing for the left one. """ self.x[self.n_node_right_wing:self.n_node_wing_total] = self.x[1:self.n_node_right_wing] self.y[self.n_node_right_wing:self.n_node_wing_total] = - self.y[1:self.n_node_right_wing] self.z[self.n_node_right_wing:self.n_node_wing_total] = self.z[1:self.n_node_right_wing] self.frame_of_reference_delta[self.n_elem_per_wing:2*self.n_elem_per_wing, :, :] = self.frame_of_reference_delta[:self.n_elem_per_wing, :, :] * (-1) self.elem_stiffness[self.n_elem_per_wing:2*self.n_elem_per_wing] = self.elem_stiffness[:self.n_elem_per_wing] self.elem_mass[self.n_elem_per_wing:2*self.n_elem_per_wing] = self.elem_mass[:self.n_elem_per_wing] self.beam_number[self.n_elem_per_wing:2*self.n_elem_per_wing] = 1 self.boundary_conditions[self.n_node_wing_total-1] = -1 # free tip self.conn[self.n_elem_per_wing:2*self.n_elem_per_wing, :] = self.conn[:self.n_elem_per_wing, :] + self.n_node_right_wing - 1 self.conn[self.n_elem_per_wing, 0] = 0 def set_x_coordinate_fuselage(self): if self.vertical_wing_position == 0: n_nodes_fuselage = self.n_node_fuselage + 1 else: n_nodes_fuselage = self.n_node_fuselage if self.fuselage_discretisation == 'uniform': x_coord_fuselage = np.linspace(0, self.fuselage_length, n_nodes_fuselage) - self.offset_nose_wing_beam elif self.fuselage_discretisation == '1-cosine': x_coord_fuselage = np.linspace(0, 1, n_nodes_fuselage) x_coord_fuselage = 0.5*(1.0 - np.cos(x_coord_fuselage*np.pi)) x_coord_fuselage *= self.fuselage_length x_coord_fuselage -= self.offset_nose_wing_beam else: raise "ERROR Specified fuselage discretisation '{}' unknown".format(self.fuselage_discretisation) self.idx_junction = self.find_index_of_closest_entry(x_coord_fuselage, self.x[0]) self.idx_junction_global = self.idx_junction + self.n_node_wing_total if self.vertical_wing_position == 0: if self.enforce_uniform_fuselage_discretisation: self.x[:self.n_node_wing_total] += x_coord_fuselage[self.idx_junction] x_coord_fuselage = np.delete(x_coord_fuselage, self.idx_junction) self.x[self.n_node_wing_total:self.n_node_fuselage_tail] = x_coord_fuselage def adjust_fuselage_connectivities(self): idx_in_conn = np.where(self.conn == self.idx_junction_global) self.conn[idx_in_conn[0][0]+1:, :] -= 1 if idx_in_conn[0][0] == 2: # if middle node, correct end node of element self.conn[idx_in_conn[0][0], 1] -= 1 for i_match in range(np.shape(idx_in_conn)[1]): #several matches possible if junction node is not middle node self.conn[idx_in_conn[0][i_match], idx_in_conn[1][i_match]] = 0 def add_additional_element_for_low_wing(self): self.x[-1] = self.x[0] self.y[-1] = self.y[0] self.z[-1] = self.vertical_wing_position / 2 self.conn[-1, 0] = 0 self.conn[-1, 1] = self.idx_junction_global self.conn[-1, 2] = self.n_node - 1 self.elem_stiffness[-1] = 1 self.elem_mass[-1] = 1 self.beam_number[-1] = 3 def set_beam_properties_fuselage(self): self.set_x_coordinate_fuselage() self.beam_number[self.n_elem_per_wing*2:] = 2 for ielem in range(self.n_elem_per_wing * 2,self.n_elem): self.conn[ielem, :] = ((np.ones((3, )) * (ielem-self.n_elem_per_wing * 2) * (self.n_node_elem - 1)) + [0, 2, 1]) + self.n_node_wing_total for ilocalnode in range(self.n_node_elem): self.frame_of_reference_delta[ielem, ilocalnode, :] = [0.0, 1.0, 0.0] self.elem_stiffness[self.n_elem_per_wing*2:] = 1 self.elem_mass[self.n_elem_per_wing*2:] = 1 if self.vertical_wing_position == 0: self.adjust_fuselage_connectivities() else: self.add_additional_element_for_low_wing() self.boundary_conditions[self.n_node_wing_total] = -1 # fuselage nose self.boundary_conditions[self.n_node_wing_total + self.n_node_fuselage - 1] = -1 # fuselage tail def find_index_of_closest_entry(self, array_values, target_value): return np.argmin(np.abs(array_values - target_value)) def set_thrust(self, value): self.thrust = value def write_input_file(self): """ Writes previously defined parameters to an .h5 file which serves later as an input file for SHARPy. """ with h5.File(self.route + '/' + self.case_name + '.fem.h5', 'a') as h5file: h5file.create_dataset('coordinates', data=np.column_stack((self.x, self.y, self.z))) h5file.create_dataset('connectivities', data=self.conn) h5file.create_dataset('num_node_elem', data=self.n_node_elem) h5file.create_dataset('num_node', data=self.n_node) h5file.create_dataset('num_elem', data=self.n_elem) h5file.create_dataset('stiffness_db', data=self.stiffness) h5file.create_dataset('elem_stiffness', data=self.elem_stiffness) h5file.create_dataset('mass_db', data=self.mass) h5file.create_dataset('elem_mass', data=self.elem_mass) h5file.create_dataset('frame_of_reference_delta', data=self.frame_of_reference_delta) h5file.create_dataset('boundary_conditions', data=self.boundary_conditions) h5file.create_dataset('beam_number', data=self.beam_number) h5file.create_dataset('structural_twist', data=self.structural_twist) h5file.create_dataset('app_forces', data=self.app_forces) ================================================ FILE: sharpy/cases/templates/template_wt.py ================================================ """ template_wt Functions needed to generate a wind turbines Notes: To load this library: import sharpy.cases.templates.template_wt as template_wt """ import pandas as pd import numpy as np import scipy.interpolate as scint import math import sys import sharpy.utils.generate_cases as gc import sharpy.utils.algebra as algebra import sharpy.utils.h5utils as h5 from sharpy.utils.constants import deg2rad import sharpy.aero.utils.airfoilpolars as ap import sharpy.utils.cout_utils as cout if not cout.check_running_unittest(): cout.cout_wrap.print_screen = True cout.cout_wrap.print_file = False ###################################################################### # AUX FUNCTIONS ###################################################################### def create_node_radial_pos_from_elem_centres(root_elem_centres_tip, num_node, num_elem, num_node_elem): """ create_node_radial_pos_from_elem_centres Define the position of the nodes adn the elements in the blade from the list of element centres Args: root_elem_centres_tip (np.array): - First value: Radial position of the beginning of the blade - Last value: Radial position of the tip of the blade - Rest of the values: Radial position the rest of the strucutral element centres num_node (int): number of nodes num_elem (int): number of elements num_node_elem (int): number of nodes in each element Returns: node_r (np.array): Radial position of the nodes elem_r (np.array): Radial position of the elements Notes: Radial positions are measured from the hub centre and measured in the rotation plane """ elem_r = root_elem_centres_tip[1:-1] node_r = np.zeros((num_node, ), ) node_r[0] = root_elem_centres_tip[0] node_r[-2] = root_elem_centres_tip[-2] node_r[-1] = root_elem_centres_tip[-1] for ielem in range(num_elem-1): node_r[ielem*(num_node_elem-1)+1] = elem_r[ielem] node_r[ielem*(num_node_elem-1)+2] = 0.5*(elem_r[ielem]+elem_r[ielem+1]) return node_r, elem_r def create_blade_coordinates(num_node, node_r, node_y, node_z): """ create_blade_coordinates Creates SHARPy format of the nodes coordinates and applies prebending and presweept to node radial position Args: num_node (int): number of nodes node_r (np.array): Radial position of the nodes node_y (np.array): Displacement of each point IN the rotation plane node_z (np.array): Displacement of each point OUT OF the rotation plane Returns: coordinates (np.array): nodes coordinates """ coordinates = np.zeros((num_node, 3),) coordinates[:, 0] = node_r coordinates[:, 1] = node_y coordinates[:, 2] = node_z return coordinates ###################################################################### # FROM excel type04 ###################################################################### def spar_from_excel_type04(op_params, geom_params, excel_description, options): """ spar_from_excel_type04 Function needed to generate a spar floating wind turbine rotor from an excel database type04. Rotor + tower + floating spar Args: op_param (dict): Dictionary with operating parameters geom_param (dict): Dictionray with geometical parameters excel_description (dict): Dictionary describing the sheets of the excel file option (dict): Dictionary with the different options for the wind turbine generation Returns: floating (sharpy.utils.generate_cases.AeroelasticInfromation): Aeroelastic information of the spar floating wind turbine """ # Generate the tower + rotor wt, LC, MB, hub_nodes = generate_from_excel_type03(op_params, geom_params, excel_description, options) # Remove base clam wt.StructuralInformation.boundary_conditions[0] = 0 excel_file_name = excel_description['excel_file_name'] excel_sheet_parameters = excel_description['excel_sheet_parameters'] excel_sheet_structural_spar = excel_description['excel_sheet_structural_spar'] tol_remove_points = geom_params['tol_remove_points'] TowerBaseHeight = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'TowerBaseHeight') # Generate the spar if options['concentrate_spar']: mtower = wt.StructuralInformation.mass_db[0, 0, 0] PlatformTotalMass = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'PlatformTotalMass') PlatformIrollCM = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'PlatformIrollCM') PlatformIpitchCM = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'PlatformIpitchCM') PlatformIyawCM = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'PlatformIyawCM') PlatformCMbelowSWL = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'PlatformCMbelowSWL') mpoint = PlatformTotalMass - mtower*TowerBaseHeight IyawPoint = PlatformIyawCM - (1./6)*mtower*TowerBaseHeight**3 IpitchPoint = PlatformIpitchCM + PlatformTotalMass*PlatformCMbelowSWL**2 - (1./3)*mtower*TowerBaseHeight**3 IrollPoint = PlatformIrollCM + PlatformTotalMass*PlatformCMbelowSWL**2 - (1./3)*mtower*TowerBaseHeight**3 xpoint = (PlatformTotalMass*PlatformCMbelowSWL + 0.5*mtower*TowerBaseHeight**2)/mpoint iner_mat = np.zeros((6,6)) iner_mat[0:3, 0:3] = mpoint*np.eye(3) iner_mat[0:3, 3:6] = -mpoint*algebra.skew(np.array([-xpoint, 0, 0])) iner_mat[3:6, 0:3] = -iner_mat[0:3, 3:6] iner_mat[3, 3] = IyawPoint iner_mat[4, 4] = IpitchPoint iner_mat[5, 5] = IrollPoint base_stiffness_top = wt.StructuralInformation.stiffness_db[0, :, :] base_mass_top = wt.StructuralInformation.mass_db[0, :, :] base_stiffness_bot = 100*base_stiffness_top base_mass_bot = base_mass_top num_lumped_mass_mat = 1 else: SparHeight = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'SparHeight') SparBallastMass = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'SparBallastMass') SparBallastDepth = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'SparBallastDepth') # Read from excel file SparHtFract = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_spar, 'SparHtFract') SparMassDen = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_spar, 'SparMassDen') SparFAStif = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_spar, 'SparFAStif') SparSSStif = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_spar, 'SparSSStif') SparGJStif = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_spar, 'SparGJStif') SparEAStif = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_spar, 'SparEAStif') SparFAIner = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_spar, 'SparFAIner') SparSSIner = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_spar, 'SparSSIner') SparFAcgOf = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_spar, 'SparFAcgOf') SparSScgOf = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_spar, 'SparSScgOf') ElevationSpar = SparHtFract*SparHeight spar = gc.AeroelasticInformation() spar.StructuralInformation.num_elem = len(ElevationSpar) - 2 spar.StructuralInformation.num_node_elem = 3 spar.StructuralInformation.compute_basic_num_node() # Interpolate excel variables into the correct locations node_r, elem_r = create_node_radial_pos_from_elem_centres(ElevationSpar, spar.StructuralInformation.num_node, spar.StructuralInformation.num_elem, spar.StructuralInformation.num_node_elem) # Stiffness elem_EA = np.interp(elem_r, ElevationSpar, SparEAStif) elem_EIz = np.interp(elem_r, ElevationSpar, SparSSStif) elem_EIy = np.interp(elem_r, ElevationSpar, SparFAStif) elem_GJ = np.interp(elem_r, ElevationSpar, SparGJStif) # Stiffness: estimate unknown properties cout.cout_wrap.print_file = False cout.cout_wrap('WARNING: The poisson cofficient is assumed equal to 0.3', 3) cout.cout_wrap('WARNING: Cross-section area is used as shear area', 3) poisson_coef = 0.3 elem_GAy = elem_EA/2.0/(1.0+poisson_coef) elem_GAz = elem_EA/2.0/(1.0+poisson_coef) # Inertia elem_mass_per_unit_length = np.interp(elem_r, ElevationSpar, SparMassDen) elem_mass_iner_y = np.interp(elem_r, ElevationSpar, SparFAIner) elem_mass_iner_z = np.interp(elem_r, ElevationSpar, SparSSIner) # TODO: check yz axis and Flap-edge elem_pos_cg_B = np.zeros((spar.StructuralInformation.num_elem, 3),) elem_pos_cg_B[:, 1] = np.interp(elem_r, ElevationSpar, SparSScgOf) elem_pos_cg_B[:, 2] = np.interp(elem_r, ElevationSpar, SparFAcgOf) # Stiffness: estimate unknown properties cout.cout_wrap('WARNING: Using perpendicular axis theorem to compute the inertia around xB', 3) elem_mass_iner_x = elem_mass_iner_y + elem_mass_iner_z # Create the tower spar.StructuralInformation.create_mass_db_from_vector(elem_mass_per_unit_length, elem_mass_iner_x, elem_mass_iner_y, elem_mass_iner_z, elem_pos_cg_B) spar.StructuralInformation.create_stiff_db_from_vector(elem_EA, elem_GAy, elem_GAz, elem_GJ, elem_EIy, elem_EIz) coordinates = np.zeros((spar.StructuralInformation.num_node, 3),) coordinates[:, 0] = node_r spar.StructuralInformation.generate_1to1_from_vectors( num_node_elem=spar.StructuralInformation.num_node_elem, num_node=spar.StructuralInformation.num_node, num_elem=spar.StructuralInformation.num_elem, coordinates=coordinates, stiffness_db=spar.StructuralInformation.stiffness_db, mass_db=spar.StructuralInformation.mass_db, frame_of_reference_delta='y_AFoR', vec_node_structural_twist=np.zeros((spar.StructuralInformation.num_node,),), num_lumped_mass=1) spar.StructuralInformation.boundary_conditions[0] = -1 spar.StructuralInformation.boundary_conditions[-1] = 1 # Include ballast mass dist = np.abs(coordinates[:, 0] + SparBallastDepth) min_dist = np.amin(dist) loc_min = np.where(dist == min_dist)[0] cout.cout_wrap("Ballast at node %d at position %f" % (loc_min, coordinates[loc_min, 0]), 2) spar.StructuralInformation.lumped_mass_nodes[0] = loc_min spar.StructuralInformation.lumped_mass[0] = SparBallastMass spar.StructuralInformation.lumped_mass_inertia[0] = np.zeros((3,3)) spar.StructuralInformation.lumped_mass_position[0] = np.zeros((3,)) spar.AerodynamicInformation.set_to_zero(spar.StructuralInformation.num_node_elem, spar.StructuralInformation.num_node, spar.StructuralInformation.num_elem) base_stiffness_bot = spar.StructuralInformation.stiffness_db[-1, :, :] base_mass_bot = spar.StructuralInformation.mass_db[-1, :, :] base_stiffness_top = base_stiffness_bot base_mass_top = base_mass_bot num_lumped_mass_mat = 0 # Generate tower base NodesBase = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'NodesBase') base = gc.AeroelasticInformation() base.StructuralInformation.num_node = NodesBase base.StructuralInformation.num_node_elem = 3 base.StructuralInformation.compute_basic_num_elem() node_coord = np.zeros((base.StructuralInformation.num_node, 3)) node_coord[:, 0] = np.linspace(0., TowerBaseHeight, base.StructuralInformation.num_node) base_stiffness_db = np.zeros((base.StructuralInformation.num_elem, 6, 6)) base_mass_db = np.zeros((base.StructuralInformation.num_elem, 6, 6)) vec_node_structural_twist = np.zeros((base.StructuralInformation.num_node,)) # for ielem in range(base.StructuralInformation.num_elem): # base_stiffness_db[ielem, :, :] = base_stiffness.copy() # base_mass_db[ielem, :, :] = base_mass.copy() for ielem in range(base.StructuralInformation.num_elem): inode_cent = 2*ielem + 1 rel_dist_to_bot = node_coord[inode_cent, 0]/TowerBaseHeight rel_dist_to_top = (node_coord[-1, 0] - node_coord[inode_cent, 0])/TowerBaseHeight base_stiffness_db[ielem, :, :] = (base_stiffness_bot*rel_dist_to_top + base_stiffness_top*rel_dist_to_bot) base_mass_db[ielem, :, :] = (base_mass_bot*rel_dist_to_top + base_mass_top*rel_dist_to_bot) base.StructuralInformation.generate_1to1_from_vectors( base.StructuralInformation.num_node_elem, base.StructuralInformation.num_node, base.StructuralInformation.num_elem, node_coord, base_stiffness_db, base_mass_db, 'y_AFoR', vec_node_structural_twist, num_lumped_mass=0, num_lumped_mass_mat=num_lumped_mass_mat) base.StructuralInformation.boundary_conditions[0] = 1 base.StructuralInformation.body_number *= 0 base.AerodynamicInformation.set_to_zero(base.StructuralInformation.num_node_elem, base.StructuralInformation.num_node, base.StructuralInformation.num_elem) if options['concentrate_spar']: base.StructuralInformation.lumped_mass_mat_nodes = np.array([0], dtype=int) base.StructuralInformation.lumped_mass_mat = np.zeros((1, 6, 6)) base.StructuralInformation.lumped_mass_mat[0, :, :] = iner_mat nodes_base = base.StructuralInformation.num_node + 0 for inode in range(len(hub_nodes)): hub_nodes[inode] += nodes_base wt.StructuralInformation.coordinates[:, 0] += TowerBaseHeight base.assembly(wt) base.remove_duplicated_points(1e-6, skip=hub_nodes) for ielem in range(base.StructuralInformation.num_elem): if not base.StructuralInformation.body_number[ielem] == 0: base.StructuralInformation.body_number[ielem] -= 1 for ilc in range(len(LC)): LC[ilc].node_in_body += nodes_base - 1 spar = base # Just a rename for the return else: # Assembly spar.assembly(base) spar.remove_duplicated_points(1e-6) spar.StructuralInformation.body_number *= 0 nodes_spar = spar.StructuralInformation.num_node + 0 for inode in range(len(hub_nodes)): hub_nodes[inode] += nodes_spar wt.StructuralInformation.coordinates[:, 0] += TowerBaseHeight spar.assembly(wt) spar.remove_duplicated_points(1e-6, skip=hub_nodes) for ielem in range(spar.StructuralInformation.num_elem): if not spar.StructuralInformation.body_number[ielem] == 0: spar.StructuralInformation.body_number[ielem] -= 1 # Update Lagrange Constraints and Multibody information for ilc in range(len(LC)): LC[ilc].node_in_body += nodes_spar - 1 MB[0].FoR_movement = 'free' for ibody in range(1, len(MB)): MB[ibody].FoR_position[0] += TowerBaseHeight return spar, LC, MB ###################################################################### # FROM excel type03 ###################################################################### def rotor_from_excel_type03(in_op_params, in_geom_params, in_excel_description, in_options): """ rotor_from_excel_type03 Function needed to generate a wind turbine rotor from an excel database type03 Args: op_param (dict): Dictionary with operating parameters geom_param (dict): Dictionray with geometical parameters excel_description (dict): Dictionary describing the sheets of the excel file option (dict): Dictionary with the different options for the wind turbine generation Returns: rotor (sharpy.utils.generate_cases.AeroelasticInfromation): Aeroelastic information of the rotor """ # Default values op_params = {} op_params['rotation_velocity'] = None # Rotation velocity of the rotor op_params['pitch_deg'] = None # pitch angle in degrees op_params['wsp'] = 0. # wind speed (It may be needed for discretisation purposes) op_params['dt'] = 0. # time step (It may be needed for discretisation purposes) geom_params = {} geom_params['chord_panels'] = None # Number of panels on the blade surface in the chord direction geom_params['tol_remove_points'] = 1e-3 # maximum distance to remove adjacent points geom_params['n_points_camber'] = 100 # number of points to define the camber of the airfoil geom_params['h5_cross_sec_prop'] = None # h5 containing mass and stiffness matrices along the blade geom_params['m_distribution'] = 'uniform' # options = {} options['camber_effect_on_twist'] = False # When true plain airfoils are used and the blade is twisted and preloaded based on thin airfoil theory options['user_defined_m_distribution_type'] = None # type of distribution of the chordwise panels when 'm_distribution' == 'user_defined' options['include_polars'] = False # options['separate_blades'] = False # Keep blades as different bodies options['twist_in_aero'] = False # Twist the aerodynamic surface instead of the structure excel_description = {} excel_description['excel_file_name'] = 'database_excel_type02.xlsx' excel_description['excel_sheet_parameters'] = 'parameters' excel_description['excel_sheet_structural_blade'] = 'structural_blade' excel_description['excel_sheet_discretization_blade'] = 'discretization_blade' excel_description['excel_sheet_aero_blade'] = 'aero_blade' excel_description['excel_sheet_airfoil_info'] = 'airfoil_info' excel_description['excel_sheet_airfoil_chord'] = 'airfoil_coord' # Overwrite the default values with the values of the input arguments for key in in_op_params: op_params[key] = in_op_params[key] for key in in_geom_params: geom_params[key] = in_geom_params[key] for key in in_options: options[key] = in_options[key] for key in in_excel_description: excel_description[key] = in_excel_description[key] # Put the dictionaries information into variables (to avoid changing the function) rotation_velocity = op_params['rotation_velocity'] pitch_deg = op_params['pitch_deg'] wsp = op_params['wsp'] dt = op_params['dt'] chord_panels = geom_params['chord_panels'] tol_remove_points = geom_params['tol_remove_points'] n_points_camber = geom_params['n_points_camber'] h5_cross_sec_prop = geom_params['h5_cross_sec_prop'] m_distribution = geom_params['m_distribution'] camber_effect_on_twist = options['camber_effect_on_twist'] user_defined_m_distribution_type = options['user_defined_m_distribution_type'] include_polars = options['include_polars'] excel_file_name = excel_description['excel_file_name'] excel_sheet_parameters = excel_description['excel_sheet_parameters'] excel_sheet_structural_blade = excel_description['excel_sheet_structural_blade'] excel_sheet_discretization_blade = excel_description['excel_sheet_discretization_blade'] excel_sheet_aero_blade = excel_description['excel_sheet_aero_blade'] excel_sheet_airfoil_info = excel_description['excel_sheet_airfoil_info'] excel_sheet_airfoil_coord = excel_description['excel_sheet_airfoil_chord'] ###################################################################### ## BLADE ###################################################################### blade = gc.AeroelasticInformation() ###################################################################### ### STRUCTURE ###################################################################### # Read blade structural information from excel file rR_structural = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'rR') OutPElAxis = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'OutPElAxis') InPElAxis = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'InPElAxis') ElAxisAftLEc = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'ElAxisAftLEc') StrcTwst = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'StrcTwst')*deg2rad BMassDen = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'BMassDen') FlpStff = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'FlpStff') EdgStff = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'EdgStff') FlapEdgeStiff = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'FlapEdgeStiff') GJStff = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'GJStff') EAStff = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'EAStff') FlpIner = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'FlpIner') EdgIner = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'EdgIner') FlapEdgeIner = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'FlapEdgeIner') PrebendRef = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'PrebendRef') PreswpRef = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'PreswpRef') OutPcg = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'OutPcg') InPcg = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_blade, 'InPcg') # Blade parameters TipRad = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'TipRad') # HubRad = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'HubRad') # Discretization points rR = gc.read_column_sheet_type01(excel_file_name, excel_sheet_discretization_blade, 'rR') # Interpolate excel variables into the correct locations # Geometry if rR[0] < rR_structural[0]: rR_structural = np.concatenate((np.array([0.]), rR_structural),) OutPElAxis = np.concatenate((np.array([OutPElAxis[0]]), OutPElAxis),) InPElAxis = np.concatenate((np.array([InPElAxis[0]]), InPElAxis),) ElAxisAftLEc = np.concatenate((np.array([ElAxisAftLEc[0]]), ElAxisAftLEc),) StrcTwst = np.concatenate((np.array([StrcTwst[0]]), StrcTwst),) BMassDen = np.concatenate((np.array([BMassDen[0]]), BMassDen),) FlpStff = np.concatenate((np.array([FlpStff[0]]), FlpStff),) EdgStff = np.concatenate((np.array([EdgStff[0]]), EdgStff),) FlapEdgeStiff = np.concatenate((np.array([FlapEdgeStiff[0]]), FlapEdgeStiff),) GJStff = np.concatenate((np.array([GJStff[0]]), GJStff),) EAStff = np.concatenate((np.array([EAStff[0]]), EAStff),) FlpIner = np.concatenate((np.array([FlpIner[0]]), FlpIner),) EdgIner = np.concatenate((np.array([EdgIner[0]]), EdgIner),) FlapEdgeIner = np.concatenate((np.array([FlapEdgeIner[0]]), FlapEdgeIner),) PrebendRef = np.concatenate((np.array([PrebendRef[0]]), PrebendRef),) PreswpRef = np.concatenate((np.array([PreswpRef[0]]), PreswpRef),) OutPcg = np.concatenate((np.array([OutPcg[0]]), OutPcg),) InPcg = np.concatenate((np.array([InPcg[0]]), InPcg),) # Base parameters use_excel_struct_as_elem = False if use_excel_struct_as_elem: blade.StructuralInformation.num_node_elem = 3 blade.StructuralInformation.num_elem = len(rR) - 2 blade.StructuralInformation.compute_basic_num_node() node_r, elem_r = create_node_radial_pos_from_elem_centres(rR*TipRad, blade.StructuralInformation.num_node, blade.StructuralInformation.num_elem, blade.StructuralInformation.num_node_elem) else: # Use excel struct as nodes # Check the number of nodes blade.StructuralInformation.num_node_elem = 3 blade.StructuralInformation.num_node = len(rR) if ((len(rR) - 1) % (blade.StructuralInformation.num_node_elem - 1)) == 0: blade.StructuralInformation.num_elem = int((len(rR) - 1)/(blade.StructuralInformation.num_node_elem - 1)) node_r = rR*TipRad elem_rR = rR[1::2] + 0. elem_r = rR[1::2]*TipRad + 0. else: raise RuntimeError(("ERROR: Cannot build %d-noded elements from %d nodes" % (blade.StructuralInformation.num_node_elem, blade.StructuralInformation.num_node))) node_y = np.interp(rR, rR_structural, InPElAxis) + np.interp(rR, rR_structural, PreswpRef) node_z = -np.interp(rR, rR_structural, OutPElAxis) - np.interp(rR, rR_structural, PrebendRef) node_twist = -1.0*np.interp(rR, rR_structural, StrcTwst) coordinates = create_blade_coordinates(blade.StructuralInformation.num_node, node_r, node_y, node_z) if h5_cross_sec_prop is None: # Stiffness elem_EA = np.interp(elem_rR, rR_structural, EAStff) elem_EIy = np.interp(elem_rR, rR_structural, FlpStff) elem_EIz = np.interp(elem_rR, rR_structural, EdgStff) elem_EIyz = np.interp(elem_rR, rR_structural, FlapEdgeStiff) elem_GJ = np.interp(elem_rR, rR_structural, GJStff) # Stiffness: estimate unknown properties cout.cout_wrap.print_file = False cout.cout_wrap('WARNING: The poisson cofficient is assumed equal to 0.3', 3) cout.cout_wrap('WARNING: Cross-section area is used as shear area', 3) poisson_coef = 0.3 elem_GAy = elem_EA/2.0/(1.0+poisson_coef) elem_GAz = elem_EA/2.0/(1.0+poisson_coef) # Inertia elem_pos_cg_B = np.zeros((blade.StructuralInformation.num_elem, 3),) elem_pos_cg_B[:, 1] = np.interp(elem_rR, rR_structural, InPcg) elem_pos_cg_B[:, 2] = -np.interp(elem_rR, rR_structural, OutPcg) elem_mass_per_unit_length = np.interp(elem_rR, rR_structural, BMassDen) elem_mass_iner_y = np.interp(elem_rR, rR_structural, FlpIner) elem_mass_iner_z = np.interp(elem_rR, rR_structural, EdgIner) elem_mass_iner_yz = np.interp(elem_rR, rR_structural, FlapEdgeIner) # Inertia: estimate unknown properties cout.cout_wrap('WARNING: Using perpendicular axis theorem to compute the inertia around xB', 3) elem_mass_iner_x = elem_mass_iner_y + elem_mass_iner_z # Generate blade structural properties blade.StructuralInformation.create_mass_db_from_vector(elem_mass_per_unit_length, elem_mass_iner_x, elem_mass_iner_y, elem_mass_iner_z, elem_pos_cg_B, elem_mass_iner_yz) blade.StructuralInformation.create_stiff_db_from_vector(elem_EA, elem_GAy, elem_GAz, elem_GJ, elem_EIy, elem_EIz, elem_EIyz) else: # read Mass/Stiffness from database cross_prop = h5.readh5(h5_cross_sec_prop).str_prop # create mass_db/stiffness_db (interpolate at mid-node of each element) blade.StructuralInformation.mass_db = scint.interp1d( cross_prop.radius, cross_prop.M, kind='cubic', copy=False, assume_sorted=True, axis=0, bounds_error=False, fill_value='extrapolate')(node_r[1::2]) blade.StructuralInformation.stiffness_db = scint.interp1d( cross_prop.radius, cross_prop.K, kind='cubic', copy=False, assume_sorted=True, axis=0, bounds_error=False, fill_value='extrapolate')(node_r[1::2]) blade.StructuralInformation.generate_1to1_from_vectors( num_node_elem=blade.StructuralInformation.num_node_elem, num_node=blade.StructuralInformation.num_node, num_elem=blade.StructuralInformation.num_elem, coordinates=coordinates, stiffness_db=blade.StructuralInformation.stiffness_db, mass_db=blade.StructuralInformation.mass_db, frame_of_reference_delta='y_AFoR', vec_node_structural_twist=np.zeros_like(node_twist) if options['twist_in_aero'] else node_twist, num_lumped_mass=0) # Boundary conditions blade.StructuralInformation.boundary_conditions = np.zeros((blade.StructuralInformation.num_node), dtype=int) blade.StructuralInformation.boundary_conditions[0] = 1 blade.StructuralInformation.boundary_conditions[-1] = -1 ###################################################################### ### AERODYNAMICS ###################################################################### # Read blade aerodynamic information from excel file rR_aero = gc.read_column_sheet_type01(excel_file_name, excel_sheet_aero_blade, 'rR') chord_aero = gc.read_column_sheet_type01(excel_file_name, excel_sheet_aero_blade, 'BlChord') thickness_aero = gc.read_column_sheet_type01(excel_file_name, excel_sheet_aero_blade, 'BlThickness') pure_airfoils_names = gc.read_column_sheet_type01(excel_file_name, excel_sheet_airfoil_info, 'Name') pure_airfoils_thickness = gc.read_column_sheet_type01(excel_file_name, excel_sheet_airfoil_info, 'Thickness') node_ElAxisAftLEc = np.interp(node_r, rR_structural*TipRad, ElAxisAftLEc) # Read coordinates of the pure airfoils n_pure_airfoils = len(pure_airfoils_names) pure_airfoils_camber = np.zeros((n_pure_airfoils, n_points_camber, 2),) xls = pd.ExcelFile(excel_file_name) excel_db = pd.read_excel(xls, sheet_name=excel_sheet_airfoil_coord) for iairfoil in range(n_pure_airfoils): # Look for the NaN icoord = 2 while(not(math.isnan(excel_db["%s_x" % pure_airfoils_names[iairfoil]][icoord]))): icoord += 1 if(icoord == len(excel_db["%s_x" % pure_airfoils_names[iairfoil]])): break # Compute the camber of the airfoils at the defined chord points pure_airfoils_camber[iairfoil, :, 0], pure_airfoils_camber[iairfoil, :, 1] = gc.get_airfoil_camber(excel_db["%s_x" % pure_airfoils_names[iairfoil]][2:icoord], excel_db["%s_y" % pure_airfoils_names[iairfoil]][2:icoord], n_points_camber) # Basic variables surface_distribution = np.zeros((blade.StructuralInformation.num_elem), dtype=int) # Interpolate in the correct positions node_chord = np.interp(node_r, rR_aero*TipRad, chord_aero) # Define the nodes with aerodynamic properties # Look for the first element that is goint to be aerodynamic first_aero_elem = 0 while (elem_r[first_aero_elem] <= rR_aero[0]*TipRad): first_aero_elem += 1 first_aero_node = first_aero_elem*(blade.StructuralInformation.num_node_elem - 1) aero_node = np.zeros((blade.StructuralInformation.num_node,), dtype=bool) aero_node[first_aero_node:] = np.ones((blade.StructuralInformation.num_node-first_aero_node,), dtype=bool) # Define the airfoil at each stage # airfoils = blade.AerodynamicInformation.interpolate_airfoils_camber(pure_airfoils_camber,excel_aero_r, node_r, n_points_camber) node_thickness = np.interp(node_r, rR_aero*TipRad, thickness_aero) airfoils = blade.AerodynamicInformation.interpolate_airfoils_camber_thickness(pure_airfoils_camber, pure_airfoils_thickness, node_thickness, n_points_camber) airfoil_distribution = np.linspace(0, blade.StructuralInformation.num_node - 1, blade.StructuralInformation.num_node, dtype=int) # User defined m distribution if (m_distribution == 'user_defined') and (user_defined_m_distribution_type == 'last_geometric'): blade_nodes = blade.StructuralInformation.num_node udmd_by_nodes = np.zeros((blade_nodes, chord_panels[0] + 1)) for inode in range(blade_nodes): r = np.linalg.norm(blade.StructuralInformation.coordinates[inode, :]) vrel = np.sqrt(rotation_velocity**2*r**2 + wsp**2) last_length = vrel*dt/node_chord[inode] last_length = np.minimum(last_length, 0.5) if last_length <= 0.5: ratio = gc.get_factor_geometric_progression(last_length, 1., chord_panels) udmd_by_nodes[inode, -1] = 1. udmd_by_nodes[inode, 0] = 0. for im in range(chord_panels[0] - 1, 0, -1): udmd_by_nodes[inode, im] = udmd_by_nodes[inode, im + 1] - last_length last_length *= ratio # Check if (np.diff(udmd_by_nodes[inode, :]) < 0.).any(): sys.error("ERROR in the panel discretization of the blade in node %d" % (inode)) else: raise RuntimeError(("ERROR: cannot match the last panel size for node: %d" % inode)) udmd_by_nodes[inode, :] = np.linspace(0, 1, chord_panels + 1) else: udmd_by_nodes = None node_twist_aero = np.zeros_like(node_chord) if camber_effect_on_twist: cout.cout_wrap("WARNING: The steady applied Mx should be manually multiplied by the density", 3) for inode in range(blade.StructuralInformation.num_node): node_twist_aero[inode] = gc.get_aoacl0_from_camber(airfoils[inode, :, 0], airfoils[inode, :, 1]) mu0 = gc.get_mu0_from_camber(airfoils[inode, :, 0], airfoils[inode, :, 1]) r = np.linalg.norm(blade.StructuralInformation.coordinates[inode, :]) vrel = np.sqrt(rotation_velocity**2*r**2 + wsp**2) if inode == 0: dr = 0.5*np.linalg.norm(blade.StructuralInformation.coordinates[1, :] - blade.StructuralInformation.coordinates[0, :]) elif inode == len(blade.StructuralInformation.coordinates[:, 0]) - 1: dr = 0.5*np.linalg.norm(blade.StructuralInformation.coordinates[-1, :] - blade.StructuralInformation.coordinates[-2, :]) else: dr = 0.5*np.linalg.norm(blade.StructuralInformation.coordinates[inode + 1, :] - blade.StructuralInformation.coordinates[inode - 1, :]) moment_factor = 0.5*vrel**2*node_chord[inode]**2*dr # print("node", inode, "mu0", mu0, "CMc/4", 2.*mu0 + np.pi/2*node_twist_struct[inode]) blade.StructuralInformation.app_forces[inode, 3] = (2.*mu0 + np.pi/2*node_twist_aero[inode])*moment_factor airfoils[inode, :, 1] *= 0. # Write SHARPy format blade.AerodynamicInformation.create_aerodynamics_from_vec(blade.StructuralInformation, aero_node, node_chord, (node_twist + node_twist_aero) if options['twist_in_aero'] else node_twist_aero, np.pi*np.ones_like(node_chord), chord_panels, surface_distribution, m_distribution, node_ElAxisAftLEc, airfoil_distribution, airfoils, udmd_by_nodes, first_twist=False) # Read the polars of the pure airfoils if include_polars: pure_polars = [None]*n_pure_airfoils for iairfoil in range(n_pure_airfoils): excel_sheet_polar = pure_airfoils_names[iairfoil] aoa = gc.read_column_sheet_type01(excel_file_name, excel_sheet_polar, 'AoA') cl = gc.read_column_sheet_type01(excel_file_name, excel_sheet_polar, 'CL') cd = gc.read_column_sheet_type01(excel_file_name, excel_sheet_polar, 'CD') cm = gc.read_column_sheet_type01(excel_file_name, excel_sheet_polar, 'CM') polar = ap.Polar() polar.initialise(np.column_stack((aoa, cl, cd, cm))) pure_polars[iairfoil] = polar # Generate the polars for each airfoil blade.AerodynamicInformation.polars = [None]*blade.StructuralInformation.num_node for inode in range(blade.StructuralInformation.num_node): # Find the airfoils between which the node is; ipure = 0 while pure_airfoils_thickness[ipure] > node_thickness[inode]: ipure += 1 if(ipure == n_pure_airfoils): ipure -= 1 break coef = (node_thickness[inode] - pure_airfoils_thickness[ipure - 1])/(pure_airfoils_thickness[ipure] - pure_airfoils_thickness[ipure - 1]) polar = ap.interpolate(pure_polars[ipure - 1], pure_polars[ipure], coef) blade.AerodynamicInformation.polars[inode] = polar.table ###################################################################### ## ROTOR ###################################################################### # Read from excel file numberOfBlades = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'NumBl') tilt = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'ShftTilt')*deg2rad cone = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'Cone')*deg2rad # pitch = gc.read_column_sheet_type01(excel_file_name, excel_sheet_rotor, 'Pitch')*deg2rad # Apply pitch blade.StructuralInformation.rotate_around_origin(np.array([1., 0., 0.]), -pitch_deg*deg2rad) # Apply coning blade.StructuralInformation.rotate_around_origin(np.array([0., 1., 0.]), -cone) # Build the whole rotor rotor = blade.copy() hub_nodes = [0] for iblade in range(numberOfBlades-1): hub_nodes.append((iblade + 1)*blade.StructuralInformation.num_node) blade2 = blade.copy() blade2.StructuralInformation.rotate_around_origin(np.array([0., 0., 1.]), (iblade + 1)*(360.0/numberOfBlades)*deg2rad) rotor.assembly(blade2) blade2 = None if not options['separate_blades']: rotor.remove_duplicated_points(tol_remove_points) hub_nodes = [0] rotor.StructuralInformation.body_number *= 0 # Apply tilt rotor.StructuralInformation.rotate_around_origin(np.array([0., 1., 0.]), tilt) return rotor, hub_nodes def generate_from_excel_type03(op_params, geom_params, excel_description, options): """ generate_from_excel_type03 Function needed to generate a wind turbine (tower + rotor) from an excel database type03. See ``rotor_from_excel_type03'' for more information. Args: op_param (dict): Dictionary with operating parameters geom_param (dict): Dictionray with geometical parameters excel_description (dict): Dictionary describing the sheets of the excel file option (dict): Dictionary with the different options for the wind turbine generation Returns: wt (sharpy.utils.generate_cases.AeroelasticInfromation): Aeroelastic information of the wind turbine (tower + rotor) """ rotor, hub_nodes = rotor_from_excel_type03(op_params, geom_params, excel_description, options) excel_file_name = excel_description['excel_file_name'] excel_sheet_parameters = excel_description['excel_sheet_parameters'] excel_sheet_structural_tower = excel_description['excel_sheet_structural_tower'] tol_remove_points = geom_params['tol_remove_points'] rotation_velocity = op_params['rotation_velocity'] ###################################################################### ## TOWER ###################################################################### # Read from excel file HtFract = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_tower, 'HtFract') TMassDen = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_tower, 'TMassDen') TwFAStif = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_tower, 'TwFAStif') TwSSStif = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_tower, 'TwSSStif') # TODO> variables to be defined TwGJStif = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_tower, 'TwGJStif') TwEAStif = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_tower, 'TwEAStif') TwFAIner = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_tower, 'TwFAIner') TwSSIner = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_tower, 'TwSSIner') TwFAcgOf = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_tower, 'TwFAcgOf') TwSScgOf = gc.read_column_sheet_type01(excel_file_name, excel_sheet_structural_tower, 'TwSScgOf') # Define the TOWER TowerHt = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'TowerHt') Elevation = TowerHt*HtFract tower = gc.AeroelasticInformation() tower.StructuralInformation.num_elem = len(Elevation) - 2 tower.StructuralInformation.num_node_elem = 3 tower.StructuralInformation.compute_basic_num_node() # Interpolate excel variables into the correct locations node_r, elem_r = create_node_radial_pos_from_elem_centres(Elevation, tower.StructuralInformation.num_node, tower.StructuralInformation.num_elem, tower.StructuralInformation.num_node_elem) # Stiffness elem_EA = np.interp(elem_r, Elevation, TwEAStif) elem_EIz = np.interp(elem_r, Elevation, TwSSStif) elem_EIy = np.interp(elem_r, Elevation, TwFAStif) elem_GJ = np.interp(elem_r, Elevation, TwGJStif) # Stiffness: estimate unknown properties cout.cout_wrap.print_file = False cout.cout_wrap('WARNING: The poisson cofficient is assumed equal to 0.3', 3) cout.cout_wrap('WARNING: Cross-section area is used as shear area', 3) poisson_coef = 0.3 elem_GAy = elem_EA/2.0/(1.0+poisson_coef) elem_GAz = elem_EA/2.0/(1.0+poisson_coef) # Inertia elem_mass_per_unit_length = np.interp(elem_r, Elevation, TMassDen) elem_mass_iner_y = np.interp(elem_r, Elevation, TwFAIner) elem_mass_iner_z = np.interp(elem_r, Elevation, TwSSIner) # TODO: check yz axis and Flap-edge elem_pos_cg_B = np.zeros((tower.StructuralInformation.num_elem, 3),) elem_pos_cg_B[:, 1] = np.interp(elem_r, Elevation, TwSScgOf) elem_pos_cg_B[:, 2] = np.interp(elem_r, Elevation, TwFAcgOf) # Stiffness: estimate unknown properties cout.cout_wrap('WARNING: Using perpendicular axis theorem to compute the inertia around xB', 3) elem_mass_iner_x = elem_mass_iner_y + elem_mass_iner_z # Create the tower tower.StructuralInformation.create_mass_db_from_vector(elem_mass_per_unit_length, elem_mass_iner_x, elem_mass_iner_y, elem_mass_iner_z, elem_pos_cg_B) tower.StructuralInformation.create_stiff_db_from_vector(elem_EA, elem_GAy, elem_GAz, elem_GJ, elem_EIy, elem_EIz) coordinates = np.zeros((tower.StructuralInformation.num_node, 3),) coordinates[:, 0] = node_r tower.StructuralInformation.generate_1to1_from_vectors( num_node_elem=tower.StructuralInformation.num_node_elem, num_node=tower.StructuralInformation.num_node, num_elem=tower.StructuralInformation.num_elem, coordinates=coordinates, stiffness_db=tower.StructuralInformation.stiffness_db, mass_db=tower.StructuralInformation.mass_db, frame_of_reference_delta='y_AFoR', vec_node_structural_twist=np.zeros((tower.StructuralInformation.num_node,),), num_lumped_mass=1) tower.StructuralInformation.boundary_conditions = np.zeros((tower.StructuralInformation.num_node), dtype = int) tower.StructuralInformation.boundary_conditions[0] = 1 # Nacelle properties from excel file NacelleMass = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'NacMass') NacelleMass_x = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'NacMass_x') NacelleMass_z = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'NacMass_z') # NacelleYawIner = gc.read_column_sheet_type01(excel_file_name, excel_sheet_nacelle, 'NacelleYawIner') # Include nacelle mass tower.StructuralInformation.lumped_mass_nodes = np.array([tower.StructuralInformation.num_node - 1], dtype=int) tower.StructuralInformation.lumped_mass = np.array([NacelleMass], dtype=float) if not NacelleMass_x is None and not NacelleMass_z is None: tower.StructuralInformation.lumped_mass_position = np.array([np.array([NacelleMass_z, 0, NacelleMass_x])], dtype=float) else: cout.cout_wrap('WARNING: Nacelle mass placed at tower top', 3) tower.AerodynamicInformation.set_to_zero(tower.StructuralInformation.num_node_elem, tower.StructuralInformation.num_node, tower.StructuralInformation.num_elem) # Overhang tilt = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'ShftTilt')*deg2rad if not tilt == 0.: raise NonImplementedError("Non-zero tilt not supported") NodesOverhang = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'NodesOverhang') overhang_len = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'overhang') if NodesOverhang == 0: with_overhang = False else: with_overhang = True overhang = gc.AeroelasticInformation() overhang.StructuralInformation.num_node = NodesOverhang overhang.StructuralInformation.num_node_elem = 3 overhang.StructuralInformation.compute_basic_num_elem() node_pos = np.zeros((overhang.StructuralInformation.num_node, 3), ) node_pos[:, 0] += tower.StructuralInformation.coordinates[-1, 0] node_pos[:, 0] += np.linspace(0., -overhang_len*np.sin(tilt*deg2rad), overhang.StructuralInformation.num_node) node_pos[:, 2] = np.linspace(0., overhang_len*np.cos(tilt*deg2rad), overhang.StructuralInformation.num_node) # TODO: change the following by real values # Same properties as the last element of the tower # cout.cout_wrap("WARNING: Using the structural properties of the last tower section for the overhang", 3) # oh_mass_per_unit_length = tower.StructuralInformation.mass_db[-1, 0, 0] # oh_mass_iner = tower.StructuralInformation.mass_db[-1, 3, 3] cout.cout_wrap("WARNING: Using the structural properties (*0.1) of the last tower section for the overhang", 3) oh_mass_per_unit_length = tower.StructuralInformation.mass_db[-1, 0, 0]/10. oh_mass_iner = tower.StructuralInformation.mass_db[-1, 3, 3]/10. oh_EA = tower.StructuralInformation.stiffness_db[-1, 0, 0] oh_GA = tower.StructuralInformation.stiffness_db[-1, 1, 1] oh_GJ = tower.StructuralInformation.stiffness_db[-1, 3, 3] oh_EI = tower.StructuralInformation.stiffness_db[-1, 4, 4] overhang.StructuralInformation.generate_uniform_sym_beam(node_pos, oh_mass_per_unit_length, oh_mass_iner, oh_EA, oh_GA, oh_GJ, oh_EI, num_node_elem=3, y_BFoR='y_AFoR', num_lumped_mass=0) overhang.StructuralInformation.boundary_conditions[-1] = -1 overhang.AerodynamicInformation.set_to_zero(overhang.StructuralInformation.num_node_elem, overhang.StructuralInformation.num_node, overhang.StructuralInformation.num_elem) tower.assembly(overhang) tower.remove_duplicated_points(tol_remove_points) tower.StructuralInformation.body_number *= 0 # Hub mass HubMass = gc.read_column_sheet_type01(excel_file_name, excel_sheet_parameters, 'HubMass') if HubMass is not None: if with_overhang: tower.StructuralInformation.add_lumped_mass(tower.StructuralInformation.num_node -1, HubMass, inertia=np.zeros((3, 3)), pos=np.zeros((3))) else: n_hub_nodes = len(hub_nodes) for inode_hub in range(n_hub_nodes): rotor.StructuralInformation.add_lumped_mass(hub_nodes[inode_hub], HubMass/n_hub_nodes, inertia=np.zeros((3, 3)), pos=np.zeros((3))) else: cout.cout_wrap('WARNING: HubMass not found', 3) for inode in range(len(hub_nodes)): hub_nodes[inode] += tower.StructuralInformation.num_node ###################################################################### ## WIND TURBINE ###################################################################### # Assembly the whole case wt = tower.copy() hub_position = tower.StructuralInformation.coordinates[-1, :] if not with_overhang: hub_position += np.array([0., 0., overhang_len]) rotor.StructuralInformation.coordinates += hub_position wt.assembly(rotor) ###################################################################### ## MULTIBODY ###################################################################### LC = [] for iblade in range(len(hub_nodes)): # Define the boundary condition between the rotor and the tower tip LC1 = gc.LagrangeConstraint() LC1.behaviour = 'hinge_node_FoR_constant_vel' LC1.node_in_body = tower.StructuralInformation.num_node - 1 LC1.body = 0 LC1.body_FoR = iblade + 1 if with_overhang: LC1.rot_vect = np.array([-1., 0., 0.])*rotation_velocity LC1.rel_posB = np.zeros((3)) else: LC1.rot_vect = np.array([0., 0., 1.])*rotation_velocity LC1.rel_posB = np.array([0., 0., overhang_len]) LC.append(LC1) # Define the multibody infromation for the tower and the rotor MB = [] MB1 = gc.BodyInformation() MB1.body_number = 0 MB1.FoR_position = np.zeros((6,),) MB1.FoR_velocity = np.zeros((6,),) MB1.FoR_acceleration = np.zeros((6,),) MB1.FoR_movement = 'prescribed' MB1.quat = np.array([1.0, 0.0, 0.0, 0.0]) MB.append(MB1) numberOfBlades = len(hub_nodes) for iblade in range(numberOfBlades): MB2 = gc.BodyInformation() MB2.body_number = iblade + 1 MB2.FoR_position = np.concatenate((hub_position, np.zeros((3)))) MB2.FoR_velocity = np.array([0., 0., 0., 0., 0., rotation_velocity]) MB2.FoR_acceleration = np.zeros((6,),) MB2.FoR_movement = 'free' blade_azimuth = (iblade*(360.0/numberOfBlades)*deg2rad) MB2.quat = algebra.euler2quat(np.array([0.0, tilt, blade_azimuth])) MB.append(MB2) ###################################################################### ## RETURN ###################################################################### return wt, LC, MB, hub_nodes ###################################################################### # FROM excel type02 ###################################################################### def rotor_from_excel_type02(chord_panels, rotation_velocity, pitch_deg, excel_file_name='database_excel_type02.xlsx', excel_sheet_parameters='parameters', excel_sheet_structural_blade='structural_blade', excel_sheet_discretization_blade='discretization_blade', excel_sheet_aero_blade='aero_blade', excel_sheet_airfoil_info='airfoil_info', excel_sheet_airfoil_coord='airfoil_coord', excel_sheet_structural_tower='structural_tower', m_distribution='uniform', h5_cross_sec_prop=None, n_points_camber=100, tol_remove_points=1e-3, user_defined_m_distribution_type=None, camber_effect_on_twist=False, wsp=0., dt=0.): # Warning for back compatibility cout.cout_wrap('rotor_from_excel_type02 is obsolete! rotor_from_excel_type03 instead!', 3) # Assign values to dictionaries op_params = {} op_params['rotation_velocity'] = rotation_velocity op_params['pitch_deg'] = pitch_deg op_params['wsp'] = wsp op_params['dt'] = dt geom_params = {} geom_params['chord_panels'] = chord_panels geom_params['tol_remove_points'] = tol_remove_points geom_params['n_points_camber'] = n_points_camber geom_params['h5_cross_sec_prop'] = h5_cross_sec_prop geom_params['m_distribution'] = m_distribution options = {} options['camber_effect_on_twist'] = camber_effect_on_twist options['user_defined_m_distribution_type'] = user_defined_m_distribution_type options['include_polars'] = False options['twist_in_aero'] = False excel_description = {} excel_description['excel_file_name'] = excel_file_name excel_description['excel_sheet_parameters'] = excel_sheet_parameters excel_description['excel_sheet_structural_blade'] = excel_sheet_structural_blade excel_description['excel_sheet_discretization_blade'] = excel_sheet_discretization_blade excel_description['excel_sheet_aero_blade'] = excel_sheet_aero_blade excel_description['excel_sheet_airfoil_info'] = excel_sheet_airfoil_info excel_description['excel_sheet_airfoil_chord'] = excel_sheet_airfoil_coord rotor = rotor_from_excel_type03(op_params, geom_params, excel_description, options) return rotor def generate_from_excel_type02(chord_panels, rotation_velocity, pitch_deg, excel_file_name='database_excel_type02.xlsx', excel_sheet_parameters='parameters', excel_sheet_structural_blade='structural_blade', excel_sheet_discretization_blade='discretization_blade', excel_sheet_aero_blade='aero_blade', excel_sheet_airfoil_info='airfoil_info', excel_sheet_airfoil_coord='airfoil_coord', excel_sheet_structural_tower='structural_tower', m_distribution='uniform', h5_cross_sec_prop=None, n_points_camber=100, tol_remove_points=1e-3, user_defined_m_distribution_type=None, camber_effect_on_twist=False, wsp=0., dt=0.): # Warning for back compatibility cout.cout_wrap('generate_from_excel_type02 is obsolete! rotor_from_excel_type03 instead!', 3) # Assign values to dictionaries op_params = {} op_params['rotation_velocity'] = rotation_velocity op_params['pitch_deg'] = pitch_deg op_params['wsp'] = wsp op_params['dt'] = dt geom_params = {} geom_params['chord_panels'] = chord_panels geom_params['tol_remove_points'] = tol_remove_points geom_params['n_points_camber'] = n_points_camber geom_params['h5_cross_sec_prop'] = h5_cross_sec_prop geom_params['m_distribution'] = m_distribution options = {} options['camber_effect_on_twist'] = camber_effect_on_twist options['user_defined_m_distribution_type'] = user_defined_m_distribution_type options['include_polars'] = False excel_description = {} excel_description['excel_file_name'] = excel_file_name excel_description['excel_sheet_parameters'] = excel_sheet_parameters excel_description['excel_sheet_structural_tower'] = excel_sheet_structural_tower excel_description['excel_sheet_structural_blade'] = excel_sheet_structural_blade excel_description['excel_sheet_discretization_blade'] = excel_sheet_discretization_blade excel_description['excel_sheet_aero_blade'] = excel_sheet_aero_blade excel_description['excel_sheet_airfoil_info'] = excel_sheet_airfoil_info excel_description['excel_sheet_airfoil_chord'] = excel_sheet_airfoil_coord wt = generate_from_excel_type03(op_params, geom_params, excel_description, options) return wt ================================================ FILE: sharpy/controllers/__init__.py ================================================ import importlib import os import sharpy.utils.controller_interface as controller_interface import sharpy.utils.sharpydir as sharpydir files = controller_interface.controller_list_from_path(os.path.dirname(__file__)) import_path = os.path.realpath(os.path.dirname(__file__)) import_path = import_path.replace(sharpydir.SharpyDir, "") if import_path[0] == "/": import_path = import_path[1:] import_path = import_path.replace("/", ".") for file in files: controller_interface.controllers[file] = importlib.import_module(import_path + "." + file) ================================================ FILE: sharpy/controllers/bladepitchpid.py ================================================ import numpy as np import os from control import ss, forced_response, TransferFunction import sharpy.utils.controller_interface as controller_interface import sharpy.utils.settings as settings import sharpy.utils.control_utils as control_utils import sharpy.utils.cout_utils as cout import sharpy.utils.algebra as algebra from sharpy.utils.constants import deg2rad @controller_interface.controller class BladePitchPid(controller_interface.BaseController): r""" """ controller_id = 'BladePitchPid' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() # PID parameters settings_types['P'] = 'float' settings_default['P'] = None settings_description['P'] = 'Proportional gain of the controller' settings_types['I'] = 'float' settings_default['I'] = 0.0 settings_description['I'] = 'Integral gain of the controller' settings_types['D'] = 'float' settings_default['D'] = 0.0 settings_description['D'] = 'Differential gain of the controller' # Filter settings_types['lp_cut_freq'] = 'float' settings_default['lp_cut_freq'] = 0. settings_description['lp_cut_freq'] = 'Cutting frequency of the low pass filter of the process value in Hz. Choose 0 for no filter' settings_types['anti_windup_lim'] = 'list(float)' settings_default['anti_windup_lim'] = [-1., -1.] settings_description['anti_windup_lim'] = ('Limits of actuation to apply anti windup.' + 'Use the same number to deactivate.') # Set point parameters settings_types['sp_type'] = 'str' settings_default['sp_type'] = None settings_description['sp_type'] = ( 'Quantity used to define the' + ' set point') settings_options['sp_type'] = ['rbm', 'pitch', 'gen_vel'] settings_types['sp_source'] = 'str' settings_default['sp_source'] = None settings_description['sp_source'] = ( 'Source used to define the' + ' set point') settings_options['sp_source'] = ['file', 'const'] settings_types['sp_time_history_file'] = 'str' settings_default['sp_time_history_file'] = '' settings_description['sp_time_history_file'] = ('Route and file name of the time ' + 'history of the desired set point.' + 'Used for ``sp_source = file``') settings_types['sp_const'] = 'float' settings_default['sp_const'] = 0. settings_description['sp_const'] = 'Constant set point. Only used for ``sp_source`` = `const ``' # Other parameters settings_types['dt'] = 'float' settings_default['dt'] = None settings_description['dt'] = 'Time step of the simulation' settings_types['ntime_steps'] = 'int' settings_default['ntime_steps'] = None settings_description['ntime_steps'] = 'Number of time steps' #settings_types['tower_body'] = 'int' #settings_default['tower_body'] = 0 #settings_description['tower_body'] = 'Body number of the tower' #settings_types['tower_top_node'] = 'int' #settings_default['tower_top_node'] = 0 #settings_description['tower_top_node'] = 'Global node number of the tower top' settings_types['blade_num_body'] = 'list(int)' settings_default['blade_num_body'] = [0,] settings_description['blade_num_body'] = 'Body number of the blade(s) to pitch' settings_types['max_pitch_rate'] = 'float' settings_default['max_pitch_rate'] = 0.1396 settings_description['max_pitch_rate'] = 'Maximum pitch rate [rad/s]' settings_types['pitch_sp'] = 'float' settings_default['pitch_sp'] = 0. settings_description['pitch_sp'] = 'Pitch set point [rad]' settings_types['initial_pitch'] = 'float' settings_default['initial_pitch'] = 0. settings_description['initial_pitch'] = 'Initial pitch [rad]' settings_types['initial_rotor_vel'] = 'float' settings_default['initial_rotor_vel'] = 0. settings_description['initial_rotor_vel'] = 'Initial rotor velocity [rad/s]' settings_types['min_pitch'] = 'float' settings_default['min_pitch'] = 0. settings_description['min_pitch'] = 'Minimum pitch [rad]' settings_types['max_pitch'] = 'float' settings_default['max_pitch'] = 1.5707963267948966 settings_description['max_pitch'] = 'Maximum pitch [rad]' settings_types['nocontrol_steps'] = 'int' settings_default['nocontrol_steps'] = -1 settings_description['nocontrol_steps'] = 'Time steps without control action' # Generator and drive train model settings_types['gen_model_const_var'] = 'str' settings_default['gen_model_const_var'] = '' settings_description['gen_model_const_var'] = 'Generator metric to be kept constant at a value `target_gen_value`' settings_options['gen_model_const_var'] = ['power', 'torque'] settings_types['gen_model_const_value'] = 'float' settings_default['gen_model_const_value'] = 3945990.325 settings_description['gen_model_const_value'] = 'Constant value of the generator metric to be kept constant ' settings_types['GBR'] = 'float' settings_default['GBR'] = 97. settings_description['GBR'] = 'Gear box ratio' settings_types['inertia_dt'] = 'float' settings_default['inertia_dt'] = 43776046.25 settings_description['inertia_dt'] = 'Drive train inertia' settings_types['newmark_damp'] = 'float' settings_default['newmark_damp'] = 1e-4 settings_description['newmark_damp'] = 'Damping of the time integration newmark-beta scheme' # Output parameters settings_types['write_controller_log'] = 'bool' settings_default['write_controller_log'] = True settings_description['write_controller_log'] = ( 'Write a time history of input, required input, ' + 'and control') settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.in_dict = None self.data = None self.settings = None self.prescribed_sp_time_history = None self.prescribed_sp = list() self.system_pv = list() self.controller_implementation = None self.log_fname = None def initialise(self, data, in_dict, controller_id=None, restart=False): self.in_dict = in_dict settings.to_custom_types(self.in_dict, self.settings_types, self.settings_default, no_ctype=True) self.settings = self.in_dict self.newmark_beta = 0.5 + self.settings['newmark_damp'] if self.settings['write_controller_log']: folder = data.output_folder + '/controllers/' if not os.path.exists(folder): os.makedirs(folder) self.log_fname = (folder + self.controller_id + ".dat") fid = open(self.log_fname, 'a') fid.write(('#'+ 1*'{:>2} ' + 10*'{:>12} ' + '{:>12}\n'). format('tstep', 'time', 'ref_state', 'state', 'Pcontrol', 'Icontrol', 'Dcontrol', 'control', 'gen_torque', 'rotor_vel', 'pitch_vel', 'pitch')) fid.close() # save input time history if self.settings['sp_source'] == 'file': self.prescribed_sp_time_history = np.loadtxt(self.settings['sp_time_history_file']) # Init PID controller self.controller_implementation = control_utils.PID(self.settings['P'], self.settings['I'], self.settings['D'], self.settings['dt']) if not self.settings['anti_windup_lim'][0] == self.settings['anti_windup_lim'][1]: self.controller_implementation.set_anti_windup_lim(self.settings['anti_windup_lim']) if self.settings['lp_cut_freq'] == 0.: self.filter_pv = False else: self.filter_pv = True w0 = self.settings['lp_cut_freq']*2*np.pi self.filter = TransferFunction(np.array([w0]), np.array([1., w0])) self.min_it_filter = int(1./(self.settings['lp_cut_freq']*self.settings['dt'])) self.pitch = self.settings['initial_pitch'] self.rotor_vel = self.settings['initial_rotor_vel'] self.rotor_acc = 0. def control(self, data, controlled_state): r""" Main routine of the controller. Input is `data` (the self.data in the solver), and `currrent_state` which is a dictionary with ['structural', 'aero'] time steps for the current iteration. :param data: problem data containing all the information. :param controlled_state: `dict` with two vars: `structural` and `aero` containing the `timestep_info` that will be returned with the control variables. :returns: A `dict` with `structural` and `aero` time steps and control input included. """ # TODO: move this to the initialisation with restart if len(self.system_pv) == 0: for it in range(data.ts - 1): self.system_pv.append(0.) self.prescribed_sp.append(0.) struct_tstep = controlled_state['structural'] aero_tstep = controlled_state['aero'] if not "info" in controlled_state: controlled_state['info'] = dict() time = self.settings['dt']*data.ts # Compute the rotor velocity aero_torque = self.compute_aero_torque(data.structure, struct_tstep) # if self.settings['variable_speed']: if True: #ielem, inode_in_elem = data.structure.node_master_elem[self.settings['tower_top_node'] #node_cga = ag.quat2rotation(struct_tstep.mb_quat[self.settings['tower_body'], :]) #cab = ag.crv2rotation(struct_tstep.psi[ielem, inode_in_elem, :]) #FoR_cga = ag.quat2rotation(struct_tstep.mb_quat[self.settings['blade_num_body'][0], :]) #ini_rotor_vel = ag.multiply_matrices(cab.T, node_cga.T, FoR_cga, # struct_tstep.mb_FoR_vel[self.settings['blade_num_body'][0], 3:6])[2] #ini_rotor_acc = ag.multiply_matrices(cab.T, node_cga.T, FoR_cga, # struct_tstep.mb_FoR_acc[self.settings['blade_num_body'][0], 3:6])[2] self.rotor_vel, self.rotor_acc = self.drive_train_model(aero_torque, self.rotor_vel, self.rotor_acc) else: self.rotor_vel = self.settings['sp_const'] self.rotor_acc = 0. # System set point prescribed_sp = self.compute_prescribed_sp(time) # System process value sys_pv = self.compute_system_pv(struct_tstep, data.structure, gen_vel=self.rotor_vel*self.settings['GBR']) if data.ts < self.settings['nocontrol_steps']: sys_pv = prescribed_sp self.system_pv[-1] = sys_pv return controlled_state else: controlled_state['info']['rotor_vel'] = self.rotor_vel # Apply filter # Filter only after five periods of the cutoff frequency if self.filter_pv and (len(self.system_pv) > self.min_it_filter): nit = len(self.system_pv) time = np.linspace(0, (nit - 1)*self.settings['dt'], nit) # print(time.shape, len(self.system_pv)) T, filtered_pv, xout = forced_response(self.filter, T=time, U=self.system_pv) else: filtered_pv = self.system_pv # get current state input delta_pitch_ref, detail = self.controller_wrapper( required_input=self.prescribed_sp, current_input=filtered_pv, control_param={'P': self.settings['P'], 'I': self.settings['I'], 'D': self.settings['D']}, i_current=data.ts) # NREL controller does error = state - reference. Here is done the other way delta_pitch_ref *= -1. # Limit pitch and pitch rate target_pitch = delta_pitch_ref + self.settings['pitch_sp'] pitch_rate = (target_pitch - self.pitch)/self.settings['dt'] if pitch_rate < -self.settings['max_pitch_rate']: pitch_rate = -self.settings['max_pitch_rate'] elif pitch_rate > self.settings['max_pitch_rate']: pitch_rate = self.settings['max_pitch_rate'] next_pitch = self.pitch + pitch_rate*self.settings['dt'] if next_pitch < self.settings['min_pitch']: pitch_rate = 0. next_pitch = self.settings['min_pitch'] if next_pitch > self.settings['max_pitch']: pitch_rate = 0. next_pitch = self.settings['max_pitch'] self.pitch = next_pitch controlled_state['info']['pitch_vel'] = -pitch_rate # Apply control order change_quat = False if change_quat: for ibody in self.settings['blade_num_body']: quat = algebra.rotate_quaternion(struct_tstep.mb_quat[ibody, :], delta_pitch*np.array([1., 0., 0.])) struct_tstep.mb_quat[ibody, :] = quat.copy() if ibody == 0: struct_tstep.quat = quat.copy() change_vel = False if change_vel: for ibody in self.settings['blade_num_body']: struct_tstep.mb_FoR_vel[ibody, 3] = pitch_rate struct_tstep.mb_FoR_acc[ibody, 3] = (data.structure.timestep_info[data.ts - 1].mb_FoR_vel[ibody, 3] - struct_tstep.mb_FoR_vel[ibody, 3])/self.settings['dt'] if ibody == 0: struct_tstep.for_vel[3] = struct_tstep.mb_FoR_vel[ibody, 3] struct_tstep.for_acc[3] = struct_tstep.mb_FoR_acc[ibody, 3] data.structure.dynamic_input[data.ts - 1]['for_vel'] = struct_tstep.for_vel.copy() data.structure.dynamic_input[data.ts - 1]['for_acc'] = struct_tstep.for_acc.copy() if False: data.aero.generate_zeta_timestep_info(struct_tstep, aero_tstep, data.structure, data.aero.aero_settings, dt=self.settings['dt']) fid = open(self.log_fname, 'a') fid.write(('{:>6d} ' + 10*'{:>12.6f} ' + '{:>12.6f}\n').format(data.ts, data.ts*self.settings['dt'], self.prescribed_sp[-1], self.system_pv[-1], detail[0], detail[1], detail[2], delta_pitch_ref, aero_torque/self.settings['GBR'], self.rotor_vel, pitch_rate, self.pitch)) fid.close() return controlled_state def compute_prescribed_sp(self, time): """ Compute the set point relevant for the controller """ if self.settings['sp_source'] == 'file': sp = np.interp(time, self.prescribed_sp_time_history[:, 0], self.prescribed_sp_time_history[:, 1]) self.prescribed_sp.append(sp) elif self.settings['sp_source'] == 'const': self.prescribed_sp.append(self.settings['sp_const']) return self.prescribed_sp[-1] def compute_system_pv(self, struct_tstep, beam, **kwargs): """ Compute the process value relevant for the controller """ if self.settings['sp_type'] == 'pitch': pitch = algebra.quat2euler(struct_tstep.mb_quat[self.settings['blade_num_body'][0]])[0] self.system_pv.append(pitch) elif self.settings['sp_type'] == 'rbm': steady, unsteady, grav = struct_tstep.extract_resultants(beam, force_type=['steady', 'unsteady', 'gravity'], ibody=0) rbm = steady[4] + unsteady[4] + grav[4] self.system_pv.append(rbm) elif self.settings['sp_type'] == 'gen_vel': self.system_pv.append(kwargs['gen_vel']) return self.system_pv[-1] def controller_wrapper(self, required_input, current_input, control_param, i_current): self.controller_implementation.set_point(required_input[i_current - 1]) control_param, detailed_control_param = self.controller_implementation(current_input[-1]) return (control_param, detailed_control_param) def __exit__(self, *args): # self.log.close() pass def drive_train_model(self, aero_torque, ini_rot_vel, ini_rot_acc): # Assuming contant generator torque demand if self.settings['gen_model_const_var'] == 'power': gen_torque = self.settings['gen_model_const_value']/self.settings['GBR']/ini_rot_vel elif self.settings['gen_model_const_var'] == 'torque': gen_torque = self.settings['gen_model_const_value'] rot_acc = (aero_torque - self.settings['GBR']*gen_torque)/self.settings['inertia_dt'] rot_vel = ini_rot_vel + rot_acc*self.settings['dt'] return rot_vel, rot_acc def compute_aero_torque(self, beam, struct_tstep): # Compute total forces total_forces = np.zeros(6) for ibody in self.settings['blade_num_body']: steady, unsteady, grav = struct_tstep.extract_resultants(beam, force_type=['steady', 'unsteady', 'grav'], ibody=ibody) # total_forces += steady + unsteady + grav total_forces += steady + unsteady # Compute equivalent forces at hub position hub_elem = np.where(beam.body_number == self.settings['blade_num_body'][0])[0][0] hub_node = beam.connectivities[hub_elem, 0] hub_pos = struct_tstep.pos[hub_node, :] hub_forces = np.zeros(6) hub_forces[:3] = total_forces[:3].copy() hub_forces[3:6] = total_forces[3:6] - np.cross(hub_pos, total_forces[:3]) return hub_forces[5] @staticmethod def compute_blade_pitch(beam, struct_tstep, tower_ibody=0, blade_ibody=1): # Tower top tt_elem = np.where(beam.body_number == tower_ibody)[0][-1] tt_node = beam.connectivities[tt_elem, 1] ielem, inode_in_elem = beam.node_master_elem[tt_node] ca0b = algebra.crv2rotation(struct_tstep.psi[ielem, inode_in_elem, :]) cga0 = algebra.quat2rotation(struct_tstep.mb_quat[tower_ibody, :]) zg_tower_top = cga0 @ ca0b @ np.array([0., 0., 1.]) # blade root cga = algebra.quat2rotation(struct_tstep.mb_quat[blade_ibody, :]) zg_hub = cga @ np.array([0., 0., 1.]) pitch = algebra.angle_between_vectors(zg_tower_top, zg_hub) return pitch ================================================ FILE: sharpy/controllers/controlsurfacepidcontroller.py ================================================ import numpy as np import os import sharpy.utils.controller_interface as controller_interface import sharpy.utils.settings as settings import sharpy.utils.control_utils as control_utils import sharpy.utils.cout_utils as cout @controller_interface.controller class ControlSurfacePidController(controller_interface.BaseController): r""" """ controller_id = 'ControlSurfacePidController' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['time_history_input_file'] = 'str' settings_default['time_history_input_file'] = None settings_description['time_history_input_file'] = 'Route and file name of the time history of desired state' settings_types['P'] = 'float' settings_default['P'] = None settings_description['P'] = 'Proportional gain of the controller' settings_types['I'] = 'float' settings_default['I'] = 0.0 settings_description['I'] = 'Integral gain of the controller' settings_types['D'] = 'float' settings_default['D'] = 0.0 settings_description['D'] = 'Differential gain of the controller' settings_types['input_type'] = 'str' settings_default['input_type'] = None settings_description['input_type'] = ( 'Quantity used to define the' + ' reference state. Supported: `pitch`') settings_types['dt'] = 'float' settings_default['dt'] = None settings_description['dt'] = 'Time step of the simulation' settings_types['controlled_surfaces'] = 'list(int)' settings_default['controlled_surfaces'] = None settings_description['controlled_surfaces'] = ( 'Control surface indices to be actuated by this controller') settings_types['controlled_surfaces_coeff'] = 'list(float)' settings_default['controlled_surfaces_coeff'] = [1.] settings_description['controlled_surfaces_coeff'] = ( 'Control surface deflection coefficients. ' + 'For example, for antisymmetric deflections => [1, -1].') settings_types['write_controller_log'] = 'bool' settings_default['write_controller_log'] = True settings_description['write_controller_log'] = ( 'Write a time history of input, required input, ' + 'and control') supported_input_types = ['pitch', 'roll', 'pos_'] settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.in_dict = None self.data = None self.settings = None self.prescribed_input_time_history = None # Time histories are ordered such that the [i]th element of each # is the state of the controller at the time of returning. # That means that for the timestep i, # state_input_history[i] == input_time_history_file[i] + error[i] self.p_error_history = list() self.i_error_history = list() self.d_error_history = list() self.real_state_input_history = list() self.control_history = list() self.controller_implementation = None self.n_control_surface = 0 self.log = None def initialise(self, data, in_dict, controller_id=None, restart=False): self.in_dict = in_dict settings.to_custom_types(self.in_dict, self.settings_types, self.settings_default) self.settings = self.in_dict self.controller_id = controller_id # validate that the input_type is in the supported ones valid = False for t in self.supported_input_types: if t in self.settings['input_type']: valid = True break if not valid: cout.cout_wrap('The input_type {} is not supported by {}'.format( self.settings['input_type'], self.controller_id), 3) cout.cout_wrap('The supported ones are:', 3) for i in self.supported_input_types: cout.cout_wrap(' {}'.format(i), 3) raise NotImplementedError() if self.settings['write_controller_log']: folder = data.output_folder + '/controllers/' if not os.path.exists(folder): os.makedirs(folder) self.log = open(folder + self.controller_id + ".log.csv", "w+") self.log.write(('#'+ 1*'{:>2},' + 6*'{:>12},' + '{:>12}\n'). format('tstep', 'time', 'Ref. state', 'state', 'Pcontrol', 'Icontrol', 'Dcontrol', 'control')) self.log.flush() # save input time history try: self.prescribed_input_time_history = ( np.loadtxt(self.settings['time_history_input_file'], delimiter=',')) except OSError: raise OSError('File {} not found in Controller'.format(self.settings['time_history_input_file'])) # Init PID controller self.controller_implementation = control_utils.PID(self.settings['P'], self.settings['I'], self.settings['D'], self.settings['dt']) # check that controlled_surfaces_coeff has the correct number of parameters # if len() == 1 and == 1.0, then expand to number of surfaces. # if len(coeff) /= n_surfaces, throw error self.n_control_surface = len(self.settings['controlled_surfaces']) if (len(self.settings['controlled_surfaces_coeff']) == self.n_control_surface): # All good, pass checks pass elif (len(self.settings['controlled_surfaces_coeff']) == 1 and self.settings['controlled_surfaces_coeff'][0] == 1.0): # default value, fill with 1.0 self.settings['controlled_surfaces_coeff'] = np.ones( (self.n_control_surface,), dtype=float) else: raise ValueError('controlled_surfaces_coeff does not have as many' + ' elements as controller_surfaces') def control(self, data, controlled_state): r""" Main routine of the controller. Input is `data` (the self.data in the solver), and `currrent_state` which is a dictionary with ['structural', 'aero'] time steps for the current iteration. :param data: problem data containing all the information. :param controlled_state: `dict` with two vars: `structural` and `aero` containing the `timestep_info` that will be returned with the control variables. :returns: A `dict` with `structural` and `aero` time steps and control input included. """ # get current state input self.real_state_input_history.append(self.extract_time_history(controlled_state)) i_current = len(self.real_state_input_history) # apply it where needed. control_command, detail = self.controller_wrapper( required_input=self.prescribed_input_time_history, current_input=self.real_state_input_history, control_param={'P': self.settings['P'], 'I': self.settings['I'], 'D': self.settings['D']}, i_current=i_current) controlled_state['aero'].control_surface_deflection = ( np.array(self.settings['controlled_surfaces_coeff'])*control_command) self.log.write(('{:>6d},' + 6*'{:>12.6f},' + '{:>12.6f}\n').format(i_current, i_current*self.settings['dt'], self.prescribed_input_time_history[i_current - 1], self.real_state_input_history[i_current - 1], detail[0], detail[1], detail[2], control_command)) return controlled_state def extract_time_history(self, controlled_state): output = 0.0 if self.settings['input_type'] == 'pitch': step = controlled_state['structural'] euler = step.euler_angles() output = euler[1] elif self.settings['input_type'] == 'roll': step = controlled_state['structural'] euler = step.euler_angles() output = euler[0] elif 'pos_z(' in self.settings['input_type']: node_str = self.settings['input_type'] node_str = node_str.replace('pos(', '') node_str = node_str.replace(')', '') node = int(node_str) step = controlled_state['structural'] pos = step.pos[node, :] output = pos[2] else: raise NotImplementedError( "input_type {} is not yet implemented in extract_time_history()" .format(self.settings['input_type'])) return output def controller_wrapper(self, required_input, current_input, control_param, i_current): self.controller_implementation.set_point(required_input[i_current - 1]) control_param, detailed_control_param = self.controller_implementation(current_input[-1]) return (control_param, detailed_control_param) def __exit__(self, *args): self.log.close() ================================================ FILE: sharpy/controllers/multibodycontroller.py ================================================ import numpy as np import os import sharpy.utils.controller_interface as controller_interface import sharpy.utils.settings as settings @controller_interface.controller class MultibodyController(controller_interface.BaseController): r""" """ controller_id = "MultibodyController" settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types["ang_history_input_file"] = "str" settings_default["ang_history_input_file"] = None settings_description["ang_history_input_file"] = "Route and file name of the time history of desired CRV rotation" settings_types["ang_vel_history_input_file"] = "str" settings_default["ang_vel_history_input_file"] = "" settings_description["ang_vel_history_input_file"] = ("Route and file name of the time history of desired CRV " "velocity") settings_types["psi_dot_init"] = "list(float)" settings_default["psi_dot_init"] = [0., 0., 0.] settings_description["psi_dot_init"] = "Initial rotation velocity of hinge" settings_types["dt"] = "float" settings_default["dt"] = None settings_description["dt"] = "Time step of the simulation" settings_types["write_controller_log"] = "bool" settings_default["write_controller_log"] = True settings_description["write_controller_log"] = ( "Write a time history of input, required input, " + "and control" ) settings_table = settings.SettingsTable() __doc__ += settings_table.generate( settings_types, settings_default, settings_description, settings_options ) def __init__(self): self.in_dict = None # this also holds the settings dict, kept to be consistent with other controllers self.data = None self.settings = None self.prescribed_ang_time_history = None self.prescribed_ang_vel_time_history = None # Time histories are ordered such that the [i]th element of each # is the state of the controller at the time of returning. # That means that for the timestep i, # state_input_history[i] == input_time_history_file[i] + error[i] self.real_state_input_history = list() self.control_history = list() self.controller_implementation = None self.log = None def initialise(self, data, in_dict, controller_id=None, restart=False): self.in_dict = in_dict settings.to_custom_types( self.in_dict, self.settings_types, self.settings_default ) self.settings = self.in_dict self.controller_id = controller_id # whilst PID control is not here implemented, I have left the remains for if it gets implemented in future if self.settings["write_controller_log"]: folder = data.output_folder + "/controllers/" if not os.path.exists(folder): os.makedirs(folder) self.log = open(folder + self.controller_id + ".log.csv", "w+") self.log.write( ("#" + 1 * "{:>2}," + 6 * "{:>12}," + "{:>12}\n").format( "tstep", "time", "Ref. state", "state", "Pcontrol", "Icontrol", "Dcontrol", "control", ) ) self.log.flush() # save input time history try: self.prescribed_ang_time_history = np.loadtxt( self.settings["ang_history_input_file"], delimiter="," ) except: try: self.prescribed_ang_time_history = np.load( self.settings["ang_history_input_file"] ) except: raise OSError( "File {} not found in Controller".format( self.settings["ang_history_input_file"] ) ) if self.settings["ang_vel_history_input_file"]: try: self.prescribed_ang_vel_time_history = np.loadtxt( self.settings["ang_vel_history_input_file"], delimiter="," ) except: try: self.prescribed_ang_vel_time_history = np.load( self.settings["ang_vel_history_input_file"] ) except: raise OSError( "File {} not found in Controller".format( self.settings["ang_vel_history_input_file"] ) ) def control(self, data, controlled_state): r""" Main routine of the controller. Input is `data` (the self.data in the solver), and `currrent_state` which is a dictionary with ['structural', 'aero'] time steps for the current iteration. :param data: problem data containing all the information. :param controlled_state: `dict` with two vars: `structural` and `aero` containing the `timestep_info` that will be returned with the control variables. :returns: A `dict` with `structural` and `aero` time steps and control input included. """ control_command = self.prescribed_ang_time_history[data.ts - 1, :] if self.prescribed_ang_vel_time_history is None: if data.ts == 1: psi_dot = self.settings["psi_dot_init"] else: psi_dot = ( self.prescribed_ang_time_history[data.ts - 1, :] - self.prescribed_ang_time_history[data.ts - 2, :] ) / self.settings["dt"] else: psi_dot = self.prescribed_ang_vel_time_history[data.ts - 1, :] if controlled_state["structural"].mb_prescribed_dict is None: controlled_state["structural"].mb_prescribed_dict = dict() controlled_state["structural"].mb_prescribed_dict[self.controller_id] = { "psi": control_command, "psi_dot": psi_dot, "delta_psi": control_command - self.prescribed_ang_time_history[0, :]} return controlled_state, control_command def controller_wrapper( self, required_input, current_input, control_param, i_current ): self.controller_implementation.set_point(required_input[i_current - 1]) control_param, detailed_control_param = self.controller_implementation( current_input[-1] ) return control_param, detailed_control_param def __exit__(self, *args): self.log.close() ================================================ FILE: sharpy/controllers/takeofftrajectorycontroller.py ================================================ import ctypes as ct import numpy as np import os import scipy.interpolate as interpolate import sharpy.utils.controller_interface as controller_interface import sharpy.utils.settings as settings import sharpy.utils.control_utils as control_utils import sharpy.utils.cout_utils as cout import sharpy.structure.utils.lagrangeconstraints as lc @controller_interface.controller class TakeOffTrajectoryController(controller_interface.BaseController): r""" """ controller_id = 'TakeOffTrajectoryController' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['trajectory_input_file'] = 'str' settings_default['trajectory_input_file'] = None settings_description['trajectory_input_file'] = 'Route and file name of the trajectory file given as a csv with columns: time, x, y, z' settings_types['dt'] = 'float' settings_default['dt'] = None settings_description['dt'] = 'Time step of the simulation' settings_types['trajectory_method'] = 'str' settings_default['trajectory_method'] = 'lagrange' settings_description['trajectory_method'] = ( 'Trajectory controller method. For now, "lagrange" is the supported option') settings_types['controlled_constraint'] = 'str' settings_default['controlled_constraint'] = None settings_description['controlled_constraint'] = ('Name of the controlled constraint in the multibody context' + ' Usually, it is something like `constraint_00`.') settings_types['controller_log_route'] = 'str' settings_default['controller_log_route'] = './output/' settings_description['controller_log_route'] = ( 'Directory where the log will be stored') settings_types['write_controller_log'] = 'bool' settings_default['write_controller_log'] = True settings_description['write_controller_log'] = ( 'Controls if the log from the controller is written or not.') settings_types['free_trajectory_structural_solver'] = 'str' settings_default['free_trajectory_structural_solver'] = '' settings_description['free_trajectory_structural_solver'] = ( 'If different than and empty string, the structural solver' + ' will be changed after the end of the trajectory has been reached') settings_types['free_trajectory_structural_substeps'] = 'int' settings_default['free_trajectory_structural_substeps'] = 0 settings_description['free_trajectory_structural_substeps'] = ( 'Controls the structural solver' + ' structural substeps once the end of the trajectory has been reached') settings_types['initial_ramp_length_structural_substeps'] = 'int' settings_default['initial_ramp_length_structural_substeps'] = 10 settings_description['initial_ramp_length_structural_substeps'] = ( 'Controls the number of timesteps that are used to increase the' + ' structural substeps from 0') settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.in_dict = None self.data = None self.settings = None self.input_history = None self.trajectory_interp = None self.trajectory_vel_interp = None self.t_limits = np.zeros((2,)) self.controlled_body = None self.controlled_node = None self.log = None def initialise(self, data, in_dict, controller_id=None, restart=False): self.in_dict = in_dict settings.to_custom_types(self.in_dict, self.settings_types, self.settings_default) self.settings = self.in_dict self.controller_id = controller_id if self.settings['write_controller_log']: # TODO substitute for table writer in cout_utils. folder = data.output_folder + '/controllers/' if not os.path.exists(folder): os.makedirs(folder) self.log = open(folder + self.controller_id + ".log.csv", "w+") self.log.write(('#'+ 1*'{:>2},' + 6*'{:>12},' + '{:>12}\n'). format('tstep', 'time', 'Ref. state', 'state', 'Pcontrol', 'Icontrol', 'Dcontrol', 'control')) self.log.flush() # save input time history try: self.input_history = ( np.loadtxt( self.settings['trajectory_input_file'], delimiter=',')) except OSError: raise OSError('File {} not found in {}'.format( self.settings['time_history_input_file'], self.controller_id)) self.process_trajectory() def control(self, data, controlled_state): r""" Main routine of the controller. Input is `data` (the self.data in the solver), and `currrent_state` which is a dictionary with ['structural', 'aero'] time steps for the current iteration. :param data: problem data containing all the information. :param controlled_state: `dict` with two vars: `structural` and `aero` containing the `timestep_info` that will be returned with the control variables. :returns: A `dict` with `structural` and `aero` time steps and control input included. """ # get current state input # note: with or without the -1? time = (data.ts - 1)*self.settings['dt'] i_current = data.ts try: constraint = controlled_state['structural'].\ mb_dict[self.settings['controlled_constraint']] except KeyError: return controlled_state if self.controlled_body is None or self.controlled_node is None: self.controlled_body = constraint['body_number'] self.controlled_node = constraint['node_number'] # reset info to include only fresh info controlled_state['info'] = dict() # apply it where needed. traj_command, end_of_traj = self.controller_wrapper(time) if end_of_traj: lc.remove_constraint(controlled_state['structural'].mb_dict, self.settings['controlled_constraint']) if not self.settings['free_trajectory_structural_solver'] == '': controlled_state['info']['structural_solver'] = ( self.settings['free_trajectory_structural_solver']) controlled_state['info']['structural_substeps'] = ( self.settings['free_trajectory_structural_substeps']) return controlled_state constraint['velocity'][:] = traj_command if self.settings['write_controller_log']: self.log.write(('{:>6d},' + 3*'{:>12.6f},' + '{:>12.6f}\n').format(i_current, time, traj_command[0], traj_command[1], traj_command[2])) if self.settings['initial_ramp_length_structural_substeps'] >= 0: if (i_current < self.settings['initial_ramp_length_structural_substeps']): controlled_state['info']['structural_substeps'] = \ ct.c_int(i_current - 1) elif (i_current == self.settings['initial_ramp_length_structural_substeps']): controlled_state['info']['structural_substeps'] = None return controlled_state def process_trajectory(self, dxdt=True): """ See https://docs.scipy.org/doc/scipy-0.15.1/reference/generated/scipy.interpolate.UnivariateSpline.html """ self.trajectory_interp = [] # Make sure s = 0.5 is ok. self.t_limits[:] = (np.min(self.input_history[:, 0]), np.max(self.input_history[:, 0])) for i_dim in range(3): self.trajectory_interp.append( interpolate.UnivariateSpline(self.input_history[:, 0], self.input_history[:, i_dim + 1], k=1, s=0., ext='raise')) if dxdt: self.trajectory_vel_interp = [] for i_dim in range(3): self.trajectory_vel_interp.append( self.trajectory_interp[i_dim].derivative()) def controller_wrapper(self, t): output_traj = np.zeros((3,)) end_of_traj = False if self.settings['trajectory_method'] == 'lagrange': # check that t is in input limits if self.t_limits[0] <= t <= self.t_limits[1]: # return velocities for i_dim in range(3): output_traj[i_dim] = self.trajectory_vel_interp[i_dim](t) else: for i_dim in range(3): output_traj[i_dim] = np.nan end_of_traj = True else: raise NotImplementedError('The trajectory_method ' + self.settings['trajectory_method'] + ' is not yet implemented.') return output_traj, end_of_traj def __exit__(self, *args): self.log.close() ================================================ FILE: sharpy/generators/__init__.py ================================================ """Generators Velocity field generators prescribe the flow conditions for your problem. For instance, you can have an aircraft at a prescribed fixed location in a velocity field towards the aircraft. Alternatively, you can have a free moving aircraft in a static velocity field. Dynamic Control Surface generators enable the user to prescribe a certain control surface deflection in time. """ import importlib import os import sharpy.utils.generator_interface as generator_interface import sharpy.utils.sharpydir as sharpydir files = generator_interface.generator_list_from_path(os.path.dirname(__file__)) import_path = os.path.realpath(os.path.dirname(__file__)) import_path = import_path.replace(sharpydir.SharpyDir, "") if import_path[0] == "/": import_path = import_path[1:] import_path = import_path.replace("/", ".") for file in files: generator_interface.generators[file] = importlib.import_module(import_path + "." + file) ================================================ FILE: sharpy/generators/bumpvelocityfield.py ================================================ import numpy as np import sharpy.utils.generator_interface as generator_interface import sharpy.utils.settings as settings import sharpy.utils.exceptions as exc @generator_interface.generator class BumpVelocityField(generator_interface.BaseGenerator): r""" Bump Velocity Field Generator ``BumpVelocityField`` is a class inherited from ``BaseGenerator`` The ``BumpVelocityField`` class generates a bump-shaped gust profile velocity field, and the profile has the characteristics specified by the user. To call this generator, the ``generator_id = BumpVelocityField`` shall be used. This is parsed as the value for the ``velocity_field_generator`` key in the desired aerodynamic solver's settings. The resultant velocity, $w_g$, is calculated as follows: .. math:: w_g = \frac{w_0}{4}\left( 1 + \cos(\frac{(x - x_0)}{H_x} \right)\left( 1 + \cos(\frac{(y - y_0)}{H_y} \right) Notes: For now, only simulations where the inertial FoR is fixed are supported. """ generator_id = 'BumpVelocityField' generator_classification = 'velocity-field' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['gust_intensity'] = 'float' settings_default['gust_intensity'] = None settings_description['gust_intensity'] = 'Intensity of the gust' settings_types['x0'] = 'float' settings_default['x0'] = 0.0 settings_description['x0'] = 'x location of the centre of the bump' settings_types['y0'] = 'float' settings_default['y0'] = 0.0 settings_description['y0'] = 'y location of the centre of the bump' settings_types['hx'] = 'float' settings_default['hx'] = 1. settings_description['hx'] = 'Gust gradient in the x direction' settings_types['hy'] = 'float' settings_default['hy'] = 1. settings_description['hy'] = 'Gust gradient in the y direction' settings_types['relative_motion'] = 'bool' settings_default['relative_motion'] = False settings_description['relative_motion'] = 'When true the gust will move at the prescribed velocity' settings_types['u_inf'] = 'float' settings_default['u_inf'] = None settings_description['u_inf'] = 'Free stream velocity' settings_types['u_inf_direction'] = 'list(float)' settings_default['u_inf_direction'] = np.array([1.0, 0, 0]) settings_description['u_inf_direction'] = 'Free stream velocity direction' table = settings.SettingsTable() __doc__ += table.generate(settings_types, settings_default, settings_description) def __init__(self): self.in_dict = dict() self.settings = dict() self.u_inf = 0. self.u_inf_direction = None def initialise(self, in_dict, restart=False): self.in_dict = in_dict settings.to_custom_types(self.in_dict, BumpVelocityField.settings_types, BumpVelocityField.settings_default) self.settings = self.in_dict self.u_inf = self.settings['u_inf'] self.u_inf_direction = self.in_dict['u_inf_direction'] def generate(self, params, uext): zeta = params['zeta'] override = params['override'] for_pos = params['for_pos'] t = params['t'] def gust_shape(x, y, z, hx, hy, x0, y0, w0): vel = np.zeros((3,)) if np.abs(x - x0) > hx or np.abs(y - y0) > hy: return vel vel[2] = 0.25*w0*(1 + np.cos((x - x0)/hx * np.pi))*(1 + np.cos((y - y0)/hy * np.pi)) return vel for i_surf in range(len(zeta)): if override: uext[i_surf].fill(0.0) for i in range(zeta[i_surf].shape[1]): for j in range(zeta[i_surf].shape[2]): uext[i_surf][:, i, j] += gust_shape(zeta[i_surf][0, i, j] + for_pos[0], zeta[i_surf][1, i, j] + for_pos[1], zeta[i_surf][2, i, j] + for_pos[2], self.settings['hx'], self.settings['hy'], self.settings['x0'], self.settings['y0'], self.settings['gust_intensity']) if self.settings['relative_motion']: uext[i_surf][:, i, j] += self.u_inf*t ================================================ FILE: sharpy/generators/dynamiccontrolsurface.py ================================================ import numpy as np import sharpy.utils.generator_interface as generator_interface import sharpy.utils.settings as settings import sharpy.utils.cout_utils as cout_utils @generator_interface.generator class DynamicControlSurface(generator_interface.BaseGenerator): """ Dynamic Control Surface deflection Generator The object generates a deflection in radians based on the time series given as a single vector in the input data. A first order finite-differences scheme is used to calculate the deflection rate based on the provided time step increment. To call this generator, the ``generator_id = DynamicControlSurface`` key shall be used for the setting `control_surface_deflection` in the ``AerogridLoader`` solver. One instance of this generator will be created for each control surface, thus, a group of settings should be defined for each control surface (``cs0_settings``, ``cs1_settings`` ... in the example below). All of these groups of settings should be collected as values in a dictionary which keys are the associated control surface number in string format. This dictionary should be parsed to the variable ``control_surface_deflection_generator_settings`` in ``AerogridLoader``. This is shown better in the example below: Examples: .. code-block: cs0_settings = {} # these are the settings for control surface number 0 cs1_settings = {} # these are the settings for control surface number 1 dict_of_cs = {'0': cs0_settings, '1': cs1_settings} # This dictionary groups all the settings for all the control surfaces settings = {} settings['AerogridLoader] = {'control_surface_deflection' : ['DynamicControlSurface], 'control_surface_deflection_generator_settings: dict_of_cs} Attributes: deflection (np.array): Array of deflection of the control surface deflection_dot (np.array): Array of the time derivative of the cs deflection. Calculated using 1st order finite differences. """ generator_id = 'DynamicControlSurface' generator_classification = 'utils' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['dt'] = 'float' settings_default['dt'] = None settings_description['dt'] = 'Time step increment' settings_types['deflection_file'] = 'str' settings_default['deflection_file'] = None settings_description['deflection_file'] = 'Path to the file with the deflection information' settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, header_line='This generator takes the following inputs:') def __init__(self): self.in_dict = dict() self.deflection = None self.deflection_dot = None def initialise(self, in_dict, restart=False): self.in_dict = in_dict settings.to_custom_types(self.in_dict, self.settings_types, self.settings_default, no_ctype=True) # load file try: self.deflection = np.loadtxt(self.in_dict['deflection_file']) except OSError: cout_utils.cout_wrap('Unable to find control surface deflection file input', 4) raise FileNotFoundError('Could not locate deflection file: ' '{:s}'.format(self.in_dict['deflection_file'])) else: cout_utils.cout_wrap('\tSuccess loading file {:s}'.format(self.in_dict['deflection_file']), 2) # deflection velocity self.deflection_dot = np.zeros_like(self.deflection) self.deflection_dot[0:-1] = np.diff(self.deflection)/self.in_dict['dt'] self.deflection_dot[-1] = 0 def generate(self, params): it = params['it'] return self.deflection[it], self.deflection_dot[it] def __call__(self, params): return self.generate(params) ================================================ FILE: sharpy/generators/floatingforces.py ================================================ import numpy as np import h5py as h5 import ctypes as ct import os from scipy.fft import fft, ifft from scipy.interpolate import interp1d from control import forced_response, TransferFunction import sharpy.utils.cout_utils as cout import sharpy.utils.generator_interface as generator_interface import sharpy.utils.settings as settings import sharpy.utils.solver_interface as solver_interface from sharpy.utils.constants import deg2rad import sharpy.utils.h5utils as h5utils import sharpy.utils.algebra as algebra def compute_xf_zf(hf, vf, l, w, EA, cb): """ Fairlead location (xf, zf) computation """ root1, root2, ln1, ln2, lb = rename_terms(vf, hf, w, l) # Define if there is part of the mooring line on the bed if lb <= 0: nobed = True else: nobed = False # Compute the position of the fairlead if nobed: xf = hf/w*(ln1 - ln2) + hf*l/EA zf = hf/w*(root1 - root2) + 1./EA*(vf*l-w*l**2/2) else: xf = lb + hf/w*ln1 + hf*l/EA if not cb == 0.: xf += cb*w/2/EA*(-lb**2 + (lb - hf/cb/w)*np.maximum((lb - hf/cb/w), 0)) zf = hf/w*(root1 - 1) + vf**2/2/EA/w return xf, zf def compute_jacobian(hf, vf, l, w, EA, cb): """ Analytical computation of the Jacobian of equations in function compute_xf_zf """ root1, root2, ln1, ln2, lb = rename_terms(vf, hf, w, l) # Compute their deivatives der_root1_hf = 0.5*(1. + (vf/hf)**2)**(-0.5)*(2*vf/hf*(-vf/hf/hf)) der_root1_vf = 0.5*(1. + (vf/hf)**2)**(-0.5)*(2*vf/hf/hf) der_root2_hf = 0.5*(1. + ((vf - w*l)/hf)**2)**(-0.5)*(2.*(vf - w*l)/hf*(-(vf - w*l)/hf/hf)) der_root2_vf = 0.5*(1. + ((vf - w*l)/hf)**2)**(-0.5)*(2.*(vf - w*l)/hf/hf) der_ln1_hf = 1./(vf/hf + root1)*(vf/hf/hf + der_root1_hf) der_ln1_vf = 1./(vf/hf + root1)*(1./hf + der_root1_vf) der_ln2_hf = 1./((vf - w*l)/hf + root2)*(-(vf - w*l)/hf/hf + der_root2_hf) der_ln2_vf = 1./((vf - w*l)/hf + root2)*(1./hf + der_root2_vf) der_lb_hf = 0. der_lb_vf = -1./w # Define if there is part of the mooring line on the bed if lb <= 0: nobed = True else: nobed = False # Compute the Jacobian if nobed: der_xf_hf = 1./w*(ln1 - ln2) + hf/w*(der_ln1_hf + der_ln2_hf) + l/EA der_xf_vf = hf/w*(der_ln1_vf + der_ln2_vf) der_zf_hf = 1./w*(root1 - root2) + hf/w*(der_root1_hf - der_root2_hf) der_zf_vf = hf/w*(der_root1_vf - der_root2_vf) + 1./EA*l else: der_xf_hf = der_lb_hf + 1./w*ln1 + hf/w*der_ln1_hf + l/EA if not cb == 0.: arg1_max = l - vf/w - hf/cb/w if arg1_max > 0.: der_xf_hf += cb*w/2/EA*(2*(arg1_max)*(-1/cb/w)) der_xf_vf = der_lb_vf + hf/w*der_ln1_vf + cb*w/2/EA*(-2.*lb*der_lb_vf) if not cb == 0.: arg1_max = l - vf/w - hf/cb/w if arg1_max > 0.: der_xf_vf += cb*w/2/EA*(2.*(lb - hf/cb/w)*der_lb_vf) der_zf_hf = 1/w*(root1 - 1) + hf/w*der_root1_hf der_zf_vf = hf/w*der_root1_vf + vf/EA/w J = np.array([[der_xf_hf, der_xf_vf],[der_zf_hf, der_zf_vf]]) return J def rename_terms(vf, hf, w, l): """ Rename some terms for convenience """ root1 = np.sqrt(1. + (vf/hf)**2) root2 = np.sqrt(1. + ((vf - w*l)/hf)**2) ln1 = np.log(vf/hf + root1) ln2 = np.log((vf - w*l)/hf + root2) lb = l - vf/w return root1, root2, ln1, ln2, lb def quasisteady_mooring(xf, zf, l, w, EA, cb, hf0=None, vf0=None): """ Computation of the forces generated by the mooring system It performs a Newton-Raphson iteration based on the known equations in compute_xf_zf function and the Jacobian """ # Initialise guess for hf0 and vf0 if xf == 0: lambda0 = 1e6 elif np.sqrt(xf**2 + zf**2) > l: lambda0 = 0.2 else: lambda0 = np.sqrt(3*((l**2 - zf**2)/xf**2 - 1)) if hf0 is None: hf0 = np.abs(w*xf/2/lambda0) if vf0 is None: vf0 = w/2*(zf/np.tanh(lambda0) + l) # Compute the solution through Newton-Raphson iteration hf_est = hf0 + 0. vf_est = vf0 + 0. xf_est, zf_est = compute_xf_zf(hf_est, vf_est, l, w, EA, cb) # print("initial: ", xf_est, zf_est) tol = 1e-6 error = 2*tol max_iter = 10000 it = 0 while ((error > tol) and (it < max_iter)): J_est = compute_jacobian(hf_est, vf_est, l, w, EA, cb) inv_J_est = np.linalg.inv(J_est) hf_est += inv_J_est[0, 0]*(xf - xf_est) + inv_J_est[0, 1]*(zf - zf_est) vf_est += inv_J_est[1, 0]*(xf - xf_est) + inv_J_est[1, 1]*(zf - zf_est) xf_est, zf_est = compute_xf_zf(hf_est, vf_est, l, w, EA, cb) error = np.maximum(np.abs(xf - xf_est), np.abs(zf - zf_est)) it += 1 if ((it == max_iter) and (error > tol)): cout.cout_wrap(("Mooring system did not converge. error %f" % error), 4) print("Mooring system did not converge. error %f" % error) return hf_est, vf_est def wave_radiation_damping(K, qdot, it, dt): """ This function computes the wave radiation damping assuming K constant """ qdot_int = np.zeros((6,)) for idof in range(6): qdot_int[idof] = np.trapz(np.arange(0, it + 1, 1)*dt, qdot[0:it, idof]) return np.dot(K, qdot_int) def change_of_to_sharpy(matrix_of): """ Change between frame of reference of OpenFAST and the usual one in SHARPy """ sub_mat = np.array([[0., 0, 1], [0., -1, 0], [1., 0, 0]]) C_of_s = np.zeros((6,6)) C_of_s[0:3, 0:3] = sub_mat C_of_s[3:6, 3:6] = sub_mat matrix_sharpy = np.dot(C_of_s.T, np.dot(matrix_of, C_of_s)) return matrix_sharpy def rfval(num, den, z): """ Evaluate a rational function given by the coefficients of the numerator (num) and denominator (den) at z """ return np.polyval(num, z)/np.polyval(den, z) def matrix_from_rf(dict_rf, w): """ Create a matrix from the rational function approximation of each one of the elements """ H = np.zeros((6, 6)) for i in range(6): for j in range(6): pos = "%d_%d" % (i, j) H[i, j] = rfval(dict_rf[pos]['num'], dict_rf[pos]['den'], w) return H def response_freq_dep_matrix(H, omega_H, q, it_, dt): """ Compute the frequency response of a system with a transfer function depending on the frequency F(t) = H(omega) * q(t) """ it = it_ + 1 omega_fft = np.linspace(0, 1/(2*dt), it//2)[:it//2] fourier_q = fft(q[:it, :], axis=0) fourier_f = np.zeros_like(fourier_q) ndof = q.shape[1] f = np.zeros((ndof)) # Compute the constant component if type(H) is np.ndarray: interp_H = interp1d(omega_H, H, axis=0) H_omega = interp_H(omega_fft[0]) elif type(H) is tuple: H_omega = matrix_from_rf(H, omega_fft[0]) else: cout.cout_wrap(("ERROR: Not implemented response_freq_dep_matrix for type(H) %s" % type(H)), 4) fourier_f[0, :] = np.dot(H_omega, fourier_q[0, :]) # Compute the rest of the terms for iomega in range(1, omega_fft.shape[0]): # Interpolate H at omega if type(H) is np.ndarray: H_omega = interp_H(omega_fft[iomega]) elif type(H) is dict: H_omega = matrix_from_rf(H, omega_fft[iomega]) fourier_f[iomega, :] = np.dot(H_omega, fourier_q[iomega, :]) fourier_f[-iomega, :] = np.dot(H_omega, fourier_q[-iomega, :]) # Compute the inverse Fourier tranform f[:] = np.real(ifft(fourier_f, axis=0)[it_, :]) return f def compute_equiv_hd_added_mass(f, q): """ Compute the matrix H that satisfies f = Hq H represents the added mass effects so it has to be symmetric. For the OC3 platfrom the following statements hold: - z-y symmetry - Non-diagonal non-zero terms: (1,5) and (2,4). Zero-indexed """ if (q == 0).all(): return np.zeros((6,6)) q_mat = np.array([[q[0], 0, 0, 0, 0, 0], [0, q[1], 0, 0, q[5], 0], [0, q[2], 0, 0, 0, q[4]], [0, 0, q[3], 0, 0, 0], [0, 0, 0, q[4], 0, q[2]], [0, 0, 0, q[5], q[1], 0]]) hv = np.dot(np.linalg.inv(q_mat), f) H = np.array([[hv[0], 0, 0, 0, 0, 0], [0, hv[1], 0, 0, 0, hv[4]], [0, 0, hv[1], 0, hv[5], 0], [0, 0, 0, hv[2], 0, 0], [0, 0, hv[5], 0, hv[3], 0], [0, hv[4], 0, 0, 0, hv[3]]]) return H def jonswap_spectrum(Tp, Hs, w): """ This function computes the one-sided spectrum of the JONSWAP wave data [2] Jonkman, J. M. Dynamics modeling and loads analysis of an offshore floating wind turbine. 2007. NREL/TP-500-41958 """ nomega = w.shape[0] spectrum = np.zeros((nomega)) for iomega in range(nomega): # Compute the scaling factor if w[iomega] <= 2*np.pi/Tp: sigma = 0.07 else: sigma = 0.09 # Compute the peak shape parameter param = Tp/np.sqrt(Hs) if param <= 3.6: gamma = 5. elif param > 5: gamma = 1. else: gamma = np.exp(5.75 - 1.15*param) # Compute one-sided spectrum omega = w[iomega] if omega == 0: spectrum[iomega] = 0. else: param = omega*Tp/2/np.pi spectrum[iomega] = (1./2/np.pi)*(5./16)*(Hs**2*Tp)*param**(-5) spectrum[iomega] *= np.exp(-5./4*param**(-4)) spectrum[iomega] *= (1. - 0.287*np.log(gamma)) spectrum[iomega] *= gamma**np.exp(-0.5*((param - 1.)/sigma)**2) return spectrum def noise_freq_1s(w): """ Generates a frequency representation of a white noise """ sigma = 1. #/np.sqrt(2) nomega = w.shape[0] wn = np.zeros((nomega, ), dtype=np.complex) u1 = np.random.random(size=nomega) #+ 0j u2 = np.random.random(size=nomega) #+ 0j wn[0] = 0. + 0j for iomega in range(1, nomega): u1w = u1[iomega] u2w = u2[iomega] wn[iomega] = np.sqrt(-2.*np.log(u1w))*(np.cos(2*np.pi*u2w) + 1j*np.sin(2*np.pi*u2w)) return wn*sigma @generator_interface.generator class FloatingForces(generator_interface.BaseGenerator): r""" Floating forces generator Generates the forces associated the floating support of offshore wind turbines. Currently supports spar configurations. The hydrostatic forces model includes buoyancy: an initial vertical force and the restoring forces associated with heave, roll and pitch. See the implementation in [1]. The mooring model is the quasisteady implementation of Jonkman [2] .However, equation 2-37b is thought to be wrong (it is just a copy from eq 2-35b) This was corrected according to the theory review of MAP++ [3]. The default values have been obtained from the OC3 platform report [1] [1] Jonkman, J. Definition of the Floating System for Phase IV of OC3. 2010. NREL/TP-500-47535 [2] Jonkman, J. M. Dynamics modeling and loads analysis of an offshore floating wind turbine. 2007. NREL/TP-500-41958 [3] https://map-plus-plus.readthedocs.io/en/latest/theory.html (accessed on Octorber 14th, 2020) """ generator_id = 'FloatingForces' generator_classification = 'runtime' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['n_time_steps'] = 'int' settings_default['n_time_steps'] = None settings_description['n_time_steps'] = 'Number of time steps' settings_types['dt'] = 'float' settings_default['dt'] = None settings_description['dt'] = 'Time step' settings_types['water_density'] = 'float' settings_default['water_density'] = 1025 # kg/m3 settings_description['water_density'] = 'Water density' settings_types['gravity_on'] = 'bool' settings_default['gravity_on'] = True settings_description['gravity_on'] = 'Flag to include gravitational forces' settings_types['gravity'] = 'float' settings_default['gravity'] = 9.81 settings_description['gravity'] = 'Gravity' settings_types['gravity_dir'] = 'list(float)' settings_default['gravity_dir'] = [1., 0., 0.] settings_description['gravity_dir'] = 'Gravity direction' settings_types['floating_file_name'] = 'str' settings_default['floating_file_name'] = './oc3.floating.h5' settings_description['floating_file_name'] = 'File containing the information about the floating dynamics' settings_types['cd_multiplier'] = 'float' settings_default['cd_multiplier'] = 1. settings_description['cd_multiplier'] = 'Multiply the drag coefficient by this number to increase dissipation' settings_types['add_damp_diag'] = 'list(float)' settings_default['add_damp_diag'] = [0., 0., 0., 0., 0., 0.] settings_description['add_damp_diag'] = 'Diagonal terms to include in the additional damping matrix' settings_types['add_damp_ts'] = 'int' settings_default['add_damp_ts'] = 0 settings_description['add_damp_ts'] = 'Timesteps in which ``add_damp_diag`` will be used' settings_types['method_matrices_freq'] = 'str' settings_default['method_matrices_freq'] = 'constant' settings_description['method_matrices_freq'] = 'Method to compute frequency-dependent matrices' settings_options['method_matrices_freq'] = ['constant', 'rational_function'] settings_types['matrices_freq'] = 'float' settings_default['matrices_freq'] = 4.8 # Close to the upper limit defined in the oc3 report settings_description['matrices_freq'] = 'Frequency [rad/s] to interpolate frequency-dependent matrices' settings_types['steps_constant_matrices'] = 'int' settings_default['steps_constant_matrices'] = 8 settings_description['steps_constant_matrices'] = 'Time steps to compute with constant matrices computed at ``matrices_freq``. Irrelevant in ``method_matrices_freq``=``constant``' settings_types['added_mass_in_mass_matrix'] = 'bool' settings_default['added_mass_in_mass_matrix'] = True settings_description['added_mass_in_mass_matrix'] = 'Include the platform added mass in the mass matrix of the system' settings_types['concentrate_spar'] = 'bool' settings_default['concentrate_spar'] = False settings_description['concentrate_spar'] = 'Compute CD as if the spar properties were concentrated at the base' settings_types['method_wave'] = 'str' settings_default['method_wave'] = 'sin' settings_description['method_wave'] = 'Method to compute wave forces' settings_options['method_wave'] = ['sin', 'jonswap'] settings_types['wave_amplitude'] = 'float' settings_default['wave_amplitude'] = 0. settings_description['wave_amplitude'] = 'Wave amplitude. Only used in ``method_wave = sin``' settings_types['wave_freq'] = 'float' settings_default['wave_freq'] = 0. settings_description['wave_freq'] = 'Wave circular frequency [rad/s]. Only used in ``method_wave = sin``' settings_types['wave_Tp'] = 'float' settings_default['wave_Tp'] = 0. settings_description['wave_Tp'] = 'Wave peak spectral period [s]. Only used in ``method_wave = jonswap``' settings_types['wave_Hs'] = 'float' settings_default['wave_Hs'] = 0. settings_description['wave_Hs'] = 'Significant wave height [m]. Only used in ``method_wave = jonswap``' settings_types['wave_incidence'] = 'float' settings_default['wave_incidence'] = 0. settings_description['wave_incidence'] = 'Wave incidence in rad' settings_types['write_output'] = 'bool' settings_default['write_output'] = False settings_description['write_output'] = 'Write forces to an output file' settings_types['folder'] = 'str' settings_default['folder'] = 'output' settings_description['folder'] = 'Folder for the output files' settings_types['log_filename'] = 'str' settings_default['log_filename'] = 'log_floating_forces' settings_description['log_filename'] = 'Log file name to write outputs' setting_table = settings.SettingsTable() __doc__ += setting_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.in_dict = dict() self.water_density = None self.gravity = None self.gravity_dir = None self.floating_data = None self.mooring_node = None self.n_mooring_lines = None self.anchor_pos = None self.fairlead_pos_A = None self.hf_prev = list() # Previous value of hf just for initialisation self.vf_prev = list() self.buoyancy_node = None self.buoy_F0 = None self.buoy_rest_mat = None self.wave_forces_node = None self.q = None self.qdot = None self.qdotdot = None self.log_filename = None self.added_mass_in_mass_matrix = None def initialise(self, in_dict=None, data=None, restart=False): self.in_dict = in_dict settings.to_custom_types(self.in_dict, self.settings_types, self.settings_default, self.settings_options, no_ctype=True) self.settings = self.in_dict self.water_density = self.settings['water_density'] self.gravity = self.settings['gravity'] self.gravity_dir = self.settings['gravity_dir'] # Platform dofs if not restart: self.q = np.zeros((self.settings['n_time_steps'] + 1, 6)) self.qdot = np.zeros_like(self.q) self.qdotdot = np.zeros_like(self.q) else: increase_ts = self.settings['n_time_steps'] + 1 - self.q.shape[0] self.q = np.concatenate((self.q, np.zeros((increase_ts, 6))), axis=0) self.qdot = np.concatenate((self.qdot, np.zeros((increase_ts, 6))), axis=0) self.qdotdot = np.concatenate((self.qdotdot, np.zeros((increase_ts, 6))), axis=0) # Read the file with the floating information fid = h5.File(self.settings['floating_file_name'], 'r') self.floating_data = h5utils.load_h5_in_dict(fid) fid.close() # Mooringlines parameters self.mooring_node = self.floating_data['mooring']['node'] self.n_mooring_lines = self.floating_data['mooring']['n_lines'] self.anchor_pos = np.zeros((self.n_mooring_lines, 3)) self.fairlead_pos_A = np.zeros((self.n_mooring_lines, 3)) if not restart: self.hf_prev = [None]*self.n_mooring_lines self.vf_prev = [None]*self.n_mooring_lines if self.n_mooring_lines > 0: theta = 2.*np.pi/self.n_mooring_lines R = algebra.rotation3d_x(theta) self.anchor_pos[0, 0] = -self.floating_data['mooring']['anchor_depth'] self.anchor_pos[0, 2] = self.floating_data['mooring']['anchor_radius'] self.fairlead_pos_A[0, 0] = -self.floating_data['mooring']['fairlead_depth'] self.fairlead_pos_A[0, 2] = self.floating_data['mooring']['fairlead_radius'] for imoor in range(1, self.n_mooring_lines): self.anchor_pos[imoor, :] = np.dot(R, self.anchor_pos[imoor - 1, :]) self.fairlead_pos_A[imoor, :] = np.dot(R, self.fairlead_pos_A[imoor - 1, :]) # Hydrostatics self.buoyancy_node = self.floating_data['hydrostatics']['node'] self.buoy_F0 = np.zeros((6,), dtype=float) self.buoy_F0[0:3] = (self.floating_data['hydrostatics']['V0']* self.settings['water_density']* self.settings['gravity']*self.settings['gravity_dir']) self.buoy_rest_mat = self.floating_data['hydrostatics']['buoyancy_restoring_matrix'] # hydrodynamics self.cd = self.floating_data['hydrodynamics']['CD']*self.settings['cd_multiplier'] if self.settings['method_matrices_freq'] == 'constant': interp_am = interp1d(self.floating_data['hydrodynamics']['ab_freq_rads'], self.floating_data['hydrodynamics']['added_mass_matrix'], axis=0) self.hd_added_mass_const = interp_am(self.settings['matrices_freq']) interp_d = interp1d(self.floating_data['hydrodynamics']['ab_freq_rads'], self.floating_data['hydrodynamics']['damping_matrix'], axis=0) self.hd_damping_const = interp_d(self.settings['matrices_freq']) elif self.settings['method_matrices_freq'] == 'rational_function': self.hd_added_mass_const = self.floating_data['hydrodynamics']['added_mass_matrix'][-1, :, :] self.hd_damping_const = self.floating_data['hydrodynamics']['damping_matrix'][-1, :, :] self.added_mass_in_mass_matrix = self.settings['added_mass_in_mass_matrix'] if ((self.added_mass_in_mass_matrix) and (not restart)): cout.cout_wrap(("Including added mass in mass matrix"), 2) if data.structure.lumped_mass_mat is None: data.structure.lumped_mass_mat_nodes = np.array([self.buoyancy_node]) data.structure.lumped_mass_mat = np.array([self.hd_added_mass_const]) else: data.structure.lumped_mass_mat_nodes = np.concatenate((data.structure.lumped_mass_mat_nodes, np.array([self.buoyancy_node])), axis=0) data.structure.lumped_mass_mat = np.concatenate((data.structure.lumped_mass_mat, np.array([self.hd_added_mass_const])), axis=0) data.structure.add_lumped_mass_to_element(self.buoyancy_node, self.hd_added_mass_const) data.structure.generate_fortran() if self.settings['method_matrices_freq'] == 'rational_function': ninput = 6 noutput = 6 hd_K_num = [None]*noutput hd_K_den = [None]*noutput for ioutput in range(noutput): hd_K_num[ioutput] = [None]*ninput hd_K_den[ioutput] = [None]*ninput for iinput in range(ninput): pos = "%d_%d" % (ioutput, iinput) hd_K_num[ioutput][iinput] = self.floating_data['hydrodynamics']['K_rf'][pos]['num'] hd_K_den[ioutput][iinput] = self.floating_data['hydrodynamics']['K_rf'][pos]['den'] self.hd_K = TransferFunction(hd_K_num, hd_K_den) self.ab_freq_rads = self.floating_data['hydrodynamics']['ab_freq_rads'] if restart: self.x0_K.extend([None]*increase_ts) else: self.x0_K = [None]*(self.settings['n_time_steps'] + 1) self.x0_K[0] = 0. # Wave forces self.wave_forces_node = self.floating_data['wave_forces']['node'] if self.settings['method_wave'] == 'sin': interp_x1 = interp1d(self.floating_data['wave_forces']['xi_freq_rads'], self.floating_data['wave_forces']['xi'], axis=0, bounds_error=False, fill_value=(self.floating_data['wave_forces']['xi'][0, ...], self.floating_data['wave_forces']['xi'][-1, ...])) xi_matrix2 = interp_x1(self.settings['wave_freq']) interp_x2 = interp1d(self.floating_data['wave_forces']['xi_beta_deg']*deg2rad, xi_matrix2, axis=0) self.xi_interp = interp_x2(self.settings['wave_incidence']) elif self.settings['method_wave'] == 'jonswap': interp_x1 = interp1d(self.floating_data['wave_forces']['xi_beta_deg']*deg2rad, self.floating_data['wave_forces']['xi'], axis=1) xi_matrix = interp_x1(self.settings['wave_incidence']) self.freq_wave_forces_variables(self.settings['wave_Tp'], self.settings['wave_Hs'], self.settings['dt'], np.arange(self.settings['n_time_steps'] + 1)*self.settings['dt'], xi_matrix, self.floating_data['wave_forces']['xi_freq_rads']) # Log file if not os.path.exists(self.settings['folder']): os.makedirs(self.settings['folder']) folder = self.settings['folder'] + '/' + data.settings['SHARPy']['case'] + '/floatingforces/' if not os.path.exists(folder): os.makedirs(folder) self.log_filename = folder + self.settings['log_filename'] + '.h5' def write_output(self, ts, k, mooring, mooring_yaw, hydrostatic, hydrodynamic_qdot, hydrodynamic_qdotdot, hd_correct_grav, drag, waves): output = dict() output['ts'] = ts output['k'] = k output['q'] = self.q[ts, :] output['qdot'] = self.qdot[ts, :] output['qdotdot'] = self.qdotdot[ts, :] output['mooring_forces'] = mooring output['mooring_yaw'] = mooring_yaw output['hydrostatic'] = hydrostatic output['hydrodynamic_qdot'] = hydrodynamic_qdot output['hydrodynamic_qdotdot'] = hydrodynamic_qdotdot output['hydrodynamic_correct_grav'] = hd_correct_grav output['drag'] = drag output['waves'] = waves fid = h5.File(self.log_filename, 'a') group_name = "ts%d_k%d" % (ts, k) if fid.__contains__(group_name): del fid[group_name] group = fid.create_group(group_name) for key, value in output.items(): group.create_dataset(key, data=value) fid.close() debug_output = False if debug_output: print("q: ", self.q[ts, :]) print("qdot: ", self.qdot[ts, :]) print("qdotdot: ", self.qdotdot[ts, :]) print("mooring: ", mooring) print("mooring_yaw: ", mooring_yaw) print("hydrostatic: ", hydrostatic) print("hydrodynamic_qdot: ", hydrodynamic_qdot) print("hydrodynamic_qdotdot: ", hydrodynamic_qdotdot) print("hydrodynamic_correct_grav: ", hd_correct_grav) print("drag: ", drag) print("waves: ", waves) return def update_dof_vector(self, beam, struct_tstep, it, k): if True: cga = struct_tstep.cga() self.q[it, 0:3] = (np.dot(cga, struct_tstep.pos[self.buoyancy_node, :]) + struct_tstep.for_pos[0:3]) self.q[it, 3:6] = algebra.quat2euler(struct_tstep.quat) self.qdot[it, 0:3] = np.dot(cga, struct_tstep.for_vel[0:3]) self.qdot[it, 3:6] = np.dot(cga, struct_tstep.for_vel[3:6]) self.qdotdot[it, 0:3] = np.dot(cga, struct_tstep.for_acc[0:3]) self.qdotdot[it, 3:6] = np.dot(cga, struct_tstep.for_acc[3:6]) else: self.q[it, :] = self.q[it-1, :] self.qdot[it, :] = self.qdot[it-1, :] self.qdotdot[it, :] = self.qdotdot[it-1, :] return def freq_wave_forces_variables(self, Tp, Hs, dt, time, xi, w_xi): """ Compute the frequency arrays needed for wave forces """ # Compute time and frequency discretisations ntime_steps = time.shape[0] nomega = ntime_steps//2 + 1 w = np.zeros((nomega)) w_temp = np.fft.fftfreq(ntime_steps, d=dt)*2.*np.pi w[:ntime_steps//2] = w_temp[:ntime_steps//2] if ntime_steps%2 == 0: w[-1] = -1*w_temp[ntime_steps//2] else: w[-1] = w_temp[ntime_steps//2] nomega_2s = ntime_steps # Compute the one-sided spectrums noise_freq = noise_freq_1s(w) jonswap_1s = jonswap_spectrum(Tp, Hs, w)*2.*np.pi jonswap_freq = np.sqrt(2*ntime_steps/dt*jonswap_1s/2) + 0j jonswap_freq[0] = np.sqrt(ntime_steps/dt*jonswap_1s[0]/2) + 0j # The DC values does not have the 2 self.noise_freq = noise_freq self.jonswap_freq = jonswap_freq self.omega = w self.nomega_2s = nomega_2s self.xi_interp = np.zeros((nomega, 6), dtype=np.complex) for iomega in range(nomega): for idim in range(6): self.xi_interp[iomega, idim] = np.interp(self.omega[iomega], w_xi, xi[:, idim]) def time_wave_forces(self, dx, grav): """ Compute the time evolution of wave forces """ # Compute the two-sided spectrum force_freq_2s = np.zeros((self.nomega_2s, 6), dtype=np.complex) for idim in range(6): for iomega in range(self.omega.shape[0]): k = self.omega[iomega]**2/grav force_freq_2s[iomega, idim] = (self.noise_freq[iomega]* 0.5*self.jonswap_freq[iomega]* self.xi_interp[iomega, idim]* np.exp(-1j*k*dx)) if not iomega == 0: if not ((iomega == self.omega.shape[0] - 1) and (self.nomega_2s%2 == 0)): force_freq_2s[-iomega, idim] = (np.conj(self.noise_freq[iomega])* 0.5*self.jonswap_freq[iomega]* np.conj(self.xi_interp[iomega, idim])* np.exp(1j*k*dx)) # Compute the inverse Fourier transform force_waves = ifft(force_freq_2s, axis=0) return np.real(force_waves) def generate(self, params): # Renaming for convenience data = params['data'] struct_tstep = params['struct_tstep'] aero_tstep = params['aero_tstep'] k = params['fsi_substep'] # Update dof vector self.update_dof_vector(data.structure, struct_tstep, data.ts, k) # Mooring lines mooring_forces = np.zeros((self.n_mooring_lines, 2)) cga = struct_tstep.cga() ielem, inode_in_elem = data.structure.node_master_elem[self.mooring_node] cab = algebra.crv2rotation(struct_tstep.psi[ielem, inode_in_elem]) cbg = np.dot(cab.T, cga.T) moor_force_out = 0. moor_mom_out = 0. for imoor in range(self.n_mooring_lines): fairlead_pos_G = (np.dot(cga, self.fairlead_pos_A[imoor, :]) + struct_tstep.for_pos[0:3]) fl_to_anchor_G = self.anchor_pos[imoor, :] - fairlead_pos_G xf = np.sqrt(fl_to_anchor_G[1]**2 + fl_to_anchor_G[2]**2) zf = np.abs(fl_to_anchor_G[0]) hf, vf = quasisteady_mooring(xf, zf, self.floating_data['mooring']['unstretched_length'], self.floating_data['mooring']['apparent_weight'], self.floating_data['mooring']['EA'], self.floating_data['mooring']['seabed_drag_coef'], hf0=self.hf_prev[imoor], vf0=self.vf_prev[imoor]) mooring_forces[imoor, :] = np.array([hf, vf]) # Save the results to initialise the computation in the next time step self.hf_prev[imoor] = hf + 0. self.vf_prev[imoor] = vf + 0. # Convert to the adequate reference system horizontal_unit_vec = algebra.unit_vector(fl_to_anchor_G) horizontal_unit_vec[0] = 0. horizontal_unit_vec = algebra.unit_vector(horizontal_unit_vec) force_fl = hf*horizontal_unit_vec + vf*np.array([-1., 0., 0.]) # Move the forces to the mooring node force_cl = np.zeros((6,)) force_cl[0:3] = force_fl mooring_node_pos_G = (np.dot(cga, struct_tstep.pos[self.mooring_node, :]) + struct_tstep.for_pos[0:3]) r_fairlead_G = fairlead_pos_G - mooring_node_pos_G force_cl[3:6] = np.cross(r_fairlead_G, force_fl) struct_tstep.runtime_unsteady_forces[self.mooring_node, 0:3] += np.dot(cbg, force_cl[0:3]) struct_tstep.runtime_unsteady_forces[self.mooring_node, 3:6] += np.dot(cbg, force_cl[3:6]) # Yaw moment generated by the mooring system yaw = np.array([self.q[data.ts, 3], 0., 0.]) mooring_yaw = -self.floating_data['mooring']['yaw_spring_stif']*yaw struct_tstep.runtime_unsteady_forces[self.mooring_node, 3:6] += np.dot(cbg, mooring_yaw) # Hydrostatic model ielem, inode_in_elem = data.structure.node_master_elem[self.buoyancy_node] cab = algebra.crv2rotation(struct_tstep.psi[ielem, inode_in_elem]) cbg = np.dot(cab.T, cga.T) hs_f_g = - np.dot(self.buoy_rest_mat, self.q[data.ts, :]) add_damp = self.floating_data['hydrodynamics']['additional_damping'].copy() if data.ts < self.settings['add_damp_ts']: add_damp += np.diag(self.settings['add_damp_diag']) hd_f_qdot_g = -np.dot(add_damp, self.qdot[data.ts, :]) if ((self.settings['method_matrices_freq'] == 'constant') or (data.ts < self.settings['steps_constant_matrices'])): hd_f_qdot_g -= np.dot(self.hd_damping_const, self.qdot[data.ts, :]) hd_f_qdotdot_g = -np.dot(self.hd_added_mass_const, self.qdotdot[data.ts, :]) elif self.settings['method_matrices_freq'] == 'rational_function': # Damping (T, yout, xout) = forced_response(self.hd_K, T=[0, self.settings['dt']], U=self.qdot[data.ts-1:data.ts+1, :].T, X0=self.x0_K[data.ts-1]) # transpose=True) self.x0_K[data.ts] = xout[:, 1] hd_f_qdot_g -= yout[:, 1] hd_f_qdotdot_g = np.zeros((6)) else: cout.cout_wrap(("ERROR: Unknown method_matrices_freq %s" % self.settings['method_matrices_freq']), 4) # Correct gravity forces if needed if self.added_mass_in_mass_matrix and self.settings['gravity_on']: # Correct unreal gravity forces from added mass gravity_b = np.zeros((6,),) gravity_b[0:3] = np.dot(cbg, -self.settings['gravity_dir'])*self.settings['gravity'] hd_correct_grav = -np.dot(self.hd_added_mass_const, gravity_b) struct_tstep.runtime_steady_forces[self.buoyancy_node, :] += hd_correct_grav else: hd_correct_grav = np.zeros((6)) struct_tstep.runtime_steady_forces[self.buoyancy_node, 0:3] += np.dot(cbg, self.buoy_F0[0:3] + hs_f_g[0:3]) struct_tstep.runtime_steady_forces[self.buoyancy_node, 3:6] += np.dot(cbg, self.buoy_F0[3:6] + hs_f_g[3:6]) struct_tstep.runtime_unsteady_forces[self.buoyancy_node, 0:3] += np.dot(cbg, hd_f_qdot_g[0:3] + hd_f_qdotdot_g[0:3]) struct_tstep.runtime_unsteady_forces[self.buoyancy_node, 3:6] += np.dot(cbg, hd_f_qdot_g[3:6] + hd_f_qdotdot_g[3:6]) # Nonlinear drag coefficeint if self.settings['concentrate_spar']: spar_node_pos = np.zeros((100, 3)) + struct_tstep.pos[self.floating_data['hydrodynamics']['CD_node'], :] spar_node_pos[:, 0] += np.linspace(-self.floating_data['hydrodynamics']['CD_spar_length'], 0, 100) spar_node_pos_dot = np.zeros((100, 3)) else: spar_node_pos = struct_tstep.pos[self.floating_data['hydrodynamics']['CD_first_node'] : self.floating_data['hydrodynamics']['CD_last_node'] + 1, :] spar_node_pos_dot = struct_tstep.pos_dot[self.floating_data['hydrodynamics']['CD_first_node'] : self.floating_data['hydrodynamics']['CD_last_node'] + 1, :] total_drag_force = np.zeros((6)) for inode in range(len(spar_node_pos)): if self.settings['concentrate_spar']: ielem, inode_in_elem = data.structure.node_master_elem[self.floating_data['hydrodynamics']['CD_node']] else: ielem, inode_in_elem = data.structure.node_master_elem[inode + self.floating_data['hydrodynamics']['CD_first_node']] cab = algebra.crv2rotation(struct_tstep.psi[ielem, inode_in_elem]) cbg = np.dot(cab.T, cga.T) if inode == 0: delta_x = 0.5*np.linalg.norm(spar_node_pos[1, :] - spar_node_pos[0, :]) elif inode == len(spar_node_pos) - 1: delta_x = 0.5*np.linalg.norm(spar_node_pos[inode, :] - spar_node_pos[inode - 1, :]) else: delta_x = 0.5*np.linalg.norm(spar_node_pos[inode + 1, :] - spar_node_pos[inode - 1, :]) vel_a = (struct_tstep.for_vel[0:3] + np.cross(struct_tstep.for_vel[3:6], spar_node_pos[inode, :]) + spar_node_pos_dot[inode, :]) # Remove velocity along the x axis vel_b = np.dot(cab.T, vel_a) vel_b[0] = 0. vel_g = np.dot(cbg.T, vel_b) drag_force = (-0.5*self.water_density*np.linalg.norm(vel_g)*vel_g*delta_x* self.floating_data['hydrodynamics']['spar_diameter']* self.cd) if self.settings['concentrate_spar']: r = spar_node_pos[inode, :] - struct_tstep.pos[self.floating_data['hydrodynamics']['CD_node'], :] else: r = spar_node_pos[inode, :] - struct_tstep.pos[self.floating_data['hydrostatics']['node'], :] drag_moment = np.cross(r, drag_force) total_drag_force[0:3] += drag_force total_drag_force[3:6] += drag_moment if self.settings['concentrate_spar']: struct_tstep.runtime_unsteady_forces[self.floating_data['hydrodynamics']['CD_node'], 0:3] += np.dot(cbg, drag_force) struct_tstep.runtime_unsteady_forces[self.floating_data['hydrodynamics']['CD_node'], 3:6] += np.dot(cbg, drag_moment) else: struct_tstep.runtime_unsteady_forces[inode + self.floating_data['hydrodynamics']['CD_first_node'], 0:3] += np.dot(cbg, drag_force) # Wave loading ielem, inode_in_elem = data.structure.node_master_elem[self.wave_forces_node] cab = algebra.crv2rotation(struct_tstep.psi[ielem, inode_in_elem]) cbg = np.dot(cab.T, cga.T) wave_node_pos = struct_tstep.for_pos[0:3] + np.dot(cga, struct_tstep.pos[self.wave_forces_node, :]) dx = (wave_node_pos[1]*np.sin(self.settings['wave_incidence']) + wave_node_pos[2]*np.cos(self.settings['wave_incidence'])) wave_forces_g = np.zeros((6)) if self.settings['method_wave'] == 'sin': phase = (self.settings['wave_freq']*data.ts*self.settings['dt'] + dx*self.settings['wave_freq']**2/self.settings['gravity']) for idim in range(6): wave_forces_g[idim] = np.real(self.settings['wave_amplitude']*self.xi_interp[idim]*(np.cos(phase) + 1j*np.sin(phase))) elif self.settings['method_wave'] == 'jonswap': wave_forces_g = self.time_wave_forces(dx, self.settings['gravity'])[data.ts, :] struct_tstep.runtime_unsteady_forces[self.wave_forces_node, 0:3] += np.dot(cbg, wave_forces_g[0:3]) struct_tstep.runtime_unsteady_forces[self.wave_forces_node, 3:6] += np.dot(cbg, wave_forces_g[3:6]) # Write output if self.settings['write_output']: self.write_output(data.ts, k, mooring_forces, mooring_yaw, hs_f_g, hd_f_qdot_g, hd_f_qdotdot_g, hd_correct_grav, total_drag_force, wave_forces_g) ================================================ FILE: sharpy/generators/gridbox.py ================================================ import numpy as np import sharpy.utils.generator_interface as generator_interface import sharpy.utils.settings as settings import numpy as np import ctypes as ct import copy @generator_interface.generator class GridBox(generator_interface.BaseGenerator): """ GridBox Generatex a grid within a box to be used to generate the flow field during the postprocessing """ generator_id = 'GridBox' generator_classification = 'utils' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['coords_0'] = 'list(float)' settings_default['coords_0'] = [0., 0., 0.] settings_description['coords_0'] = 'First bounding box corner' settings_types['coords_1'] = 'list(float)' settings_default['coords_1'] = [10., 0., 10.] settings_description['coords_1'] = 'Second bounding box corner' settings_types['spacing'] = 'list(float)' settings_default['spacing'] = [1., 1., 1.] settings_description['spacing'] = 'Spacing parameters of the bbox' settings_types['moving'] = 'bool' settings_default['moving'] = False settings_description['moving'] = 'If ``True``, the box moves with the body frame of reference. It does not rotate with it, though' settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.in_dict = dict() self.settings = None def initialise(self, in_dict, restart=False): self.in_dict = in_dict settings.to_custom_types(self.in_dict, self.settings_types, self.settings_default) self.settings = copy.deepcopy(self.in_dict) self.x0 = self.in_dict['coords_0'][0] self.y0 = self.in_dict['coords_0'][1] self.z0 = self.in_dict['coords_0'][2] self.x1 = self.in_dict['coords_1'][0] self.y1 = self.in_dict['coords_1'][1] self.z1 = self.in_dict['coords_1'][2] self.dx = self.in_dict['spacing'][0] self.dy = self.in_dict['spacing'][1] self.dz = self.in_dict['spacing'][2] def generate(self, params): if self.settings['moving']: for_pos = params['for_pos'] else: for_pos = np.zeros((3,)) nx = np.abs(int((self.x1-self.x0)/self.dx + 1)) ny = np.abs(int((self.y1-self.y0)/self.dy + 1)) nz = np.abs(int((self.z1-self.z0)/self.dz + 1)) xarray = np.linspace(self.x0, self.x1, nx) + for_pos[0] yarray = np.linspace(self.y0, self.y1, ny) + for_pos[1] zarray = np.linspace(self.z0, self.z1, nz) + for_pos[2] grid = [] for iz in range(nz): grid.append(np.zeros((3, nx, ny), dtype=ct.c_double)) for ix in range(nx): for iy in range(ny): grid[iz][0, ix, iy] = xarray[ix] grid[iz][1, ix, iy] = yarray[iy] grid[iz][2, ix, iy] = zarray[iz] from tvtk.api import tvtk vtk_info = tvtk.RectilinearGrid() vtk_info.dimensions = np.array([nx, ny, nz], dtype=int) vtk_info.x_coordinates = xarray vtk_info.y_coordinates = yarray vtk_info.z_coordinates = zarray return vtk_info, grid ================================================ FILE: sharpy/generators/gustvelocityfield.py ================================================ """Gust Velocity Field Generators These generators are used to create a gust velocity field. :class:`.GustVelocityField` is the main class that should be parsed as the ``velocity_field_input`` to the desired aerodynamic solver. The remaining classes are the specific gust profiles and parsed as ``gust_shape``. Examples: The typical input to the aerodynamic solver settings would therefore read similar to: >>> aero_settings = {'': '', >>> 'velocity_field_generator': 'GustVelocityField', >>> 'velocity_field_input': {'u_inf': 1, >>> 'gust_shape': '', >>> 'gust_parameters': ''}} """ import numpy as np from abc import ABCMeta import sharpy.utils.generator_interface as generator_interface import sharpy.utils.settings as settings from scipy.interpolate import interp1d dict_of_gusts = {} doc_settings_description = 'The ``GustVelocityField`` generator takes the following settings as a dictionary assigned' \ 'to ``gust_parameters``.' def gust(arg): global dict_of_gusts try: arg.gust_id except AttributeError: raise AttributeError('Class defined as gust has no gust_id attribute') dict_of_gusts[arg.gust_id] = arg return arg # @gust class BaseGust(metaclass=ABCMeta): settings_types = dict() settings_default = dict() settings_description = dict() def __init__(self): self.settings = dict() self._u_inf = None self._u_inf_direction = None @property def u_inf(self): return self._u_inf @u_inf.setter def u_inf(self, value): self._u_inf = value @property def u_inf_direction(self): return self._u_inf_direction @u_inf_direction.setter def u_inf_direction(self, value): self._u_inf_direction = value @gust class one_minus_cos(BaseGust): r""" One minus cos gust (single bump) .. math:: U_z = \frac{u_{de}}{2}\left[1-\cos\left(\frac{2\pi x}{S}\right)\right] This gust can be used by using the setting ``gust_shape = '1-cos'`` in :class:`.GustVelocityField`. """ gust_id = '1-cos' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['gust_length'] = 'float' settings_default['gust_length'] = 0.0 settings_description['gust_length'] = 'Length of gust, :math:`S`.' settings_types['gust_intensity'] = 'float' settings_default['gust_intensity'] = 0.0 settings_description['gust_intensity'] = 'Intensity of the gust :math:`u_{de}`.' settings_types['gust_component'] = 'int' settings_default['gust_component'] = 2 settings_description['gust_component'] = 'Component of the gust velocity in the G-frame (x,y,z)->(0,1,2).' setting_table = settings.SettingsTable() __doc__ += setting_table.generate(settings_types, settings_default, settings_description, header_line=doc_settings_description) def initialise(self, in_dict, restart=False): self.settings = in_dict settings.to_custom_types(self.settings, self.settings_types, self.settings_default) def gust_shape(self, x, y, z, time=0): gust_length = self.settings['gust_length'] gust_intensity = self.settings['gust_intensity'] vel = np.zeros((3,)) if x > 0.0 or x < -gust_length: return vel vel[self.settings['gust_component']] = (1.0 - np.cos(2.0 * np.pi * x / gust_length)) * gust_intensity * 0.5 return vel @gust class DARPA(BaseGust): r""" Discrete, non-uniform span model .. math:: U_z = \frac{u_{de}}{2}\left[1-\cos\left(\frac{2\pi x}{S}\right)\right]\cos\left(\frac{\pi y}{b}\right) This gust can be used by using the setting ``gust_shape = 'DARPA'`` in :class:`.GustVelocityField`. """ gust_id = 'DARPA' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['gust_length'] = 'float' settings_default['gust_length'] = 0.0 settings_description['gust_length'] = 'Length of gust' settings_types['gust_intensity'] = 'float' settings_default['gust_intensity'] = 0.0 settings_description['gust_intensity'] = 'Intensity of the gust' settings_types['span'] = 'float' settings_default['span'] = 0. settings_description['span'] = 'Wing span' settings_types['gust_component'] = 'int' settings_default['gust_component'] = 2 settings_description['gust_component'] = 'Component of the gust velocity in the G-frame (x,y,z)->(0,1,2).' setting_table = settings.SettingsTable() __doc__ += setting_table.generate(settings_types, settings_default, settings_description, header_line=doc_settings_description) def initialise(self, in_dict, restart=False): self.settings = in_dict settings.to_custom_types(self.settings, self.settings_types, self.settings_default) def gust_shape(self, x, y, z, time=0): gust_length = self.settings['gust_length'] gust_intensity = self.settings['gust_intensity'] span = self.settings['span'] vel = np.zeros((3,)) if x > 0.0 or x < -gust_length: return vel vel[self.settings['gust_component']] = (1.0 - np.cos(2.0 * np.pi * x / gust_length)) * gust_intensity * 0.5 vel[self.settings['gust_component']] *= -np.cos(y / span * np.pi) return vel @gust class continuous_sin(BaseGust): r""" Continuous sinusoidal gust model .. math:: U_z = \frac{u_{de}}{2}\sin\left(\frac{2\pi x}{S}\right) This gust can be used by using the setting ``gust_shape = 'continuous_sin'`` in :class:`GustVelocityField`. """ gust_id = 'continuous_sin' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['gust_length'] = 'float' settings_default['gust_length'] = 0.0 settings_description['gust_length'] = 'Length of gust' settings_types['gust_intensity'] = 'float' settings_default['gust_intensity'] = 0.0 settings_description['gust_intensity'] = 'Intensity of the gust' settings_types['gust_component'] = 'int' settings_default['gust_component'] = 2 settings_description['gust_component'] = 'Component of the gust velocity in the G-frame (x,y,z)->(0,1,2).' setting_table = settings.SettingsTable() __doc__ += setting_table.generate(settings_types, settings_default, settings_description, header_line=doc_settings_description) def initialise(self, in_dict, restart=False): self.settings = in_dict settings.to_custom_types(self.settings, self.settings_types, self.settings_default) def gust_shape(self, x, y, z, time=0): gust_length = self.settings['gust_length'] gust_intensity = self.settings['gust_intensity'] vel = np.zeros((3,)) if x > 0.0: return vel vel[self.settings['gust_component']] = 0.5 * gust_intensity * np.sin(2 * np.pi * x / gust_length) return vel @gust class time_varying_global(BaseGust): r""" Similar to the previous one but the velocity changes instanteneously in the whole flow field. It is not fed into the solid. This gust can be used by using the setting ``gust_shape = 'time varying global'`` in :class:`GustVelocityField`. """ gust_id = 'time varying global' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['file'] = 'str' settings_default['file'] = '' settings_description['file'] = 'File with the information (only for time varying)' settings_types['gust_component'] = ['list(int)', 'int'] settings_default['gust_component'] = [0, 1, 2] settings_description['gust_component'] = 'Component of the gust velocity in the G-frame to be considered (x,y,z)->(0,1,2).' setting_table = settings.SettingsTable() __doc__ += setting_table.generate(settings_types, settings_default, settings_description, header_line=doc_settings_description) def __init__(self): super().__init__() self.file_info = None self.list_interpolated_velocity_field_functions = [] def initialise(self, in_dict, restart=False): self.settings = in_dict settings.to_custom_types(self.settings, self.settings_types, self.settings_default) self.file_info = np.loadtxt(self.settings['file']) self.initialise_interpolation_functions() def initialise_interpolation_functions(self): for idim in self.settings['gust_component']: self.list_interpolated_velocity_field_functions.append(interp1d(self.file_info[:, 0], self.file_info[:, idim+1], bounds_error=False,fill_value="extrapolate")) def gust_shape(self, x, y, z, time=0): vel = np.zeros((3,)) for counter, idim in enumerate(self.settings['gust_component']): vel[idim] = self.list_interpolated_velocity_field_functions[counter](time) return vel @gust class time_varying(time_varying_global): r""" The inflow velocity changes with time but it is uniform in space. It is read from a 4 column file: .. math:: time[s] \Delta U_x \Delta U_y \Delta U_z This gust can be used by using the setting ``gust_shape = 'time varying'`` in :class:.`GustVelocityField`. """ gust_id = 'time varying' def initialise_interpolation_functions(self): for idim in self.settings['gust_component']: self.list_interpolated_velocity_field_functions.append(interp1d(-self.file_info[::-1, 0] * self.u_inf, self.file_info[::-1, idim+1], bounds_error=False,fill_value="extrapolate")) def gust_shape(self, x, y, z, time=0): vel = np.zeros((3,)) d = np.dot(np.array([x, y, z]), self.u_inf_direction) if d <= 0.0: for counter, idim in enumerate(self.settings['gust_component']): vel[idim] = self.list_interpolated_velocity_field_functions[counter](d) return vel @gust class span_sine(BaseGust): r""" This gust can be used by using the setting ``gust_shape = 'span sine'`` in :class:`GustVelocityField`. """ gust_id = 'span sine' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['gust_intensity'] = 'float' settings_default['gust_intensity'] = 0.0 settings_description['gust_intensity'] = 'Intensity of the gust' settings_types['span'] = 'float' settings_default['span'] = 0. settings_description['span'] = 'Wing span' settings_types['periods_per_span'] = 'int' settings_default['periods_per_span'] = 1 settings_description['periods_per_span'] = 'Number of times that the sine is repeated in the span of the wing' settings_types['perturbation_dir'] = 'list(float)' settings_default['perturbation_dir'] = np.array([0, 0, 1.]) settings_description['perturbation_dir'] = 'Direction in which the perturbation will be applied in A FoR' settings_types['span_dir'] = 'list(float)' settings_default['span_dir'] = np.array([0, 1., 0]) settings_description['span_dir'] = 'Direction of the span of the wing' settings_types['span_with_gust'] = 'float' settings_default['span_with_gust'] = 0. settings_description['span_with_gust'] = 'Extension of the span to which the gust will be applied' setting_table = settings.SettingsTable() __doc__ += setting_table.generate(settings_types, settings_default, settings_description, header_line=doc_settings_description) def initialise(self, in_dict, restart=False): self.settings = in_dict settings.to_custom_types(self.settings, self.settings_types, self.settings_default) if self.settings['span_with_gust'] == 0: self.settings['span_with_gust'] = self.settings['span'] def gust_shape(self, x, y, z, time=0): d = np.dot(np.array([x, y, z]), self.settings['span_dir']) if np.abs(d) <= self.settings['span_with_gust'] / 2: vel = 0.5 * self.settings['gust_intensity'] * np.sin( d * 2. * np.pi / (self.settings['span'] / self.settings['periods_per_span'])) else: vel = np.zeros((3,)) return vel * self.settings['perturbation_dir'] @generator_interface.generator class GustVelocityField(generator_interface.BaseGenerator): r""" Gust Velocity Field Generator ``GustVelocityField`` is a class inherited from ``BaseGenerator`` The ``GustVelocityField`` class generates a gust profile velocity field, and the profile has the characteristics specified by the user. To call this generator, the ``generator_id = GustVelocityField`` shall be used. This is parsed as the value for the ``velocity_field_generator`` key in the desired aerodynamic solver's settings. Notation: :math:`u_{de}` is the gust intensity, :math:`S` is the gust length and :math:`b` is the wing span. :math:`x` and :math:`y` refer to the chordwise and spanwise distance penetrated into the gust, respectively. Several gust profiles are available. Your chosen gust profile should be parsed to ``gust_shape`` and the corresponding settings as a dictionary to ``gust_parameters``. """ generator_id = 'GustVelocityField' generator_classification = 'velocity-field' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['u_inf'] = 'float' settings_default['u_inf'] = None settings_description['u_inf'] = 'Free stream velocity' settings_types['u_inf_direction'] = 'list(float)' settings_default['u_inf_direction'] = [1., 0., 0.] settings_description['u_inf_direction'] = 'Free stream velocity relative component' settings_types['offset'] = 'float' settings_default['offset'] = 0.0 settings_description['offset'] = 'Spatial offset of the gust with respect to origin' settings_types['relative_motion'] = 'bool' settings_default['relative_motion'] = False settings_description['relative_motion'] = 'If true, the gust is convected with u_inf' settings_types['gust_shape'] = 'str' settings_default['gust_shape'] = None settings_description['gust_shape'] = 'Gust profile shape' settings_types['gust_parameters'] = 'dict' settings_default['gust_parameters'] = dict() settings_description['gust_parameters'] = 'Dictionary of parameters specific of the gust_shape selected' setting_table = settings.SettingsTable() __doc__ += setting_table.generate(settings_types, settings_default, settings_description, header_line='This generator takes the following settings') def __init__(self): self.settings = dict() self.gust = None self.u_inf = None self.u_inf_direction = None self.implemented_gusts = dict_of_gusts def initialise(self, in_dict, restart=False): self.settings = in_dict settings.to_custom_types(self.settings, self.settings_types, self.settings_default) # check that the gust type is valid if not (self.settings['gust_shape'] in self.implemented_gusts): raise AttributeError('The gust shape ' + self.settings['gust_shape'] + ' is not implemented') self.gust = dict_of_gusts[self.settings['gust_shape']]() self.u_inf = self.settings['u_inf'] self.u_inf_direction = self.settings['u_inf_direction'] # set gust properties self.gust.u_inf = self.u_inf self.gust.u_inf_direction = self.u_inf_direction self.gust.initialise(self.settings['gust_parameters'], restart=restart) def generate(self, params, uext): zeta = params['zeta'] override = params['override'] if self.settings['gust_shape'] == 'span sine': ts = 0 t = 0 dt = 0 else: ts = params['ts'] t = params['t'] dt = params['dt'] for_pos = params['for_pos'][0:3] for i_surf in range(len(zeta)): if override: uext[i_surf].fill(0.0) for i in range(zeta[i_surf].shape[1]): for j in range(zeta[i_surf].shape[2]): total_offset_val = self.settings['offset'] if self.settings['relative_motion']: uext[i_surf][:, i, j] += self.settings['u_inf'] * self.settings['u_inf_direction'] total_offset_val -= self.settings['u_inf'] * t total_offset = total_offset_val * self.settings['u_inf_direction'] + for_pos uext[i_surf][:, i, j] += self.gust.gust_shape( zeta[i_surf][0, i, j] + total_offset[0], zeta[i_surf][1, i, j] + total_offset[1], zeta[i_surf][2, i, j] + total_offset[2], t ) ================================================ FILE: sharpy/generators/helicoidalwake.py ================================================ import numpy as np import sharpy.utils.generator_interface as generator_interface import sharpy.utils.settings as settings import sharpy.utils.algebra as algebra @generator_interface.generator class HelicoidalWake(generator_interface.BaseGenerator): r""" Helicoidal wake shape generator ``HelicoidalWake`` class inherited from ``BaseGenerator`` The object creates a helicoidal wake shedding from the trailing edge based on the time step ``dt``, the incoming velocity magnitude ``u_inf``, direction ``u_inf_direction``, the rotation velocity ``rotation_velocity`` and the shear parameters """ generator_id = 'HelicoidalWake' generator_classification = 'wake' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['u_inf'] = 'float' settings_default['u_inf'] = None settings_description['u_inf'] = 'Free stream velocity magnitude' settings_types['u_inf_direction'] = 'list(float)' settings_default['u_inf_direction'] = None settings_description['u_inf_direction'] = '``x``, ``y`` and ``z`` relative components of the free stream velocity' settings_types['dt'] = 'float' settings_default['dt'] = None settings_description['dt'] = 'Time step' settings_types['dphi1'] = 'float' settings_default['dphi1'] = -1.0 settings_description['dphi1'] = 'Size of the first wake panel in radians' settings_types['ndphi1'] = 'int' settings_default['ndphi1'] = 1 settings_description['ndphi1'] = 'Number of panels with size ``dphi1``' settings_types['r'] = 'float' settings_default['r'] = 1. settings_description['r'] = 'Growth rate after ``ndphi1`` panels' settings_types['dphimax'] = 'float' settings_default['dphimax'] = -1.0 settings_description['dphimax'] = 'Maximum panel size in radians' # Shear parameters settings_types['shear_direction'] = 'list(float)' settings_default['shear_direction'] = np.array([1.0, 0, 0]) settings_description['shear_direction'] = '``x``, ``y`` and ``z`` relative components of the direction along which shear applies' settings_types['shear_exp'] = 'float' settings_default['shear_exp'] = 0. settings_description['shear_exp'] = 'Exponent of the shear law' settings_types['h_ref'] = 'float' settings_default['h_ref'] = 1. settings_description['h_ref'] = 'Reference height at which ``u_inf`` is defined' settings_types['h_corr'] = 'float' settings_default['h_corr'] = 1. settings_description['h_corr'] = 'Height to correct the shear law' # Rotation settings_types['rotation_velocity'] = 'list(float)' settings_default['rotation_velocity'] = None settings_description['rotation_velocity'] = 'Rotation velocity' setting_table = settings.SettingsTable() __doc__ += setting_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.in_dict = dict() self.u_inf = 0. self.u_inf_direction = None self.rotation_velocity = None self.dt = None self.dphi1 = None self.ndphi1 = None self.r = None self.dphimax = None self.shear_direction = None self.shear_exp = None self.h_ref = None self.h_corr = None def initialise(self, data, in_dict, restart=False): self.in_dict = in_dict settings.to_custom_types(self.in_dict, self.settings_types, self.settings_default, no_ctype=True) self.u_inf = self.in_dict['u_inf'] self.u_inf_direction = self.in_dict['u_inf_direction'] self.rotation_velocity = self.in_dict['rotation_velocity'] self.dt = self.in_dict['dt'] if self.in_dict['dphi1'] == -1: self.dphi1 = np.linalg.norm(self.rotation_velocity)*self.dt else: self.dphi1 = self.in_dict['dphi1'] self.ndphi1 = self.in_dict['ndphi1'] self.r = self.in_dict['r'] if self.in_dict['dphimax'] == -1: self.dphimax = self.dphi1 else: self.dphimax = self.in_dict['dphimax'] self.shear_direction = self.in_dict['shear_direction'] self.shear_exp = self.in_dict['shear_exp'] self.h_ref = self.in_dict['h_ref'] self.h_corr = self.in_dict['h_corr'] def generate(self, params): # Renaming for convenience zeta = params['zeta'] zeta_star = params['zeta_star'] gamma = params['gamma'] gamma_star = params['gamma_star'] dist_to_orig = params['dist_to_orig'] nsurf = len(zeta) for isurf in range(nsurf): M, N = zeta_star[isurf][0, :, :].shape angle = 0. for i in range(M): # Compute the step in azimuthal angle angle -= self.get_dphi(i, self.dphi1, self.ndphi1, self.r, self.dphimax) delta_t = -angle/np.linalg.norm(self.rotation_velocity) rot = algebra.rotation_matrix_around_axis(algebra.unit_vector(self.rotation_velocity), angle) for j in range(N): # Define the helicoidal aux_zeta_TE = zeta[isurf][:, -1, j] - (self.h_ref - self.h_corr)*self.shear_direction aux_zeta_TE = np.dot(rot, aux_zeta_TE) + (self.h_ref - self.h_corr)*self.shear_direction # Translate according to u_inf depending on the height h = np.dot(aux_zeta_TE, self.shear_direction) + self.h_corr zeta_star[isurf][:, i, j] = aux_zeta_TE + self.u_inf*self.u_inf_direction*(h/self.h_ref)**self.shear_exp*delta_t # zeta_star[isurf][:, i, j] = zeta[isurf][:, -1, j] + self.u_inf*self.u_inf_direction*self.dt*i # print(zeta_star[isurf][:, i, j]) gamma[isurf] *= 0. gamma_star[isurf] *= 0. for isurf in range(nsurf): M, N = zeta_star[isurf][0, :, :].shape dist_to_orig[isurf][0, :] = 0. for j in range(0, N): for i in range(1, M): dist_to_orig[isurf][i, j] = (dist_to_orig[isurf][i - 1, j] + np.linalg.norm(zeta_star[isurf][:, i, j] - zeta_star[isurf][:, i - 1, j])) dist_to_orig[isurf][:, j] /= dist_to_orig[isurf][-1, j] @staticmethod def get_dphi(i, dphi1, ndphi1, r, dphimax): if i == 0: dphi = 0. elif i <= ndphi1: dphi = dphi1 else: dphi = dphi1*r**(i - ndphi1) dphi = min(dphi, dphimax) return dphi ================================================ FILE: sharpy/generators/modifystructure.py ================================================ import numpy as np import sharpy.utils.generator_interface as generator_interface import sharpy.utils.settings as settings @generator_interface.generator class ModifyStructure(generator_interface.BaseGenerator): """ ``ModifyStructure`` generator. This generator allows the user to modify structural parameters at runtime. At the moment, changes to lumped masses are supported. For each lumped mass you want to change, set ``change_variable`` to ``lumped_mass``, and the ``variable_index`` and ``file_list`` as specified in :class:`~sharpy.generators.modifystructure.ChangeLumpedMass`. This generator is called at the start of each time step in ``DynamicCoupled``. """ generator_id = 'ModifyStructure' generator_classification = 'runtime' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['change_variable'] = 'list(str)' settings_default['change_variable'] = None settings_description['change_variable'] = 'Structural variable to modify' settings_options['change_variable'] = ['lumped_mass'] settings_types['variable_index'] = 'list(int)' settings_default['variable_index'] = None settings_description['variable_index'] = 'List of indices of variables to change. ' \ 'For instance the 1st lumped mass would be ``[0]``' settings_types['file_list'] = 'list(str)' settings_default['file_list'] = None settings_description['file_list'] = 'File path for each variable containing the changing info, in the ' \ 'appropriate format. See each of the allowed variables for the correct format.' def __init__(self): self.settings = None self.num_changes = None # :int number of variables that are changed self.variables = [] # :list of changed variables objects self.control_objects = {} #: dictionary of changed variable name and its control object as value def initialise(self, in_dict, **kwargs): structure = kwargs['data'].structure self.settings = in_dict settings.to_custom_types(self.settings, self.settings_types, self.settings_default, no_ctype=True, options=self.settings_options) self.num_changes = len(self.settings['change_variable']) if 'lumped_mass' in self.settings['change_variable']: self.control_objects['lumped_mass'] = LumpedMassControl() lumped_mass_variables = [] for i in range(self.num_changes): var_type = self.settings['change_variable'][i] if var_type == 'lumped_mass': variable = ChangeLumpedMass(var_index=self.settings['variable_index'][i], file=self.settings['file_list'][i]) variable.initialise(structure) self.variables.append(variable) lumped_mass_variables.append(i) self.control_objects['lumped_mass'].append(i) else: raise NotImplementedError('Variable {:s} not yet coded to be modified in runtime'.format(var_type)) try: self.control_objects['lumped_mass'].set_unchanged_vars_to_zero(structure) except KeyError: pass def generate(self, params): data = params['data'] ts = data.ts structure = data.structure for variable in self.variables: variable(structure, ts) # should only be called once per time step try: self.control_objects['lumped_mass'].execute_change(structure) except KeyError: pass # for future variables supported, have the control objects have the same signatures such that they may be # called in a loop class ChangedVariable: """ Base class of a changed variable Attributes: name (str): Name of the changed variable variable_index (int): Index of the variable to change file (str): Name of the file containing the input data (.txt) original (np.ndarray): Original value of the desired value in the appropriate format current_value (np.ndarray): Running track of the current value of the desired variable """ def __init__(self, name, var_index, file): self.name = name self.variable_index = var_index self.file = file self.original = None self.target_value = None self.current_value = None # initially def initialise(self, structure): self.get_original(structure) self.load_file() self.current_value = self.original # initially def __call__(self, structure, ts): pass def get_original(self, structure): # should be overridden for the desired variable class. it should set self.original in the appropriate format pass def load_file(self): self.target_value = np.loadtxt(self.file) class ChangeLumpedMass(ChangedVariable): """ Lumped Mass to be modified The arguments are parsed as items of the list in the settings for ``variable_index`` and ``file_list``. For those variables marked where ``change_variables = 'lumped_mass'``. The file should contain a time varying series with the following 10 columns: * Lumped mass * Lumped mass position in the material frame ``B`` (3 columns for ``xb``, ``yb`` and ``zb``) * Lumped mass inertia in the material frame ``B`` (6 columns for ``ixx``, ``iyy``, ``izz``, ``ixy``, ``ixz`` and ``iyz``. Not all 10 columns are necessary in the input file, missing columns are ignored and left unchanged. There should be one row per time step. If there are not enough entries for the number of time steps in the simulation, the changed variable value remains unchanged after all rows have been processed. Args: var_index (int): Index of lumped mass. NOT the lumped mass node. file (str): Path to file containing time history of the lumped mass. """ def __init__(self, var_index, file): super().__init__('lumped_mass', var_index=var_index, file=file) def __call__(self, structure, ts): try: # lumped masses get added (+=) at structure.lump_masses(), therefore the increment with respect to the # previous time step must be provided. This is such that this generator is backwards compatible with the # way lumped masses are assembled. delta = self.target_value[ts] - self.current_value except IndexError: # input file has less entries than the simulation time steps structure.lumped_mass[self.variable_index] = 0 structure.lumped_mass_position[self.variable_index, :] = np.zeros(3) structure.lumped_mass_inertia[self.variable_index, :, :] = np.zeros((3, 3)) else: structure.lumped_mass[self.variable_index] = delta[0] structure.lumped_mass_position[self.variable_index, :] = delta[1:4] ixx, iyy, izz, ixy, ixz, iyz = delta[-6:] inertia = np.block([[ixx, ixy, ixz], [ixy, iyy, iyz], [ixz, iyz, izz]]) structure.lumped_mass_inertia[self.variable_index, :, :] = inertia self.current_value += delta def load_file(self): """Sets ``self.target_value`` by reading from the file. If the input does not have as many columns as needed (10), these get padded with the original value such that they are not changed at runtime. """ super().load_file() n_values = len(self.original) try: n_target_values = self.target_value.shape[1] # number of columns except IndexError: n_target_values = 1 if n_target_values != n_values: # if not enough column entries pad with original values self.target_value = np.column_stack((self.target_value, self.original[-(n_values - n_target_values):] * np.ones((self.target_value.shape[0], n_values - n_target_values) ) )) def get_original(self, structure): m = structure.lumped_mass[self.variable_index] pos = structure.lumped_mass_position[self.variable_index, :] inertia = structure.lumped_mass_inertia[self.variable_index, :, :] self.original = np.hstack((m, pos, np.diag(inertia), inertia[0, 1], inertia[0, 2], inertia[1, 2])) class LumpedMassControl: """Lumped Mass Control Class This class is instantiated when at least one lumped mass is modified. It allows control over unchanged lumped masses and calls the method to execute the change. Attributes: lumped_mass_variables (list): List of integers containing the indices of the variables to change. These indices refer to the order in which they are provided in the general settings for the generator. """ def __init__(self): self.lumped_mass_variables = [] def set_unchanged_vars_to_zero(self, structure): """ Sets the lumped masses variables of unchanged lumped masses to zero. This is to avoid the lumped mass changing during execution Args: structure (sharpy.structure.models.beam.Beam): SHARPy structure object """ for i_lumped_mass in range(len(structure.lumped_mass)): if i_lumped_mass not in self.lumped_mass_variables: structure.lumped_mass[i_lumped_mass] *= 0 structure.lumped_mass_position[i_lumped_mass] *= 0 structure.lumped_mass_inertia[i_lumped_mass] *= 0 @staticmethod def execute_change(structure): """Executes the change in the lumped masses. Called only once per time step when all the changed lumped mass variables have been processed. """ # called once all variables changed structure.lump_masses() structure.generate_fortran() def append(self, i): self.lumped_mass_variables.append(i) ================================================ FILE: sharpy/generators/polaraeroforces.py ================================================ import numpy as np import os import sharpy.utils.generator_interface as generator_interface import sharpy.utils.settings as settings import sharpy.utils.algebra as algebra from sharpy.aero.utils.utils import magnitude_and_direction_of_relative_velocity, local_stability_axes, span_chord from sharpy.utils.generate_cases import get_aoacl0_from_camber @generator_interface.generator class PolarCorrection(generator_interface.BaseGenerator): r""" This generator corrects the aerodynamic forces from UVLM based on the airfoil polars provided by the user in the ``aero.h5`` file. Polars are entered for each airfoil, in a table comprising ``AoA (rad), CL, CD, CM``. This ``generator_id = 'PolarCorrection'`` and can be used in the coupled solvers through the ``correct_forces_method`` setting as: .. python:: settings = dict() # SHARPy settings settings['StaticCoupled']['correct_forces_method'] = 'PolarCorrection' settings['StaticCoupled']['correct_forces_settings'] = {'cd_from_cl': 'off', # recommended settings (default) 'correct_lift': 'off', 'moment_from_polar': 'off'} These are the steps needed to correct the forces: 1. The force coming from UVLM is divided into induced drag (parallel to the incoming flow velocity) and lift (the remaining force). If ``cd_from_cl == 'on'``. 2. The viscous drag and pitching moment are found at the computed lift coefficient. Then forces and moments are updated Else, the angle of attack is computed: 2. The angle of attack is computed based on that lift force and the angle of zero lift computed from the airfoil polar and assuming the potential flow lift curve slope of :math:`2 \pi` 3. The drag force is computed based on the angle of attack and the polars provided by the user 4. If ``correct_lift == 'on'``, the lift coefficient is also corrected with the polar data. Else, only the UVLM results are used. The pitching moment is added in a similar manner as the viscous drag. However, if ``moment_from_polar == 'on'`` and ``correct_lift == 'on'``, the total moment (the one used for the FSI) is computed just from polar data, overriding any moment computed in SHARPy. That is, the moment will include the polar pitching moment, and moments due to lift and drag computed from the polar data. """ generator_id = 'PolarCorrection' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['correct_lift'] = 'bool' settings_default['correct_lift'] = False settings_description['correct_lift'] = 'Correct lift according to the polars' settings_types['cd_from_cl'] = 'bool' settings_default['cd_from_cl'] = False settings_description['cd_from_cl'] = 'Interpolate the C_D for the given C_L, as opposed to getting the C_D from ' \ 'the section AoA.' settings_types['moment_from_polar'] = 'bool' settings_default['moment_from_polar'] = False settings_description['moment_from_polar'] = 'If ``correct_lift`` is selected, it will compute the pitching moment ' \ 'simply from polar derived data, i.e. the polars Cm and the moments' \ 'arising from the lift and drag (derived from the polar) contribution. ' \ 'Else, it will add the polar Cm to the moment already computed by ' \ 'SHARPy.' settings_types['add_rotation'] = 'bool' settings_default['add_rotation'] = False settings_description['add_rotation'] = 'Add rotation velocity. Probably needed in steady computations' settings_types['rot_vel_g'] = 'list(float)' settings_default['rot_vel_g'] = [0., 0., 0.] settings_description['rot_vel_g'] = 'Rotation velocity in G FoR. Only used if add_rotation = True' settings_types['centre_rot_g'] = 'list(float)' settings_default['centre_rot_g'] = [0., 0., 0.] settings_description['centre_rot_g'] = 'Centre of rotation in G FoR. Only used if add_rotation = True' settings_types['skip_surfaces'] = 'list(int)' settings_default['skip_surfaces'] = [] settings_description['skip_surfaces'] = 'Surfaces on which force correction is skipped.' settings_types['aoa_cl0'] = 'list(float)' settings_default['aoa_cl0'] = [] settings_description['aoa_cl0'] = 'Angle of attack for which zero lift is achieved specified in deg for each airfoil.' settings_types['write_induced_aoa'] = 'bool' settings_default['write_induced_aoa'] = False settings_description['write_induced_aoa'] = 'Write induced aoa of each node to txt file.' settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options, header_line='This generator takes in the following settings.') def __init__(self): self.folder = None self.cd_from_cl = None self.list_aoa_cl0 = None self.settings = None self.aero = None self.structure = None self.rho = None self.vortex_radius = None self.n_node = None self.flag_node_shared_by_multiple_surfaces = None def initialise(self, in_dict, **kwargs): self.settings = in_dict settings.to_custom_types(self.settings, self.settings_types, self.settings_default) self.aero = kwargs.get('aero') self.structure = kwargs.get('structure') self.n_node = self.structure.num_node self.rho = kwargs.get('rho') self.vortex_radius = kwargs.get('vortex_radius', 1e-6) self.list_aoa_cl0 = self.settings['aoa_cl0'] self.cd_from_cl = self.settings['cd_from_cl'] self.folder = kwargs.get('output_folder') + '/aoa_induced/' if not self.cd_from_cl and len(self.list_aoa_cl0) == 0: # compute aoa for cl0 if not specified in settings self.compute_aoa_cl0_from_airfoil_data(self.aero) self.check_for_special_cases(self.aero) def generate(self, **params): """ Keyword Args: aero_kstep (:class:`sharpy.utils.datastructures.AeroTimeStepInfo`): Current aerodynamic substep structural_kstep (:class:`sharpy.utils.datastructures.StructTimeStepInfo`): Current structural substep struct_forces (np.array): Array with the aerodynamic forces mapped on the structure in the B frame of reference Returns: np.array: New corrected structural forces """ aero_kstep = params['aero_kstep'] structural_kstep = params['structural_kstep'] struct_forces = params['struct_forces'] ts = params['ts'] aerogrid = self.aero structure = self.structure rho = self.rho correct_lift = self.settings['correct_lift'] moment_from_polar = self.settings['moment_from_polar'] list_aoa_induced = [] data_dict = aerogrid.data_dict if aerogrid.polars is None: return struct_forces new_struct_forces = np.zeros_like(struct_forces) nnode = struct_forces.shape[0] # Compute induced velocities at the structural points cga = algebra.quat2rotation(structural_kstep.quat) pos_g = np.array([cga.dot(structural_kstep.pos[inode]) + np.array([0, 0, 0]) for inode in range(nnode)]) for inode in range(nnode): new_struct_forces[inode, :] = struct_forces[inode, :].copy() if data_dict['aero_node'][inode]: ielem, inode_in_elem = structure.node_master_elem[inode] iairfoil = data_dict['airfoil_distribution'][ielem, inode_in_elem] isurf = aerogrid.struct2aero_mapping[inode][0]['i_surf'] if isurf not in self.settings['skip_surfaces']: i_n = aerogrid.struct2aero_mapping[inode][0]['i_n'] polar = aerogrid.polars[iairfoil] cab = algebra.crv2rotation(structural_kstep.psi[ielem, inode_in_elem, :]) cgb = np.dot(cga, cab) if not self.cd_from_cl: airfoil = str(data_dict['airfoil_distribution'][ielem, inode_in_elem]) aoa_0cl = self.list_aoa_cl0[int(airfoil)] # computing surface area of panels contributing to force dir_span, span, dir_chord, chord = span_chord(i_n, aero_kstep.zeta[isurf]) area = span * chord area = self.correct_surface_area(inode, aerogrid.struct2aero_mapping, aero_kstep.zeta, area) # Define the relative velocity and its direction urel, dir_urel = magnitude_and_direction_of_relative_velocity(structural_kstep.pos[inode, :], structural_kstep.pos_dot[inode, :], structural_kstep.for_vel[:], cga, aero_kstep.u_ext[isurf][:, :, i_n], self.settings['add_rotation'], self.settings['rot_vel_g'], self.settings['centre_rot_g'],) # Coefficient to change from aerodynamic coefficients to forces (and viceversa) coef = 0.5 * rho * np.linalg.norm(urel) ** 2 * area # Stability axes - projects forces in B onto S c_bs = local_stability_axes(cgb.T.dot(dir_urel), cgb.T.dot(dir_chord)) forces_s = c_bs.T.dot(struct_forces[inode, :3]) moment_s = c_bs.T.dot(struct_forces[inode, 3:]) lift_force = forces_s[2] # Compute the associated lift cl = np.sign(lift_force) * np.linalg.norm(lift_force) / coef if self.cd_from_cl: # Compute the drag from the UVLM computed lift cd, cm = polar.get_cdcm_from_cl(cl) else: """ Compute L, D, M from polar depending on: ii) Compute the effective angle of attack from potential flow theory or specified it as setting input. The local lift curve slope is 2pi and the zero-lift angle of attack is given by thin airfoil theory or specified it as setting input. From this, the effective angle of attack is computed for the section and includes 3D effects. """ aoa = cl / 2 / np.pi + aoa_0cl list_aoa_induced.append(aoa) # Compute the coefficients associated to that angle of attack cl_polar, cd, cm = polar.get_coefs(aoa) if correct_lift: # Use polar generated CL rather than UVLM computed CL cl = cl_polar # Recompute the forces based on the coefficients (side force is uncorrected) forces_s[0] += cd * coef # add viscous drag to induced drag from UVLM forces_s[2] = cl * coef new_struct_forces[inode, 0:3] = c_bs.dot(forces_s) # Pitching moment # The panels are shifted by 0.25 of a panel aft from the leading edge panel_shift = 0.25 * (aero_kstep.zeta[isurf][:, 1, i_n] - aero_kstep.zeta[isurf][:, 0, i_n]) ref_point = aero_kstep.zeta[isurf][:, 0, i_n] + 0.25 * chord * dir_chord - panel_shift # viscous contribution (pure moment) moment_s[1] += cm * coef * chord # moment due to drag arm = cgb.T.dot(ref_point - pos_g[inode]) # in B frame moment_polar_drag = algebra.cross3(c_bs.T.dot(arm), cd * dir_urel * coef) # in S frame moment_s += moment_polar_drag # Pitching moment if moment_from_polar: # The panels are shifted by 0.25 of a panel aft from the leading edge panel_shift = 0.25 * (aero_kstep.zeta[isurf][:, 1, i_n] - aero_kstep.zeta[isurf][:, 0, i_n]) ref_point = aero_kstep.zeta[isurf][:, 0, i_n] + 0.25 * chord * dir_chord - panel_shift new_struct_forces[inode, 3:6] = c_bs.dot(moment_s) # viscous contribution (pure moment) moment_s[1] += cm * coef * chord # moment due to drag arm = cgb.T.dot(ref_point - pos_g[inode]) # in B frame moment_polar_drag = algebra.cross3(c_bs.T.dot(arm), cd * dir_urel * coef) # in S frame moment_s += moment_polar_drag # moment due to lift (if corrected) if correct_lift and moment_from_polar: # add moment from scratch: cm_polar + cm_drag_polar + cl_lift_polar moment_s = np.zeros(3) moment_s[1] = cm * coef * chord moment_s += moment_polar_drag moment_polar_lift = algebra.cross3(c_bs.T.dot(arm), forces_s[2] * np.array([0, 0, 1])) moment_s += moment_polar_lift new_struct_forces[inode, 3:6] = c_bs.dot(moment_s) if self.settings['write_induced_aoa']: self.write_induced_aoa_of_each_node(ts, list_aoa_induced) return new_struct_forces def correct_surface_area(self, inode, struct2aero_mapping, zeta_ts, area): ''' Corrects the surface area if the structural node is shared by multiple surfaces. For example, when the wing is split into right and left wing both surfaces share the center node. Necessary for cl calculation as the force on the node is already the sum of the forces generated at the adjacent panels of each surface. Args: inode (int): global node id struct2aero_mapping (list of dicts): maps the structural (global) nodes to aero surfaces and nodes zeta_ts (array): zeta of current aero timestep Returns: float: corrected surface area of other surfaces ''' if self.flag_shared_node_by_surfaces[inode]: n_surfaces_shared_by_node = len(struct2aero_mapping[inode]) # add area for all other surfaces connected to this node for isurf in range(1,n_surfaces_shared_by_node): shared_surf = struct2aero_mapping[inode][isurf]['i_surf'] i_n_shared_surf = struct2aero_mapping[inode][isurf]['i_n'] _, span_shared_surf, _, chord_shared_surf = span_chord(i_n_shared_surf, zeta_ts[shared_surf]) area += span_shared_surf * chord_shared_surf return area def check_for_special_cases(self, aerogrid): ''' Checks if the outboard node is shared by multiple surfaces. Args: aerogrid :class:`~sharpy.aero.models.AerogridLoader ''' # check if outboard node of aerosurface self.flag_shared_node_by_surfaces = np.zeros((self.n_node,1)) for inode in range(self.n_node): if aerogrid.data_dict['aero_node'][inode]: i_n = aerogrid.struct2aero_mapping[inode][0]['i_n'] isurf = aerogrid.struct2aero_mapping[inode][0]['i_surf'] N = aerogrid.dimensions[isurf, 1] if i_n in [0, N]: if len(aerogrid.struct2aero_mapping[inode]) > 1: self.flag_shared_node_by_surfaces[inode] = 1 def write_induced_aoa_of_each_node(self,ts, list_aoa_induced): ''' Writes induced aoa of each node to txt file for each timestep. Args: ts (int): simulation timestep list_aoa_induced (list(float)): list with induced aoa of each node ''' if ts == 0 and not os.path.exists(self.folder): os.makedirs(self.folder) np.savetxt(self.folder + '/aoa_induced_ts_{}.txt'.format(ts), np.transpose(np.transpose(np.array(list_aoa_induced))), fmt='%10e', delimiter=',', header='aoa_induced', comments='#') def compute_aoa_cl0_from_airfoil_data(self, aerogrid): """ Computes the angle of attack for which zero lift is achieved for every airfoil """ self.list_aoa_cl0 = np.zeros((len(aerogrid.data_dict['airfoils']),1)) for i, airfoil in enumerate(aerogrid.data_dict['airfoils']): airfoil_coords = aerogrid.data_dict['airfoils'][airfoil] self.list_aoa_cl0[i] = get_aoacl0_from_camber(airfoil_coords[:, 0], airfoil_coords[:, 1]) @generator_interface.generator class EfficiencyCorrection(generator_interface.BaseGenerator): """ The efficiency and constant terms are introduced by means of the array ``airfoil_efficiency`` in the ``aero.h5`` .. math:: \mathbf{f}_{struct}^B &= \varepsilon^f_0 \mathbf{f}_{i,struct}^B + \varepsilon^f_1\\ \mathbf{m}_{struct}^B &= \varepsilon^m_0 \mathbf{m}_{i,struct}^B + \varepsilon^m_1 Notice that the moment correction is applied on top of the force correction. As a consequence, the aerodynamic moments generated by the forces on the vertices are corrected sequentially by both efficiencies. See Also: The SHARPy case files documentation for a detailed overview on how to include the airfoil efficiencies. Returns: np.ndarray: corresponding aerodynamic force at the structural node from the force and moment at a grid vertex """ generator_id = 'EfficiencyCorrection' settings_types = dict() settings_default = dict() def __init__(self): self.aero = None self.structure = None def initialise(self, in_dict, **kwargs): self.aero = kwargs.get('aero') self.structure = kwargs.get('structure') def generate(self, **params): """ Keyword Args: aero_kstep (:class:`sharpy.utils.datastructures.AeroTimeStepInfo`): Current aerodynamic substep structural_kstep (:class:`sharpy.utils.datastructures.StructTimeStepInfo`): Current structural substep struct_forces (np.array): Array with the aerodynamic forces mapped on the structure in the B frame of reference Returns: np.array: New corrected structural forces """ struct_forces = params['struct_forces'] n_node = self.structure.num_node n_elem = self.structure.num_elem data_dict = self.aero.data_dict new_struct_forces = np.zeros_like(struct_forces) # load airfoil efficiency (if it exists); else set to one (to avoid multiple ifs in the loops) airfoil_efficiency = data_dict['airfoil_efficiency'] # force efficiency dimensions [n_elem, n_node_elem, 2, [fx, fy, fz]] - all defined in B frame force_efficiency = np.zeros((n_elem, 3, 2, 3)) force_efficiency[:, :, 0, :] = 1. force_efficiency[:, :, :, 1] = airfoil_efficiency[:, :, :, 0] force_efficiency[:, :, :, 2] = airfoil_efficiency[:, :, :, 1] # moment efficiency dimensions [n_elem, n_node_elem, 2, [mx, my, mz]] - all defined in B frame moment_efficiency = np.zeros((n_elem, 3, 2, 3)) moment_efficiency[:, :, 0, :] = 1. moment_efficiency[:, :, :, 0] = airfoil_efficiency[:, :, :, 2] for inode in range(n_node): i_elem, i_local_node = self.structure.node_master_elem[inode] new_struct_forces[inode, :] = struct_forces[inode, :].copy() new_struct_forces[inode, 0:3] *= force_efficiency[i_elem, i_local_node, 0, :] # element wise multiplication new_struct_forces[inode, 0:3] += force_efficiency[i_elem, i_local_node, 1, :] new_struct_forces[inode, 3:6] *= moment_efficiency[i_elem, i_local_node, 0, :] new_struct_forces[inode, 3:6] += moment_efficiency[i_elem, i_local_node, 1, :] return new_struct_forces ================================================ FILE: sharpy/generators/shearvelocityfield.py ================================================ import numpy as np import sharpy.utils.generator_interface as generator_interface import sharpy.utils.settings as settings @generator_interface.generator class ShearVelocityField(generator_interface.BaseGenerator): r""" Shear Velocity Field Generator ``ShearVelocityField`` class inherited from ``BaseGenerator`` The object creates a steady velocity field with shear .. math:: \hat{u} = \hat{u}\_\infty \left( \frac{h - h\_\mathrm{corr}}{h\_\mathrm{ref}} \right)^{\mathrm{shear}\_\mathrm{exp}} .. math:: h = \zeta \cdot \mathrm{shear}\_\mathrm{direction} """ generator_id = 'ShearVelocityField' generator_classification = 'velocity-field' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['u_inf'] = 'float' settings_default['u_inf'] = None settings_description['u_inf'] = 'Free stream velocity magnitude' settings_types['u_inf_direction'] = 'list(float)' settings_default['u_inf_direction'] = np.array([1.0, 0, 0]) settings_description['u_inf_direction'] = '``x``, ``y`` and ``z`` relative components of the free stream velocity' settings_types['shear_direction'] = 'list(float)' settings_default['shear_direction'] = np.array([.0, 0, 1.0]) settings_description['shear_direction'] = '``x``, ``y`` and ``z`` relative components of the direction along which shear applies' settings_types['shear_exp'] = 'float' settings_default['shear_exp'] = 0. settings_description['shear_exp'] = 'Exponent of the shear law' settings_types['h_ref'] = 'float' settings_default['h_ref'] = 1. settings_description['h_ref'] = 'Reference height at which ``u_inf`` is defined' settings_types['h_corr'] = 'float' settings_default['h_corr'] = 0. settings_description['h_corr'] = 'Height to correct shear law' setting_table = settings.SettingsTable() __doc__ += setting_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.in_dict = dict() self.u_inf = 0. self.u_inf_direction = None self.shear_direction = None self.shear_exp = None self.h_ref = None self.h_corr = None def initialise(self, in_dict, restart=False): self.in_dict = in_dict settings.to_custom_types(self.in_dict, self.settings_types, self.settings_default) self.u_inf = self.in_dict['u_inf'] self.u_inf_direction = self.in_dict['u_inf_direction'] self.shear_direction = self.in_dict['shear_direction'] self.shear_exp = self.in_dict['shear_exp'] self.h_ref = self.in_dict['h_ref'] self.h_corr = self.in_dict['h_corr'] def generate(self, params, uext): zeta = params['zeta'] override = params['override'] for i_surf in range(len(zeta)): if override: uext[i_surf].fill(0.0) for i in range(zeta[i_surf].shape[1]): for j in range(zeta[i_surf].shape[2]): h = np.dot(zeta[i_surf][:, i, j], self.shear_direction) + self.h_corr uext[i_surf][:, i, j] += self.u_inf*self.u_inf_direction*(h/self.h_ref)**self.shear_exp ================================================ FILE: sharpy/generators/steadyvelocityfield.py ================================================ import numpy as np import sharpy.utils.generator_interface as generator_interface import sharpy.utils.settings as settings @generator_interface.generator class SteadyVelocityField(generator_interface.BaseGenerator): """ Steady Velocity Field Generator ``SteadyVelocityField`` class inherited from ``BaseGenerator`` The object creates a steady velocity field with the velocity and flow direction specified by the user. To call this generator, the ``generator_id = SteadyVelocityField`` shall be used. This is parsed as the value for the ``velocity_field_generator`` key in the desired aerodynamic solver's settings. Args: in_dict (dict): Input data in the form of dictionary. See acceptable entries below: Attributes: settings_types (dict): Acceptable data types of the input data settings_default (dict): Default values for input data should the user not provide them u_inf (float): Free stream velocity selection u_inf_direction (list(float)): ``x``, ``y`` and ``z`` relative contributions to the free stream velocity See Also: .. py:class:: sharpy.utils.generator_interface.BaseGenerator """ generator_id = 'SteadyVelocityField' generator_classification = 'velocity-field' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['u_inf'] = 'float' settings_default['u_inf'] = None settings_description['u_inf'] = 'Module of the free stream velocity' settings_types['u_inf_direction'] = 'list(float)' settings_default['u_inf_direction'] = np.array([1.0, 0, 0]) settings_description['u_inf_direction'] = 'Direction of the free stream velocity' settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.in_dict = dict() self.u_inf = 0. self.u_inf_direction = None def initialise(self, in_dict, restart=False): self.in_dict = in_dict settings.to_custom_types(self.in_dict, self.settings_types, self.settings_default) self.u_inf = self.in_dict['u_inf'] self.u_inf_direction = self.in_dict['u_inf_direction'] def generate(self, params, uext): zeta = params['zeta'] override = params['override'] for i_surf in range(len(zeta)): if override: uext[i_surf].fill(0.0) for i in range(zeta[i_surf].shape[1]): for j in range(zeta[i_surf].shape[2]): uext[i_surf][:, i, j] += self.u_inf*self.u_inf_direction ================================================ FILE: sharpy/generators/straightwake.py ================================================ import numpy as np import sharpy.utils.generator_interface as generator_interface import sharpy.utils.settings as settings import sharpy.utils.solver_interface as solver_interface import sharpy.utils.cout_utils as cout @generator_interface.generator class StraightWake(generator_interface.BaseGenerator): r""" Straight wake shape generator This generator creates a straight wake shedding from the trailing edge based on the time step ``dt``, the incoming velocity magnitude ``u_inf`` and direction ``u_inf_direction``. It is to be used as ``wake_generator`` in :class:`~sharpy.solvers.aerogridloader.AerogridLoader`. A wake where panels grow downstream is supported by using the settings ``dx1``, ``ndx1``, ``r`` and ``dxmax`` as described below. Note that the wake will always have ``m_star`` panels, as specified in :class:`~sharpy.solvers.aerogridloader.AerogridLoader`, thus these settings will modify the effective length of the wake. Once the maximum size of panel ``dxmax`` is achieved, all panels are size ``dxmax`` thereinafter until ``m_star`` panels are created. """ generator_id = 'StraightWake' generator_classification = 'wake' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['u_inf'] = 'float' settings_default['u_inf'] = 1. # None settings_description['u_inf'] = 'Free stream velocity magnitude' settings_types['u_inf_direction'] = 'list(float)' settings_default['u_inf_direction'] = np.array([1.0, 0, 0]) # None settings_description['u_inf_direction'] = '``x``, ``y`` and ``z`` relative components of the free stream velocity' settings_types['dt'] = 'float' settings_default['dt'] = 0.1 # None settings_description['dt'] = 'Time step' settings_types['dx1'] = ['float', 'list(float)'] settings_default['dx1'] = -1.0 settings_description['dx1'] = 'Size of the first wake panel' settings_types['ndx1'] = ['int', 'list(int)'] settings_default['ndx1'] = 1 settings_description['ndx1'] = 'Number of panels with size ``dx1``' settings_types['r'] = ['float', 'list(float)'] settings_default['r'] = 1. settings_description['r'] = 'Growth rate after ``ndx1`` panels' settings_types['dxmax'] = ['float', 'list(float)'] settings_default['dxmax'] = -1.0 settings_description['dxmax'] = 'Maximum panel size' setting_table = settings.SettingsTable() __doc__ += setting_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.in_dict = dict() self.u_inf = 0. self.u_inf_direction = None self.dt = None def initialise(self, data, in_dict=None, restart=False): self.in_dict = in_dict # For backwards compatibility if len(self.in_dict.keys()) == 0: cout.cout_wrap("WARNING: The code will run for backwards compatibility. \ In future releases you will need to define a 'wake_shape_generator' in ``AerogridLoader''. \ Please, check the documentation", 3) # Look for an aerodynamic solver if 'StaticUvlm' in data.settings: aero_solver_name = 'StaticUvlm' aero_solver_settings = data.settings['StaticUvlm'] elif 'SHWUvlm' in data.settings: aero_solver_name = 'SHWUvlm' aero_solver_settings = data.settings['SHWUvlm'] elif 'StaticCoupled' in data.settings: aero_solver_name = data.settings['StaticCoupled']['aero_solver'] aero_solver_settings = data.settings['StaticCoupled']['aero_solver_settings'] elif 'DynamicCoupled' in data.settings: aero_solver_name = data.settings['DynamicCoupled']['aero_solver'] aero_solver_settings = data.settings['DynamicCoupled']['aero_solver_settings'] elif 'StepUvlm' in data.settings: aero_solver_name = 'StepUvlm' aero_solver_settings = data.settings['StepUvlm'] else: raise RuntimeError("ERROR: aerodynamic solver not found") # Get the minimum parameters needed to define the wake aero_solver = solver_interface.solver_from_string(aero_solver_name) settings.to_custom_types(aero_solver_settings, aero_solver.settings_types, aero_solver.settings_default) if 'dt' in aero_solver_settings.keys(): dt = aero_solver_settings['dt'] elif 'rollup_dt' in aero_solver_settings.keys(): dt = aero_solver_settings['rollup_dt'] else: # print(aero_solver['velocity_field_input']['u_inf']) dt = 1./aero_solver_settings['velocity_field_input']['u_inf'] self.in_dict = {'u_inf': aero_solver_settings['velocity_field_input']['u_inf'], 'u_inf_direction': aero_solver_settings['velocity_field_input']['u_inf_direction'], 'dt': dt} settings.to_custom_types(self.in_dict, self.settings_types, self.settings_default, no_ctype=True) self.u_inf = self.in_dict['u_inf'] self.u_inf_direction = self.in_dict['u_inf_direction'] self.dt = self.in_dict['dt'] self.dx1 = self.in_dict['dx1'] self.ndx1 = self.in_dict['ndx1'] self.r = self.in_dict['r'] self.dxmax = self.in_dict['dxmax'] def generate(self, params): # Renaming for convenience zeta = params['zeta'] zeta_star = params['zeta_star'] gamma = params['gamma'] gamma_star = params['gamma_star'] dist_to_orig = params['dist_to_orig'] nsurf = len(zeta) for isurf in range(nsurf): r_surf, ndx1_surf, dx1_surf, dxmax_surf = self.get_all_surface_parameters(isurf) M, N = zeta_star[isurf][0, :, :].shape for j in range(N): zeta_star[isurf][:, 0, j] = zeta[isurf][:, -1, j] for i in range(1, M): deltax = self.get_deltax(i, dx1_surf, ndx1_surf, r_surf, dxmax_surf) zeta_star[isurf][:, i, j] = zeta_star[isurf][:, i - 1, j] + deltax*self.u_inf_direction gamma[isurf] *= 0. gamma_star[isurf] *= 0. for isurf in range(nsurf): M, N = zeta_star[isurf][0, :, :].shape dist_to_orig[isurf][0] = 0. for j in range(0, N): for i in range(1, M): dist_to_orig[isurf][i, j] = (dist_to_orig[isurf][i - 1, j] + np.linalg.norm(zeta_star[isurf][:, i, j] - zeta_star[isurf][:, i - 1, j])) dist_to_orig[isurf][:, j] /= dist_to_orig[isurf][-1, j] @staticmethod def get_deltax(i, dx1, ndx1, r, dxmax): if (i < ndx1 + 1) : deltax = dx1 else: deltax = dx1*r**(i - ndx1) deltax = min(deltax, dxmax) return deltax @staticmethod def get_surface_parameter(parameter, i_surf): if np.isscalar(parameter): return parameter else: return parameter[i_surf] def get_all_surface_parameters(self,i_surf): r_surf = self.get_surface_parameter(self.r, i_surf) dx1_surf = self.get_surface_parameter(self.dx1, i_surf) ndx1_surf = self.get_surface_parameter(self.ndx1, i_surf) dxmax_surf = self.get_surface_parameter(self.dxmax, i_surf) if dx1_surf == -1.: dx1_surf = self.u_inf*self.dt if dxmax_surf == -1.: dxmax_surf = dx1_surf return r_surf, ndx1_surf, dx1_surf, dxmax_surf ================================================ FILE: sharpy/generators/trajectorygenerator.py ================================================ import numpy as np from numpy.polynomial import polynomial as P import scipy as sc from scipy import interpolate import sharpy.utils.generator_interface as generator_interface import sharpy.utils.settings as settings import sharpy.utils.cout_utils as cout @generator_interface.generator class TrajectoryGenerator(generator_interface.BaseGenerator): """ ``TrajectoryGenerator`` is used to generate nodal positions or velocities for trajectory constraints such as the ones included in the multibody solver. It is usually called from a ``Controller`` module. """ generator_id = 'TrajectoryGenerator' generator_classification = 'utils' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['angle_end'] = 'float' settings_default['angle_end'] = 0.0 settings_description['angle_end'] = 'Trajectory angle wrt horizontal at release' settings_types['veloc_end'] = 'float' settings_default['veloc_end'] = None settings_description['veloc_end'] = 'Release velocity at release' settings_types['shape'] = 'str' settings_default['shape'] = 'quadratic' settings_description['shape'] = 'Shape of the ``z`` vs ``x`` function. ``quadratic`` or ``linear`` are supported' settings_types['acceleration'] = 'str' settings_default['acceleration'] = 'linear' settings_description['acceleration'] = 'Acceleration law, possible values are ``linear`` or ``constant``' settings_types['dt'] = 'float' settings_default['dt'] = None settings_description['dt'] = 'Time step of the simulation' settings_types['coords_end'] = 'list(float)' settings_default['coords_end'] = None settings_description['coords_end'] = 'Coordinates of the final ramp point' settings_types['plot'] = 'bool' settings_default['plot'] = False settings_description['plot'] = 'Plot the ramp shape. Requires matplotlib installed' settings_types['print_info'] = 'bool' settings_default['print_info'] = False settings_description['print_info'] = 'Print information on runtime' settings_types['time_offset'] = 'float' settings_default['time_offset'] = 0.0 settings_description['time_offset'] = 'Time interval before the start of the ramp acceleration' settings_types['offset'] = 'list(float)' settings_default['offset'] = np.zeros((3,)) settings_description['offset'] = 'Coordinates of the starting point of the simulation' settings_types['return_velocity'] = 'bool' settings_default['return_velocity'] = False settings_description['return_velocity'] = 'If ``True``, nodal velocities are given, if ``False``, coordinates are the output' settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.in_dict = dict() self.x_vec = None self.y_vec = None self.x_dot_vec = None self.y_dot_vec = None self.time = None self.n_steps = None self.travel_time = None self.curve_length = None self.implemented_shapes = ["linear", "quadratic"] self.implemented_accelerations = ["constant", "linear"] def initialise(self, in_dict, restart=False): self.in_dict = in_dict settings.to_custom_types(self.in_dict, self.settings_types, self.settings_default) # input validation self.in_dict['shape'] = self.in_dict['shape'].lower() self.in_dict['acceleration'] = self.in_dict['acceleration'].lower() self.calculate_trajectory() if self.in_dict['print_info']: self.print_info() if self.in_dict['plot']: self.plot() if self.in_dict['return_velocity']: self.diff_trajectory() def print_info(self): cout.cout_wrap('Trajectory information:', 2) cout.cout_wrap('\t-travel time: {}'.format(self.travel_time), 2) cout.cout_wrap('\t-curve length: {}'.format(self.curve_length), 2) cout.cout_wrap('-------------------------------', 2) def plot(self): try: import matplotlib.pyplot as plt plt.figure() plt.scatter(self.x_vec, self.y_vec, c=self.time_vec) plt.colorbar(orientation='horizontal') plt.axis('equal') plt.show() except ModuleNotFoundError: import warnings warnings.warn('Unable to import matplotlib, skipping plot') def get_n_steps(self): return self.n_steps def __call__(self, params): return self.generate(params) def generate(self, params): it = params['it'] if self.n_steps is not None: if it >= self.n_steps: return [None]*3 if self.in_dict['return_velocity']: return np.array([self.x_dot_vec[it], 0.0, self.y_dot_vec[it]]) + self.in_dict['offset'] else: return np.array([self.x_vec[it], 0.0, self.y_vec[it]]) + self.in_dict['offset'] def calculate_trajectory(self): in_dict = self.in_dict # shape variables shape_polynomial = np.zeros((3,)) # [c, b, a] results in y = c + b*x + a*x^2 curve_length = 0.0 # calculate coefficients and curve length if in_dict['shape'] == "linear": shape_polynomial[0] = 0.0 shape_polynomial[1] = in_dict['coords_end'][1]/in_dict['coords_end'][0] if np.isnan(shape_polynomial[1]): shape_polynomial[1] = 0.0 shape_polynomial[2] = 0.0 curve_length = linear_curve_length(shape_polynomial, in_dict['coords_end']) elif in_dict['shape'] == "quadratic": shape_polynomial[2] = (np.arctan(in_dict['angle_end'].value) - in_dict['coords_end'][1]/in_dict['coords_end'][0])/in_dict['coords_end'][0] shape_polynomial[1] = np.arctan(in_dict['angle_end'].value) - 2.0*shape_polynomial[2]*in_dict['coords_end'][0] shape_polynomial[0] = 0.0 curve_length = quadratic_curve_length(shape_polynomial, in_dict['coords_end']) curve_length = np.abs(curve_length) # acceleration acceleration_position_coefficients = None travel_time = 0.0 if in_dict['acceleration'] == 'constant': acceleration_position_coefficients = constant_acceleration_position_coeffs(curve_length, np.abs(in_dict['veloc_end'].value)) travel_time = constant_acceleration_travel_time(curve_length, np.abs(in_dict['veloc_end'].value)) elif in_dict['acceleration'] == 'linear': acceleration_position_coefficients = linear_acceleration_position_coeffs(curve_length, np.abs(in_dict['veloc_end'].value)) travel_time = linear_acceleration_travel_time(curve_length, np.abs(in_dict['veloc_end'].value)) # time n_steps_travel = int(round(travel_time/in_dict['dt'].value)) n_steps_offset = int(round(self.in_dict['time_offset'].value/in_dict['dt'].value)) self.n_steps = n_steps_offset + n_steps_travel self.time_vec = np.zeros((self.n_steps, )) self.time_vec[n_steps_offset:] = np.linspace(0.0, n_steps_travel*in_dict['dt'].value, n_steps_travel) # with t I get s s_vec = P.polyval(self.time_vec, acceleration_position_coefficients) # need to get a function for x(s) # sample s(x) and create an interpolator n_samples = 1000 sampled_x_vec = np.linspace(0.0, in_dict['coords_end'][0], n_samples) sampled_s_vec = np.zeros((n_samples, )) for i_sample in range(n_samples): if in_dict['shape'] == "linear": sampled_s_vec[i_sample] = np.abs(linear_curve_length(shape_polynomial, np.array([sampled_x_vec[i_sample], 0.0]))) elif in_dict['shape'] == 'quadratic': sampled_s_vec[i_sample] = np.abs(quadratic_curve_length(shape_polynomial, np.array([sampled_x_vec[i_sample], 0.0]))) x_of_s_interp = sc.interpolate.interp1d(sampled_s_vec, sampled_x_vec, kind='quadratic', fill_value='extrapolate') self.x_vec = x_of_s_interp(s_vec) # with x, I obtain y and done self.y_vec = P.polyval(self.x_vec, shape_polynomial) self.travel_time = travel_time self.curve_length = curve_length def diff_trajectory(self): dt = self.in_dict['dt'].value self.x_dot_vec = np.gradient(self.x_vec, dt) self.y_dot_vec = np.gradient(self.y_vec, dt) def linear_curve_length(shape_polynomial, coords_end): dzdx_end = shape_polynomial[1] length = coords_end[0]*np.sqrt(dzdx_end**2 + 1) return length def quadratic_curve_length(shape_polynomial, coords_end): dzdx_end = 2.0*shape_polynomial[2]*coords_end[0] + shape_polynomial[1] a = shape_polynomial[2] b = shape_polynomial[1] xe = coords_end[0] length = (2.0*a*xe + b)*np.sqrt((2.0*a*xe + b)**2 + 1.0) length += np.arcsinh(2.0*a*xe + b) length -= b*np.sqrt(b**2 + 1.0) length -= np.arcsinh(b) length /= 4.0*a return length def constant_acceleration_position_coeffs(s_e, s_dot_e): coeffs = (0.0, 0.0, (4.0*s_dot_e**2)/s_e, 0.0) return coeffs def linear_acceleration_position_coeffs(s_e, s_dot_e): coeff = ((6.0*s_e)**(2./3.)/(2.0*s_dot_e))**(-3.) coeffs = (0.0, 0.0, 0.0, (1./6.)*coeff) return coeffs def constant_acceleration_travel_time(s_e, s_dot_e): return 2.0*s_e/s_dot_e def linear_acceleration_travel_time(s_e, s_dot_e): return 3.0*s_e/s_dot_e ================================================ FILE: sharpy/generators/turbvelocityfield.py ================================================ import numpy as np import scipy.interpolate as interpolate import h5py as h5 import os from lxml import objectify, etree import sharpy.utils.generator_interface as generator_interface import sharpy.utils.settings as settings import sharpy.utils.cout_utils as cout @generator_interface.generator class TurbVelocityField(generator_interface.BaseGenerator): r""" Turbulent Velocity Field Generator ``TurbVelocitityField`` is a class inherited from ``BaseGenerator`` The ``TurbVelocitityField`` class generates a velocity field based on the input from an [XDMF](http://www.xdmf.org) file. It supports time-dependant fields as well as frozen turbulence. To call this generator, the ``generator_id = TurbVelocityField`` shall be used. This is parsed as the value for the ``velocity_field_generator`` key in the desired aerodynamic solver's settings. Supported files: - `field_id.xdmf`: Steady or Unsteady XDMF file This generator also performs time interpolation between two different time steps. For now, only linear interpolation is possible. Space interpolation is done through `scipy.interpolate` trilinear interpolation. However, turbulent fields are read directly from the binary file and not copied into memory. This is performed using `np.memmap`. The overhead of this procedure is ~18% for the interpolation stage, however, initially reading the binary velocity field (which will be much more common with time-domain simulations) is faster by a factor of 1e4. Also, memory savings are quite substantial: from 6Gb for a typical field to a handful of megabytes for the whole program. Args: in_dict (dict): Input data in the form of dictionary. See acceptable entries below: Attributes: See Also: .. py:class:: sharpy.utils.generator_interface.BaseGenerator """ generator_id = 'TurbVelocityField' generator_classification = 'velocity-field' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Output solver-specific information in runtime.' settings_types['turbulent_field'] = 'str' settings_default['turbulent_field'] = None settings_description['turbulent_field'] = 'XDMF file path of the velocity field' settings_types['offset'] = 'list(float)' settings_default['offset'] = np.zeros((3,)) settings_description['offset'] = 'Spatial offset in the 3 dimensions' settings_types['centre_y'] = 'bool' settings_default['centre_y'] = True settings_description['centre_y'] = 'Flat for changing the domain to [``-y_max/2``, ``y_max/2``]' settings_types['periodicity'] = 'str' settings_default['periodicity'] = 'xy' settings_description['periodicity'] = 'Axes in which periodicity is enforced' settings_types['frozen'] = 'bool' settings_default['frozen'] = True settings_description['frozen'] = 'If ``True``, the turbulent field will not be updated in time' settings_types['store_field'] = 'bool' settings_default['store_field'] = False settings_description['store_field'] = 'If ``True``, the xdmf snapshots are stored in memory. Only two at a time for the linear interpolation' settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.in_dict = dict() self.settings = dict() self.file = None self.extension = None self.grid_data = dict() self.interpolator = 3*[None] self.x_periodicity = False self.y_periodicity = False # variables for interpolator wrapper self._t0 = -1 self._t1 = -1 self._it0 = -1 self._it1 = -1 self._interpolator0 = None self._interpolator1 = None self.coeff = 0. self.double_initialisation = True self.vel_holder0 = 3*[None] self.vel_holder1 = 3*[None] def initialise(self, in_dict, restart=False): self.in_dict = in_dict settings.to_custom_types(self.in_dict, self.settings_types, self.settings_default) self.settings = self.in_dict _, self.extension = os.path.splitext(self.settings['turbulent_field']) if self.extension == '.h5': self.read_btl(self.settings['turbulent_field']) if self.extension == '.xdmf': self.read_xdmf(self.settings['turbulent_field']) if 'z' in self.settings['periodicity']: raise ValueError('Periodicitiy setting in TurbVelocityField cannot be z.\n A turbulent boundary layer is not periodic in the z direction!') if 'x' in self.settings['periodicity']: self.x_periodicity = True if 'y' in self.settings['periodicity']: self.y_periodicity = True # ADC: VERY VERY UGLY. NEED A BETTER WAY def interpolator_wrapper0(self, coords, i_dim=0): coeff = self.get_coeff() return (1.0 - self.coeff)*self._interpolator0[i_dim](coords) + self.coeff*self._interpolator1[i_dim](coords) def interpolator_wrapper1(self, coords, i_dim=1): coeff = self.get_coeff() return (1.0 - self.coeff)*self._interpolator0[i_dim](coords) + self.coeff*self._interpolator1[i_dim](coords) def interpolator_wrapper2(self, coords, i_dim=2): coeff = self.get_coeff() return (1.0 - self.coeff)*self._interpolator0[i_dim](coords) + self.coeff*self._interpolator1[i_dim](coords) def get_coeff(self): return self.coeff def init_interpolator(self): if self.settings['frozen']: self.interpolator = self._interpolator0 return # continuing the ugliness self.interpolator[0] = self.interpolator_wrapper0 self.interpolator[1] = self.interpolator_wrapper1 self.interpolator[2] = self.interpolator_wrapper2 # these functions need to define the interpolators def read_btl(self, in_file): """ Legacy function, not using the custom format based on HDF5 anymore. """ raise NotImplementedError('The BTL reader is not up to date!') def read_xdmf(self, in_file): """ Reads the xml file `.xdmf`. Writes the self.grid_data data structure with all the information necessary. Note: this function does not load any turbulence data (such as ux000, ...), it only reads the header information contained in the xdmf file. """ # store route of file for the other files self.route = os.path.dirname(os.path.abspath(in_file)) # file to string with open(in_file, 'r') as self.file: data = self.file.read().replace('\n', '') # parse data # this next line is necessary to avoid problems with parsing in the Time part: # quantity at vertex 0 - (aM,aN)=(1,0) --> quantity at vertex 1 - (aM,aN)=(1,1) --> quantity at vertex 2 - (aM,aN)=(0,1) --> quantity at vertex 3 """ wcv=np.array([ (1-aM)*(1-aN), aM*(1-aN), aM*aN, aN*(1-aM) ]) return wcv def get_Wvc_scalar(Map,aM=0.5,aN=0.5): """ Produce scalar interpolation matrix Wvc for state-space realisation. Important: this will not work for coordinates extrapolation, as it would require information about the panel size. It works for other forces/scalar quantities extrapolation. It assumes the quantity at the collocation point is determined proportionally to the weight associated to each vertex and obtained through get_panel_wcv. """ # initialise K,Kzeta=Map.K,Map.Kzeta Wvc=np.zeros((Kzeta,K)) # retrieve weights wcv=get_panel_wcv(aM,aN) wvc=wcv.T # define mapping panels to vertices (for scalars) if not hasattr(Map,'Mvp1d_scalar'): Map.map_panels_to_vertices_1D_scalar() # loop through panels for jj in range(K): # loop through local vertex index for vv in range(4): # vertex 1d index ii = Map.Mpv1d_scalar[jj,vv] # allocate Wvc[ii,jj]=wvc[vv] return Wvc def get_Wvc_vector(Wvc_scalar): Kzeta,K=Wvc_scalar.shape Wvc=np.zeros((3*Kzeta,3*K)) for cc in range(3): Wvc[cc*Kzeta:(cc+1)*Kzeta,cc*K:(cc+1)*K]=Wvc_scalar return Wvc def get_Wnv_vector(SurfGeo,aM=0.5,aN=0.5): """ Provide projection matrix from nodal velocities to normal velocity at collocation points """ # initialise Map=SurfGeo.maps K,Kzeta=Map.K,Map.Kzeta # retrieve scaling matrix Wvc_scalar=get_Wvc_scalar(Map,aM,aN) Wvc=get_Wvc_vector(Wvc_scalar) Wcv=Wvc.T del Wvc_scalar, Wvc # Build Wnc matrix Nmat=SurfGeo.normals.reshape((3,K)) Wnc=np.concatenate([ np.diag(Nmat[0,:]),np.diag(Nmat[1,:]),np.diag(Nmat[2,:]) ], axis=1) Wnv=np.dot(Wnc,Wcv) return Wnv # ----------------------------------------------------------------------------- # # if __name__=='__main__': # # import read # import gridmapping # import surface # import matplotlib.pyplot as plt # # # select test case # fname='../test/h5input/goland_mod_Nsurf01_M003_N004_a040.aero_state.h5' # haero=read.h5file(fname) # tsdata=haero.ts00000 # # # select surface and retrieve data # ss=0 # M,N=tsdata.dimensions[ss] # Map=gridmapping.AeroGridMap(M,N) # SurfGeo=surface.AeroGridGeo(Map,tsdata.zeta[ss]) # # # generate geometry data # SurfGeo.generate_areas() # SurfGeo.generate_normals() # #SurfGeo.aM,SurfGeo.aN=0.25,0.75 # SurfGeo.generate_collocations() # # # # ---------------------------------------------------------------- Test Wvc # zeta_vec=SurfGeo.zeta.reshape(-1,order='C') # Wvc_scalar=get_Wvc_scalar(Map) # Wvc=get_Wvc_vector(Wvc_scalar) # zetac_vec=np.dot(Wvc.T,zeta_vec) # zetac=zetac_vec.reshape(Map.shape_pan_vect) # SurfGeo.plot(plot_normals=False) # SurfGeo.ax.scatter(zetac[0],zetac[1],zetac[2],zdir='z',s=6,c='b',marker='+') # # # way back - can't work # # zetav_vec=np.dot(Wvc,zetac_vec) # # zetav=zetav_vec.reshape(Map.shape_vert_vect) # # SurfGeo.ax.scatter(zetav[0],zetav[1],zetav[2],zdir='z',s=6,c='k',marker='+') # # plt.show() # plt.close('all') # # # # ---------------------------------------------------------------- Test wnv # # generate non-zero field of external force # u_ext=tsdata.u_ext[ss] # u_ext[0,:,:]=u_ext[0,:,:]-20.0 # u_ext[1,:,:]=u_ext[1,:,:]+60.0 # u_ext[2,:,:]=u_ext[2,:,:]+30.0 # u_ext=u_ext+np.random.rand(*u_ext.shape) # # interpolate velocity at collocation points # # # compute normal velocity at panels # wcv=get_panel_wcv(aM=0.5,aN=0.5) # u_norm=np.zeros((M,N)) # for mm in range(M): # for nn in range(N): # # get velocity at panel corners # mpv=SurfGeo.maps.from_panel_to_vertices(mm,nn) # uc=np.zeros((3,)) # for vv in range(4): # uc=uc+wcv[vv]*u_ext[:,mpv[vv,0],mpv[vv,1]] # u_norm[mm,nn]=np.dot(uc,SurfGeo.normals[:,mm,nn]) # u_norm_vec_ref=u_norm.reshape(-1,order='C') # # compute normal velocity through projection matrix # u_ext_vec=u_ext.reshape(-1,order='C') # Wnv=get_Wnv_vector(SurfGeo) # u_norm_vec=np.dot(Wnv,u_ext_vec) # # assert np.max(np.abs(u_norm_vec-u_norm_vec_ref))<1e-12, 'Wnv not correct' ================================================ FILE: sharpy/linear/src/lib_dbiot.py ================================================ """Induced Velocity Derivatives Calculate derivatives of induced velocity. Methods: - eval_seg_exp and eval_seg_exp_loop: profide ders in format [Q_{x,y,z},ZetaPoint_{x,y,z}] and use fully-expanded analytical formula. - eval_panel_exp: iterates through whole panel - eval_seg_comp and eval_seg_comp_loop: profide ders in format [Q_{x,y,z},ZetaPoint_{x,y,z}] and use compact analytical formula. """ import numpy as np from sharpy.aero.utils.uvlmlib import eval_panel_cpp import sharpy.utils.algebra as algebra from sharpy.utils.constants import cfact_biot ### looping through panels svec = [0, 1, 2, 3] # seg. no. # avec =[ 0, 1, 2, 3] # 1st vertex no. # bvec =[ 1, 2, 3, 0] # 2nd vertex no. LoopPanel = [(0, 1), (1, 2), (2, 3), (3, 0)] # used in eval_panel_{exp/comp} def eval_panel_cpp_coll(zetaP, ZetaPanel, vortex_radius, gamma_pan=1.0): DerP, DerVertices = eval_panel_cpp(zetaP, ZetaPanel, vortex_radius, gamma_pan) return DerP def eval_seg_exp(ZetaP, ZetaA, ZetaB, vortex_radius, gamma_seg=1.0): """ Derivative of induced velocity Q w.r.t. collocation and segment coordinates in format: [ (ZetaP,ZetaA,ZetaB), (x,y,z) of Zeta, (x,y,z) of Q] Warning: function optimised for performance. Variables are scaled during the execution. """ DerP = np.zeros((3, 3)) DerA = np.zeros((3, 3)) DerB = np.zeros((3, 3)) eval_seg_exp_loop(DerP, DerA, DerB, ZetaP, ZetaA, ZetaB, gamma_seg, vortex_radius) return DerP, DerA, DerB def eval_seg_exp_loop(DerP, DerA, DerB, ZetaP, ZetaA, ZetaB, gamma_seg, vortex_radius): """ Derivative of induced velocity Q w.r.t. collocation (DerC) and segment coordinates in format. To optimise performance, the function requires the derivative terms to be pre-allocated and passed as input. Each Der* term returns derivatives in the format [ (x,y,z) of Zeta, (x,y,z) of Q] Warning: to optimise performance, variables are scaled during the execution. """ RA = ZetaP - ZetaA RB = ZetaP - ZetaB RAB = ZetaB - ZetaA Vcr = algebra.cross3(RA, RB) vcr2 = np.dot(Vcr, Vcr) # numerical radious vortex_radious_here = vortex_radius * algebra.norm3d(RAB) if vcr2 < vortex_radious_here ** 2: return # scaling ra1, rb1 = algebra.norm3d(RA), algebra.norm3d(RB) ra2, rb2 = ra1 ** 2, rb1 ** 2 rainv = 1. / ra1 rbinv = 1. / rb1 ra_dir, rb_dir = RA * rainv, RB * rbinv ra3inv, rb3inv = rainv ** 3, rbinv ** 3 Vcr = Vcr / vcr2 diff_vec = ra_dir - rb_dir vdot_prod = np.dot(diff_vec, RAB) T2 = vdot_prod / vcr2 # Extract components ra_x, ra_y, ra_z = RA rb_x, rb_y, rb_z = RB rab_x, rab_y, rab_z = RAB vcr_x, vcr_y, vcr_z = Vcr ra2_x, ra2_y, ra2_z = RA ** 2 rb2_x, rb2_y, rb2_z = RB ** 2 ra_vcr_x, ra_vcr_y, ra_vcr_z = 2. * algebra.cross3(RA, Vcr) rb_vcr_x, rb_vcr_y, rb_vcr_z = 2. * algebra.cross3(RB, Vcr) vcr_sca_x, vcr_sca_y, vcr_sca_z = Vcr * ra3inv vcr_scb_x, vcr_scb_y, vcr_scb_z = Vcr * rb3inv # # ### derivatives indices: # # # the 1st is the component of the vaiable w.r.t derivative are taken. # # # the 2nd is the component of the output dQ_dRA = np.array( [[-vdot_prod * rb_vcr_x * vcr_x + vcr_sca_x * ( rab_x * (ra2 - ra2_x) - ra_x * ra_y * rab_y - ra_x * ra_z * rab_z), -T2 * rb_z - vdot_prod * rb_vcr_x * vcr_y + vcr_sca_y * ( rab_x * (ra2 - ra2_x) - ra_x * ra_y * rab_y - ra_x * ra_z * rab_z), T2 * rb_y - vdot_prod * rb_vcr_x * vcr_z + vcr_sca_z * ( rab_x * (ra2 - ra2_x) - ra_x * ra_y * rab_y - ra_x * ra_z * rab_z)], [T2 * rb_z - vdot_prod * rb_vcr_y * vcr_x + vcr_sca_x * ( rab_y * (ra2 - ra2_y) - ra_x * ra_y * rab_x - ra_y * ra_z * rab_z), -vdot_prod * rb_vcr_y * vcr_y + vcr_sca_y * ( rab_y * (ra2 - ra2_y) - ra_x * ra_y * rab_x - ra_y * ra_z * rab_z), -T2 * rb_x - vdot_prod * rb_vcr_y * vcr_z + vcr_sca_z * ( rab_y * (ra2 - ra2_y) - ra_x * ra_y * rab_x - ra_y * ra_z * rab_z)], [-T2 * rb_y - vdot_prod * rb_vcr_z * vcr_x + vcr_sca_x * ( rab_z * (ra2 - ra2_z) - ra_x * ra_z * rab_x - ra_y * ra_z * rab_y), T2 * rb_x - vdot_prod * rb_vcr_z * vcr_y + vcr_sca_y * ( rab_z * (ra2 - ra2_z) - ra_x * ra_z * rab_x - ra_y * ra_z * rab_y), -vdot_prod * rb_vcr_z * vcr_z + vcr_sca_z * ( rab_z * (ra2 - ra2_z) - ra_x * ra_z * rab_x - ra_y * ra_z * rab_y)]]) dQ_dRB = np.array( [[vdot_prod * ra_vcr_x * vcr_x + vcr_scb_x * ( rab_x * (-rb2 + rb2_x) + rab_y * rb_x * rb_y + rab_z * rb_x * rb_z), T2 * ra_z + vdot_prod * ra_vcr_x * vcr_y + vcr_scb_y * ( rab_x * (-rb2 + rb2_x) + rab_y * rb_x * rb_y + rab_z * rb_x * rb_z), -T2 * ra_y + vdot_prod * ra_vcr_x * vcr_z + vcr_scb_z * ( rab_x * (-rb2 + rb2_x) + rab_y * rb_x * rb_y + rab_z * rb_x * rb_z)], [-T2 * ra_z + vdot_prod * ra_vcr_y * vcr_x + vcr_scb_x * ( rab_x * rb_x * rb_y + rab_y * (-rb2 + rb2_y) + rab_z * rb_y * rb_z), vdot_prod * ra_vcr_y * vcr_y + vcr_scb_y * ( rab_x * rb_x * rb_y + rab_y * (-rb2 + rb2_y) + rab_z * rb_y * rb_z), T2 * ra_x + vdot_prod * ra_vcr_y * vcr_z + vcr_scb_z * ( rab_x * rb_x * rb_y + rab_y * (-rb2 + rb2_y) + rab_z * rb_y * rb_z)], [T2 * ra_y + vdot_prod * ra_vcr_z * vcr_x + vcr_scb_x * ( rab_x * rb_x * rb_z + rab_y * rb_y * rb_z + rab_z * (-rb2 + rb2_z)), -T2 * ra_x + vdot_prod * ra_vcr_z * vcr_y + vcr_scb_y * ( rab_x * rb_x * rb_z + rab_y * rb_y * rb_z + rab_z * (-rb2 + rb2_z)), vdot_prod * ra_vcr_z * vcr_z + vcr_scb_z * ( rab_x * rb_x * rb_z + rab_y * rb_y * rb_z + rab_z * (-rb2 + rb2_z))]]) dQ_dRAB = np.array( [[vcr_x * diff_vec[0], vcr_y * diff_vec[0], vcr_z * diff_vec[0]], [vcr_x * diff_vec[1], vcr_y * diff_vec[1], vcr_z * diff_vec[1]], [vcr_x * diff_vec[2], vcr_y * diff_vec[2], vcr_z * diff_vec[2]]]) DerP += (cfact_biot * gamma_seg) * (dQ_dRA + dQ_dRB).T # w.r.t. P DerA += (cfact_biot * gamma_seg) * (-dQ_dRAB - dQ_dRA).T # w.r.t. A DerB += (cfact_biot * gamma_seg) * (dQ_dRAB - dQ_dRB).T # w.r.t. B def eval_panel_exp(zetaP, ZetaPanel, vortex_radius, gamma_pan=1.0): """ Computes derivatives of induced velocity w.r.t. coordinates of target point, zetaP, and panel coordinates. Returns two elements: - DerP: derivative of induced velocity w.r.t. ZetaP, with: DerP.shape=(3,3) : DerC[ Uind_{x,y,z}, ZetaC_{x,y,z} ] - DerVertices: derivative of induced velocity wrt panel vertices, with: DerVertices.shape=(4,3,3) : DerVertices[ vertex number {0,1,2,3}, Uind_{x,y,z}, ZetaC_{x,y,z} ] """ DerP = np.zeros((3, 3)) DerVertices = np.zeros((4, 3, 3)) for aa, bb in LoopPanel: eval_seg_exp_loop(DerP, DerVertices[aa, :, :], DerVertices[bb, :, :], zetaP, ZetaPanel[aa, :], ZetaPanel[bb, :], gamma_pan, vortex_radius) return DerP, DerVertices # ------------------------------------------------------------------------------ # Compact Formula # ------------------------------------------------------------------------------ def Dvcross_by_skew3d(Dvcross, rv): """ Fast matrix multiplication of der(vcross)*skew(rv), where vcross = (rv x sv)/|rv x sv|^2 The function exploits the property that the output matrix is symmetric. DvCross is a list containing the lower diagonal elements """ P = np.empty((3, 3)) P[0, 0] = Dvcross[1][0] * rv[2] - Dvcross[2][0] * rv[1] P[0, 1] = Dvcross[2][0] * rv[0] - Dvcross[0][0] * rv[2] P[0, 2] = Dvcross[0][0] * rv[1] - Dvcross[1][0] * rv[0] # P[1, 0] = P[0, 1] P[1, 1] = Dvcross[2][1] * rv[0] - Dvcross[1][0] * rv[2] P[1, 2] = Dvcross[1][0] * rv[1] - Dvcross[1][1] * rv[0] # P[2, 0] = P[0, 2] P[2, 1] = P[1, 2] P[2, 2] = Dvcross[2][0] * rv[1] - Dvcross[2][1] * rv[0] return P # def Dvcross_by_skew3d(Dvcross,rv): # """ # Fast matrix multiplication of der(vcross)*skew(rv), where # vcross = (rv x sv)/|rv x sv|^2 # The function exploits the property that the output matrix is symmetric. # """ # P=np.empty((3,3)) # P[0,0]=Dvcross[0,1]*rv[2]-Dvcross[0,2]*rv[1] # P[0,1]=Dvcross[0,2]*rv[0]-Dvcross[0,0]*rv[2] # P[0,2]=Dvcross[0,0]*rv[1]-Dvcross[0,1]*rv[0] # # # P[1,0]=P[0,1] # P[1,1]=Dvcross[1,2]*rv[0]-Dvcross[0,1]*rv[2] # P[1,2]=Dvcross[0,1]*rv[1]-Dvcross[1,1]*rv[0] # # # P[2,0]=P[0,2] # P[2,1]=P[1,2] # P[2,2]=Dvcross[0,2]*rv[1]-Dvcross[1,2]*rv[0] # return P def der_runit(r, rinv, minus_rinv3): # alloc upper diag Der = np.empty((3, 3)) Der[0, 0] = rinv + minus_rinv3 * r[0] ** 2 Der[0, 1] = minus_rinv3 * r[0] * r[1] Der[0, 2] = minus_rinv3 * r[0] * r[2] Der[1, 1] = rinv + minus_rinv3 * r[1] ** 2 Der[1, 2] = minus_rinv3 * r[1] * r[2] Der[2, 2] = rinv + minus_rinv3 * r[2] ** 2 # alloc lower Der[1, 0] = Der[0, 1] Der[2, 0] = Der[0, 2] Der[2, 1] = Der[1, 2] return Der def eval_seg_comp(ZetaP, ZetaA, ZetaB, vortex_radius, gamma_seg=1.0): DerP = np.zeros((3, 3)) DerA = np.zeros((3, 3)) DerB = np.zeros((3, 3)) eval_seg_comp_loop(DerP, DerA, DerB, ZetaP, ZetaA, ZetaB, gamma_seg, vortex_radius) return DerP, DerA, DerB def eval_seg_comp_loop(DerP, DerA, DerB, ZetaP, ZetaA, ZetaB, gamma_seg, vortex_radius): """ Derivative of induced velocity Q w.r.t. collocation and segment coordinates in format: [ (x,y,z) of Q, (x,y,z) of Zeta ] Warning: function optimised for performance. Variables are scaled during the execution. """ vortex_radius_sq = vortex_radius*vortex_radius Cfact = cfact_biot * gamma_seg RA = ZetaP - ZetaA RB = ZetaP - ZetaB RAB = ZetaB - ZetaA Vcr = algebra.cross3(RA, RB) vcr2 = np.dot(Vcr, Vcr) # numerical radious if vcr2 < (vortex_radius_sq * algebra.normsq3d(RAB)): return ### other constants ra1, rb1 = algebra.norm3d(RA), algebra.norm3d(RB) rainv = 1. / ra1 rbinv = 1. / rb1 Tv = RA * rainv - RB * rbinv dotprod = np.dot(RAB, Tv) ### --------------------------------------------- cross-product derivatives # lower triangular part only vcr2inv = 1. / vcr2 vcr4inv = vcr2inv * vcr2inv diag_fact = Cfact * vcr2inv * dotprod off_fact = -2. * Cfact * vcr4inv * dotprod Dvcross = [ [diag_fact + off_fact * Vcr[0] ** 2], [off_fact * Vcr[0] * Vcr[1], diag_fact + off_fact * Vcr[1] ** 2], [off_fact * Vcr[0] * Vcr[2], off_fact * Vcr[1] * Vcr[2], diag_fact + off_fact * Vcr[2] ** 2]] ### ------------------------------------------ difference terms derivatives Vsc = Vcr * vcr2inv * Cfact Ddiff = np.array([RAB * Vsc[0], RAB * Vsc[1], RAB * Vsc[2]]) dQ_dRAB = np.array([Tv * Vsc[0], Tv * Vsc[1], Tv * Vsc[2]]) ### ---------------------------------------------- Final assembly (crucial) # ps: calling Dvcross_by_skew3d does not slow down execution. dQ_dRA = Dvcross_by_skew3d(Dvcross, -RB) \ + np.dot(Ddiff, der_runit(RA, rainv, -rainv ** 3)) dQ_dRB = Dvcross_by_skew3d(Dvcross, RA) \ - np.dot(Ddiff, der_runit(RB, rbinv, -rbinv ** 3)) DerP += dQ_dRA + dQ_dRB # w.r.t. P DerA -= dQ_dRAB + dQ_dRA # w.r.t. A DerB += dQ_dRAB - dQ_dRB # w.r.t. B # ### collocation point only # DerP +=Dvcross_by_skew3d(Dvcross,RA-RB)+np.dot(Ddiff, # der_runit(RA,rainv,minus_rainv3)-der_runit(RB,rbinv,minus_rbinv3)) def eval_panel_comp(zetaP, ZetaPanel, vortex_radius, gamma_pan=1.0): """ Computes derivatives of induced velocity w.r.t. coordinates of target point, zetaP, and panel coordinates. Returns two elements: - DerP: derivative of induced velocity w.r.t. ZetaP, with: DerP.shape=(3,3) : DerC[ Uind_{x,y,z}, ZetaC_{x,y,z} ] - DerVertices: derivative of induced velocity wrt panel vertices, with: DerVertices.shape=(4,3,3) : DerVertices[ vertex number {0,1,2,3}, Uind_{x,y,z}, ZetaC_{x,y,z} ] """ DerP = np.zeros((3, 3)) DerVertices = np.zeros((4, 3, 3)) for aa, bb in LoopPanel: eval_seg_comp_loop(DerP, DerVertices[aa, :, :], DerVertices[bb, :, :], zetaP, ZetaPanel[aa, :], ZetaPanel[bb, :], gamma_pan, vortex_radius) return DerP, DerVertices def eval_panel_fast(zetaP, ZetaPanel, vortex_radius, gamma_pan=1.0): """ Computes derivatives of induced velocity w.r.t. coordinates of target point, zetaP, and panel coordinates. Returns two elements: - DerP: derivative of induced velocity w.r.t. ZetaP, with: DerP.shape=(3,3) : DerC[ Uind_{x,y,z}, ZetaC_{x,y,z} ] - DerVertices: derivative of induced velocity wrt panel vertices, with: DerVertices.shape=(4,3,3) : DerVertices[ vertex number {0,1,2,3}, Uind_{x,y,z}, ZetaC_{x,y,z} ] The function is based on eval_panel_comp, but minimises operationsby recycling variables. """ vortex_radius_sq = vortex_radius*vortex_radius DerP = np.zeros((3, 3)) DerVertices = np.zeros((4, 3, 3)) ### ---------------------------------------------- Compute common variables # these are constants or variables depending only on vertices and P coords Cfact = cfact_biot * gamma_pan # distance vertex ii-th from P R_list = zetaP - ZetaPanel r1_list = [algebra.norm3d(R_list[ii]) for ii in svec] r1inv_list = [1. / r1_list[ii] for ii in svec] Runit_list = [R_list[ii] * r1inv_list[ii] for ii in svec] Der_runit_list = [ der_runit(R_list[ii], r1inv_list[ii], -r1inv_list[ii] ** 3) for ii in svec] ### ------------------------------------------------- Loop through segments for aa, bb in LoopPanel: RAB = ZetaPanel[bb, :] - ZetaPanel[aa, :] # segment vector Vcr = algebra.cross3(R_list[aa], R_list[bb]) vcr2 = np.dot(Vcr, Vcr) if vcr2 < (vortex_radius_sq * algebra.normsq3d(RAB)): continue Tv = Runit_list[aa] - Runit_list[bb] dotprod = np.dot(RAB, Tv) ### ----------------------------------------- cross-product derivatives # lower triangular part only vcr2inv = 1. / vcr2 vcr4inv = vcr2inv * vcr2inv diag_fact = Cfact * vcr2inv * dotprod off_fact = -2. * Cfact * vcr4inv * dotprod Dvcross = [ [diag_fact + off_fact * Vcr[0] ** 2], [off_fact * Vcr[0] * Vcr[1], diag_fact + off_fact * Vcr[1] ** 2], [off_fact * Vcr[0] * Vcr[2], off_fact * Vcr[1] * Vcr[2], diag_fact + off_fact * Vcr[2] ** 2]] ### ---------------------------------------- difference term derivative Vsc = Vcr * vcr2inv * Cfact Ddiff = np.array([RAB * Vsc[0], RAB * Vsc[1], RAB * Vsc[2]]) ### ---------------------------------------------------- RAB derivative dQ_dRAB = np.array([Tv * Vsc[0], Tv * Vsc[1], Tv * Vsc[2]]) ### ------------------------------------------ Final assembly (crucial) dQ_dRA = Dvcross_by_skew3d(Dvcross, -R_list[bb]) \ + np.dot(Ddiff, Der_runit_list[aa]) dQ_dRB = Dvcross_by_skew3d(Dvcross, R_list[aa]) \ - np.dot(Ddiff, Der_runit_list[bb]) DerP += dQ_dRA + dQ_dRB # w.r.t. P DerVertices[aa, :, :] -= dQ_dRAB + dQ_dRA # w.r.t. A DerVertices[bb, :, :] += dQ_dRAB - dQ_dRB # w.r.t. B # ### collocation point only # DerP +=Dvcross_by_skew3d(Dvcross,RA-RB)+np.dot(Ddiff, # der_runit(RA,rainv,minus_rainv3)-der_runit(RB,rbinv,minus_rbinv3)) return DerP, DerVertices def eval_panel_fast_coll(zetaP, ZetaPanel, vortex_radius, gamma_pan=1.0): """ Computes derivatives of induced velocity w.r.t. coordinates of target point, zetaP, coordinates. Returns two elements: - DerP: derivative of induced velocity w.r.t. ZetaP, with: DerP.shape=(3,3) : DerC[ Uind_{x,y,z}, ZetaC_{x,y,z} ] The function is based on eval_panel_fast, but does not perform operations required to compute the derivatives w.r.t. the panel coordinates. """ vortex_radius_sq = vortex_radius*vortex_radius DerP = np.zeros((3, 3)) ### ---------------------------------------------- Compute common variables # these are constants or variables depending only on vertices and P coords Cfact = cfact_biot * gamma_pan # distance vertex ii-th from P R_list = zetaP - ZetaPanel r1_list = [algebra.norm3d(R_list[ii]) for ii in svec] r1inv_list = [1. / r1_list[ii] for ii in svec] Runit_list = [R_list[ii] * r1inv_list[ii] for ii in svec] Der_runit_list = [ der_runit(R_list[ii], r1inv_list[ii], -r1inv_list[ii] ** 3) for ii in svec] ### ------------------------------------------------- Loop through segments for aa, bb in LoopPanel: RAB = ZetaPanel[bb, :] - ZetaPanel[aa, :] # segment vector Vcr = algebra.cross3(R_list[aa], R_list[bb]) vcr2 = np.dot(Vcr, Vcr) if vcr2 < (vortex_radius_sq * algebra.normsq3d(RAB)): continue Tv = Runit_list[aa] - Runit_list[bb] dotprod = np.dot(RAB, Tv) ### ----------------------------------------- cross-product derivatives # lower triangular part only vcr2inv = 1. / vcr2 vcr4inv = vcr2inv * vcr2inv diag_fact = Cfact * vcr2inv * dotprod off_fact = -2. * Cfact * vcr4inv * dotprod Dvcross = [ [diag_fact + off_fact * Vcr[0] ** 2], [off_fact * Vcr[0] * Vcr[1], diag_fact + off_fact * Vcr[1] ** 2], [off_fact * Vcr[0] * Vcr[2], off_fact * Vcr[1] * Vcr[2], diag_fact + off_fact * Vcr[2] ** 2]] ### ---------------------------------------- difference term derivative Vsc = Vcr * vcr2inv * Cfact Ddiff = np.array([RAB * Vsc[0], RAB * Vsc[1], RAB * Vsc[2]]) ### ------------------------------------------ Final assembly (crucial) # dQ_dRA=Dvcross_by_skew3d(Dvcross,-R_list[bb])\ # +np.dot(Ddiff, Der_runit_list[aa] ) # dQ_dRB=Dvcross_by_skew3d(Dvcross, R_list[aa])\ # -np.dot(Ddiff, Der_runit_list[bb] ) DerP += Dvcross_by_skew3d(Dvcross, RAB) + \ +np.dot(Ddiff, Der_runit_list[aa] - Der_runit_list[bb]) return DerP if __name__ == '__main__': import cProfile ### verify consistency amongst models gamma = 4. zetaP = np.array([3.0, 5.5, 2.0]) zeta0 = np.array([1.0, 3.0, 0.9]) zeta1 = np.array([5.0, 3.1, 1.9]) zeta2 = np.array([4.8, 8.1, 2.5]) zeta3 = np.array([0.9, 7.9, 1.7]) ZetaPanel = np.array([zeta0, zeta1, zeta2, zeta3]) zetap = 0.3 * zeta1 + 0.7 * zeta2 # ZetaPanel=np.array([[ 1.221, -0.064, -0.085], # [ 1.826, -0.064, -0.141], # [ 1.933, 1.456, -0.142], # [ 1.327, 1.456, -0.087]]) # zetaP=np.array([-0.243, 0.776, 0.037]) ### verify model consistency DPcpp, DVcpp = eval_panel_cpp(zetaP, ZetaPanel, 1e-6, gamma_pan=gamma) # vortex_radius DPexp, DVexp = eval_panel_exp(zetaP, ZetaPanel, gamma_pan=gamma) DPcomp, DVcomp = eval_panel_comp(zetaP, ZetaPanel, gamma_pan=gamma) DPfast, DVfast = eval_panel_fast(zetaP, ZetaPanel, vortex_radius, gamma_pan=gamma) DPfast_coll = eval_panel_fast_coll(zetaP, ZetaPanel, vortex_radius, gamma_pan=gamma) ermax = max(np.max(np.abs(DPcpp - DPexp)), np.max(np.abs(DVcpp - DVexp))) assert ermax < 1e-15, 'eval_panel_cpp not matching with eval_panel_exp' ermax = max(np.max(np.abs(DPcomp - DPexp)), np.max(np.abs(DVcomp - DVexp))) assert ermax < 1e-15, 'eval_panel_comp not matching with eval_panel_exp' ermax = max(np.max(np.abs(DPfast - DPexp)), np.max(np.abs(DVfast - DVexp))) assert ermax < 1e-15, 'eval_panel_fast not matching with eval_panel_exp' ermax = np.max(np.abs(DPfast_coll - DPexp)) assert ermax < 1e-15, 'eval_panel_fast_coll not matching with eval_panel_exp' ### profiling def run_eval_panel_cpp(): for ii in range(10000): eval_panel_cpp(zetaP, ZetaPanel, 1e-6, gamma_pan=3.) # vortex_radius def run_eval_panel_fast(vortex_radius=vortex_radius_def): for ii in range(10000): eval_panel_fast(zetaP, ZetaPanel, vortex_radius, gamma_pan=3.) def run_eval_panel_fast_coll(vortex_radius=vortex_radius_def): for ii in range(10000): eval_panel_fast_coll(zetaP, ZetaPanel, vortex_radius, gamma_pan=3.) def run_eval_panel_comp(): for ii in range(10000): eval_panel_comp(zetaP, ZetaPanel, gamma_pan=3.) def run_eval_panel_exp(): for ii in range(10000): eval_panel_exp(zetaP, ZetaPanel, gamma_pan=3.) print('------------------------------------------ profiling eval_panel_cpp') cProfile.runctx('run_eval_panel_cpp()', globals(), locals()) print('----------------------------------------- profiling eval_panel_fast') cProfile.runctx('run_eval_panel_fast()', globals(), locals()) print('------------------------------------ profiling eval_panel_fast_coll') cProfile.runctx('run_eval_panel_fast_coll()', globals(), locals()) print('----------------------------------------- profiling eval_panel_comp') cProfile.runctx('run_eval_panel_comp()', globals(), locals()) print('------------------------------------------ profiling eval_panel_exp') cProfile.runctx('run_eval_panel_exp()', globals(), locals()) ================================================ FILE: sharpy/linear/src/lib_ucdncdzeta.py ================================================ r"""Induced Velocity Derivatives with respect to Panel Normal Calculate derivative of .. math:: \boldsymbol{u}_c\frac{\partial\boldsymbol{n}_c}{\partial\boldsymbol{zeta}} with respect to local panel coordinates. """ import numpy as np import sharpy.utils.algebra as algebra dR_dZeta = np.array( [[[[-1, 0, 0], [0, 0, 0]], [[0, -1, 0], [0, 0, 0]], [[0, 0, -1], [0, 0, 0]]], [[[0, 0, 0], [-1, 0, 0]], [[0, 0, 0], [0, -1, 0]], [[0, 0, 0], [0, 0, -1]]], [[[1, 0, 0], [0, 0, 0]], [[0, 1, 0], [0, 0, 0]], [[0, 0, 1], [0, 0, 0]]], [[[0, 0, 0], [1, 0, 0]], [[0, 0, 0], [0, 1, 0]], [[0, 0, 0], [0, 0, 1]]]]) def eval(Zeta00, Zeta01, Zeta02, Zeta03, Uc): """ Returns a 4 x 3 array, containing the derivative of Wnc*Uc w.r.t the panel vertices coordinates. """ R02 = Zeta02 - Zeta00 R13 = Zeta03 - Zeta01 crR02R13 = algebra.cross3(R02, R13) norm_crR02R13 = np.linalg.norm(crR02R13) cub_crR02R13 = norm_crR02R13 ** 3 Acr = algebra.cross3(crR02R13, R13) Bcr = algebra.cross3(crR02R13, R02) Cdot = np.dot(crR02R13, Uc) uc_x, uc_y, uc_z = Uc r02_x, r02_y, r02_z = R02 r13_x, r13_y, r13_z = R13 crR13Uc_x, crR13Uc_y, crR13Uc_z = algebra.cross3(R13, Uc) crR02Uc_x, crR02Uc_y, crR02Uc_z = algebra.cross3(R02, Uc) crR02R13_x, crR02R13_y, crR02R13_z = crR02R13 Acr_x, Acr_y, Acr_z = Acr Bcr_x, Bcr_y, Bcr_z = Bcr # dUnorm_dR.shape=(2,3) dUnorm_dR = np.array( [[Acr_x * Cdot / cub_crR02R13 + crR13Uc_x / norm_crR02R13, Acr_y * Cdot / cub_crR02R13 + crR13Uc_y / norm_crR02R13, Acr_z * Cdot / cub_crR02R13 + crR13Uc_z / norm_crR02R13], [-Bcr_x * Cdot / cub_crR02R13 - crR02Uc_x / norm_crR02R13, -Bcr_y * Cdot / cub_crR02R13 - crR02Uc_y / norm_crR02R13, -Bcr_z * Cdot / cub_crR02R13 - crR02Uc_z / norm_crR02R13]]) # Allocate dUnorm_dZeta = np.zeros((4, 3)) for vv in range(4): # loop through panel vertices for cc_zeta in range(3): # loop panel vertices component for rr in range(2): # loop segments R02, R13 for cc_rvec in range(3): # loop segment component dUnorm_dZeta[vv, cc_zeta] += \ dUnorm_dR[rr, cc_rvec] * dR_dZeta[vv, cc_zeta, rr, cc_rvec] return dUnorm_dZeta if __name__ == '__main__': # calculate normal def get_panel_normal(zetav_here): """From Surface.AeroGridSurface""" r02 = zetav_here[2, :] - zetav_here[0, :] r13 = zetav_here[3, :] - zetav_here[1, :] nvec = np.cross(r02, r13) nvec = nvec / np.linalg.norm(nvec) return nvec # define panel vertices zeta00 = np.array([1.0, 0.2, 1.0]) zeta01 = np.array([3.9, 0.1, 0.8]) zeta02 = np.array([4.0, 3.5, 0.9]) zeta03 = np.array([1.2, 3.2, 1.1]) zeta_panel = np.array([zeta00, zeta01, zeta02, zeta03]) # reference normal nvec0 = get_panel_normal(zeta_panel) # reference normal velocity ucoll = np.array([2, 6, 3]) unorm0 = np.dot(nvec0, ucoll) # analytical derivative dUnorm_dZeta = eval(zeta00, zeta01, zeta02, zeta03, ucoll) # numerical derivative Dnum = np.zeros((4, 3)) step = 1e-6 for ii in range(4): for jj in range(3): delta = np.zeros((4, 3)) delta[ii, jj] = step nvec_pert = get_panel_normal(zeta_panel + delta) unorm_pert = np.dot(nvec_pert, ucoll) Dnum[ii, jj] = (unorm_pert - unorm0) / step assert np.max(np.abs(dUnorm_dZeta - Dnum)) < step, 'Derivative not accurate!' ================================================ FILE: sharpy/linear/src/libfit.py ================================================ """Fitting Tools Library @author: Salvatore Maraniello @date: 15 Jan 2018 """ import warnings import numpy as np from scipy.signal import tf2ss import scipy.linalg as scalg import scipy.optimize as scopt import multiprocessing as mpr import itertools def fpoly(kv, B0, B1, B2, dyv, ddyv): return B0 + B1 * dyv + B2 * ddyv def fpade(kv, Cv, B0, B1a, B1b, B2, dyv, ddyv): # evaluate transfer function from unsteady function Cv return B0 * Cv + (B1a * Cv + B1b) * dyv + B2 * ddyv def getC(kv, Yv, B0, B1a, B1b, B2, dyv, ddyv): # evaluate unsteady function C required to perfectly match Yv Den = B0 + B1a * dyv # kkvec=np.abs(Den)<1e-8 C = (Yv - B1b * dyv - B2 * ddyv) / Den # C[kkvec]=1.0 # if np.sum(kkvec)>0: embed() return C def rfa(cnum, cden, kv, ds=None): """ Evaluates over the frequency range kv.the rational function approximation: [cnum[-1] + cnum[-2] z + ... + cnum[0] z**Nnum ]/... [cden[-1] + cden[-2] z + ... + cden[0] z**Nden] where the numerator and denominator polynomial orders, Nnum and Nden, are the length of the cnum and cden arrays and: - z=exp(1.j*kv*ds), with ds sampling time if ds is given (discrete-time system) - z=1.*kv, if ds is None (continuous time system) """ if ds == None: # continuous-time LTI system zv = 1.j * kv else: # discrete-time LTI system zv = np.exp(1.j * kv * ds) return np.polyval(cnum, zv) / np.polyval(cden, zv) def rfader(cnum, cden, kv, m=1, ds=None): """ Evaluates over the frequency range kv.the derivative of order m of the rational function approximation: [cnum[-1] + cnum[-2] z + ... + cnum[0] z**Nnum ]/... [cden[-1] + cden[-2] z + ... + cden[0] z**Nden] where the numerator and denominator polynomial orders, Nnum and Nden, are the length of the cnum and cden arrays and: - z=exp(1.j*kv*ds), with ds sampling time if ds is given (discrete-time system) - z=1.*kv, if ds is None (continuous time system) """ if ds == None: # continuous-time LTI system zv = 1.j * kv dzv = 1.j raise NameError('Never tested for continuous systems!') else: # discrete-time LTI system zv = np.exp(1.j * kv * ds) dzv = 1.j * ds * zv Nv = np.polyval(cnum, zv) Dv = np.polyval(cden, zv) dNv = np.polyval(np.polyder(cnum), zv) dDv = np.polyval(np.polyder(cden), zv) return dzv * (dNv * Dv - Nv * dDv) / Dv ** 2 def fitfrd(kv, yv, N, dt=None, mag=0, eng=None): """ Wrapper for fitfrd (mag=0) and fitfrdmag (mag=1) functions in continuous and discrete time (if ds in input). Input: kv,yv: frequency array and frequency response N: order for rational function approximation mag=1,0: Flag for determining method to use dt (optional): sampling time for DLTI systems """ raise NameError('Please use fitfrd function in matwrapper module!') return None def get_rfa_res(xv, kv, Yv, Nnum, Nden, ds=None): """ Returns magnitude of the residual Yfit-Yv of a RFA approximation at each point kv. The coefficients of the approximations are: - cnum=xv[:Nnum] - cdem=xv[Nnum:] where cnum and cden are as per the 'rfa' function. """ assert Nnum + Nden == len(xv), 'Nnum+Nden must be equal to len(xv)!' cnum = xv[:Nnum] cden = xv[Nnum:] Yfit = rfa(cnum, cden, kv, ds) return np.abs(Yfit - Yv) def get_rfa_res_norm(xv, kv, Yv, Nnum, Nden, ds=None, method='mix'): """ Define residual scalar norm of Pade approximation of coefficients cnum=xv[:Nnum] and cden[Nnum:] (see get_rfa_res and rfa function) and time-step ds (if discrete time). """ ErvAbs = get_rfa_res(xv, kv, Yv, Nnum, Nden, ds) if method == 'H2': res = np.sum(ErvAbs ** 2) elif method == 'Hinf': res = np.max(ErvAbs) elif method == 'mix': res = np.sum(ErvAbs ** 2) + np.max(ErvAbs) return res def rfa_fit_dev(kv, Yv, Nnum, Nden, TolAbs, ds=None, Stability=True, NtrialMax=10, Cfbound=1e2, OutFull=False, Print=False): """ Find best fitting RFA approximation from frequency response Yv over the frequency range kv for both continuous (ds=None) and discrete (ds>0) LTI systems. The RFA approximation is found through a 2-stage strategy: a. an evolutionary algoryhtm is run to determine the optimal fitting coefficients b. the search is refined through a least squares algorithm. and is stopped as soon as: 1. the maximum absolute error in frequency response of the RFA falls below ``TolAbs`` 2. the maximum number of iterations is reached. Input: - kv: frequency range for approximation - Yv: frequency response vector over kv - TolAbs: maximum admissible absolute difference in frequency response between RFA and original system. - Nnum,Ndem: number of coefficients for Pade approximation. - ds: sampling time for DLTI systems - NtrialMax: maximum number of repetition of global and least square optimisations - Cfbouds: maximum absolute values of coeffieicnts (only for evolutionary algorithm) - OutFull: if False, only outputs optimal coefficients of RFA. Otherwise, outputs cost and RFA coefficients of each trial. Output: - cnopt: optimal coefficients (numerator) - cdopt: optimal coefficients (denominator) Important: - this function has the same objective as fitfrd in matwrapper module. While generally slower, the global optimisation approach allows to verify the results from fitfrd. """ def pen_max_eig(cdvec): """ Computes maximum eigenvalues from denominator coefficients of RFA and evaluates a log barrier that ensures """ eigs = np.roots(cdvec) eigmax = np.max(np.abs(eigs)) eiglim = 0.999 pen = 0. Fact = 1e3 * TolAbs / np.log(2) if eigmax > eiglim: pen = Fact * np.log((eigmax + 1. - 2. * eiglim) / (1. - eiglim)) else: pen = 0. return pen if Stability: def fcost_dev(xv, kv, Yv, Nnum, Nden, ds, method): """ xv is a vector such that the coeff: cnum=xv[:Nnum] cden=xv[Nnum:] """ xvpass = np.concatenate((xv, np.array([1, ]))) error_fit = get_rfa_res_norm(xvpass, kv, Yv, Nnum, Nden, ds, method) pen_stab = pen_max_eig(xvpass[Nnum:]) return error_fit + pen_stab def fcost_lsq(xv, kv, Yv, Nnum, Nden, ds): xvpass = np.concatenate((xv, np.array([1, ]))) res_fit = get_rfa_res(xvpass, kv, Yv, Nnum, Nden, ds) pen_stab = pen_max_eig(xvpass[Nnum:]) return res_fit + pen_stab else: def fcost_dev(xv, kv, Yv, Nnum, Nden, ds, method): """ xv is a vector such that the coeff: cnum=xv[:Nnum] cden=xv[Nnum:] """ xvpass = np.concatenate((xv, np.array([1, ]))) return get_rfa_res_norm(xvpass, kv, Yv, Nnum, Nden, ds, method) def fcost_lsq(xv, kv, Yv, Nnum, Nden, ds): xvpass = np.concatenate((xv, np.array([1, ]))) return get_rfa_res(xvpass, kv, Yv, Nnum, Nden, ds) Nx = Nnum + Nden - 1 # List of optimal solutions found by the evolutionary and least squares # algorithms XvOptDev, XvOptLsq = [], [] Cdev, Clsq = [], [] tt = 0 cost_best = 1e32 while cost_best > TolAbs and tt < NtrialMax: tt += 1 ### Evolutionary algorithm res = scopt.differential_evolution( # popsize=100, strategy='best1bin', func=fcost_dev, args=(kv, Yv, Nnum, Nden, ds, 'Hinf'), bounds=Nx * ((-Cfbound, Cfbound),)) xvdev = res.x cost_dev = fcost_dev(xvdev, kv, Yv, Nnum, Nden, ds, 'Hinf') # is this the best solution? if cost_dev < cost_best: cost_best = cost_dev xvopt = xvdev # store this solution if OutFull: XvOptDev.append(xvdev) Cdev.append(cost_dev) ### Least squares fitting - unbounded # method only local, but do not move to the end of global search: best # results can be found even when starting from a "worse" solution xvlsq = scopt.leastsq(fcost_lsq, x0=xvdev, args=(kv, Yv, Nnum, Nden, ds))[0] cost_lsq = fcost_dev(xvlsq, kv, Yv, Nnum, Nden, ds, 'Hinf') # is this the best solution? if cost_lsq < cost_best: cost_best = cost_lsq xvopt = xvlsq # store this solution if OutFull: XvOptLsq.append(xvlsq) Clsq.append(cost_lsq) ### Print and move on if Print: print('Trial %.2d: cost dev: %.3e, cost lsq: %.3e' \ % (tt, cost_dev, cost_lsq)) if cost_best > TolAbs: warnings.warn( 'RFA error (%.2e) greater than specified tolerance (%.2e)!' \ % (cost_best, TolAbs)) ### add 1 to denominator cnopt = xvopt[:Nnum] cdopt = np.hstack([xvopt[Nnum:], 1.]) # if np.abs(cdopt[-1])>1e-2: # cdscale=cdopt[-1] # else: # cdscale=1.0 # cnopt=cnopt/cdscale # cdopt=cdopt/cdscale # determine outputs Outputs = (cnopt, cdopt) if OutFull: for tt in range(Ntrial): if np.abs(XvOptDev[tt][-1]) > 1e-2: XvOptDev[tt] = XvOptDev[tt] / XvOptDev[tt][-1] if np.abs(XvOptLsq[tt][-1]) > 1e-2: XvOptLsq[tt] = XvOptLsq[tt] / XvOptLsq[tt][-1] Outputs = Outputs + (XvOptDev, XvOptLsq, Cdev, Clsq) return Outputs def poly_fit(kv, Yv, dyv, ddyv, method='leastsq', Bup=None): """ Find best II order fitting polynomial from frequency response Yv over the frequency range kv for both continuous (ds=None) and discrete (ds>0) LTI systems. Input: - kv: frequency points - Yv: frequency response - dyv,ddyv: frequency responses of I and II order derivatives - method='leastsq','dev': algorithm for minimisation - Bup (only 'dev' method): bounds for bv coefficients as per scipy.optimize.differential_evolution. This is a length 3 array. Important: - this function attributes equal weight to each data-point! """ if method == 'leastsq': # pointwise residual def funRes(bv, kv, Yv, dyv, ddyv): B0, B1, B2 = bv rv = fpoly(kv, B0, B1, B2, dyv, ddyv) - Yv return np.concatenate((rv.real, rv.imag)) # solve bvopt, cost = scopt.leastsq(funRes, x0=[0., 0., 0.], args=(kv, Yv, dyv, ddyv)) elif method == 'dev': # use genetic algorithm with objective a sum of H2 and Hinf norms of # residual def funRes(bv, kv, Yv, dyv, ddyv): B0, B1, B2 = bv rv = fpoly(kv, B0, B1, B2, dyv, ddyv) - Yv Nk = len(kv) rvsq = rv * rv.conj() # H2norm=np.sqrt(np.trapz(rvsq/(Nk-1.))) # return H2norm+np.linalg.norm(rv,np.inf) return np.sum(rvsq) # prepare bounds if Bup is None: Bounds = 3 * ((-Bup, Bup),) else: assert len(Bup) == 3, 'Bup must be a length 3 list/array' Bounds = ((-Bup[0], Bup[0]), (-Bup[1], Bup[1]), (-Bup[2], Bup[2]),) res = scopt.differential_evolution( func=funRes, args=(kv, Yv, dyv, ddyv), strategy='best1bin', bounds=Bounds) bvopt = res.x cost = funRes(bvopt, kv, Yv, dyv, ddyv) return bvopt, cost def rfa_mimo(Yfull, kv, ds, tolAbs, Nnum, Nden, Dmatrix=None, NtrialMax=6, Ncpu=4, method='independent'): """ Given the frequency response of a MIMO DLTI system, this function returns the A,B,C,D matrices associated to the rational function approximation of the original system. Input: - Yfull: frequency response (as per libss.freqresp) of full size system over the frequencies kv. - kv: array of frequencies over which the RFA approximation is evaluated. - tolAbs: absolute tolerance for the rfa fitting - Nnum: number of numerator coefficients for RFA - Nden: number of denominator coefficients for RFA - NtrialMax: maximum number of attempts - method=['intependent']. Method used to produce the system: - intependent: each input-output combination is treated separately. The resulting system is a collection of independent SISO DLTIs """ Nout, Nin, Nk = Yfull.shape assert Nk == len(kv), 'Frequency response Yfull not compatible with frequency range kv' iivec = range(Nin) oovec = range(Nout) plist = list(itertools.product(oovec, iivec)) args_const = (Nnum, Nden, tolAbs, ds, True, NtrialMax, 1e2, False, False) with mpr.Pool(Ncpu) as pool: if Dmatrix is None: P = [pool.apply_async(rfa_fit_dev, args=(kv, Yfull[oo, ii, :],) + args_const) for oo, ii in plist] else: P = [pool.apply_async(rfa_fit_dev, args=(kv, Yfull[oo, ii, :] - Dmatrix[oo, ii],) + args_const) for oo, ii in plist] R = [pp.get() for pp in P] Asub = [] Bsub = [] Csub = [] Dsub = [] for oo in range(Nout): Ainner = [] Binner = [] Cinner = [] Dinner = [] for ii in range(Nin): # get coeffs pp = Nin * oo + ii cnopt, cdopt = R[pp] # assert plist[pp]==(oo,ii), 'Something wrong in loop' A, B, C, D = tf2ss(cnopt, cdopt) Ainner.append(A) Binner.append(B) Cinner.append(C) Dinner.append(D) # build s-s for each output Asub.append(scalg.block_diag(*Ainner)) Bsub.append(scalg.block_diag(*Binner)) Csub.append(np.block(Cinner)) Dsub.append(np.block(Dinner)) Arfa = scalg.block_diag(*Asub) Brfa = np.vstack(Bsub) Crfa = scalg.block_diag(*Csub) if Dmatrix is not None: Drfa = np.vstack(Dsub) + Dmatrix else: Drfa = np.vstack(Dsub) return Arfa, Brfa, Crfa, Drfa # if __name__ == '__main__': # import libss # import matplotlib.pyplot as plt # # ### common params # ds = 2. / 40. # fs = 1. / ds # fn = fs / 2. # kn = 2. * np.pi * fn # kv = np.linspace(0, kn, 301) # # # build a state-space # cfnum = np.array([4, 1.25, 1.5]) # cfden = np.array([2, .5, 1]) # A, B, C, D = tf2ss(cfnum, cfden) # SS = libss.StateSpace(A, B, C, D, dt=ds) # Cvref = libss.freqresp(SS, kv) # Cvref = Cvref[0, 0, :] # # # Find fitting # Nnum, Nden = 3, 3 # cnopt, cdopt = rfa_fit_dev(kv, Cvref, Nnum, Nden, ds, True, 3, Cfbound=1e3, # OutFull=False, Print=True) # # print('Error coefficients (DLTI):') # print('Numerator: ' + 3 * '%.2e ' % tuple(np.abs(cnopt - cfnum))) # print('Denominator: ' + 3 * '%.2e ' % tuple(np.abs(cnopt - cfnum))) # # # Visualise # Cfit = rfa(cnopt, cdopt, kv, ds) # # fig = plt.figure('Transfer function', (10, 4)) # ax1 = fig.add_subplot(111) # ax1.plot(kv, Cvref.real, color='r', lw=2, ls='-', label=r'ref - real') # ax1.plot(kv, Cvref.imag, color='r', lw=2, ls='--', label=r'ref - imag') # ax1.plot(kv, Cfit.real, color='k', lw=1, ls='-', label=r'RFA - real') # ax1.plot(kv, Cfit.imag, color='k', lw=1, ls='--', label=r'RFA - imag') # ax1.legend(ncol=1, frameon=True, columnspacing=.5, labelspacing=.4) # ax1.grid(color='0.85', linestyle='-') # plt.show() ================================================ FILE: sharpy/linear/src/libsparse.py ================================================ ''' Collect tools to manipulate sparse and/or mixed dense/sparse matrices. author: S. Maraniello date: Dec 2018 Comment: manipulating large linear system may require using both dense and sparse matrices. While numpy/scipy automatically handle most operations between mixed dense/sparse arrays, some (e.g. dot product) require more attention. This library collects methods to handle these situations. Classes: scipy.sparse matrices are wrapped so as to ensure compatibility with numpy arrays upon conversion to dense. - csc_matrix: this is a wrapper of scipy.csc_matrix. - SupportedTypes: types supported for operations - WarningTypes: due to some bugs in scipy (v.1.1.0), sum (+) operations between np.ndarray and scipy.sparse matrices can result in numpy.matrixlib.defmatrix.matrix types. This list contains such undesired types that can result from dense/sparse operations and raises a warning if required. (b) convert these types into numpy.ndarrays. Methods: - dot: handles matrix dot products across different types. - solve: solves linear systems Ax=b with A and b dense, sparse or mixed. - dense: convert matrix to numpy array Warning: - only sparse types into SupportedTypes are supported! To Do: - move these methods into an algebra module? ''' import warnings import numpy as np import scipy.sparse as sparse from scipy.sparse._sputils import upcast_char import scipy.sparse.linalg as spalg # --------------------------------------------------------------------- Classes class csc_matrix(sparse.csc_matrix): ''' Wrapper of scipy.csc_matrix that ensures best compatibility with numpy.ndarray. The following methods have been overwritten to ensure that numpy.ndarray are returned instead of numpy.matrixlib.defmatrix.matrix. - todense - _add_dense Warning: this format is memory inefficient to allocate new sparse matrices. Consider using: - scipy.sparse.lil_matrix, which supports slicing, or - scipy.sparse.coo_matrix, though slicing is not supported :( ''' def __init__(self,arg1, shape=None, dtype=None, copy=False): super().__init__(arg1, shape=shape, dtype=dtype, copy=copy) def todense(self): ''' As per scipy.spmatrix.todense but returns a numpy.ndarray. ''' return super().toarray() def _add_dense(self, other): if other.shape != self.shape: raise ValueError('Incompatible shapes.') dtype = upcast_char(self.dtype.char, other.dtype.char) order = self._swap('CF')[0] result = np.array(other, dtype=dtype, order=order, copy=True) M, N = self._swap(self.shape) y = result if result.flags.c_contiguous else result.T sparse._sparsetools.csr_todense(M, N, self.indptr, self.indices, self.data, y) return result #np.matrix(result, copy=False) SupportedTypes=[np.ndarray,csc_matrix] WarningTypes=[np.matrixlib.defmatrix.matrix] # --------------------------------------------------------------------- Methods def block_dot(A, B): ''' dot product between block matrices. Inputs: A, B: are nested lists of dense/sparse matrices of compatible shape for block matrices product. Empty blocks can be defined with None. (see numpy.block) ''' rA, cA = len(A), len(A[0]) rB, cB = len(B), len(B[0]) for arow,brow in zip(A,B): assert len(brow) == cB,\ 'B rows do not contain the same number of column blocks' assert len(arow) == cA,\ 'A rows do not contain the same number of column blocks' assert cA==rB, 'Columns of A not equal to rows of B!' P=[] for ii in range(rA): prow = cB * [None] for jj in range(cB): # check first that the result will not be None Continue = False for kk in range(cA): if A[ii][kk] is not None and B[kk][jj] is not None: Continue = True break if Continue: prow[jj] = 0. for kk in range(cA): if A[ii][kk] is not None and B[kk][jj] is not None: prow[jj] += dot( A[ii][kk], B[kk][jj] ) P.append(prow) return P def block_matrix_dot_vector(A, v): ''' dot product between block matrix and block vector Inputs: A, v: are nested lists of dense/sparse matrices of compatible shape for block matrices product. Empty blocks can be defined with None. (see numpy.block) ''' rA, cA = len(A), len(A[0]) rv = len(B) for arow in A: assert len(arow) == cA,\ 'A rows do not contain the same number of column blocks' assert cA==rv, 'Columns of A not equal to rows of v!' P=[None]*rA for ii in range(rA): for jj in range(cA): # check first that the result will not be None if A[ii][jj] is not None and B[jj] is not None: P[ii] += dot(A[ii][jj], B[jj]) return P def block_sum(A, B, factA = None, factB = None): ''' dot product between block matrices. Inputs: A, B: are nested lists of dense/sparse matrices of compatible shape for block matrices product. Empty blocks can be defined with None. (see numpy.block) ''' rA, cA = len(A), len(A[0]) rB, cB = len(B), len(B[0]) assert cA==cB and rA==rB, 'Block matrices do not have same size' for arow,brow in zip(A,B): assert len(brow) == cB,\ 'B rows do not contain the same number of column blocks' assert len(arow) == cA,\ 'A rows do not contain the same number of column blocks' P=[] for ii in range(rA): prow = cA * [None] for jj in range(cA): if A[ii][jj] is None: if B[ii][jj] is None: prow[jj] = None else: if factB is None: prow[jj] = B[ii][jj] else: prow[jj] = factB*B[ii][jj] else: if B[ii][jj] is None: if factA is None: prow[jj] = A[ii][jj] else: prow[jj] = factA*A[ii][jj] else: if factA is None and factA is None: prow[jj] = A[ii][jj] + B[ii][jj] elif factA is None: prow[jj] = A[ii][jj] + factB*B[ii][jj] elif factB is None: prow[jj] = factA*A[ii][jj] + B[ii][jj] else: prow[jj] = factA*A[ii][jj] + factB*B[ii][jj] P.append(prow) return P def dot(A,B,type_out=None): ''' Method to compute C = A*B , where * is the matrix product, with dense/sparse/mixed matrices. The format (sparse or dense) of C is specified through 'type_out'. If type_out==None, the output format is sparse if both A and B are sparse, dense otherwise. The following formats are supported: - numpy.ndarray - scipy.csc_matrix ''' # determine types: tA=type(A) tB=type(B) assert tA in SupportedTypes, 'Type of A matrix (%s) not supported'%tA assert tB in SupportedTypes, 'Type of B matrix (%s) not supported'%tB if type_out == None: type_out=tA else: assert type_out in SupportedTypes, 'type_out not supported' # multiply # if tA==float or tb==float: # C = A*B # else: if tA==np.ndarray and tB==csc_matrix: C=(B.transpose()).dot(A.transpose()).transpose() # C=A.dot(B.todense()) else: C=A.dot(B) # format output if tA != type_out: if type_out==csc_matrix: return csc_matrix(C) else: return C.toarray() return C def solve(A,b): ''' Wrapper of numpy.linalg.solve and scipy.sparse.linalg.spsolve for solution of the linear system A x = b. - if A is a dense numpy array np.linalg.solve is called for solution. Note that if B is sparse, this requires convertion to dense. In this case, solution through LU factorisation of A should be considered to exploit the sparsity of B. - if A is sparse, scipy.sparse.linalg.spsolve is used. ''' # determine types: tA=type(A) tB=type(b) assert tA in SupportedTypes, 'Type of A matrix (%s) not supported'%tA assert tB in SupportedTypes, 'Type of B matrix (%s) not supported'%tB # multiply if tA==np.ndarray: if tB==csc_matrix: x=np.linalg.solve(A,b.toarray()) else: x=np.linalg.solve(A,b) else: x=spalg.spsolve(A,b) assert type(x) in SupportedTypes, 'Unexpected output type!' return x def dense(M): ''' If required, converts sparse array to dense. ''' if type(M) == csc_matrix: return np.array(M.toarray()) elif type(M) == csc_matrix: return M.toarray() return M def eye_as(M): ''' Produces an identity matrix as per M, in shape and type ''' tM=type(M) assert tM in SupportedTypes, 'Type %s not supported!'%tM nrows=M.shape[0] assert nrows==M.shape[1], 'Not a square matrix!' if tM==csc_matrix: D=csc_matrix((nrows,nrows)) D.setdiag(1.) elif tM==np.ndarray: D=np.eye(nrows) return D def zeros_as(M): ''' Produces an identity matrix as per M, in shape and type ''' tM=type(M) assert tM in SupportedTypes, 'Type %s not supported!'%tM nrows,ncols=M.shape if tM==csc_matrix: D=csc_matrix((nrows,ncols)) elif tM==np.ndarray: D=np.zeros_like(M) return D # ----------------------------------------------------------------------------- if __name__=='__main__': import unittest class Test_module(unittest.TestCase): ''' Test methods into this module ''' def setUp(self): self.A=np.random.rand(3,4) self.B=np.random.rand(4,2) def test_dense_plus_csc_matrix_type(self): A=self.A Asp=csc_matrix(A) for aa in [A,Asp]: for bb in [A,Asp]: tsum=type(aa+bb) tdiff=type(aa-bb) for tout,strout in zip([tsum,tdiff],['(+)','(-)']): if tout not in SupportedTypes: if tout in WarningTypes: warnings.warn( 'Undesired type (%s) resulting from %s operations between %s and %s types'\ %(tout,strout,type(aa),type(bb))) else: raise NameError( 'Unexpected type (%s) resulting from %s operations between %s and %s types'\ %(tout,strout,type(aa),type(bb))) def test_zeros_as(self): A=np.zeros((4,2)) A1=zeros_as(A) A2=zeros_as(csc_matrix(A)) assert np.max(np.abs(A-A1))<1e-16, 'Error in libsparse.zeros_as' assert np.max(np.abs(A-A2))<1e-16, 'Error in libsparse.zeros_as' def test_eye_as(self): A=np.random.rand(4,4) D0=np.eye(4) D1=eye_as(A) D2=eye_as(csc_matrix(A)) assert np.max(np.abs(D0-D1))<1e-12, 'Error in libsparse.eye_as' assert np.max(np.abs(D0-D2))<1e-12, 'Error in libsparse.eye_as' def test_dot(self): A,B=self.A,self.B C0=np.dot(A,B) # reference C1=dot(A,B) C2=dot(A,csc_matrix(B)) C3=dot(csc_matrix(A),B) C4=dot(csc_matrix(A),csc_matrix(B)) assert np.max(np.abs(C0-C1))<1e-12, 'Error in libsparse.dot' assert np.max(np.abs(C0-C2))<1e-12, 'Error in libsparse.dot' assert np.max(np.abs(C0-C3))<1e-12, 'Error in libsparse.dot' def test_solve(self): A=np.random.rand(4,4) B=np.random.rand(4,2) Asp=csc_matrix(A) Bsp=csc_matrix(B) X0=np.linalg.solve(A,B) X1=solve(A,B) X2=solve(A,Bsp) X3=solve(Asp,B) X4=solve(Asp,Bsp) assert np.max(np.abs(X0-X1))<1e-12, 'Error in libsparse.solve' assert np.max(np.abs(X0-X2))<1e-12, 'Error in libsparse.solve' assert np.max(np.abs(X0-X3))<1e-12, 'Error in libsparse.solve' assert np.max(np.abs(X0-X4))<1e-12, 'Error in libsparse.solve' outprint='Testing libsparse' print('\n' + 70*'-') print((70-len(outprint))*' ' + outprint ) print(70*'-') unittest.main() ================================================ FILE: sharpy/linear/src/libss.py ================================================ """ Linear Time Invariant systems author: S. Maraniello date: 15 Sep 2017 (still basement...) Library of methods to build/manipulate state-space models. The module supports the sparse arrays types defined in libsparse. The module includes: Classes: - StateSpace: provides a class to build DLTI/LTI systems with full and/or sparse matrices and wraps many of the methods in these library. Methods include: - freqresp: wraps the freqresp function - addGain: adds gains in input/output. This is not a wrapper of addGain, as the system matrices are overwritten Methods for state-space manipulation: - couple: feedback coupling. Does not support sparsity - freqresp: calculate frequency response. Supports sparsity. - series: series connection between systems - parallel: parallel connection between systems - SSconv: convert state-space model with predictions and delays - addGain: add gains to state-space model. - join2: merge two state-space models into one. - join: merge a list of state-space models into one. - sum state-space models and/or gains - scale_SS: scale state-space model - simulate: simulates discrete time solution - Hnorm_from_freq_resp: compute H norm of a frequency response - adjust_phase: remove discontinuities from a frequency response Special Models: - SSderivative: produces DLTI of a numerical derivative scheme - SSintegr: produces DLTI of an integration scheme - build_SS_poly: build state-space model with polynomial terms. Filtering: - butter Utilities: - get_freq_from_eigs: clculate frequency corresponding to eigenvalues Comments: - the module supports sparse matrices hence relies on libsparse. to do: - remove unnecessary coupling routines - couple function can handle sparse matrices but only outputs dense matrices - verify if typical coupled systems are sparse - update routine - add method to automatically determine whether to use sparse or dense? """ import copy import warnings import numpy as np import scipy.signal as scsig import scipy.linalg as scalg from sharpy.linear.utils.ss_interface import LinearVector, StateVariable, InputVariable, OutputVariable import scipy.interpolate as scint import h5py import sharpy.utils.h5utils as h5utils # dependency import sharpy.linear.src.libsparse as libsp # ------------------------------------------------------------- Dedicated class class StateSpace: """ Wrap state-space models allocation into a single class and support both full and sparse matrices. The class emulates scipy.signal.ltisys.StateSpaceContinuous scipy.signal.ltisys.StateSpaceDiscrete but supports sparse matrices and other functionalities. Methods: - get_mats: return matrices as tuple - check_types: check matrices types are supported - freqresp: calculate frequency response over range. - addGain: project inputs/outputs - scale: allows scaling a system """ def __init__(self, A, B, C, D, dt=None): """ Allocate state-space model (A,B,C,D). If dt is not passed, a continuous-time system is assumed. """ self.A = A self.B = B self.C = C self.D = D self.dt = dt self.check_types() # vector variable tracking self._input_variables = None # type: LinearVector self._state_variables = None self._output_variables = None # verify dimensions assert self.A.shape == (self.states, self.states), 'A and B rows not matching' assert self.C.shape[1] == self.states, 'A and C columns not matching' assert self.D.shape[0] == self.outputs, 'C and D rows not matching' try: assert self.D.shape[1] == self.inputs, 'B and D columns not matching' except IndexError: assert self.inputs == 1, 'D shape does not match number of inputs' @property def inputs(self): """Number of inputs :math:`m` to the system.""" if self.B.shape.__len__() == 1: return 1 else: return self.B.shape[1] @property def outputs(self): """Number of outputs :math:`p` of the system.""" return self.C.shape[0] @property def states(self): """Number of states :math:`n` of the system.""" return self.A.shape[0] @property def input_variables(self): return self._input_variables @input_variables.setter def input_variables(self, variables): if variables.variable_class is not InputVariable: raise TypeError('LinearVector does not include InputVariable s') if variables.size != self.inputs: raise IndexError('Size of LinearVector of InputVariable s ({:g}) is not the same as the number of ' 'inputs in the ' 'system ({:g})'.format(variables.size, self.inputs)) self._input_variables = variables @property def output_variables(self): return self._output_variables @output_variables.setter def output_variables(self, variables): if variables.variable_class is not OutputVariable: raise TypeError('LinearVector does not include OutputVariable s') if variables.size != self.outputs: raise IndexError('Size of LinearVector of OutputVariable s ({:g}) is not the same as the number of ' 'outputs in the ' 'system ({:g})'.format(variables.size, self.outputs)) self._output_variables = variables @property def state_variables(self): return self._state_variables @state_variables.setter def state_variables(self, variables): if variables.variable_class is not StateVariable: raise TypeError('LinearVector does not include StateVariable s') if variables.size != self.states: raise IndexError('Size of LinearVector of StateVariable s ({:g}) is not the same as the number ' 'of states in the ' 'system ({:g})'.format(variables.size, self.states)) self._state_variables = variables def initialise_variables(self, *variable_tuple, var_type='in'): if var_type == 'in' or var_type == 'input': var_class = InputVariable elif var_type == 'out' or var_type == 'output': var_class = OutputVariable elif var_type == 'state': var_class = StateVariable else: raise TypeError('Unknown variable type') list_of_variables = [] for ith, var_dict in enumerate(variable_tuple): list_of_variables.append(var_class(name=var_dict['name'], size=var_dict['size'], index=var_dict.get('index', ith))) if var_type == 'in' or var_type == 'input': self._input_variables = LinearVector(list_of_variables) elif var_type == 'out' or var_type == 'output': self._output_variables = LinearVector(list_of_variables) elif var_type == 'state': self._state_variables = LinearVector(list_of_variables) def __repr__(self): str_out = '' str_out += 'State-space object\n' str_out += 'States: {:g}\n'.format(self.states) str_out += 'Inputs: {:g}\n'.format(self.inputs) str_out += 'Outputs: {:g}\n'.format(self.outputs) if self.dt is not None: str_out += 'dt: {:g}'.format(self.dt) if self.input_variables is not None: str_out += '\nInput Variables:\n' + str(self.input_variables) if self.state_variables is not None: str_out += 'State Variables:\n' + str(self.state_variables) if self.output_variables is not None: str_out += 'Output Variables:\n' + str(self.output_variables) return str_out def check_types(self): assert type(self.A) in libsp.SupportedTypes, \ 'Type of A matrix (%s) not supported' % type(self.A) assert type(self.B) in libsp.SupportedTypes, \ 'Type of B matrix (%s) not supported' % type(self.B) assert type(self.C) in libsp.SupportedTypes, \ 'Type of C matrix (%s) not supported' % type(self.C) assert type(self.D) in libsp.SupportedTypes, \ 'Type of D matrix (%s) not supported' % type(self.D) def get_mats(self): return self.A, self.B, self.C, self.D def freqresp(self, wv): """ Calculate frequency response over frequencies wv Note: this wraps frequency response function. """ dlti = True if self.dt is None: dlti = False return freqresp(self, wv, dlti=dlti) def addGain(self, K, where): """ Projects input u or output y the state-space system through the gain matrix K. The input 'where' determines whether inputs or outputs are projected as: - where='in': inputs are projected such that: u_new -> u=K*u_new -> SS -> y => u_new -> SSnew -> y - where='out': outputs are projected such that: u -> SS -> y -> y_new=K*y => u -> SSnew -> ynew Args: K (np.array or Gain): gain matrix or Gain object where (str): ``in`` or ``out`` Warning: This is not a wrapper of the addGain method in this module, as the state-space matrices are directly overwritten. """ assert where in ['in', 'out'], \ 'Specify whether gains are added to input or output' with_vars = False if isinstance(K, Gain): gain = K K = K.value with_vars = True if where == 'in': self.B = libsp.dot(self.B, K) self.D = libsp.dot(self.D, K) if with_vars: self._input_variables = gain.input_variables if where == 'out': self.C = libsp.dot(K, self.C) self.D = libsp.dot(K, self.D) if with_vars: self._output_variables = gain.output_variables def scale(self, input_scal=1., output_scal=1., state_scal=1.): """ Given a state-space system, scales the equations such that the original state, input and output, (x, u and y), are substituted by xad=x/state_scal uad=u/input_scal yad=y/output_scal The entries input_scal/output_scal/state_scal can be: - floats: in this case all input/output are scaled by the same value - lists/arrays of length Nin/Nout: in this case each dof will be scaled by a different factor If the original system has form: xnew=A*x+B*u y=C*x+D*u the transformation is such that: xnew=A*x+(B*uref/xref)*uad yad=1/yref( C*xref*x+D*uref*uad ) """ scale_SS(self, input_scal, output_scal, state_scal, byref=True) def project(self, wt, v): """ Given 2 transformation matrices, ``(WT, V)`` of shapes ``(Nk, self.states)`` and ``(self.states, Nk)`` respectively, this routine projects the state space model states according to: .. math:: Anew = WT A V \\ Bnew = WT B \\ Cnew = C V \\ Dnew = D \\ The projected model has the same number of inputs/outputs as the original one, but Nk states. Args: wt (Gain or np.ndarray): Left projection matrix v (Gain or np.ndarray): Righty projection matrix """ if isinstance(wt, Gain) and isinstance(v, Gain): self.A = libsp.dot(wt.value, libsp.dot(self.A, v.value)) self.B = libsp.dot(wt.value, self.B) self.C = libsp.dot(self.C, v.value) self.state_variables = LinearVector.transform(v.input_variables, to_type=StateVariable) else: self.A = libsp.dot(wt, libsp.dot(self.A, v)) self.B = libsp.dot(wt, self.B) self.C = libsp.dot(self.C, v) def truncate(self, N): """ Retains only the first N states. """ assert N > 0 and N <= self.states, 'N must be in [1,self.states]' self.A = self.A[:N, :N] self.B = self.B[:N, :] self.C = self.C[:, :N] # self.states = N # No need to update, states is now a property. NG 26/3/19 def max_eig(self): """ Returns most unstable eigenvalue """ ev = np.linalg.eigvals(self.A) if self.dt is None: return np.max(ev.real) else: return np.max(np.abs(ev)) def eigvals(self): """ Returns: np.ndarray: Eigenvalues of the system """ if self.dt: return eigvals(self.A, dlti=True) else: return eigvals(self.A, dlti=False) def disc2cont(self): r""" Transform a discrete time system to a continuous time system using a bilinear (Tustin) transformation. Wrapper of :func:`~sharpy.linear.src.libss.disc2cont` """ if self.dt: self = disc2cont(self) def retain_inout_channels(self, retain_channels, where): """ Retain selected input or output channels only. Args: retain_channels (list): List of channels to retain where (str): ``in`` or ``out`` for input/output channels """ retain_inout_channels(self, retain_channels, where) def summary(self): msg = 'State-space system\nStates: %g\nInputs: %g\nOutputs: %g\n' % (self.states, self.inputs, self.outputs) return msg def transfer_function_evaluation(self, s): r""" Returns the transfer function of the system evaluated at :math:`s\in\mathbb{C}`. Args: s (complex): Point in the complex plane at which to evaluate the transfer function. Returns: np.ndarray: Transfer function evaluated at :math:`s`. """ a, b, c, d = self.get_mats() n = a.shape[0] return c.dot(scalg.inv(s * np.eye(n) - a)).dot(b) + d def save(self, path): """Save state-space object to h5 file""" with h5py.File(path, 'w') as f: f.create_dataset('a', data=self.A) f.create_dataset('b', data=self.B) f.create_dataset('c', data=self.C) f.create_dataset('d', data=self.D) if self.dt: f.create_dataset('dt', data=self.dt) if self.input_variables is not None: self.input_variables.add_to_h5_file(f) self.output_variables.add_to_h5_file(f) self.state_variables.add_to_h5_file(f) @classmethod def load_from_h5(cls, h5_file_name): """ Loads a state-space object from an h5 file, including variable information Args: h5_file_name (str): Path to file Returns: StateSpace: loaded state-space from file """ with h5py.File(h5_file_name, 'r') as f: data_dict = h5utils.load_h5_in_dict(f) new_ss = cls(data_dict['a'], data_dict['b'], data_dict['c'], data_dict['d'], dt=data_dict.get('dt')) input_variables = data_dict.get('InputVariable') if input_variables is not None: new_ss.input_variables = LinearVector.load_from_h5_file('InputVariable', data_dict['InputVariable']) new_ss.output_variables = LinearVector.load_from_h5_file('OutputVariable', data_dict['OutputVariable']) new_ss.state_variables = LinearVector.load_from_h5_file('StateVariable', data_dict['StateVariable']) return new_ss else: return new_ss def remove_inputs(self, *input_remove_list): """ Removes inputs through their variable names. Needs that the ``StateSpace`` attribute ``input_variables`` is defined. Args: input_remove_list (list(str)): List of inputs to remove """ if self.input_variables is None: raise AttributeError('No input variables have been defined for the current state-space object. Define ' 'some variables prior to using the remove_inputs() method.') self.input_variables.remove(*input_remove_list) i = 0 retain_input_array = None for variable in self.input_variables: if i == 0: retain_input_array = variable.cols_loc else: retain_input_array = np.hstack((retain_input_array, variable.cols_loc)) i += 1 if retain_input_array is not None: if type(self.B) is libsp.csc_matrix: self.B = libsp.csc_matrix(self.B[:, retain_input_array]) self.D = libsp.csc_matrix(self.D[:, retain_input_array]) else: self.B = self.B[:, retain_input_array] self.D = self.D[:, retain_input_array] self.input_variables.update_locations() def remove_outputs(self, *output_remove_list): """ Removes outputs through their variable names. Needs that the ``StateSpace`` attribute ``output_variables`` is defined. Args: output_remove_list (list(str)): List of outputs to remove """ if self.output_variables is None: raise AttributeError('No output variables have been defined for the current state-space object. Define ' 'some variables prior to using the remove_outputs() method.') new_outputs = 0 for variable in self.output_variables: if variable.name not in output_remove_list: new_outputs += variable.size out_gain = np.zeros((new_outputs, self.outputs)) worked_outputs = 0 for variable in self.output_variables: if variable.name not in output_remove_list: index = variable.rows_loc out_gain[worked_outputs:worked_outputs + variable.size, index] = np.eye(variable.size) worked_outputs += variable.size if new_outputs != self.outputs: if type(self.B) is libsp.csc_matrix: self.C = libsp.csc_matrix(out_gain.dot(self.C)) self.D = libsp.csc_matrix(out_gain.dot(self.D)) else: self.C = out_gain.dot(self.C) self.D = out_gain.dot(self.D) self.output_variables.remove(*output_remove_list) self.output_variables.update_locations() @classmethod def from_scipy(cls, scipy_ss): """ Transforms a ``scipy.signal.lti`` or dlti into a StateSpace class Args: scipy_ss (scipy.signal.ltisys.StateSpaceContinous or scipy.signal.ltisys.StateSpaceDiscrete): Scipy State Space object. Returns: StateSpace: SHARPy state space object """ a = scipy_ss.A b = scipy_ss.B c = scipy_ss.C d = scipy_ss.D return cls(a, b, c, d, dt=scipy_ss.dt) class Gain: def __init__(self, value, input_vars=None, output_vars=None): self.value = value self._input_variables = None self._output_variables = None if input_vars is not None: self.input_variables = input_vars if output_vars is not None: self.output_variables = output_vars @property def input_variables(self): return self._input_variables @input_variables.setter def input_variables(self, variables): if variables.variable_class is not InputVariable: raise TypeError('LinearVector does not include InputVariable s') if variables.size != self.inputs: raise IndexError('Size of LinearVector of InputVariable s ({:g}) is not the same as the number of ' 'inputs in the ' 'system ({:g})'.format(variables.size, self.inputs)) self._input_variables = variables @property def output_variables(self): return self._output_variables @output_variables.setter def output_variables(self, variables): if variables.variable_class is not OutputVariable: raise TypeError('LinearVector does not include OutputVariable s') if variables.size != self.outputs: raise IndexError('Size of LinearVector of OutputVariable s ({:g}) is not the same as the number of ' 'outputs in the ' 'system ({:g})'.format(variables.size, self.outputs)) self._output_variables = variables @property def inputs(self): """Number of inputs :math:`m` to the system.""" if self.value.shape.__len__() == 1: return 1 else: return self.value.shape[1] @property def outputs(self): """Number of outputs :math:`p` of the gain.""" return self.value.shape[0] def dot(self, elem): """ Dot product of two Gains Args: elem (np.array or Gain): Returns: np.array or Gain: new matrix/Gain containing the dot product """ if type(elem) is Gain: LinearVector.check_connection(elem.output_variables, self.input_variables) new_gain_value = libsp.dot(self.value, elem.value) return Gain(new_gain_value, input_vars=elem.input_variables.copy(), output_vars=self.output_variables.copy()) else: return self.value.dot(elem) def __repr__(self): str_out = '' str_out += 'Gain object\n' str_out += 'Inputs: {:g}\n'.format(self.inputs) str_out += 'Outputs: {:g}\n'.format(self.outputs) if self.input_variables is not None: str_out += '\nInput Variables:\n' + str(self.input_variables) if self.output_variables is not None: str_out += 'Output Variables:\n' + str(self.output_variables) return str_out def transpose(self): """ Transposes the gain, such that the inputs become the outputs and vice-versa. """ if self.input_variables is not None: temp_input_var = self.input_variables.copy() input_variables = LinearVector.transform(self.output_variables, to_type=InputVariable) output_variables = LinearVector.transform(temp_input_var, to_type=OutputVariable) return Gain(self.value.T, input_vars=input_variables, output_vars=output_variables) else: return Gain(self.value.T) @property def T(self): return self.transpose() def copy(self): if self.input_variables is not None: return Gain(self.value, input_vars=self.input_variables.copy(), output_vars=self.output_variables.copy()) else: return Gain(self.value) def save(self, path): """Save gain object to h5 file""" with h5py.File(path, 'w') as f: f.create_dataset('gain', data=self.value) if self.input_variables is not None: self.input_variables.add_to_h5_file(f) self.output_variables.add_to_h5_file(f) def add_as_group_to_h5(self, h5_file_handle, group_name): """ Adds gain to an h5 file handle Args: h5_file_handle (h5py.File): writeable h5 file handle group_name (str): Desired group name to save gain in h5 """ gain_group = h5_file_handle.create_group(group_name) gain_group.create_dataset(name='gain', data=self.value) if self.input_variables is not None: self.input_variables.add_to_h5_file(gain_group) self.output_variables.add_to_h5_file(gain_group) @classmethod def load_from_h5(cls, h5_file_name): """ Returns a gain object from an .h5 file Args: h5_file_name (str): Path to h5 file Returns: Gain: instance of a Gain """ with h5py.File(h5_file_name, 'r') as f: data_dict = h5utils.load_h5_in_dict(f) return cls.load_from_dict(data_dict) @classmethod def load_from_dict(cls, data_dict): """ Returns a Gain from a dictionary of data, useful for loading from a group of gains in a single .h5 file Args: data_dict (dict): Dictionary with keys: ``gain`` and (if available) ``InputVariable`` and ``OutputVariable``. Returns: Gain: instance of Gain """ input_variables = data_dict.get('InputVariable') if input_variables is not None: input_variables = LinearVector.load_from_h5_file('InputVariable', data_dict['InputVariable']) output_variables = LinearVector.load_from_h5_file('OutputVariable', data_dict['OutputVariable']) return cls(data_dict['gain'], input_vars=input_variables, output_vars=output_variables) else: return cls(data_dict['gain']) @classmethod def save_multiple_gains(cls, h5_file_name, *gains_names_tuple): """ Saves multiple gains to a single h5 file Args: h5_file_name (str): Path to h5 file *gains_names_tuple (tuple): ``(gain_name (str), gain(Gain))`` tuples to save. The gain name will be the name given on the h5 file """ with h5py.File(h5_file_name, 'w') as f: for name, gain in gains_names_tuple: gain.add_as_group_to_h5(f, name) @classmethod def load_multiple_gains(cls, h5_file_name): """ Loads multiple gains from a single h5 file Args: h5_file_name (str): Path to h5 file Returns: dict: Dictionary of loaded gains in a gain_name: Gain dictionary """ with h5py.File(h5_file_name, 'r') as f: data_dict = h5utils.load_h5_in_dict(f) out_gains = {} for gain_name, gain_data in data_dict.items(): out_gains[gain_name] = cls.load_from_dict(gain_data) return out_gains class ss_block(): """ State-space model in block form. This class has the same purpose as "StateSpace", but the A, B, C, D are allocated in the form of nested lists. The format is similar to the one used in numpy.block but: 1. Block matrices can contain both dense and sparse matrices 2. Empty blocks are defined through None type Methods: - remove_block: drop one of the blocks from the s-s model - addGain: project inputs/outputs - project: project state """ def __init__(self, A, B, C, D, S_states, S_inputs, S_outputs, dt=None): """ Allocate state-space model (A,B,C,D) in block form starting from nested lists of full/sparse matrices (as per numpy.block). Input: - A, B, C, D: lists of matrices defining the state-space model. - S_states, S_inputs, S_outputs: lists with dimensions of of each block representing the states, inputs and outputs of the model. - dt: time-step. In None, a continuous-time system is assumed. """ self.A = A self.B = B self.C = C self.D = D self.dt = dt self.S_u = S_inputs self.S_y = S_outputs self.S_x = S_states # determine number of blocks self.blocks_u = len(S_inputs) self.blocks_y = len(S_outputs) self.blocks_x = len(S_states) # determine inputs/outputs/states self.inputs = sum(S_inputs) self.outputs = sum(S_outputs) self.states = sum(S_states) self.check_sizes() def check_sizes(self): pass def remove_block(self, where, index): """ Remove a block from either inputs or outputs. Inputs: - where = {'in', 'out'}: determined whether to remove inputs or outputs - index: index of block to remove """ assert where in ['in', 'out'], "'where' must be equal to {'in', 'out'}" if where == 'in': for ii in range(self.blocks_x): del self.B[ii][index] for ii in range(self.blocks_y): del self.D[ii][index] if where == 'out': for ii in range(self.blocks_y): del self.C[ii] del self.D[ii] def addGain(self, K, where): """ Projects input u or output y the state-space system through the gain block matrix K. The input 'where' determines whether inputs or outputs are projected as: - where='in': inputs are projected such that: u_new -> u=K*u_new -> SS -> y => u_new -> SSnew -> y - where='out': outputs are projected such that: u -> SS -> y -> y_new=K*y => u -> SSnew -> ynew Input: K must be a list of list of matrices. The size of K must be compatible with either B or C for block matrix product. """ assert where in ['in', 'out'], \ 'Specify whether gains are added to input or output' rows, cols = self.get_sizes(K) if where == 'in': self.B = libsp.block_dot(self.B, K) self.D = libsp.block_dot(self.D, K) self.S_u = cols self.blocks_u = len(cols) self.inputs = sum(cols) if where == 'out': self.C = libsp.block_dot(K, self.C) self.D = libsp.block_dot(K, self.D) self.S_y = rows self.blocks_y = len(rows) self.outputs = sum(rows) def get_sizes(self, M): """ Get the size of each block in M. """ rM, cM = len(M), len(M[0]) rows = rM * [None] cols = cM * [None] for ii in range(rM): for jj in range(cM): if M[ii][jj] is not None: rhere, chere = M[ii][jj].shape if rows[ii] is None: # allocate rows[ii] = rhere else: # check assert rows[ii] == rhere, \ 'Block (%d,%d) has inconsistent size with other in same row!' % (ii, jj) if cols[jj] is None: # allocate cols[jj] = chere else: # check assert cols[jj] == chere, \ 'Block (%d,%d) has inconsistent size with other in same column!' % (ii, jj) return rows, cols def project(self, WT, V, by_arrays=True, overwrite=False): """ Given 2 transformation matrices, (W,V) of shape (Nk,self.states), this routine projects the state space model states according to: Anew = W^T A V Bnew = W^T B Cnew = C V Dnew = D The projected model has the same number of inputs/outputs as the original one, but Nk states. Inputs: - WT = W^T - V = V - by_arrays: if True, W, V are either numpy.array or sparse matrices. If False, they are block matrices. - overwrite: if True, overwrites the A, B, C matrices """ if by_arrays: # transform to block structures II0 = 0 Vblock = [] WTblock = [[]] for ii in range(self.blocks_x): iivec = range(II0, II0 + self.S_x[ii]) Vblock.append([V[iivec, :]]) WTblock[0].append(WT[:, iivec]) II0 += self.S_x[ii] else: Vblock = V WTblock = WT if overwrite: self.A = libsp.block_dot(WTblock, libsp.block_dot(self.A, Vblock)) self.B = libsp.block_dot(WTblock, self.B) self.C = libsp.block_dot(self.C, Vblock) else: return (libsp.block_dot(WTblock, libsp.block_dot(self.A, Vblock)), libsp.block_dot(WTblock, self.B), libsp.block_dot(self.C, Vblock)) def solve_step(self, xn, un): # TODO: add options about predictor ... xn1 = libsp.block_sum(libsp.block_dot(self.A, xn), libsp.block_dot(self.B, un)) yn = libsp.block_sum(libsp.block_dot(self.C, xn), libsp.block_dot(self.D, un)) return xn1, yn def get_mats(self): A = np.zeros((self.states, self.states)) B = np.zeros((self.states, self.inputs)) C = np.zeros((self.outputs, self.states)) D = np.zeros((self.outputs, self.inputs)) iloc = 0 for i in range(self.blocks_x): jloc = 0 for j in range(self.blocks_x): if not self.A[i][j] is None: if type(self.A[i][j]) == libsp.csc_matrix: A[iloc:iloc+self.S_x[i], jloc:jloc+self.S_x[j]] = self.A[i][j].todense() else: A[iloc:iloc+self.S_x[i], jloc:jloc+self.S_x[j]] = self.A[i][j].copy() jloc += self.S_x[j] iloc += self.S_x[i] iloc = 0 for i in range(self.blocks_x): jloc = 0 for j in range(self.blocks_u): if not self.B[i][j] is None: # print(i, j, iloc, jloc, self.S_x[i], self.S_u[j], self.B[i][j].shape) # print(iloc, iloc+self.S_x[i], jloc, jloc+self.S_u[j]) if type(self.B[i][j]) == libsp.csc_matrix: B[iloc:iloc+self.S_x[i], jloc:jloc+self.S_u[j]] = self.B[i][j].todense() else: B[iloc:iloc+self.S_x[i], jloc:jloc+self.S_u[j]] = self.B[i][j].copy() jloc += self.S_u[j] iloc += self.S_x[i] iloc = 0 for i in range(self.blocks_y): jloc = 0 for j in range(self.blocks_x): if not self.C[i][j] is None: if type(self.C[i][j]) == libsp.csc_matrix: C[iloc:iloc+self.S_y[i], jloc:jloc+self.S_x[j]] = self.C[i][j].todense() else: C[iloc:iloc+self.S_y[i], jloc:jloc+self.S_x[j]] = self.C[i][j].copy() jloc += self.S_x[j] iloc += self.S_y[i] iloc = 0 for i in range(self.blocks_y): jloc = 0 for j in range(self.blocks_u): if not self.D[i][j] is None: if type(self.D[i][j]) == libsp.csc_matrix: D[iloc:iloc+self.S_y[i], jloc:jloc+self.S_u[j]] = self.D[i][j].todense() else: D[iloc:iloc+self.S_y[i], jloc:jloc+self.S_u[j]] = self.D[i][j].copy() jloc += self.S_u[j] iloc += self.S_y[i] return A, B, C, D # ---------------------------------------- Methods for state-space manipulation def project(ss_here, WT, V): """ Given 2 transformation matrices, (WT,V) of shapes (Nk,self.states) and (self.states,Nk) respectively, this routine returns a projection of the state space ss_here according to: Anew = WT A V Bnew = WT B Cnew = C V Dnew = D The projected model has the same number of inputs/outputs as the original one, but Nk states. """ Ap = libsp.dot(WT, libsp.dot(ss_here.A, V)) Bp = libsp.dot(WT, ss_here.B) Cp = libsp.dot(ss_here.C, V) return StateSpace(Ap, Bp, Cp, ss_here.D, ss_here.dt) def couple(ss01, ss02, K12, K21, out_sparse=False): """ Couples 2 dlti systems ss01 and ss02 through the gains K12 and K21, where K12 transforms the output of ss02 into an input of ss01. Other inputs: - out_sparse: if True, the output system is stored as sparse (not recommended) """ if ss01.dt is None and ss02.dt is None: pass else: try: assert np.abs(ss01.dt - ss02.dt) < 1e-10 * ss01.dt, 'Time-steps not matching!' except TypeError: raise TypeError('One of the systems to couple is discrete and the other continuous') if ss01.input_variables is not None and ss02.input_variables is not None \ and isinstance(K12, Gain) and isinstance(K21, Gain): with_enhanced_vars = True LinearVector.check_connection(K12.output_variables, ss01.input_variables) LinearVector.check_connection(ss02.output_variables, K12.input_variables) LinearVector.check_connection(K21.output_variables, ss02.input_variables) LinearVector.check_connection(ss01.output_variables, K21.input_variables) K21 = K21.value K12 = K12.value else: with_enhanced_vars = False assert K12.shape == (ss01.inputs, ss02.outputs), \ 'Gain K12 shape not matching with systems number of inputs/outputs' assert K21.shape == (ss02.inputs, ss01.outputs), \ 'Gain K21 shape not matching with systems number of inputs/outputs' A1, B1, C1, D1 = ss01.get_mats() A2, B2, C2, D2 = ss02.get_mats() # extract size Nx1, Nu1 = B1.shape Ny1 = C1.shape[0] Nx2, Nu2 = B2.shape Ny2 = C2.shape[0] # terms to invert maxD1 = np.max(np.abs(D1)) maxD2 = np.max(np.abs(D2)) if maxD1 < 1e-32: pass if maxD2 < 1e-32: pass # compute self-influence gains K11 = libsp.dot(K12, libsp.dot(D2, K21)) K22 = libsp.dot(K21, libsp.dot(D1, K12)) # left hand side terms L1 = libsp.dot(-K11, D1) L2 = libsp.dot(-K22, D2) L1 += libsp.eye_as(L1) L2 += libsp.eye_as(L2) # coupling terms cpl_12 = libsp.solve(L1, K12) cpl_21 = libsp.solve(L2, K21) cpl_11 = libsp.dot(cpl_12, libsp.dot(D2, K21)) cpl_22 = libsp.dot(cpl_21, libsp.dot(D1, K12)) # Build coupled system if out_sparse: raise NameError('out_sparse=True not supported yet (verify if worth it first).') else: A = np.block([ [libsp.dense(A1 + libsp.dot(libsp.dot(B1, cpl_11), C1)), libsp.dense(libsp.dot(libsp.dot(B1, cpl_12), C2))], [libsp.dense(libsp.dot(libsp.dot(B2, cpl_21), C1)), libsp.dense(A2 + libsp.dot(libsp.dot(B2, cpl_22), C2))]]) C = np.block([ [libsp.dense(C1 + libsp.dot(libsp.dot(D1, cpl_11), C1)), libsp.dense(libsp.dot(libsp.dot(D1, cpl_12), C2))], [libsp.dense(libsp.dot(libsp.dot(D2, cpl_21), C1)), libsp.dense(C2 + libsp.dot(libsp.dot(D2, cpl_22), C2))]]) B = np.block([ [libsp.dense(B1 + libsp.dot(libsp.dot(B1, cpl_11), D1)), libsp.dense(libsp.dot(libsp.dot(B1, cpl_12), D2))], [libsp.dense(libsp.dot(libsp.dot(B2, cpl_21), D1)), libsp.dense(B2 + libsp.dot(libsp.dot(B2, cpl_22), D2))]]) D = np.block([ [libsp.dense(D1 + libsp.dot(libsp.dot(D1, cpl_11), D1)), libsp.dense(libsp.dot(libsp.dot(D1, cpl_12), D2))], [libsp.dense(libsp.dot(libsp.dot(D2, cpl_21), D1)), libsp.dense(D2 + libsp.dot(libsp.dot(D2, cpl_22), D2))]]) coupled_ss = StateSpace(A, B, C, D, dt=ss01.dt) if with_enhanced_vars: coupled_ss.state_variables = LinearVector.merge(ss01.state_variables, ss02.state_variables) coupled_ss.input_variables = LinearVector.merge(ss01.input_variables, ss02.input_variables) coupled_ss.output_variables = LinearVector.merge(ss01.output_variables, ss02.output_variables) return coupled_ss def disc2cont(sys): r""" Transform a discrete time system to a continuous time system using a bilinear (Tustin) transformation. Given a discrete time system with time step :math:`\Delta T`, the equivalent continuous time system is given by: .. math:: \bar{A} &= \omega_0(A-I)(I + A)^{-1} \\ \bar{B} &= \sqrt{2\omega_0}(I+A)^{-1}B \\ \bar{C} &= \sqrt{2\omega_0}C(I+A)^{-1} \\ \bar{D} &= D - C(I+A)^{-1}B where :math:`\omega_0 = \frac{2}{\Delta T}`. References: MIT OCW 6.245 Args: sys (libss.StateSpace): SHARPy discrete-time state-space object. Returns: libss.StateSpace: Converted continuous-time state-space object. """ assert sys.dt is not None, 'System to transform is not a discrete-time system.' n = sys.A.shape[0] eye = np.eye(n) eye_a_inv = np.linalg.inv(sys.A + eye) omega_0 = 2 / sys.dt a = omega_0 * (sys.A - eye).dot(eye_a_inv) b = np.sqrt(2 * omega_0) * eye_a_inv.dot(sys.B) c = np.sqrt(2 * omega_0) * sys.C.dot(eye_a_inv) d = sys.D - sys.C.dot(eye_a_inv.dot(sys.B)) sys_ct = StateSpace(a, b, c, d) if sys.input_variables is not None: sys_ct.input_variables = sys.input_variables sys_ct.state_variables = sys.state_variables sys_ct.output_variables = sys.output_variables return sys_ct def retain_inout_channels(sys, retain_channels, where): """ Retain selected input or output channels only. Args: retain_channels (list): List of channels to retain where (str): ``in`` or ``out`` for input/output channels Returns: StateSpace: Updated state-space object """ retain_m = len(retain_channels) # new number of in/out if where == 'in': m = sys.inputs # current number of in/out gain_input_vars = sys.input_variables gain_output_vars = LinearVector.transform(sys.input_variables, to_type=OutputVariable) elif where == 'out': m = sys.outputs gain_input_vars = LinearVector.transform(sys.output_variables, to_type=InputVariable) gain_output_vars = sys.output_variables.copy() else: raise NameError('Argument ``where`` can only be ``in`` or ``out``.') gain_matrix = np.zeros((retain_m, m)) for ith, channel in enumerate(retain_channels): gain_matrix[ith, channel] = 1 # Go through variables... for var in gain_input_vars: n_vars = np.sum( (np.array(retain_channels) < var.end_position) * (np.array(retain_channels) >= var.first_position)) if n_vars == 0: gain_output_vars.remove(var.name) else: gain_output_vars.modify(var.name, size=n_vars) gain_output_vars.update_indices() gain_output_vars.update_locations() gain_matrix = Gain(gain_matrix, input_vars=gain_input_vars, output_vars=gain_output_vars) if where == 'in': sys.addGain(gain_matrix.transpose(), where='in') elif where == 'out': sys.addGain(gain_matrix, where='out') else: raise NameError('Argument ``where`` can only be ``in`` or ``out``.') return sys def freqresp(SS, wv, dlti=True): """ In-house frequency response function supporting dense/sparse types Inputs: - SS: instance of StateSpace class, or scipy.signal.StateSpace* - wv: frequency range - dlti: True if discrete-time system is considered. Outputs: - Yfreq[outputs,inputs,len(wv)]: frequency response over wv Warnings: - This function may not be very efficient for dense matrices (as A is not reduced to upper Hessenberg form), but can exploit sparsity in the state-space matrices. """ assert type(SS) == StateSpace, \ 'Type %s of state-space model not supported. Use libss.StateSpace instead!' % type(SS) SS.check_types() if hasattr(SS, 'dt') and dlti: Ts = SS.dt wTs = Ts * wv zv = np.cos(wTs) + 1.j * np.sin(wTs) else: # print('Assuming a continuous time system') zv = 1.j * wv Nx = SS.A.shape[0] Ny = SS.D.shape[0] try: Nu = SS.B.shape[1] except IndexError: Nu = 1 Nw = len(wv) Yfreq = np.empty((Ny, Nu, Nw,), dtype=np.complex_) Eye = libsp.eye_as(SS.A) for ii in range(Nw): sol_cplx = libsp.solve(zv[ii] * Eye - SS.A, SS.B) Yfreq[:, :, ii] = libsp.dot(SS.C, sol_cplx, type_out=np.ndarray) + SS.D return Yfreq def series(SS01, SS02): r""" Connects two state-space blocks in series. If these are instances of DLTI state-space systems, they need to have the same type and time-step. If the input systems are sparse, they are converted to dense. The connection is such that: .. math:: u \rightarrow \mathsf{SS01} \rightarrow \mathsf{SS02} \rightarrow y \Longrightarrow u \rightarrow \mathsf{SStot} \rightarrow y where the state vector :math:`x` is :math:`[x_1, x_2]`. Args: SS01 (libss.StateSpace): State Space 1 instance. Can be DLTI/CLTI, dense or sparse. SS02 (libss.StateSpace): State Space 2 instance. Can be DLTI/CLTI, dense or sparse. Returns libss.StateSpace: Combined state space system in series in dense format. """ if type(SS01) is not type(SS02): raise TypeError('The two input systems are not of the same type') if SS01.dt != SS02.dt: raise NameError('DLTI systems do not have the same time-step. SS01 dt={:f}, SS02 dt={:f}'.format( SS01.dt, SS02.dt)) # check series connection if SS01.output_variables is not None and SS02.input_variables is not None: LinearVector.check_connection(SS01.output_variables, SS02.input_variables) # for i_var in range(SS01.output_variables.num_variables): # out1 = SS01.output_variables[i_var] # in2 = SS02.input_variables[i_var] # if out1.name != in2.name: # raise NameError('Series coupling outputs1 and inputs2 have different names') # if not (out1.rows_loc == in2.cols_loc).all: # raise IndexError('Series coupling. Output1 channels do not line up with input2 channels.') # determine size of total system Nst01, Nst02 = SS01.states, SS02.states Nst = Nst01 + Nst02 Nin = SS01.inputs Nout = SS02.outputs if SS01.outputs != SS02.inputs: raise ValueError('SS01 outputs not equal to SS02 inputs,\nSS01={:s}\nSS02={:s}'.format(str(SS01), str(SS02))) # Build A matrix A = np.zeros((Nst, Nst)) A[:Nst01, :Nst01] = libsp.dense(SS01.A) A[Nst01:, Nst01:] = libsp.dense(SS02.A) A[Nst01:, :Nst01] = libsp.dense(libsp.dot(SS02.B, SS01.C)) # Build the rest B = np.concatenate((libsp.dense(SS01.B), libsp.dense(libsp.dot(SS02.B, SS01.D))), axis=0) C = np.concatenate((libsp.dense(libsp.dot(SS02.D, SS01.C)), libsp.dense(SS02.C)), axis=1) D = libsp.dense(libsp.dot(SS02.D, SS01.D)) SStot = StateSpace(A, B, C, D, dt=SS01.dt) SStot.input_variables = SS01.input_variables try: SStot.state_variables = LinearVector.merge(SS01.state_variables, SS02.state_variables) except AttributeError: SStot.state_variables = None SStot.output_variables = SS02.output_variables return SStot def parallel(SS01, SS02): """ Returns the sum (or parallel connection of two systems). Given two state-space models with the same output, but different input: u1 --> SS01 --> y u2 --> SS02 --> y """ if type(SS01) is not type(SS02): raise NameError('The two input systems need to have the same size!') if SS01.dt != SS02.dt: raise NameError('DLTI systems do not have the same time-step!') Nout = SS02.outputs if Nout != SS01.outputs: raise NameError('DLTI systems need to have the same number of output!') # if type(SS01) is control.statesp.StateSpace: # SStot=control.parallel(SS01,SS02) # else: # determine size of total system Nst01, Nst02 = SS01.states, SS02.states Nst = Nst01 + Nst02 Nin01, Nin02 = SS01.inputs, SS02.inputs Nin = Nin01 + Nin02 # Build A,B matrix A = np.zeros((Nst, Nst)) A[:Nst01, :Nst01] = SS01.A A[Nst01:, Nst01:] = SS02.A B = np.zeros((Nst, Nin)) B[:Nst01, :Nin01] = SS01.B B[Nst01:, Nin01:] = SS02.B # Build the rest C = np.block([SS01.C, SS02.C]) D = np.block([SS01.D, SS02.D]) SStot = scsig.dlti(A, B, C, D, dt=SS01.dt) return SStot def SSconv(A, B0, B1, C, D, Bm1=None): r""" Convert a DLTI system with prediction and delay of the form: .. math:: \mathbf{x}_{n+1} &= \mathbf{A\,x}_n + \mathbf{B_0\,u}_n + \mathbf{B_1\,u}_{n+1} + \mathbf{B_{m1}\,u}_{n-1} \\ \mathbf{y}_n &= \mathbf{C\,x}_n + \mathbf{D\,u}_n into the state-space form: .. math:: \mathbf{h}_{n+1} &= \mathbf{A_h\,h}_n + \mathbf{B_h\,u}_n \\ \mathbf{y}_n &= \mathbf{C_h\,h}_n + \mathbf{D_h\,u}_n If :math:`\mathbf{B_{m1}}` is ``None``, the original state is retrieved through .. math:: \mathbf{x}_n = \mathbf{h}_n + \mathbf{B_1\,u}_n and only the :math:`\mathbf{B}` and :math:`\mathbf{D}` matrices are modified. If :math:`\mathbf{B_{m1}}` is not ``None``, the SS is augmented with the new state .. math:: \mathbf{g}_{n} = \mathbf{u}_{n-1} or, equivalently, with the equation .. math:: \mathbf{g}_{n+1} = \mathbf{u}_n leading to the new form .. math:: \mathbf{H}_{n+1} &= \mathbf{A_A\,H}_{n} + \mathbf{B_B\,u}_n \\ \mathbf{y}_n &= \mathbf{C_C\,H}_{n} + \mathbf{D_D\,u}_n where :math:`\mathbf{H} = (\mathbf{x},\,\mathbf{g})`. Args: A (np.ndarray): dynamics matrix B0 (np.ndarray): input matrix for input at current time step ``n``. Set to None if this is zero. B1 (np.ndarray): input matrix for input at time step ``n+1`` (predictor term) C (np.ndarray): output matrix D (np.ndarray): direct matrix Bm1 (np.ndarray): input matrix for input at time step ``n-1`` (delay term) Returns: tuple: tuple packed with the state-space matrices :math:`\mathbf{A},\,\mathbf{B},\,\mathbf{C}` and :math:`\mathbf{D}`. References: Franklin, GF and Powell, JD. Digital Control of Dynamic Systems, Addison-Wesley Publishing Company, 1980 Warnings: functions untested for delays (Bm1 != 0) """ # Account for u^{n+1} terms (prediction) if B0 is None: Bh = libsp.dot(A, B1) else: Bh = B0 + libsp.dot(A, B1) Dh = D + libsp.dot(C, B1) # Account for u^{n-1} terms (delay) if Bm1 is None: outs = (A, Bh, C, Dh) else: warnings.warn('Function untested when Bm1!=None') Nx, Nu, Ny = A.shape[0], Bh.shape[1], C.shape[0] AA = np.block([[A, Bm1], [np.zeros((Nu, Nx)), np.zeros((Nu, Nu))]]) BB = np.block([[Bh], [np.eye(Nu)]]) CC = np.block([C, np.zeros((Ny, Nu))]) DD = Dh outs = (AA, BB, CC, DD) return outs def addGain(SShere, Kmat, where): """ Convert input u or output y of a SS DLTI system through gain matrix K. We have the following transformations: - where='in': the input dof of the state-space are changed u_new -> Kmat*u -> SS -> y => u_new -> SSnew -> y - where='out': the output dof of the state-space are changed u -> SS -> y -> Kmat*u -> ynew => u -> SSnew -> ynew - where='parallel': the input dofs are changed, but not the output - {u_1 -> SS -> y_1 { u_2 -> y_2= Kmat*u_2 => u_new=(u_1,u_2) -> SSnew -> y=y_1+y_2 {y = y_1+y_2 - Warning: function not tested for Kmat stored in sparse format """ assert where in ['in', 'out', 'parallel-down', 'parallel-up'], \ 'Specify whether gains are added to input or output' if where == 'in': A = SShere.A B = SShere.B.dot(Kmat) C = SShere.C D = SShere.D.dot(Kmat) if where == 'out': A = SShere.A B = SShere.B C = Kmat.dot(SShere.C) D = Kmat.dot(SShere.D) if where == 'parallel-down': A = SShere.A C = SShere.C B = np.block([SShere.B, np.zeros((SShere.B.shape[0], Kmat.shape[1]))]) D = np.block([SShere.D, Kmat]) if where == 'parallel-up': A = SShere.A C = SShere.C B = np.block([np.zeros((SShere.B.shape[0], Kmat.shape[1])), SShere.B]) D = np.block([Kmat, SShere.D]) if SShere.dt == None: SSnew = StateSpace(A, B, C, D) else: SSnew = StateSpace(A, B, C, D, dt=SShere.dt) return SSnew def join2(SS1, SS2): r""" Join two state-spaces or gain matrices such that, given: .. math:: \mathbf{u}_1 \longrightarrow &\mathbf{SS}_1 \longrightarrow \mathbf{y}_1 \\ \mathbf{u}_2 \longrightarrow &\mathbf{SS}_2 \longrightarrow \mathbf{y}_2 we obtain: .. math:: \mathbf{u} \longrightarrow \mathbf{SS}_{TOT} \longrightarrow \mathbf{y} with :math:`\mathbf{u}=(\mathbf{u}_1,\mathbf{u}_2)^T` and :math:`\mathbf{y}=(\mathbf{y}_1,\mathbf{y}_2)^T`. The output :math:`\mathbf{SS}_{TOT}` is either a gain matrix or a state-space system according to the input :math:`\mathbf{SS}_1` and :math:`\mathbf{SS}_2` Args: SS1 (scsig.StateSpace or np.ndarray): State space 1 or gain 1 SS2 (scsig.StateSpace or np.ndarray): State space 2 or gain 2 Returns: scsig.StateSpace or np.ndarray: combined state space or gain matrix """ type_dlti = scsig.ltisys.StateSpaceDiscrete if isinstance(SS1, np.ndarray) and isinstance(SS2, np.ndarray): Nin01, Nin02 = SS1.shape[1], SS2.shape[1] Nout01, Nout02 = SS1.shape[0], SS2.shape[0] SStot = np.block([[SS1, np.zeros((Nout01, Nin02))], [np.zeros((Nout02, Nin01)), SS2]]) elif isinstance(SS1, np.ndarray) and isinstance(SS2, type_dlti): Nin01, Nout01 = SS1.shape[1], SS1.shape[0] Nin02, Nout02 = SS2.inputs, SS2.outputs Nx02 = SS2.A.shape[0] A = SS2.A B = np.block([np.zeros((Nx02, Nin01)), SS2.B]) C = np.block([[np.zeros((Nout01, Nx02))], [SS2.C]]) D = np.block([[SS1, np.zeros((Nout01, Nin02))], [np.zeros((Nout02, Nin01)), SS2.D]]) SStot = scsig.StateSpace(A, B, C, D, dt=SS2.dt) elif isinstance(SS1, type_dlti) and isinstance(SS2, np.ndarray): Nin01, Nout01 = SS1.inputs, SS1.outputs Nin02, Nout02 = SS2.shape[1], SS2.shape[0] Nx01 = SS1.A.shape[0] A = SS1.A B = np.block([SS1.B, np.zeros((Nx01, Nin02))]) C = np.block([[SS1.C], [np.zeros((Nout02, Nx01))]]) D = np.block([[SS1.D, np.zeros((Nout01, Nin02))], [np.zeros((Nout02, Nin01)), SS2]]) SStot = scsig.StateSpace(A, B, C, D, dt=SS1.dt) elif isinstance(SS1, type_dlti) and isinstance(SS2, type_dlti): assert SS1.dt == SS2.dt, 'State-space models must have the same time-step' Nin01, Nout01 = SS1.inputs, SS1.outputs Nin02, Nout02 = SS2.inputs, SS2.outputs Nx01, Nx02 = SS1.A.shape[0], SS2.A.shape[0] A = np.block([[SS1.A, np.zeros((Nx01, Nx02))], [np.zeros((Nx02, Nx01)), SS2.A]]) B = np.block([[SS1.B, np.zeros((Nx01, Nin02))], [np.zeros((Nx02, Nin01)), SS2.B]]) C = np.block([[SS1.C, np.zeros((Nout01, Nx02))], [np.zeros((Nout02, Nx01)), SS2.C]]) D = np.block([[SS1.D, np.zeros((Nout01, Nin02))], [np.zeros((Nout02, Nin01)), SS2.D]]) SStot = scsig.StateSpace(A, B, C, D, dt=SS1.dt) else: raise NameError('Input types not recognised in any implemented option!') return SStot def join(SS_list, wv=None): """ Given a list of state-space models belonging to the StateSpace class, creates a joined system whose output is the sum of the state-space outputs. If wv is not None, this is a list of weights, such that the output is: y = sum( wv[ii] y_ii ) Ref: equation (4.22) of Benner, P., Gugercin, S. & Willcox, K., 2015. A Survey of Projection-Based Model Reduction Methods for Parametric Dynamical Systems. SIAM Review, 57(4), pp.483–531. Warnings: - system matrices must be numpy arrays - the function does not perform any check! """ N = len(SS_list) if wv is not None: assert N == len(wv), "'weights input should have'" A = scalg.block_diag(*[getattr(ss, 'A') for ss in SS_list]) B = np.block([[getattr(ss, 'B')] for ss in SS_list]) if wv is None: C = np.block([getattr(ss, 'C') for ss in SS_list]) else: C = np.block([ww * getattr(ss, 'C') for ww, ss in zip(wv, SS_list)]) D = np.zeros_like(SS_list[0].D) for ii in range(N): if wv is None: D += SS_list[ii].D else: D += wv[ii] * SS_list[ii].D return StateSpace(A, B, C, D, SS_list[0].dt) def sum_ss(SS1, SS2, negative=False): """ Given 2 systems or gain matrices (or a combination of the two) having the same amount of input/output, the function returns a gain or state space model summing the two. Namely, given: u -> SS1 -> y1 u -> SS2 -> y2 we obtain: u -> SStot -> y1+y2 if negative=False """ type_dlti = scsig.ltisys.StateSpaceDiscrete if isinstance(SS1, np.ndarray) and isinstance(SS2, np.ndarray): SStot = SS1 + SS2 elif isinstance(SS1, np.ndarray) and isinstance(SS2, type_dlti): Kmat = SS1 A = SS2.A B = SS2.B C = SS2.C D = SS2.D + Kmat SStot = scsig.StateSpace(A, B, C, D, dt=SS2.dt) elif isinstance(SS1, type_dlti) and isinstance(SS2, np.ndarray): Kmat = SS2 A = SS1.A B = SS1.B C = SS1.C D = SS1.D + Kmat SStot = scsig.StateSpace(A, B, C, D, dt=SS2.dt) elif isinstance(SS1, type_dlti) and isinstance(SS2, type_dlti): assert np.abs(1. - SS1.dt / SS2.dt) < 1e-13, \ 'State-space models must have the same time-step' Nin01, Nout01 = SS1.inputs, SS1.outputs Nin02, Nout02 = SS2.inputs, SS2.outputs Nx01, Nx02 = SS1.A.shape[0], SS2.A.shape[0] A = np.block([[SS1.A, np.zeros((Nx01, Nx02))], [np.zeros((Nx02, Nx01)), SS2.A]]) B = np.block([[SS1.B, ], [SS2.B]]) C = np.block([SS1.C, SS2.C]) D = SS1.D + SS2.D SStot = scsig.StateSpace(A, B, C, D, dt=SS1.dt) else: raise NameError('Input types not recognised in any implemented option!') return SStot def scale_SS(SSin, input_scal=1., output_scal=1., state_scal=1., byref=True): r""" Given a state-space system, scales the equations such that the original input and output, :math:`u` and :math:`y`, are substituted by :math:`u_{AD}=\frac{u}{u_{ref}}` and :math:`y_{AD}=\frac{y}{y_{ref}}`. If the original system has form: .. math:: \mathbf{x}^{n+1} &= \mathbf{A\,x}^n + \mathbf{B\,u}^n \\ \mathbf{y}^{n} &= \mathbf{C\,x}^{n} + \mathbf{D\,u}^n the transformation is such that: .. math:: \mathbf{x}^{n+1} &= \mathbf{A\,x}^n + \mathbf{B}\,\frac{u_{ref}}{x_{ref}}\mathbf{u_{AD}}^n \\ \mathbf{y_{AD}}^{n+1} &= \frac{1}{y_{ref}}(\mathbf{C}\,x_{ref}\,\mathbf{x}^{n+1} + \mathbf{D}\,u_{ref}\,\mathbf{u_{AD}}^n) By default, the state-space model is manipulated by reference (``byref=True``) Args: SSin (scsig.dlti): original state-space formulation input_scal (float or np.ndarray): input scaling factor :math:`u_{ref}`. It can be a float or an array, in which case the each element of the input vector will be scaled by a different factor. output_scal (float or np.ndarray): output scaling factor :math:`y_{ref}`. It can be a float or an array, in which case the each element of the output vector will be scaled by a different factor. state_scal (float or np.ndarray): state scaling factor :math:`x_{ref}`. It can be a float or an array, in which case the each element of the state vector will be scaled by a different factor. byref (bool): state space manipulation order Returns: scsig.dlti: scaled state space formulation """ # check input: Nin, Nout = SSin.inputs, SSin.outputs Nstates = SSin.A.shape[0] if isinstance(input_scal, (list, np.ndarray)): assert len(input_scal) == Nin, \ 'Length of input_scal not matching number of state-space inputs!' else: input_scal = Nin * [input_scal] if isinstance(output_scal, (list, np.ndarray)): assert len(output_scal) == Nout, \ 'Length of output_scal not matching number of state-space outputs!' else: output_scal = Nout * [output_scal] if isinstance(state_scal, (list, np.ndarray)): assert len(state_scal) == Nstates, \ 'Length of state_scal not matching number of state-space states!' else: state_scal = Nstates * [state_scal] if byref: SS = SSin else: print('deep-copying state-space model before scaling') SS = copy.deepcopy(SSin) # update input related matrices for ii in range(Nin): SS.B[:, ii] = SS.B[:, ii] * input_scal[ii] SS.D[:, ii] = SS.D[:, ii] * input_scal[ii] # SS.B[:,ii]*=input_scal[ii] # SS.D[:,ii]*=input_scal[ii] # update output related matrices for ii in range(Nout): SS.C[ii, :] = SS.C[ii, :] / output_scal[ii] SS.D[ii, :] = SS.D[ii, :] / output_scal[ii] # SS.C[ii,:]/=output_scal[ii] # SS.D[ii,:]/=output_scal[ii] # update state related matrices for ii in range(Nstates): SS.B[ii, :] = SS.B[ii, :] / state_scal[ii] SS.C[:, ii] = SS.C[:, ii] * state_scal[ii] # SS.B[ii,:]/=state_scal[ii] # SS.C[:,ii]*=state_scal[ii] return SS def simulate(SShere, U, x0=None): """ Routine to simulate response to generic input. Warnings: This routine is for testing and may lack of robustness. Use scipy.signal instead. """ A, B, C, D = SShere.A, SShere.B, SShere.C, SShere.D NT = U.shape[0] Nx = A.shape[0] Ny = C.shape[0] X = np.zeros((NT, Nx)) Y = np.zeros((NT, Ny)) if x0 is not None: X[0] = x0 if len(U.shape) == 1: U = U.reshape((NT, 1)) Y[0] = libsp.dot(C, X[0]) + libsp.dot(D, U[0]) for ii in range(1, NT): X[ii] = libsp.dot(A, X[ii - 1]) + libsp.dot(B, U[ii - 1]) Y[ii] = libsp.dot(C, X[ii]) + libsp.dot(D, U[ii]) return Y, X def Hnorm_from_freq_resp(gv, method): """ Given a frequency response over a domain kv, this funcion computes the H norms through numerical integration. Note that if kv[-1] 1e-16: raise NameError('Norm is not a real number. Verify data/algorithm!') return Gnorm def adjust_phase(y, deg=True): """ Modify the phase y of a frequency response to remove discontinuities. """ if deg is True: shift = 360. else: shift = 2. * np.pi dymax = 0.0 N = len(y) for ii in range(N - 1): dy = y[ii + 1] - y[ii] if np.abs(dy) > dymax: dymax = np.abs(dy) if dy > 0.97 * shift: print('Subtracting shift to frequency response phase diagram!') y[ii + 1::] = y[ii + 1::] - shift elif dy < -0.97 * shift: y[ii + 1::] = y[ii + 1::] + shift print('Adding shift to frequency response phase diagram!') return y # -------------------------------------------------------------- Special Models def SSderivative(ds): """ Given a time-step ds, and an single input time history u, this SS model returns the output y=[u,du/ds], where du/dt is computed with second order accuracy. """ A = np.array([[0]]) Bm1 = np.array([0.5 / ds]) B0 = np.array([[-2 / ds]]) B1 = np.array([[1.5 / ds]]) C = np.array([[0], [1]]) D = np.array([[1], [0]]) # change state Aout, Bout, Cout, Dout = SSconv(A, B0, B1, C, D, Bm1) return Aout, Bout, Cout, Dout def SSintegr(ds, method='trap'): """ Builds a state-space model of an integrator. - method: Numerical scheme. Available options are: - 1tay: 1st order Taylor (fwd) I[ii+1,:]=I[ii,:] + ds*F[ii,:] - trap: I[ii+1,:]=I[ii,:] + 0.5*dx*(F[ii,:]+F[ii+1,:]) Note: other option can be constructured if information on derivative of F is available (for e.g.) """ A = np.array([[1]]) C = np.array([[1.]]) D = np.array([[0.]]) if method == '1tay': Bm1 = np.array([0.]) B0 = np.array([[ds]]) B1 = np.array([[0.]]) Aout, Bout, Cout, Dout = A, B0, C, D elif method == 'trap': Bm1 = np.array([0.]) B0 = np.array([[.5 * ds]]) B1 = np.array([[.5 * ds]]) Aout, Bout, Cout, Dout = SSconv(A, B0, B1, C, D, Bm1=None) else: raise NameError('Method %s not available!' % method) # change state return Aout, Bout, Cout, Dout def build_SS_poly(Acf, ds, negative=False): """ Builds a discrete-time state-space representation of a polynomial system whose frequency response has from: Ypoly[oo,ii](k) = -A2[oo,ii] D2(k) - A1[oo,ii] D1(k) - A0[oo,ii] where C1,D2 are discrete-time models of first and second derivatives, ds is the time-step and the coefficient matrices are such that: A{nn}=Acf[oo,ii,nn] """ Nout, Nin, Ncf = Acf.shape assert Ncf == 3, 'Acf input last dimension must be equal to 3!' Ader, Bder, Cder, Dder = SSderivative(ds) SSder = scsig.dlti(Ader, Bder, Cder, Dder, dt=ds) SSder02 = series(SSder, join2(np.array([[1]]), SSder)) SSder_all = copy.deepcopy(SSder02) for ii in range(Nin - 1): SSder_all = join2(SSder_all, SSder02) # Build polynomial forcing terms sign = 1.0 if negative == True: sign = -1.0 A0 = Acf[:, :, 0] A1 = Acf[:, :, 1] A2 = Acf[:, :, 2] Kforce = np.zeros((Nout, 3 * Nin)) for ii in range(Nin): Kforce[:, 3 * ii] = sign * (A0[:, ii]) Kforce[:, 3 * ii + 1] = sign * (A1[:, ii]) Kforce[:, 3 * ii + 2] = sign * (A2[:, ii]) SSpoly_neg = addGain(SSder_all, Kforce, where='out') return SSpoly_neg def butter(order, Wn, N=1, btype='lowpass'): """ build MIMO butterworth filter of order ord and cut-off freq over Nyquist freq ratio Wn. The filter will have N input and N output and N*ord states. Note: the state-space form of the digital filter does not depend on the sampling time, but only on the Wn ratio. As a result, this function only returns the A,B,C,D matrices of the filter state-space form. """ # build DLTI SISO num, den = scsig.butter(order, Wn, btype=btype, analog=False, output='ba') Af, Bf, Cf, Df = scsig.tf2ss(num, den) SSf = scsig.dlti(Af, Bf, Cf, Df, dt=1.0) SStot = SSf for ii in range(1, N): SStot = join2(SStot, SSf) return SStot.A, SStot.B, SStot.C, SStot.D # ----------------------------------------------------------------------- Utils def get_freq_from_eigs(eigs, dlti=True): """ Compute natural freq corresponding to eigenvalues, eigs, of a continuous or discrete-time (dlti=True) systems. Note: if dlti=True, the frequency is normalised by (1./dt), where dt is the DLTI time-step - i.e. the frequency in Hertz is obtained by multiplying fn by (1./dt). """ if dlti: fn = 0.5 * np.angle(eigs) / np.pi else: fn = np.abs(eigs.imag) return fn def eigvals(a, dlti=False): """ Ordered eigenvalaues of a matrix. Args: a (np.ndarray): Matrix. dlti (bool): If true, the eigenvalues are ordered by modulus, else by real part. Returns: np.ndarray: ordered set of eigenvalues. """ eigs = np.linalg.eigvals(a) if dlti: order = np.argsort(np.abs(eigs)) else: order = np.argsort(eigs.real) return eigs[order] # --------------------------------------------------------------------- Testing def random_ss(Nx, Nu, Ny, dt=None, use_sparse=False, stable=True): """ Define random system from number of states (Nx), inputs (Nu) and output (Ny). Args: Nx (int): Number of states Nu (int): Number of inputs Ny (int): Number of outputs dt (float (optional)): Time step for discrete systems use_sparse (bool): Use sparse matrices stable (bool): Ensure the system is stable Returns: StateSpace: State space object """ A = np.random.rand(Nx, Nx) if stable: ev, U = np.linalg.eig(A) evabs = np.abs(ev) for ee in range(len(ev)): if evabs[ee] > 0.99: ev[ee] /= 1.1 * evabs[ee] A = np.dot(U * ev, np.linalg.inv(U)).real B = np.random.rand(Nx, Nu) C = np.random.rand(Ny, Nx) D = np.random.rand(Ny, Nu) if use_sparse: ss = StateSpace(libsp.csc_matrix(A), libsp.csc_matrix(B), libsp.csc_matrix(C), libsp.csc_matrix(D), dt=dt) else: ss = StateSpace(A, B, C, D, dt=dt) ss.initialise_variables(({'name': 'input_variable', 'size': Nu}), var_type='in') ss.initialise_variables(({'name': 'output_variable', 'size': Ny}), var_type='out') ss.initialise_variables(({'name': 'state_variable', 'size': Nx}), var_type='state') return ss def compare_ss(SS1, SS2, tol=1e-10, Print=False): """ Assert matrices of state-space models are identical """ era = np.max(np.abs(libsp.dense(SS1.A) - libsp.dense(SS2.A))) if Print: print('Max. error A: %.3e' % era) erb = np.max(np.abs(libsp.dense(SS1.B) - libsp.dense(SS2.B))) if Print: print('Max. error B: %.3e' % erb) erc = np.max(np.abs(libsp.dense(SS1.C) - libsp.dense(SS2.C))) if Print: print('Max. error C: %.3e' % erc) erd = np.max(np.abs(libsp.dense(SS1.D) - libsp.dense(SS2.D))) if Print: print('Max. error D: %.3e' % erd) assert era < tol, 'Error A matrix %.2e>%.2e' % (era, tol) assert erb < tol, 'Error B matrix %.2e>%.2e' % (erb, tol) assert erc < tol, 'Error C matrix %.2e>%.2e' % (erc, tol) assert erd < tol, 'Error D matrix %.2e>%.2e' % (erd, tol) # print('System matrices identical within tolerance %.2e'%tol) return (era, erb, erc, erd) # ----------------------------------------------------------------------------- def ss_to_scipy(ss): """ Converts to a scipy.signal linear time invariant system Args: ss (libss.StateSpace): SHARPy state space object Returns: scipy.signal.dlti """ if ss.dt == None: sys = scsig.lti(ss.A, ss.B, ss.C, ss.D) else: sys = scsig.dlti(ss.A, ss.B, ss.C, ss.D, dt=ss.dt) return sys ================================================ FILE: sharpy/linear/src/lin_aeroelastic.py ================================================ """ Linear aeroelastic model based on coupled GEBM + UVLM S. Maraniello, Jul 2018 """ import warnings import numpy as np import sharpy.utils.settings import sharpy.linear.src.linuvlm as linuvlm import sharpy.linear.src.lingebm as lingebm import sharpy.linear.src.libss as libss import sharpy.utils.algebra as algebra class LinAeroEla(): r""" Future work: - settings are converted from string to type in __init__ method. - implement all settings of LinUVLM (e.g. support for sparse matrices) When integrating in SHARPy: * define: - self.setting_types - self.setting_default * use settings.to_custom_types(self.in_dict, self.settings_types, self.settings_default) for conversion to type. Args: data (sharpy.presharpy.PreSharpy): main SHARPy data class settings_linear (dict): optional settings file if they are not included in the ``data`` structure Attributes: settings (dict): solver settings for the linearised aeroelastic solution lingebm (lingebm.FlexDynamic): linearised geometrically exact beam model num_dof_str (int): number of structural degrees of freedom num_dof_rig (int): number of rigid degrees of freedom num_dof_flex (int): number of flexible degrees of freedom (``num_dof_flex+num_dof_rigid=num_dof_str``) linuvl (linuvlm.Dynamic): linearised UVLM class tsaero (sharpy.utils.datastructures.AeroTimeStepInfo): aerodynamic state timestep info tsstr (sharpy.utils.datastructures.StructTimeStepInfo): structural state timestep info dt (float): time increment q (np.array): corresponding vector of displacements of dimensions ``[1, num_dof_str]`` dq (np.array): time derivative (:math:`\dot{\mathbf{q}}`) of the corresponding vector of displacements with dimensions ``[1, num_dof_str]`` SS (scipy.signal): state space formulation (discrete or continuous time), as selected by the user """ def __init__(self, data, custom_settings_linear=None, uvlm_block=False, chosen_ts=None): self.data = data if custom_settings_linear is None: settings_here = data.settings['LinearUvlm'] else: settings_here = custom_settings_linear sharpy.utils.settings.to_custom_types(settings_here, linuvlm.settings_types_dynamic, linuvlm.settings_default_dynamic, no_ctype=True) if chosen_ts is None: self.chosen_ts = self.data.ts else: self.chosen_ts = chosen_ts ## TEMPORARY - NEED TO INCLUDE PROPER INTEGRATION OF SETTINGS try: self.rigid_body_motions = settings_here['rigid_body_motion'] except KeyError: self.rigid_body_motions = False try: self.use_euler = settings_here['use_euler'] except KeyError: self.use_euler = False if self.rigid_body_motions and settings_here['track_body']: self.track_body = True else: self.track_body = False ## ------- ### extract aeroelastic info self.dt = settings_here['dt'] ### reference to timestep_info # aero aero = data.aero self.tsaero = aero.timestep_info[self.chosen_ts] # structure structure = data.structure self.tsstr = structure.timestep_info[self.chosen_ts] # --- backward compatibility try: rho = settings_here['density'] except KeyError: warnings.warn( "Key 'density' not found in 'LinearUvlm' solver settings. '\ 'Trying to read it from 'StaticCoupled'.") rho = data.settings['StaticCoupled']['aero_solver_settings']['rho'] if type(rho) == str: rho = np.float(rho) self.tsaero.rho = rho # --- backward compatibility ### gebm if self.use_euler: self.num_dof_rig = 9 else: self.num_dof_rig = 10 self.num_dof_flex = np.sum(self.data.structure.vdof >= 0)*6 self.num_dof_str = self.num_dof_flex + self.num_dof_rig self.reshape_struct_input() try: beam_settings = settings_here['beam_settings'] except KeyError: beam_settings = dict() self.lingebm_str = lingebm.FlexDynamic(self.tsstr, structure, beam_settings) cga = algebra.quat2rotation(self.tsstr.quat) ### uvlm if uvlm_block: self.linuvlm = linuvlm.DynamicBlock( self.tsaero, dt=settings_here['dt'], dynamic_settings=settings_here, RemovePredictor=settings_here['remove_predictor'], UseSparse=settings_here['use_sparse'], integr_order=settings_here['integr_order'], ScalingDict=settings_here['ScalingDict'], for_vel=np.hstack((cga.dot(self.tsstr.for_vel[:3]), cga.dot(self.tsstr.for_vel[3:])))) else: self.linuvlm = linuvlm.Dynamic( self.tsaero, dt=settings_here['dt'], dynamic_settings=settings_here, RemovePredictor=settings_here['remove_predictor'], UseSparse=settings_here['use_sparse'], integr_order=settings_here['integr_order'], ScalingDict=settings_here['ScalingDict'], for_vel=np.hstack((cga.dot(self.tsstr.for_vel[:3]), cga.dot(self.tsstr.for_vel[3:])))) # add rotational speed # for ii in range(self.linuvlm.MS.n_surf): # self.linuvlm.MS.Surfs[ii].omega = self.tsstr.for_vel[3:] def reshape_struct_input(self): """ Reshape structural input in a column vector """ structure = self.data.structure # self.data.aero.beam tsdata = structure.timestep_info[self.chosen_ts] self.q = np.zeros(self.num_dof_str) self.dq = np.zeros(self.num_dof_str) jj = 0 # structural dofs index for node_glob in range(structure.num_node): ### detect bc at node (and no. of dofs) bc_here = structure.boundary_conditions[node_glob] if bc_here == 1: # clamp dofs_here = 0 continue elif bc_here == -1 or bc_here == 0: dofs_here = 6 jj_tra = [jj, jj + 1, jj + 2] jj_rot = [jj + 3, jj + 4, jj + 5] # retrieve element and local index ee, node_loc = structure.node_master_elem[node_glob, :] # allocate self.q[jj_tra] = tsdata.pos[node_glob, :] self.q[jj_rot] = tsdata.psi[ee, node_loc] # update jj += dofs_here # allocate FoR A quantities if self.use_euler: self.q[-9:-3] = tsdata.for_vel self.q[-3:] = algebra.quat2euler(tsdata.quat) wa = tsdata.for_vel[3:] self.dq[-9:-3] = tsdata.for_acc T = algebra.deuler_dt(self.q[-3:]) self.dq[-3:] = T.dot(wa) else: self.q[-10:-4] = tsdata.for_vel self.q[-4:] = tsdata.quat wa = tsdata.for_vel[3:] self.dq[-10:-4] = tsdata.for_acc self.dq[-4] = -0.5 * np.dot(wa, tsdata.quat[1:]) # self.dq[-3:]=-0.5*(wa*tsdata.quat[0]+np.cross(wa,tsdata.quat[1:])) def assemble_ss(self, beam_num_modes=None, wake_prop_settings=None): """Assemble State Space formulation""" data = self.data aero = self.data.aero structure = self.data.structure # data.aero.beam tsaero = self.tsaero tsstr = self.tsstr ### assemble linear uvlm self.linuvlm.assemble_ss(wake_prop_settings=wake_prop_settings) SSaero = self.linuvlm.SS ### assemble gains and stiffening term due to non-zero forces # only flexible dof accounted for self.get_gebm2uvlm_gains() ### assemble linear gebm # structural part only self.lingebm_str.assemble(beam_num_modes) SSstr_flex = self.lingebm_str.SSdisc SSstr = SSstr_flex # # rigid-body (fake) # ZeroMat=np.zeros((self.num_dof_rig,self.num_dof_rig)) # EyeMat=np.eye(self.num_dof_rig) # Astr=np.zeros((2*self.num_dof_rig,2*self.num_dof_rig)) # Bstr=np.zeros((2*self.num_dof_rig,2*self.num_dof_rig)) # Cstr=np.eye(2*self.num_dof_rig) # Dstr=np.zeros((2*self.num_dof_rig,2*self.num_dof_rig)) # Astr[:self.num_dof_flex,:self.num_dof_flex]=SSstr.A[] # SSstr_rig=scsig.dlti() # str -> aero Zblock = np.zeros((3 * self.linuvlm.Kzeta, SSstr.outputs // 2)) if self.rigid_body_motions: Kas = np.block([[self.Kdisp, Zblock], [self.Kvel_disp, self.Kvel_vel], [Zblock, Zblock]]) else: Kas = np.block([[self.Kdisp[:, :-self.num_dof_rig], Zblock], [self.Kvel_disp[:, :-self.num_dof_rig], self.Kvel_vel[:, :-self.num_dof_rig]], [Zblock, Zblock]]) # aero -> str if self.rigid_body_motions: Ksa = self.Kforces # aero --> str else: Ksa = self.Kforces[:-10, :] # aero --> str ### feedback connection self.SS = libss.couple(ss01=self.linuvlm.SS, ss02=SSstr, K12=Kas, K21=Ksa) def get_gebm2uvlm_gains(self): r""" Provides: - the gain matrices required to connect the linearised GEBM and UVLM inputs/outputs - the stiffening and damping factors to be added to the linearised GEBM equations in order to account for non-zero aerodynamic loads at the linearisation point. The function produces the gain matrices: - ``Kdisp``: gains from GEBM to UVLM grid displacements - ``Kvel_disp``: influence of GEBM dofs displacements to UVLM grid velocities. - ``Kvel_vel``: influence of GEBM dofs displacements to UVLM grid displacements. - ``Kforces`` (UVLM->GEBM) dimensions are the transpose than the Kdisp and Kvel* matrices. Hence, when allocation this term, ``ii`` and ``jj`` indices will unintuitively refer to columns and rows, respectively. And the stiffening/damping terms accounting for non-zero aerodynamic forces at the linearisation point: - ``Kss``: stiffness factor (flexible dof -> flexible dof) accounting for non-zero forces at the linearisation point. - ``Csr``: damping factor (rigid dof -> flexible dof) - ``Crs``: damping factor (flexible dof -> rigid dof) - ``Crr``: damping factor (rigid dof -> rigid dof) Stiffening and damping related terms due to the non-zero aerodynamic forces at the linearisation point: .. math:: \mathbf{F}_{A,n} = C^{AG}(\mathbf{\chi})\sum_j \mathbf{f}_{G,j} \rightarrow \delta\mathbf{F}_{A,n} = C^{AG}_0 \sum_j \delta\mathbf{f}_{G,j} + \frac{\partial}{\partial\chi}(C^{AG}\sum_j \mathbf{f}_{G,j}^0)\delta\chi The term multiplied by the variation in the quaternion, :math:`\delta\chi`, couples the forces with the rigid body equations and becomes part of :math:`\mathbf{C}_{sr}`. Similarly, the linearisation of the moments results in expression that contribute to the stiffness and damping matrices. .. math:: \mathbf{M}_{B,n} = \sum_j \tilde{X}_B C^{BA}(\Psi)C^{AG}(\chi)\mathbf{f}_{G,j} .. math:: \delta\mathbf{M}_{B,n} = \sum_j \tilde{X}_B\left(C_0^{BG}\delta\mathbf{f}_{G,j} + \frac{\partial}{\partial\Psi}(C^{BA}\delta\mathbf{f}^0_{A,j})\delta\Psi + \frac{\partial}{\partial\chi}(C^{BA}_0 C^{AG} \mathbf{f}_{G,j})\delta\chi\right) The linearised equations of motion for the geometrically exact beam model take the input term :math:`\delta \mathbf{Q}_n = \{\delta\mathbf{F}_{A,n},\, T_0^T\delta\mathbf{M}_{B,n}\}`, which means that the moments should be provided as :math:`T^T(\Psi)\mathbf{M}_B` instead of :math:`\mathbf{M}_A = C^{AB}\mathbf{M}_B`, where :math:`T(\Psi)` is the tangential operator. .. math:: \delta(T^T\mathbf{M}_B) = T^T_0\delta\mathbf{M}_B + \frac{\partial}{\partial\Psi}(T^T\delta\mathbf{M}_B^0)\delta\Psi is the linearised expression for the moments, where the first term would correspond to the input terms to the beam equations and the second arises due to the non-zero aerodynamic moment at the linearisation point and must be subtracted (since it comes from the forces) to form part of :math:`\mathbf{K}_{ss}`. In addition, the :math:`\delta\mathbf{M}_B` term depends on both :math:`\delta\Psi` and :math:`\delta\chi`, therefore those terms would also contribute to :math:`\mathbf{K}_{ss}` and :math:`\mathbf{C}_{sr}`, respectively. The contribution from the total forces and moments will be accounted for in :math:`\mathbf{C}_{rr}` and :math:`\mathbf{C}_{rs}`. .. math:: \delta\mathbf{F}_{tot,A} = \sum_n\left(C^{GA}_0 \sum_j \delta\mathbf{f}_{G,j} + \frac{\partial}{\partial\chi}(C^{AG}\sum_j \mathbf{f}_{G,j}^0)\delta\chi\right) Therefore, after running this method, the beam matrices should be updated as: >>> K_beam[:flex_dof, :flex_dof] += Kss >>> C_beam[:flex_dof, -rigid_dof:] += Csr >>> C_beam[-rigid_dof:, :flex_dof] += Crs >>> C_beam[-rigid_dof:, -rigid_dof:] += Crr Track body option The ``track_body`` setting restricts the UVLM grid to linear translation motions and therefore should be used to ensure that the forces are computed using the reference linearisation frame. The UVLM and beam are linearised about a reference equilibrium condition. The UVLM is defined in the inertial reference frame while the beam employs the body attached frame and therefore a projection from one frame onto another is required during the coupling process. However, the inputs to the UVLM (i.e. the lattice grid coordinates) are obtained from the beam deformation which is expressed in A frame and therefore the grid coordinates need to be projected onto the inertial frame ``G``. As the beam rotates, the projection onto the ``G`` frame of the lattice grid coordinates will result in a grid that is not coincident with that at the linearisation reference and therefore the grid coordinates must be projected onto the original frame, which will be referred to as ``U``. The transformation between the inertial frame ``G`` and the ``U`` frame is a function of the rotation of the ``A`` frame and the original position: .. math:: C^{UG}(\chi) = C^{GA}(\chi_0)C^{AG}(\chi) Therefore, the grid coordinates obtained in ``A`` frame and projected onto the ``G`` frame can be transformed to the ``U`` frame using .. math:: \zeta_U = C^{UG}(\chi) \zeta_G which allows the grid lattice coordinates to be projected onto the original linearisation frame. In a similar fashion, the output lattice vertex forces of the UVLM are defined in the original linearisation frame ``U`` and need to be transformed onto the inertial frame ``G`` prior to projecting them onto the ``A`` frame to use them as the input forces to the beam system. .. math:: \boldsymbol{f}_G = C^{GU}(\chi)\boldsymbol{f}_U The linearisation of the above relations lead to the following expressions that have to be added to the coupling matrices: * ``Kdisp_vel`` terms: .. math:: \delta\boldsymbol{\zeta}_U= C^{GA}_0 \frac{\partial}{\partial \boldsymbol{\chi}} \left(C^{AG}\boldsymbol{\zeta}_{G,0}\right)\delta\boldsymbol{\chi} + \delta\boldsymbol{\zeta}_G * ``Kvel_vel`` terms: .. math:: \delta\dot{\boldsymbol{\zeta}}_U= C^{GA}_0 \frac{\partial}{\partial \boldsymbol{\chi}} \left(C^{AG}\dot{\boldsymbol{\zeta}}_{G,0}\right)\delta\boldsymbol{\chi} + \delta\dot{\boldsymbol{\zeta}}_G The transformation of the forces and moments introduces terms that are functions of the orientation and are included as stiffening and damping terms in the beam's matrices: * ``Csr`` damping terms relating to translation forces: .. math:: C_{sr}^{tra} -= \frac{\partial}{\partial\boldsymbol{\chi}} \left(C^{GA} C^{AG}_0 \boldsymbol{f}_{G,0}\right)\delta\boldsymbol{\chi} * ``Csr`` damping terms related to moments: .. math:: C_{sr}^{rot} -= T^\top\widetilde{\mathbf{X}}_B C^{BG} \frac{\partial}{\partial\boldsymbol{\chi}} \left(C^{GA} C^{AG}_0 \boldsymbol{f}_{G,0}\right)\delta\boldsymbol{\chi} The ``track_body`` setting. When ``track_body`` is enabled, the UVLM grid is no longer coincident with the inertial reference frame throughout the simulation but rather it is able to rotate as the ``A`` frame rotates. This is to simulate a free flying vehicle, where, for instance, the orientation does not affect the aerodynamics. The UVLM defined in this frame of reference, named ``U``, satisfies the following convention: * The ``U`` frame is coincident with the ``G`` frame at the time of linearisation. * The ``U`` frame rotates as the ``A`` frame rotates. Transformations related to the ``U`` frame of reference: * The angle between the ``U`` frame and the ``A`` frame is always constant and equal to :math:`\boldsymbol{\Theta}_0`. * The angle between the ``A`` frame and the ``G`` frame is :math:`\boldsymbol{\Theta}=\boldsymbol{\Theta}_0 + \delta\boldsymbol{\Theta}` * The projection of a vector expressed in the ``G`` frame onto the ``U`` frame is expressed by: .. math:: \boldsymbol{v}^U = C^{GA}_0 C^{AG} \boldsymbol{v}^G * The reverse, a projection of a vector expressed in the ``U`` frame onto the ``G`` frame, is expressed by .. math:: \boldsymbol{v}^U = C^{GA} C^{AG}_0 \boldsymbol{v}^U The effect this has on the aeroelastic coupling between the UVLM and the structural dynamics is that the orientation and change of orientation of the vehicle has no effect on the aerodynamics. The aerodynamics are solely affected by the contribution of the 6-rigid body velocities (as well as the flexible DOFs velocities). """ data = self.data aero = self.data.aero structure = self.data.structure # data.aero.beam tsaero = self.tsaero tsstr = self.tsstr # allocate output Kdisp = np.zeros((3 * self.linuvlm.Kzeta, self.num_dof_str)) Kdisp_vel = np.zeros((3 * self.linuvlm.Kzeta, self.num_dof_str)) # Orientation is in velocity DOFs Kvel_disp = np.zeros((3 * self.linuvlm.Kzeta, self.num_dof_str)) Kvel_vel = np.zeros((3 * self.linuvlm.Kzeta, self.num_dof_str)) Kforces = np.zeros((self.num_dof_str, 3 * self.linuvlm.Kzeta)) Kforces2 = np.zeros((self.num_dof_str, 3 * self.linuvlm.Kzeta)) Kss = np.zeros((self.num_dof_flex, self.num_dof_flex)) Csr = np.zeros((self.num_dof_flex, self.num_dof_rig)) Crs = np.zeros((self.num_dof_rig, self.num_dof_flex)) Crr = np.zeros((self.num_dof_rig, self.num_dof_rig)) Krs = np.zeros((self.num_dof_rig, self.num_dof_flex)) # get projection matrix A->G # (and other quantities indep. from nodal position) Cga = algebra.quat2rotation(tsstr.quat) # NG 6-8-19 removing .T Cag = Cga.T # for_pos=tsstr.for_pos for_vel = tsstr.for_vel[:3] for_rot = tsstr.for_vel[3:] skew_for_rot = algebra.skew(for_rot) Der_vel_Ra = np.dot(Cga, skew_for_rot) Faero = np.zeros(3) FaeroA = np.zeros(3) # GEBM degrees of freedom jj_for_tra = range(self.num_dof_str - self.num_dof_rig, self.num_dof_str - self.num_dof_rig + 3) jj_for_rot = range(self.num_dof_str - self.num_dof_rig + 3, self.num_dof_str - self.num_dof_rig + 6) if self.use_euler: jj_euler = range(self.num_dof_str - 3, self.num_dof_str) euler = algebra.quat2euler(tsstr.quat) tsstr.euler = euler else: jj_quat = range(self.num_dof_str - 4, self.num_dof_str) jj = 0 # nodal dof index for node_glob in range(structure.num_node): ### detect bc at node (and no. of dofs) bc_here = structure.boundary_conditions[node_glob] if bc_here == 1: # clamp (only rigid-body) dofs_here = 0 jj_tra, jj_rot = [], [] # continue elif bc_here == -1 or bc_here == 0: # (rigid+flex body) dofs_here = 6 jj_tra = 6 * structure.vdof[node_glob] + np.array([0, 1, 2], dtype=int) jj_rot = 6 * structure.vdof[node_glob] + np.array([3, 4, 5], dtype=int) # jj_tra=[jj ,jj+1,jj+2] # jj_rot=[jj+3,jj+4,jj+5] else: raise NameError('Invalid boundary condition (%d) at node %d!' \ % (bc_here, node_glob)) jj += dofs_here # retrieve element and local index ee, node_loc = structure.node_master_elem[node_glob, :] # get position, crv and rotation matrix Ra = tsstr.pos[node_glob, :] # in A FoR, w.r.t. origin A-G Rg = np.dot(Cag.T, Ra) # in G FoR, w.r.t. origin A-G psi = tsstr.psi[ee, node_loc, :] psi_dot = tsstr.psi_dot[ee, node_loc, :] Cab = algebra.crv2rotation(psi) Cba = Cab.T Cbg = np.dot(Cab.T, Cag) Tan = algebra.crv2tan(psi) track_body = self.track_body ### str -> aero mapping # some nodes may be linked to multiple surfaces... for str2aero_here in aero.struct2aero_mapping[node_glob]: # detect surface/span-wise coordinate (ss,nn) nn, ss = str2aero_here['i_n'], str2aero_here['i_surf'] # print('%.2d,%.2d'%(nn,ss)) # surface panelling M = aero.aero_dimensions[ss][0] N = aero.aero_dimensions[ss][1] Kzeta_start = 3 * sum(self.linuvlm.MS.KKzeta[:ss]) shape_zeta = (3, M + 1, N + 1) for mm in range(M + 1): # get bound vertex index ii_vert = [Kzeta_start + np.ravel_multi_index( (cc, mm, nn), shape_zeta) for cc in range(3)] # get position vectors zetag = tsaero.zeta[ss][:, mm, nn] # in G FoR, w.r.t. origin A-G zetaa = np.dot(Cag, zetag) # in A FoR, w.r.t. origin A-G Xg = zetag - Rg # in G FoR, w.r.t. origin B Xb = np.dot(Cbg, Xg) # in B FoR, w.r.t. origin B # get rotation terms Xbskew = algebra.skew(Xb) XbskewTan = np.dot(Xbskew, Tan) # get velocity terms zetag_dot = tsaero.zeta_dot[ss][:, mm, nn] - Cga.dot(for_vel) # in G FoR, w.r.t. origin A-G zetaa_dot = np.dot(Cag, zetag_dot) # in A FoR, w.r.t. origin A-G # get aero force faero = tsaero.forces[ss][:3, mm, nn] Faero += faero faero_a = np.dot(Cag, faero) FaeroA += faero_a maero_g = np.cross(Xg, faero) maero_b = np.dot(Cbg, maero_g) ### ---------------------------------------- allocate Kdisp if bc_here != 1: # wrt pos - Eq 25 second term Kdisp[np.ix_(ii_vert, jj_tra)] += Cga # wrt psi - Eq 26 Kdisp[np.ix_(ii_vert, jj_rot)] -= np.dot(Cbg.T, XbskewTan) # w.r.t. position of FoR A (w.r.t. origin G) # null as A and G have always same origin in SHARPy # # ### w.r.t. quaternion (attitude changes) if self.use_euler: Kdisp_vel[np.ix_(ii_vert, jj_euler)] += \ algebra.der_Ceuler_by_v(tsstr.euler, zetaa) # Track body - project inputs as for A not moving if track_body: Kdisp_vel[np.ix_(ii_vert, jj_euler)] += \ Cga.dot(algebra.der_Peuler_by_v(tsstr.euler, zetag)) else: # Equation 25 # Kdisp[np.ix_(ii_vert, jj_quat)] += \ # algebra.der_Cquat_by_v(tsstr.quat, zetaa) Kdisp_vel[np.ix_(ii_vert, jj_quat)] += \ algebra.der_Cquat_by_v(tsstr.quat, zetaa) # Track body - project inputs as for A not moving if track_body: Kdisp_vel[np.ix_(ii_vert, jj_quat)] += \ Cga.dot(algebra.der_CquatT_by_v(tsstr.quat, zetag)) ### ------------------------------------ allocate Kvel_disp if bc_here != 1: # # wrt pos Kvel_disp[np.ix_(ii_vert, jj_tra)] += Der_vel_Ra # wrt psi (at zero psi_dot) Kvel_disp[np.ix_(ii_vert, jj_rot)] -= \ np.dot(Cga, np.dot(skew_for_rot, np.dot(Cab, XbskewTan))) # # wrt psi (psi_dot contributions - verified) Kvel_disp[np.ix_(ii_vert, jj_rot)] += np.dot(Cbg.T, np.dot( algebra.skew(np.dot(XbskewTan, psi_dot)), Tan)) if np.linalg.norm(psi) >=1e-6: Kvel_disp[np.ix_(ii_vert, jj_rot)] -= \ np.dot(Cbg.T, np.dot(Xbskew, algebra.der_Tan_by_xv(psi, psi_dot))) # # w.r.t. position of FoR A (w.r.t. origin G) # # null as A and G have always same origin in SHARPy # # ### w.r.t. quaternion (attitude changes) - Eq 30 if self.use_euler: Kvel_vel[np.ix_(ii_vert, jj_euler)] += \ algebra.der_Ceuler_by_v(tsstr.euler, zetaa_dot) # Track body if ForA is rotating if track_body: Kvel_vel[np.ix_(ii_vert, jj_euler)] += \ Cga.dot(algebra.der_Peuler_by_v(tsstr.euler, zetag_dot)) else: Kvel_vel[np.ix_(ii_vert, jj_quat)] += \ algebra.der_Cquat_by_v(tsstr.quat, zetaa_dot) # Track body if ForA is rotating if track_body: Kvel_vel[np.ix_(ii_vert, jj_quat)] += \ Cga.dot(algebra.der_CquatT_by_v(tsstr.quat, zetag_dot)) ### ------------------------------------- allocate Kvel_vel if bc_here != 1: # wrt pos_dot Kvel_vel[np.ix_(ii_vert, jj_tra)] += Cga # # wrt crv_dot Kvel_vel[np.ix_(ii_vert, jj_rot)] -= np.dot(Cbg.T, XbskewTan) # # wrt velocity of FoR A Kvel_vel[np.ix_(ii_vert, jj_for_tra)] += Cga Kvel_vel[np.ix_(ii_vert, jj_for_rot)] -= \ np.dot(Cga, algebra.skew(zetaa)) # wrt rate of change of quaternion: not implemented! ### -------------------------------------- allocate Kforces if bc_here != 1: # nodal forces Kforces[np.ix_(jj_tra, ii_vert)] += Cag # nodal moments Kforces[np.ix_(jj_rot, ii_vert)] += \ np.dot(Tan.T, np.dot(Cbg, algebra.skew(Xg))) # or, equivalently, np.dot( algebra.skew(Xb),Cbg) # total forces Kforces[np.ix_(jj_for_tra, ii_vert)] += Cag Kforces2[-10:-7, ii_vert] += Cag # total moments Kforces[np.ix_(jj_for_rot, ii_vert)] += \ np.dot(Cag, algebra.skew(zetag)) # quaternion equation # null, as not dep. on external forces ### --------------------------------------- allocate Kstiff ### flexible dof equations (Kss and Csr) if bc_here != 1: # nodal forces if self.use_euler: if not track_body: Csr[jj_tra, -3:] -= algebra.der_Peuler_by_v(tsstr.euler, faero) # Csr[jj_tra, -3:] -= algebra.der_Ceuler_by_v(tsstr.euler, Cga.T.dot(faero)) else: if not track_body: Csr[jj_tra, -4:] -= algebra.der_CquatT_by_v(tsstr.quat, faero) # Track body # if track_body: # Csr[jj_tra, -4:] -= algebra.der_Cquat_by_v(tsstr.quat, Cga.T.dot(faero)) ### moments TanTXbskew = np.dot(Tan.T, Xbskew) # contrib. of TanT (dpsi) - Eq 37 - Integration of UVLM and GEBM Kss[np.ix_(jj_rot, jj_rot)] -= algebra.der_TanT_by_xv(psi, maero_b) # contrib of delta aero moment (dpsi) - Eq 36 Kss[np.ix_(jj_rot, jj_rot)] -= \ np.dot(TanTXbskew, algebra.der_CcrvT_by_v(psi, np.dot(Cag, faero))) # contribution of delta aero moment (dquat) if self.use_euler: if not track_body: Csr[jj_rot, -3:] -= \ np.dot(TanTXbskew, np.dot(Cba, algebra.der_Peuler_by_v(tsstr.euler, faero))) # if track_body: # Csr[jj_rot, -3:] -= \ # np.dot(TanTXbskew, # np.dot(Cbg, # algebra.der_Peuler_by_v(tsstr.euler, Cga.T.dot(faero)))) else: if not track_body: Csr[jj_rot, -4:] -= \ np.dot(TanTXbskew, np.dot(Cba, algebra.der_CquatT_by_v(tsstr.quat, faero))) # Track body # if track_body: # Csr[jj_rot, -4:] -= \ # np.dot(TanTXbskew, # np.dot(Cbg, # algebra.der_CquatT_by_v(tsstr.quat, Cga.T.dot(faero)))) ### rigid body eqs (Crs and Crr) if bc_here != 1: # Changed Crs to Krs - NG 14/5/19 # moments contribution due to delta_Ra (+ sign intentional) Krs[3:6, jj_tra] += algebra.skew(faero_a) # moment contribution due to delta_psi (+ sign intentional) Krs[3:6, jj_rot] += np.dot(algebra.skew(faero_a), algebra.der_Ccrv_by_v(psi, Xb)) if self.use_euler: # total force if not track_body: Crr[:3, -3:] -= algebra.der_Peuler_by_v(tsstr.euler, faero) # total moment contribution due to change in euler angles Crr[3:6, -3:] -= algebra.der_Peuler_by_v(tsstr.euler, np.cross(zetag, faero)) Crr[3:6, -3:] += np.dot( np.dot(Cag, algebra.skew(faero)), algebra.der_Peuler_by_v(tsstr.euler, np.dot(Cab, Xb))) # if track_body: # # NG 20/8/19 - is the Cag needed here? Verify # Crr[:3, -3:] -= Cag.dot(algebra.der_Ceuler_by_v(tsstr.euler, Cga.T.dot(faero))) # # Crr[3:6, -3:] -= Cag.dot(algebra.skew(zetag).dot(algebra.der_Ceuler_by_v(tsstr.euler, Cga.T.dot(faero)))) # Crr[3:6, -3:] += Cag.dot(algebra.skew(faero)).dot(algebra.der_Ceuler_by_v(tsstr.euler, Cga.T.dot(zetag))) else: if not track_body: # total force Crr[:3, -4:] -= algebra.der_CquatT_by_v(tsstr.quat, faero) # total moment contribution due to quaternion Crr[3:6, -4:] -= algebra.der_CquatT_by_v(tsstr.quat, np.cross(zetag, faero)) Crr[3:6, -4:] += np.dot( np.dot(Cag, algebra.skew(faero)), algebra.der_CquatT_by_v(tsstr.quat, np.dot(Cab, Xb))) # Track body # if track_body: # Crr[:3, -4:] -= Cag.dot(algebra.der_Cquat_by_v(tsstr.quat, Cga.T.dot(faero))) # # Crr[3:6, -4:] -= Cag.dot(algebra.skew(zetag).dot(algebra.der_Cquat_by_v(tsstr.quat, Cga.T.dot(faero)))) # Crr[3:6, -4:] += Cag.dot(algebra.skew(faero)).dot(algebra.der_Cquat_by_v(tsstr.quat, Cga.T.dot(zetag))) # transfer self.Kdisp = Kdisp self.Kvel_disp = Kvel_disp self.Kdisp_vel = Kdisp_vel self.Kvel_vel = Kvel_vel self.Kforces = Kforces # stiffening factors self.Kss = Kss self.Krs = Krs self.Csr = Csr self.Crs = Crs self.Crr = Crr if __name__ == '__main__': import read import configobj # select test case fname = '/home/sm6110/git/uvlm3d/test/h5input/smith_Nsurf01M04N12wk10_a040.state.h5' fname = '/home/sm6110/git/uvlm3d/test/h5input/smith_Nsurf02M04N12wk10_a040.state.h5' hd = read.h5file(fname) # read some setting file_settings = fname[:-8] + 'solver.txt' dict_config = configobj.ConfigObj(file_settings) # add settings for linear solver M, cref = 4., 1. Uinf = 25. dict_config['LinearUvlm'] = {'dt': cref / M / Uinf, 'integr_order': 2, 'Uref': 1.} Sol = AeroElaDyn(tsaero=hd.tsaero00000, tsstr=hd.tsstr00000, aero2str_mapping=hd.aero2str, settings=dict_config) ================================================ FILE: sharpy/linear/src/lin_utils.py ================================================ """ Utilities functions for linear analysis """ import numpy as np # linear uvlm import sharpy.linear.src.lin_aeroelastic as lin_aeroelastic import sharpy.linear.src.libss as libss def comp_tot_force(forces, zeta, zeta_pole=np.zeros((3,))): """ Compute total force with exact displacements """ Ftot = np.zeros((3,)) Mtot = np.zeros((3,)) for ss in range(len(forces)): _, Mv, Nv = forces[ss].shape for mm in range(Mv): for nn in range(Nv): arm = zeta[ss][:, mm, nn] - zeta_pole Mtot += np.cross(arm, forces[ss][:3, mm, nn]) for cc in range(3): Ftot[cc] += forces[ss][cc, :, :].sum() return Ftot, Mtot class Info(): """ Summarise info about a data point """ def __init__(self, zeta, zeta_dot, u_ext, ftot, mtot, q, qdot, SSaero=None, SSbeam=None, Kas=None, Kftot=None, Kmtot=None, Kmtot_disp=None, Asteady_inv=None): self.zeta = zeta self.zeta_dot = zeta_dot self.u_ext = u_ext self.ftot = ftot self.mtot = mtot self.q = q self.qdot = qdot # self.SSaero = SSaero self.SSbeam = SSbeam self.Kas = Kas self.Kftot = Kftot self.Kmtot = Kmtot self.Kmtot_disp = Kmtot_disp self.Asteady_inv = Asteady_inv def solve_linear(Ref, Pert, solve_beam=True): """ Given 2 Info() classes associated to a reference linearisation point Ref and a perturbed state Pert, the method produces in output the prediction at the Pert state of a linearised model. The solution is carried on using both the aero and beam input """ ### define perturbations dq = Pert.q - Ref.q dqdot = Pert.qdot - Ref.qdot dzeta = Pert.zeta - Ref.zeta dzeta_dot = Pert.zeta_dot - Ref.zeta_dot du_ext = Pert.u_ext - Ref.u_ext num_dof_str = len(dq) dzeta_exp = np.dot(Ref.Kas[:len(dzeta), :num_dof_str], dq) SSaero = Ref.SSaero SSbeam = Ref.SSbeam # zeta in usta = np.concatenate([dzeta, dzeta_dot, du_ext]) if hasattr(Ref, 'Asteady_inv'): #x_sta = A_steady^-1 dot B u_sta xsta = np.dot(Ref.Asteady_inv, np.dot(SSaero.B, usta)) else: # x_sta = linsolve(A_steady, Bu) Asteady = np.eye(*SSaero.A.shape) - SSaero.A xsta = np.linalg.solve(Asteady, np.dot(SSaero.B, usta)) # y_sta = C x_sta + D u_sta ysta = np.dot(SSaero.C, xsta) + np.dot(SSaero.D, usta) ftot_aero = Ref.ftot + np.dot(Ref.Kftot, ysta) mtot_aero = Ref.mtot + np.dot(Ref.Kmtot, ysta) + np.dot(Ref.Kmtot_disp, dzeta) #### beam in if solve_beam: # warning: we need to add first the contribution due to velocity change!!! usta_uinf = np.concatenate([0. * dzeta, 0. * dzeta_dot, du_ext]) xsta_uinf = np.linalg.solve(Asteady, np.dot(SSaero.B, usta_uinf)) ysta_uinf = np.dot(SSaero.C, xsta_uinf) + np.dot(SSaero.D, usta_uinf) usta = np.concatenate([dq, dqdot]) if hasattr(Ref, 'Asteady_inv'): xsta = np.dot(Ref.Asteady_inv, np.dot(SSbeam.B, usta)) else: Asteady = np.eye(*SSbeam.A.shape) - SSbeam.A xsta = np.linalg.solve(Asteady, np.dot(SSbeam.B, usta)) ysta = ysta_uinf + np.dot(SSbeam.C, xsta) + np.dot(SSbeam.D, usta) ftot_beam = Ref.ftot + np.dot(Ref.Kftot, ysta) mtot_beam = Ref.mtot + np.dot(Ref.Kmtot, ysta) + np.dot(Ref.Kmtot_disp, dzeta_exp) else: ftot_beam, mtot_beam = None, None return ftot_aero, mtot_aero, ftot_beam, mtot_beam def extract_from_data(data, assemble=True, zeta_pole=np.zeros((3,)), build_Asteady_inv=False): """ Extract relevant info from data structure. If assemble is True, it will also generate a linear UVLM and the displacements/velocities gain matrices """ ### extract aero info - gets info from every panel in every surface # from an NxM matrix and reshapes it into an K by 1 column vector tsaero = data.aero.timestep_info[0] zeta = np.concatenate([tsaero.zeta[ss].reshape(-1, order='C') for ss in range(tsaero.n_surf)]) zeta_dot = np.concatenate([tsaero.zeta_dot[ss].reshape(-1, order='C') for ss in range(tsaero.n_surf)]) uext = np.concatenate([tsaero.u_ext[ss].reshape(-1, order='C') for ss in range(tsaero.n_surf)]) ftot, mtot = comp_tot_force(tsaero.forces, tsaero.zeta, zeta_pole=zeta_pole) ## TEST WHETHER SETTINGS RUN settings = dict() settings['LinearUvlm'] = {'dt': 0.1, 'integr_order': 2, 'density': 1.225, 'ScalingDict': {'length': 1., 'speed': 1., 'density': 1.}} ### extract structural info Sol = lin_aeroelastic.LinAeroEla(data, settings) gebm = Sol.lingebm_str q = Sol.q qdot = Sol.dq ### assemble if assemble is True: uvlm = Sol.linuvlm uvlm.assemble_ss() uvlm.get_total_forces_gain(zeta_pole=zeta_pole) Sol.get_gebm2uvlm_gains() Kas = np.block([[Sol.Kdisp, np.zeros((3 * uvlm.Kzeta, gebm.num_dof + 10))], [Sol.Kvel_disp, Sol.Kvel_vel], [np.zeros((3 * uvlm.Kzeta, 2 * gebm.num_dof + 20))]]) SSbeam = libss.addGain(uvlm.SS, Kas, where='in') if build_Asteady_inv: Asteady_inv = np.linalg.inv(np.eye(*uvlm.SS.A.shape) - uvlm.SS.A) else: Asteady_inv = None Out = Info(zeta, zeta_dot, uext, ftot, mtot, q, qdot, uvlm.SS, SSbeam, Kas, uvlm.Kftot, uvlm.Kmtot, uvlm.Kmtot_disp, Asteady_inv) else: Out = Info(zeta, zeta_dot, uext, ftot, mtot, q, qdot) return Out ================================================ FILE: sharpy/linear/src/lingebm.py ================================================ """ Linear beam model class S. Maraniello, Aug 2018 N. Goizueta """ import numpy as np import scipy as sc import scipy.signal as scsig import sharpy.linear.src.libss as libss import sharpy.utils.algebra as algebra import sharpy.utils.settings as settings import sharpy.utils.cout_utils as cout import sharpy.structure.utils.modalutils as modalutils import warnings from sharpy.linear.utils.ss_interface import LinearVector, StateVariable, OutputVariable, InputVariable class FlexDynamic(): r""" Define class for linear state-space realisation of GEBM flexible-body equations from SHARPy``timestep_info`` class and with the nonlinear structural information. The linearised beam takes the following arguments: Args: tsinfo (sharpy.utils.datastructures.StructImeStepInfo): Structural timestep containing the modal information structure (sharpy.solvers.beamloader.Beam): Beam class with the structural information custom_settings (dict): settings for the linearised beam State-space models can be defined in continuous or discrete time (dt required). Modal projection, either on the damped or undamped modal shapes, is also avaiable. The rad/s array wv can be optionally passed for freq. response analysis To produce the state-space equations: 1. Set the settings: a. ``modal_projection={True,False}``: determines whether to project the states onto modal coordinates. Projection over damped or undamped modal shapes can be obtained selecting: - ``proj_modes={'damped','undamped'}`` while - ``inout_coords={'modes','nodal'}`` determines whether the modal state-space inputs/outputs are modal coords or nodal degrees-of-freedom. If ``modes`` is selected, the ``Kin`` and ``Kout`` gain matrices are generated to transform nodal to modal dofs b. ``dlti={True,False}``: if true, generates discrete-time system. The continuous to discrete transformation method is determined by:: discr_method={ 'newmark', # Newmark-beta 'zoh', # Zero-order hold 'bilinear'} # Bilinear (Tustin) transformation DLTIs can be obtained directly using the Newmark-:math:`\beta` method ``discr_method='newmark'`` ``newmark_damp=xx`` with ``xx<<1.0`` for full-states descriptions (``modal_projection=False``) and modal projection over the undamped structural modes (``modal_projection=True`` and ``proj_modes``). The Zero-order holder and bilinear methods, instead, work in all descriptions, but require the continuous state-space equations. 2. Generate an instance of the beam 2. Run ``self.assemble()``. The method accepts an additional parameter, ``Nmodes``, which allows using a lower number of modes than specified in ``self.Nmodes`` Examples: >>> beam_settings = {'modal_projection': True, >>> 'inout_coords': 'modes', >>> 'discrete_time': False, >>> 'proj_modes': 'undamped', >>> 'use_euler': True} >>> >>> beam = lingebm.FlexDynamic(tsstruct0, structure=data.structure, custom_settings=beam_settings) >>> >>> beam.assemble() Notes: * Modal projection will automatically select between damped/undamped modes shapes, based on the data available from tsinfo. * If the full system matrices are available, use the modal_sol methods to override mode-shapes and eigenvectors """ def __init__(self, tsinfo, structure=None, custom_settings=dict()): # Extract settings self.settings = custom_settings ### extract timestep_info modal results # unavailable attrs will be None self.freq_natural = tsinfo.modal.get('freq_natural') self.freq_damp = tsinfo.modal.get('freq_damped') self.damping = tsinfo.modal.get('damping') self.eigs = tsinfo.modal.get('eigenvalues') self.U = tsinfo.modal.get('eigenvectors') self.V = tsinfo.modal.get('eigenvectors_left') self.Kin_damp = tsinfo.modal.get('Kin_damp') # for 'damped' modes only self.Ccut = tsinfo.modal.get('Ccut') # for 'undamp' modes only self.Mstr = tsinfo.modal.get('M') self.Cstr = tsinfo.modal.get('C') self.Kstr = tsinfo.modal.get('K') ### set other flags self.modal = self.settings['modal_projection'] self.inout_coords = self.settings['inout_coords'] self.dlti = self.settings['discrete_time'] if self.dlti: self.dt = self.settings['dt'] else: self.dt = None self.Nmodes = self.settings['num_modes'] self._num_modes = None self.num_modes = self.settings['num_modes'] self.num_dof = self.U.shape[0] if self.V is not None: self.num_dof = self.num_dof // 2 self.proj_modes = self.settings['proj_modes'] if self.V is None: self.proj_modes = 'undamped' self.discr_method = self.settings['discr_method'] self.newmark_damp = self.settings['newmark_damp'] self.use_euler = self.settings['use_euler'] self.use_principal_axes = self.settings.get('rigid_modes_ppal_axes', False) # this setting is inherited from the setting in Modal solver ### set state-space variables self.SScont = None self.SSdisc = None self.Kin = None self.Kout = None # Store structure at linearisation and linearisation conditions self.structure = structure self.tsstruct0 = tsinfo self.Minv = None # Not used anymore since M is factorized inside newmark_ss self.scaled_reference_matrices = dict() # keep reference values prior to time scaling if self.use_euler: self.euler_propagation_equations(tsinfo) if self.Mstr.shape[0] == 6*(self.tsstruct0.num_node - 1): self.clamped = True self.num_dof_rig = 0 else: self.clamped = False if self.use_euler: self.num_dof_rig = 9 else: self.num_dof_rig = 10 if self.modal: self.update_modal() self.num_dof_flex = np.sum(structure.vdof >= 0)*6 self.num_dof_str = self.num_dof_flex + self.num_dof_rig q, dq = self.reshape_struct_input() self.tsstruct0.q = q self.tsstruct0.dq = dq # Linearised gravity matrices self.Crr_grav = None self.Csr_grav = None self.Krs_grav = None self.Kss_grav = None def reshape_struct_input(self): """ Reshape structural input in a column vector """ structure = self.structure # self.data.aero.beam tsdata = self.tsstruct0 q = np.zeros(self.num_dof_str) dq = np.zeros(self.num_dof_str) jj = 0 # structural dofs index for node_glob in range(structure.num_node): ### detect bc at node (and no. of dofs) bc_here = structure.boundary_conditions[node_glob] if bc_here == 1: # clamp dofs_here = 0 continue elif bc_here == -1 or bc_here == 0: dofs_here = 6 jj_tra = [jj, jj + 1, jj + 2] jj_rot = [jj + 3, jj + 4, jj + 5] # retrieve element and local index ee, node_loc = structure.node_master_elem[node_glob, :] # allocate q[jj_tra] = tsdata.pos[node_glob, :] q[jj_rot] = tsdata.psi[ee, node_loc] # update jj += dofs_here # allocate FoR A quantities if self.use_euler: q[-9:-3] = tsdata.for_vel q[-3:] = algebra.quat2euler(tsdata.quat) wa = tsdata.for_vel[3:] dq[-9:-3] = tsdata.for_acc T = algebra.deuler_dt(q[-3:]) dq[-3:] = T.dot(wa) else: q[-10:-4] = tsdata.for_vel q[-4:] = tsdata.quat wa = tsdata.for_vel[3:] dq[-10:-4] = tsdata.for_acc dq[-4] = -0.5 * np.dot(wa, tsdata.quat[1:]) return q, dq @property def num_modes(self): return self._num_modes @num_modes.setter def num_modes(self, value): self.update_truncated_modes(value) self._num_modes = value @property def num_flex_dof(self): return np.sum(self.structure.vdof >= 0) * 6 @property def num_rig_dof(self): return self.Mstr.shape[0] - self.num_flex_dof def sort_repeated_evecs(self, evecs, evals): num_rbm = np.sum(evals.__abs__() == 0.) num_dof = evecs.shape[0] evecs_sorted = evecs.copy() if num_rbm != 0: for i in range(num_rbm): index_mode = np.argmax(evecs[:, i].__abs__()) - num_dof + num_rbm evecs_sorted[:, index_mode] = evecs[:, i] return evecs_sorted def euler_propagation_equations(self, tsstr): """ Introduce the linearised Euler propagation equations that relate the body fixed angular velocities to the Earth fixed Euler angles. This method will remove the quaternion propagation equations created by SHARPy; the resulting system will have 9 rigid degrees of freedom. Args: tsstr: Returns: """ # Verify the rigid body modes are used num_node = tsstr.num_node num_flex_dof = 6*(num_node-1) euler = algebra.quat2euler(tsstr.quat) tsstr.euler = euler if self.Mstr.shape[0] == num_flex_dof + 10: # Erase quaternion equations self.Cstr[-4:, :] = 0 self.Mstr = self.Mstr[:-1, :-1] self.Cstr = self.Cstr[:-1, :-1] self.Kstr = self.Kstr[:-1, :-1] for_rot = tsstr.for_vel[3:] Crr = np.zeros((9, 9)) # Euler angle propagation equations Crr[-3:, -6:-3] = -algebra.deuler_dt(tsstr.euler) Crr[-3:, -3:] = -algebra.der_Teuler_by_w(tsstr.euler, for_rot) self.Cstr[-9:, -9:] += Crr else: warnings.warn('Euler parametrisation not implemented - Either rigid body modes are not being used or this ' 'method has already been called.') @property def num_dof(self): self.num_dof = self.Mstr.shape[0] # Previously beam.U.shape[0] return self._num_dof @num_dof.setter def num_dof(self, value): self._num_dof = value def linearise_gravity_forces(self, tsstr=None): r""" Linearises gravity forces and includes the resulting terms in the C and K matrices. The method takes the linearisation condition (optional argument), linearises and updates: * Stiffness matrix * Damping matrix * Modal damping matrix The method works for both the quaternion and euler angle orientation parametrisation. Args: tsstr (sharpy.utils.datastructures.StructTimeStepInfo): Structural timestep at the linearisation point Notes: The gravity forces are linearised to express them in terms of the beam formulation input variables: * Nodal forces: :math:`\delta \mathbf{f}_A` * Nodal moments: :math:`\delta(T^T \mathbf{m}_B)` * Total forces (rigid body equations): :math:`\delta \mathbf{F}_A` * Total moments (rigid body equations): :math:`\delta \mathbf{M}_A` Gravity forces are naturally expressed in ``G`` (inertial) frame .. math:: \mathbf{f}_{G,0} = \mathbf{M\,g} where the :math:`\mathbf{M}` is the tangent mass matrix obtained at the linearisation reference. To obtain the gravity forces expressed in A frame we make use of the projection matrices .. math:: \mathbf{f}_A = C^{AG}(\boldsymbol{\chi}) \mathbf{f}_{G,0} that projects a vector in the inertial frame ``G`` onto the body attached frame ``A``. The projection of a vector can then be linearised as .. math:: \delta \mathbf{f}_A = C^{AG} \delta \mathbf{f}_{G,0} + \frac{\partial}{\partial \boldsymbol{\chi}}(C^{AG} \mathbf{f}_{G,0}) \delta\boldsymbol{\chi}. * Nodal forces: The linearisation of the gravity forces acting at each node is simply .. math:: \delta \mathbf{f}_A = + \frac{\partial}{\partial \boldsymbol{\chi}}(C^{AG} \mathbf{f}_{G,0}) \delta\boldsymbol{\chi} where it is assumed that :math:`\delta\mathbf{f}_G = 0`. * Nodal moments: The gravity moments can be expressed in the local node frame of reference ``B`` by .. math:: \mathbf{m}_B = \tilde{X}_{B,CG}C^{BA}(\Psi)C^{AG}(\boldsymbol{\chi})\mathbf{f}_{G,0} The linearisation is given by: .. math:: \delta \mathbf{m}_B = \tilde{X}_{B,CG} \left(\frac{\partial}{\partial\Psi}(C^{BA}\mathbf{f}_{A,0})\delta\Psi + C^{BA}\frac{\partial}{\partial\boldsymbol{\chi}}(C^{AG}\mathbf{f}_{G,0})\delta\boldsymbol{\chi}\right) However, recall that the input moments are defined in tangential space :math:`\delta(T^\top\mathbf{m}_B)` whose linearised expression is .. math:: \delta(T^T(\Psi) \mathbf{m}_B) = T_0^T \delta \mathbf{m}_B + \frac{\partial}{\partial \Psi}(T^T \mathbf{m}_{B,0})\delta\Psi where the :math:`\delta \mathbf{m}_B` term has been defined above. * Total forces: The total forces include the contribution from all flexible degrees of freedom as well as the gravity forces arising from the mass at the clamped node .. math:: \mathbf{F}_A = \sum_n \mathbf{f}_A + \mathbf{f}_{A,clamped} which becomes .. math:: \delta \mathbf{F}_A = \sum_n \delta \mathbf{f}_A + \frac{\partial}{\partial\boldsymbol{\chi}}\left(C^{AG}\mathbf{f}_{G,clamped}\right) \delta\boldsymbol{\chi}. * Total moments: The total moments, as opposed to the nodal moments, are expressed in A frame and again require the addition of the moments from the flexible structural nodes as well as the ones from the clamped node itself. .. math:: \mathbf{M}_A = \sum_n \tilde{X}_{A,n}^{CG} C^{AG} \mathbf{f}_{n,G} + \tilde{X}_{A,clamped}C^{AG}\mathbf{f}_{G, clamped} where :math:`X_{A,n}^{CG} = R_{A,n} + C^{AB}(\Psi)X_{B,n}^{CG}`. Its linearised form is .. math:: \delta X_{A,n}^{CG} = \delta R_{A,n} + \frac{\partial}{\partial \Psi}(C^{AB} X_{B,CG})\delta\Psi Therefore, the overall linearisation of the total moment is defined as .. math:: \delta \mathbf{M}_A = \tilde{X}_{A,total}^{CG} \frac{\partial}{\partial \boldsymbol{\chi}}(C^{AG}\mathbf{F}_{G, total}) \delta \boldsymbol{\chi} -\sum_n \tilde{C}^{AG}\mathbf{f}_{G,0} \delta X_{A,n}^{CG} where :math:`X_{A, total}` is the centre of gravity of the entire system expressed in ``A`` frame and :math:`\mathbf{F}_{G, total}` are the gravity forces of the overall system in ``G`` frame, including the contributions from the clamped node. The linearisation introduces damping and stiffening terms since the :math:`\delta\boldsymbol{\chi}` and :math:`\delta\boldsymbol{\Psi}` terms are found in the damping and stiffness matrices respectively. Therefore, the beam matrices need updating to account for these terms: * Terms from the linearisation of the nodal moments will be assembled in the rows corresponding to moment equations and columns corresponding to the cartesian rotation vector .. math:: K_{ss}^{m,\Psi} \leftarrow -T_0^T \tilde{X}_{B,CG} \frac{\partial}{\partial\Psi}(C^{BA}\mathbf{f}_{A,0}) -\frac{\partial}{\partial \Psi}(T^T \mathbf{m}_{B,0}) * Terms from the linearisation of the translation forces with respect to the orientation are assembled in the damping matrix, the rows corresponding to translational forces and columns to orientation degrees of freedom .. math:: C_{sr}^{f,\boldsymbol{\chi}} \leftarrow - \frac{\partial}{\partial \boldsymbol{\chi}}(C^{AG} \mathbf{f}_{G,0}) * Terms from the linearisation of the moments with respect to the orientation are assembled in the damping matrix, with the rows correspondant to the moments and the columns to the orientation degrees of freedom .. math:: C_{sr}^{m,\boldsymbol{\chi}} \leftarrow - T_0^T\tilde{X}_{B,CG}C^{BA}\frac{\partial}{\partial\boldsymbol{\chi}}(C^{AG}\mathbf{f}_{G,0}) * Terms from the linearisation of the total forces with respect to the orientation correspond to the rigid body equations in the damping matrix, the rows to the translational forces and columns to the orientation .. math:: C_{rr}^{F,\boldsymbol{\chi}} \leftarrow - \sum_n \frac{\partial}{\partial \boldsymbol{\chi}}(C^{AG} \mathbf{f}_{G,0}) * Terms from the linearisation of the total moments with respect to the orientation correspond to the rigid body equations in the damping matrix, the rows to the moments and the columns to the orientation .. math:: C_{rr}^{M,\boldsymbol{\chi}} \leftarrow - \sum_n\tilde{X}_{A,n}^{CG} \frac{\partial}{\partial \boldsymbol{\chi}}(C^{AG}\mathbf{f}_{G,0}) * Terms from the linearisation of the total moments with respect to the nodal position :math:`R_A` are included in the stiffness matrix, the rows corresponding to the moments in the rigid body equations and the columns to the nodal position .. math:: K_{rs}^{M,R} \leftarrow + \sum_n \tilde{\mathbf{f}_{A,0}} * Terms from the linearisation of the total moments with respect to the cartesian rotation vector are included in the stiffness matrix, the rows corresponding to the moments in the rigid body equations and the columns to the cartesian rotation vector .. math:: K_{rs}^{M, \Psi} \leftarrow + \sum_n \tilde{\mathbf{f}_{A,0}}\frac{\partial}{\partial \Psi}(C^{AB} X_{B,CG}) """ if tsstr is None: tsstr = self.tsstruct0 if self.settings['print_info']: try: cout.cout_wrap('\nLinearising gravity terms...') except ValueError: pass num_node = tsstr.num_node flex_dof = 6 * sum(self.structure.vdof >= 0) if self.use_euler: rig_dof = 9 # This is a rotation matrix that rotates a vector from G to A Cag = algebra.euler2rot(tsstr.euler) Cga = Cag.T # Projection matrices - this projects the vector in G t to A Pag = Cga Pga = Cag else: rig_dof = 10 # get projection matrix A->G # Cga = algebra.quat2rotation(tsstr.quat) # Pga = Cga.T # Pag = Pga.T Cag = algebra.quat2rotation(tsstr.quat) # Rotation matrix FoR G rotated by quat Pag = Cag.T Pga = Pag.T # Mass matrix partitions for CG calculations Mss = self.Mstr[:flex_dof, :flex_dof] Mrr = self.Mstr[-rig_dof:, -rig_dof:] # Initialise damping and stiffness gravity terms Crr_grav = np.zeros((rig_dof, rig_dof)) Csr_grav = np.zeros((flex_dof, rig_dof)) Crr_debug = np.zeros((rig_dof, rig_dof)) Krs_grav = np.zeros((rig_dof, flex_dof)) Kss_grav = np.zeros((flex_dof, flex_dof)) # Overall CG in A frame Xcg_A = -np.array([Mrr[2, 4], Mrr[0, 5], Mrr[1, 3]]) / Mrr[0, 0] Xcg_Askew = algebra.skew(Xcg_A) if self.settings['print_info']: cout.cout_wrap('\tM = %.2f kg' % Mrr[0, 0], 1) cout.cout_wrap('\tX_CG A -> %.2f %.2f %.2f' %(Xcg_A[0], Xcg_A[1], Xcg_A[2]), 1) FgravA = np.zeros(3) FgravG = np.zeros(3) for i_node in range(num_node): # Gravity forces at the linearisation condition (from NL SHARPy in A frame) fgravA = tsstr.gravity_forces[i_node, :3] fgravG = Pga.dot(fgravA) # fgravG = tsstr.gravity_forces[i_node, :3] mgravA = tsstr.gravity_forces[i_node, 3:] fgravA = Pag.dot(fgravG) mgravG = Pag.dot(mgravA) # Get nodal position - A frame Ra = tsstr.pos[i_node, :] # retrieve element and local index ee, node_loc = self.structure.node_master_elem[i_node, :] psi = tsstr.psi[ee, node_loc, :] Cab = algebra.crv2rotation(psi) Cba = Cab.T Cbg = Cba.dot(Pag) # Tangential operator for moments calculation Tan = algebra.crv2tan(psi) jj = 0 # nodal dof index bc_at_node = self.structure.boundary_conditions[i_node] # Boundary conditions at the node if bc_at_node == 1: # clamp (only rigid-body) dofs_at_node = 0 jj_tra, jj_rot = [], [] elif bc_at_node == -1 or bc_at_node == 0: # (rigid+flex body) dofs_at_node = 6 jj_tra = 6 * self.structure.vdof[i_node] + np.array([0, 1, 2], dtype=int) # Translations jj_rot = 6 * self.structure.vdof[i_node] + np.array([3, 4, 5], dtype=int) # Rotations else: raise NameError('Invalid boundary condition (%d) at node %d!' \ % (bc_at_node, i_node)) jj += dofs_at_node if bc_at_node != 1: # Nodal centre of gravity (in the case of additional lumped masses, else should be zero) Mss_indices = np.concatenate((jj_tra, jj_rot)) Mss_node = Mss[Mss_indices,:] Mss_node = Mss_node[:, Mss_indices] Xcg_B = Cba.dot(-np.array([Mss_node[2, 4], Mss_node[0, 5], Mss_node[1, 3]]) / Mss_node[0, 0]) Xcg_Bskew = algebra.skew(Xcg_B) # Nodal CG in A frame Xcg_A_n = Ra + Cab.dot(Xcg_B) Xcg_A_n_skew = algebra.skew(Xcg_A_n) # Nodal CG in G frame - debug Xcg_G_n = Pga.dot(Xcg_A_n) if self.settings['print_info']: cout.cout_wrap("Node %2d \t-> B %.3f %.3f %.3f" %(i_node, Xcg_B[0], Xcg_B[1], Xcg_B[2]), 2) cout.cout_wrap("\t\t\t-> A %.3f %.3f %.3f" %(Xcg_A_n[0], Xcg_A_n[1], Xcg_A_n[2]), 2) cout.cout_wrap("\t\t\t-> G %.3f %.3f %.3f" %(Xcg_G_n[0], Xcg_G_n[1], Xcg_G_n[2]), 2) cout.cout_wrap("\tNode mass:", 2) cout.cout_wrap("\t\tMatrix: %.4f" % Mss_node[0, 0], 2) # cout.cout_wrap("\t\tGrav: %.4f" % (np.linalg.norm(fgravG)/9.81), 2) if self.use_euler: if bc_at_node != 1: # Nodal moments due to gravity -> linearisation terms wrt to delta_psi Kss_grav[np.ix_(jj_rot, jj_rot)] -= Tan.dot(Xcg_Bskew.dot(algebra.der_Ccrv_by_v(psi, fgravA))) Kss_grav[np.ix_(jj_rot, jj_rot)] -= algebra.der_TanT_by_xv(psi, Xcg_Bskew.dot(Cbg.dot(fgravG))) # Nodal forces due to gravity -> linearisation terms wrt to delta_euler Csr_grav[jj_tra, -3:] -= algebra.der_Peuler_by_v(tsstr.euler, fgravG) # Nodal moments due to gravity -> linearisation terms wrt to delta_euler Csr_grav[jj_rot, -3:] -= Tan.dot(Xcg_Bskew.dot(Cba.dot(algebra.der_Peuler_by_v(tsstr.euler, fgravG)))) # Total moments -> linearisation terms wrt to delta_Ra # These terms are not affected by the Euler matrix. Sign is correct (+) Krs_grav[3:6, jj_tra] += algebra.skew(fgravA) # Total moments -> linearisation terms wrt to delta_Psi Krs_grav[3:6, jj_rot] += np.dot(algebra.skew(fgravA), algebra.der_Ccrv_by_v(psi, Xcg_B)) else: if bc_at_node != 1: # Nodal moments due to gravity -> linearisation terms wrt to delta_psi Kss_grav[np.ix_(jj_rot, jj_rot)] -= Tan.dot(Xcg_Bskew.dot(algebra.der_Ccrv_by_v(psi, fgravA))) Kss_grav[np.ix_(jj_rot, jj_rot)] -= algebra.der_TanT_by_xv(psi, Xcg_Bskew.dot(Cbg.dot(fgravG))) # Total moments -> linearisation terms wrt to delta_Ra # Check sign (in theory it should be +=) Krs_grav[3:6, jj_tra] += algebra.skew(fgravA) # Total moments -> linearisation terms wrt to delta_Psi Krs_grav[3:6, jj_rot] += np.dot(algebra.skew(fgravA), algebra.der_Ccrv_by_v(psi, Xcg_B)) # Nodal forces due to gravity -> linearisation terms wrt to delta_euler Csr_grav[jj_tra, -4:] -= algebra.der_CquatT_by_v(tsstr.quat, fgravG) # ok # Crr_grav[:3, -4:] -= algebra.der_CquatT_by_v(tsstr.quat, fgravG) # not ok - see below # Nodal moments due to gravity -> linearisation terms wrt to delta_euler Csr_grav[jj_rot, -4:] -= Tan.dot(Xcg_Bskew.dot(Cba.dot(algebra.der_CquatT_by_v(tsstr.quat, fgravG)))) # Debugging: FgravA += fgravA FgravG += fgravG if self.use_euler: # Total gravity forces acting at the A frame Crr_grav[:3, -3:] -= algebra.der_Peuler_by_v(tsstr.euler, FgravG) # Total moments due to gravity in A frame Crr_grav[3:6, -3:] -= algebra.skew(Xcg_A).dot(algebra.der_Peuler_by_v(tsstr.euler, FgravG)) else: # Total gravity forces acting at the A frame Crr_grav[:3, -4:] -= algebra.der_CquatT_by_v(tsstr.quat, FgravG) # Total moments due to gravity in A frame Crr_grav[3:6, -4:] -= algebra.skew(Xcg_A).dot(algebra.der_CquatT_by_v(tsstr.quat, FgravG)) # Update matrices self.Kstr[:flex_dof, :flex_dof] += Kss_grav if self.Kstr[:flex_dof, :flex_dof].shape != self.Kstr.shape: # If the beam is free, update rigid terms as well self.Cstr[-rig_dof:, -rig_dof:] += Crr_grav self.Cstr[:-rig_dof, -rig_dof:] += Csr_grav self.Kstr[flex_dof:, :flex_dof] += Krs_grav # Save gravity matrices for post-processing self.Crr_grav = Crr_grav self.Csr_grav = Csr_grav self.Krs_grav = Krs_grav self.Kss_grav = Kss_grav if self.modal: self.Ccut = self.U.T.dot(self.Cstr.dot(self.U)) if self.settings['print_info']: cout.cout_wrap('\tUpdated the beam C, modal C and K matrices with the terms from the gravity linearisation\n') def linearise_applied_forces(self, tsstr=None): r""" Linearise externally applied follower forces given in the local ``B`` reference frame. Updates the stiffness matrix with terms arising from this linearisation. The linearised beam equations are expressed in the following frames of reference: * Nodal forces: :math:`\delta \mathbf{f}_A` * Nodal moments: :math:`\delta(T^T \mathbf{m}_B)` * Total forces (rigid body equations): :math:`\delta \mathbf{F}_A` * Total moments (rigid body equations): :math:`\delta \mathbf{M}_A` Thus, when linearising externally applied follower forces projected onto the appropriate frame .. math:: \boldsymbol{f}_A^{ext} = C^{AB}(\boldsymbol{\psi})\boldsymbol{f}^{ext}_B the following terms appear: .. math:: \delta\boldsymbol{f}_A^{ext} = \frac{\partial}{\partial\boldsymbol{\psi}} \left(C^{AB}(\boldsymbol{\psi})\boldsymbol{f}^{ext}_{0,B}\right)\delta\boldsymbol{\psi} + C^{AB}_0\delta\boldsymbol f_B^{ext} where the :math:`\delta\boldsymbol{\psi}` is a stiffenning term that needs to be included in the stiffness matrix. The terms will appear in the rows relating to the translational degrees of freedom and the columns that correspond to the cartesian rotation vector. .. math:: K_{ss}^{f,\Psi} \leftarrow -\frac{\partial}{\partial\boldsymbol{\psi}} \left(C^{AB}(\boldsymbol{\psi})\boldsymbol{f}^{ext}_{0,B}\right) Externally applied moments in the material frame :math:`\boldsymbol{m}_B^{ext}` result in the following linearised expression: .. math:: \delta(T^\top\boldsymbol{m}_B) = \frac{\partial}{\partial\boldsymbol{\psi}}\left( T^\top(\boldsymbol{\psi})\boldsymbol{m}^{ext}_{0,B}\right)\delta\boldsymbol{\psi} + T_0^\top \delta\boldsymbol{m}_B^{ext} Which results in the following stiffenning term: .. math:: K_{ss}^{m,\Psi} \leftarrow -\frac{\partial}{\partial\boldsymbol{\psi}}\left( T^\top(\boldsymbol{\psi})\boldsymbol{m}^{ext}_{0,B}\right) The total contribution of moments must be summed up for the rigid body equations, and include contributions due to externally applied forces as well as moments: .. math:: \boldsymbol{M}_A^{ext} = \sum_n \tilde{\boldsymbol{R}}_A C^{AB}(\boldsymbol{\psi}) \boldsymbol{f}_B^{ext} + \sum C^{AB}(\boldsymbol{\psi})\boldsymbol{m}_B^{ext} The linearisation of this term becomes .. math:: \delta\boldsymbol{M}_A^{ext} = \sum\left(-\widetilde{C^{AB}_0 \boldsymbol{f}_{0,B}^{ext}}\delta \boldsymbol{R}_A + \widetilde{\boldsymbol{R}}\frac{\partial}{\partial\boldsymbol{\psi}}\left(C^{AB}\boldsymbol{f}_B\right) \delta \boldsymbol{\psi} + \widetilde{\boldsymbol{R}}C^{AB}\delta\boldsymbol{f}^{ext}_B\right) + \sum\left(\frac{\partial}{\partial\boldsymbol{\psi}}\left(C^{AB}\boldsymbol{m}_{0,B}\right) \delta\boldsymbol{\psi} + C^AB\delta\boldsymbol{m}_B^{ext}\right) which gives the following stiffenning terms in the rigid-flex partition of the stiffness matrix: .. math:: K_{ss}^{M,R} \leftarrow +\sum\widetilde{C^{AB}_0 \boldsymbol{f}_{0,B}^{ext}} .. math:: K_{ss}^{M,\Psi} \leftarrow -\sum\widetilde{\boldsymbol{R}}\frac{\partial}{\partial\boldsymbol{\psi}} \left(C^{AB}\boldsymbol{f}_{0,B}\right) and .. math:: K_{ss}^{M,\Psi} \leftarrow -\sum\frac{\partial}{\partial\boldsymbol{\psi}} \left(C^{AB}\boldsymbol{m}_{0,B}\right). Args: tsstr (sharpy.utils.datastructures.StructTimeStepInfo): Linearisation time step. """ if tsstr is None: tsstr = self.tsstruct0 # TODO: Future feature: gains for externally applied forces (i.e. thrust inputs) num_node = tsstr.num_node flex_dof = 6 * sum(self.structure.vdof >= 0) if self.use_euler: rig_dof = 9 # This is a rotation matrix that rotates a vector from G to A Cag = algebra.euler2rot(tsstr.euler) Cga = Cag.T # Projection matrices - this projects the vector in G t to A Pag = Cga Pga = Cag else: rig_dof = 10 stiff_flex = np.zeros((flex_dof, flex_dof), dtype=float) # flex-flex partition of K stiff_rig = np.zeros((rig_dof, flex_dof), dtype=float) # rig-flex partition of K for i_node in range(num_node): fext_b = self.structure.steady_app_forces[i_node, :3] mext_b = self.structure.steady_app_forces[i_node, 3:] # retrieve element and local index ee, node_loc = self.structure.node_master_elem[i_node, :] psi = tsstr.psi[ee, node_loc, :] Cab = algebra.crv2rotation(psi) Cba = Cab.T # Tangential operator for moments calculation Tan = algebra.crv2tan(psi) # Get nodal position - in A frame Ra = tsstr.pos[i_node, :] jj = 0 # nodal dof index bc_at_node = self.structure.boundary_conditions[i_node] # Boundary conditions at the node if bc_at_node == 1: # clamp (only rigid-body) dofs_at_node = 0 jj_tra, jj_rot = [], [] elif bc_at_node == -1 or bc_at_node == 0: # (rigid+flex body) dofs_at_node = 6 jj_tra = 6 * self.structure.vdof[i_node] + np.array([0, 1, 2], dtype=int) # Translations jj_rot = 6 * self.structure.vdof[i_node] + np.array([3, 4, 5], dtype=int) # Rotations else: raise NameError('Invalid boundary condition ({}) at node {}'.format(bc_at_node, i_node)) jj += dofs_at_node if bc_at_node != 1: # Externally applied follower forces stiff_flex[np.ix_(jj_tra, jj_rot)] -= algebra.der_Ccrv_by_v(psi, fext_b) stiff_rig[:3, jj_rot] -= algebra.der_Ccrv_by_v(psi, fext_b) # Rigid body contribution # Externally applied moments in B frame stiff_flex[np.ix_(jj_rot, jj_rot)] -= algebra.der_TanT_by_xv(psi, mext_b) # Total moments # force contribution stiff_rig[3:6, jj_tra] += algebra.skew(Cab.dot(fext_b)) # delta Ra term stiff_rig[3:6, jj_rot] -= algebra.skew(Ra).dot(algebra.der_Ccrv_by_v(psi, fext_b)) # delta psi term # moment contribution stiff_rig[3:6, jj_rot] -= algebra.der_Ccrv_by_v(psi, mext_b) if bc_at_node == 1: # forces applied at the A-frame (clamped node) need special attention since the # node has an associated CRV to it's master element which may not be zero. # forces applied at this node only appear in the rigid-body equations try: closest_node = self.structure.connectivities[ee, node_loc + 2] except IndexError: # node is not in the first position try: closest_node = self.structure.connectivities[ee, node_loc + 1] except IndexError: # node is the midpoint closest_node = self.structure.connectivities[ee, node_loc - 1] # indices of the node whos CRV applies to the clamped node jj_rot = 6 * self.structure.vdof[closest_node] + np.array([3, 4, 5], dtype=int) stiff_rig[:3, jj_rot] -= algebra.der_Ccrv_by_v(psi, fext_b) # Rigid body contribution # Total moments # force contribution stiff_rig[3:6, jj_rot] += algebra.skew(Cab.dot(fext_b)) # delta Ra term stiff_rig[3:6, jj_rot] -= algebra.skew(Ra).dot(algebra.der_Ccrv_by_v(psi, fext_b)) # delta psi term # moment contribution stiff_rig[3:6, jj_rot] -= algebra.der_Ccrv_by_v(psi, mext_b) self.Kstr[:flex_dof, :flex_dof] += stiff_flex if self.Kstr[:flex_dof, :flex_dof].shape != self.Kstr.shape: # free flying structure self.Kstr[-rig_dof:, :flex_dof] += stiff_rig def assemble(self, Nmodes=None): r""" Assemble state-space model Several assembly options are available: 1. Discrete-time, Newmark-:math:`\beta`: * Modal projection onto undamped modes. It uses the modal projection such that the generalised coordinates :math:`\eta` are transformed into modal space by .. math:: \mathbf{\eta} = \mathbf{\Phi\,q} where :math:`\mathbf{\Phi}` are the first ``Nmodes`` right eigenvectors. Therefore, the equation of motion can be re-written such that the modes normalise the mass matrix to become the identity matrix. .. math:: \mathbf{I_{Nmodes}}\mathbf{\ddot{q}} + \mathbf{\Lambda_{Nmodes}\,q} = 0 The system is then assembled in Newmark-:math:`\beta` form as detailed in :func:`newmark_ss` * Full size system assembly. No modifications are made to the mass, damping or stiffness matrices and the system is directly assembled by :func:`newmark_ss`. 2. Continuous time state-space Args: Nmodes (int): number of modes to retain """ ### checks assert self.inout_coords in ['modes', 'nodes'], \ 'inout_coords=%s not implemented!' % self.inout_coords dlti = self.dlti modal = self.modal num_dof = self.num_dof if Nmodes is None or Nmodes >= self.num_modes: Nmodes = self.num_modes if dlti: # ---------------------------------- assemble discrete time if self.discr_method in ['zoh', 'bilinear']: # assemble continuous-time self.dlti = False self.assemble(Nmodes) # convert into discrete self.dlti = True self.cont2disc() elif self.discr_method == 'newmark': if modal: # Modal projection if self.proj_modes == 'undamped': Phi = self.U[:, :Nmodes] Ass, Bss, Css, Dss = newmark_ss( Phi.T @ self.Mstr @ Phi, Phi.T @ self.Cstr @ Phi, Phi.T @ self.Kstr @ Phi, self.dt, self.newmark_damp) self.Kin = libss.Gain(Phi.T) self.Kin.input_variables = LinearVector([InputVariable('forces_n', size=self.Mstr.shape[0], index=0)]) self.Kin.output_variables = LinearVector([OutputVariable('Q', size=Nmodes, index=0)]) self.Kout = libss.Gain(sc.linalg.block_diag(*[Phi, Phi])) self.Kout.input_variables = LinearVector([InputVariable('q', size=Nmodes, index=0), InputVariable('q_dot', size=Nmodes, index=1)]) output_variables = LinearVector([OutputVariable('eta', size=self.num_dof_flex, index=0), OutputVariable('eta_dot', size=self.num_dof_flex, index=1)]) if not self.clamped: output_variables.add('beta_bar', size=self.num_dof_rig, index=0.5) output_variables.append('beta', size=self.num_dof_rig) self.Kout.output_variables = output_variables else: raise NameError( 'Newmark-beta discretisation not available ' \ 'for projection on damped eigenvectors') # build state-space model self.SSdisc = libss.StateSpace(Ass, Bss, Css, Dss, dt=self.dt) input_variables = LinearVector([InputVariable('Q', size=Nmodes, index=0)]) output_variables = LinearVector([OutputVariable('q', size=Nmodes, index=0), OutputVariable('q_dot', size=Nmodes, index=1)]) state_variables = output_variables.transform(output_variables, to_type=StateVariable) self.SSdisc.input_variables = input_variables self.SSdisc.output_variables = output_variables self.SSdisc.state_variables = state_variables if self.inout_coords == 'nodes': self.SSdisc = libss.addGain(self.SSdisc, self.Kin, 'in') self.SSdisc = libss.addGain(self.SSdisc, self.Kout, 'out') self.Kin, self.Kout = None, None else: # Full system Ass, Bss, Css, Dss = newmark_ss( self.Mstr, self.Cstr, self.Kstr, self.dt, self.newmark_damp) self.Kin = None self.Kout = None self.SSdisc = libss.StateSpace(Ass, Bss, Css, Dss, dt=self.dt) input_variables = LinearVector([InputVariable('forces_n', size=self.Mstr.shape[0], index=0)]) output_variables = LinearVector([OutputVariable('eta', size=self.num_dof_flex, index=0), OutputVariable('eta_dot', size=self.num_dof_flex, index=1)]) if not self.clamped: output_variables.add('beta_bar', size=self.num_dof_rig, index=0.5) output_variables.append('beta', size=self.num_dof_rig) self.SSdisc.output_variables = output_variables self.SSdisc.input_variables = input_variables self.SSdisc.state_variables = LinearVector.transform(output_variables, to_type=StateVariable) else: raise NameError( 'Discretisation method %s not available' % self.discr_method) else: # -------------------------------- assemble continuous time if modal: # Modal projection Ass = np.zeros((2 * Nmodes, 2 * Nmodes)) Css = np.eye(2 * Nmodes) iivec = np.arange(Nmodes, dtype=int) if self.proj_modes == 'undamped': Phi = self.U[:, :Nmodes] Ass[iivec, Nmodes + iivec] = 1. # Ass[Nmodes:, :Nmodes] = -np.diag(self.freq_natural[:Nmodes] ** 2) Ass[Nmodes:, :Nmodes] = -self.U.T.dot(self.Kstr.dot(self.U)) if self.Ccut is not None: Ass[Nmodes:, Nmodes:] = -self.Ccut[:Nmodes, :Nmodes] Bss = np.zeros((2 * Nmodes, Nmodes)) Dss = np.zeros((2 * Nmodes, Nmodes)) Bss[Nmodes + iivec, iivec] = 1. self.Kin = libss.Gain(Phi.T) self.Kin.input_variables = LinearVector([InputVariable('forces_n', size=self.Mstr.shape[0], index=0)]) self.Kin.output_variables = LinearVector([OutputVariable('Q', size=Nmodes, index=0)]) self.Kout = libss.Gain(sc.linalg.block_diag(*[Phi, Phi])) self.Kout.input_variables = LinearVector([InputVariable('q', size=Nmodes, index=0), InputVariable('q_dot', size=Nmodes, index=1)]) output_variables = LinearVector([OutputVariable('eta', size=self.num_dof_flex, index=0), OutputVariable('eta_dot', size=self.num_dof_flex, index=1)]) if not self.clamped: output_variables.add('beta_bar', size=self.num_dof_rig, index=0.5) output_variables.append('beta', size=self.num_dof_rig) self.Kout.output_variables = output_variables else: # damped mode shapes # The algorithm assumes that for each couple of complex conj # eigenvalues, only one eigenvalue (and the eigenvectors # associated to it) is include in self.eigs. eigs = self.eigs[:Nmodes] U = self.U[:, :Nmodes] V = self.V[:, :Nmodes] Ass[iivec, iivec] = eigs.real Ass[iivec, Nmodes + iivec] = -eigs.imag Ass[Nmodes + iivec, iivec] = eigs.imag Ass[Nmodes + iivec, Nmodes + iivec] = eigs.real Bss = np.eye(2 * Nmodes) Dss = np.zeros((2 * Nmodes, 2 * Nmodes)) self.Kin = np.block( [[self.Kin_damp[iivec, :].real], [self.Kin_damp[iivec, :].imag]]) self.Kout = np.block([2. * U.real, (-2.) * U.imag]) # build state-space model self.SScont = libss.StateSpace(Ass, Bss, Css, Dss) input_variables = LinearVector([InputVariable('Q', size=Nmodes, index=0)]) output_variables = LinearVector([OutputVariable('q', size=Nmodes, index=0), OutputVariable('q_dot', size=Nmodes, index=1)]) state_variables = output_variables.transform(output_variables, to_type=StateVariable) self.SScont.input_variables = input_variables self.SScont.output_variables = output_variables self.SScont.state_variables = state_variables if self.inout_coords == 'nodes': self.SScont = libss.addGain(self.SScont, self.Kin, 'in') self.SScont = libss.addGain(self.SScont, self.Kout, 'out') self.Kin, self.Kout = None, None else: # Full system if self.Mstr is None: raise NameError('Full-states matrices not available') Mstr, Cstr, Kstr = self.Mstr, self.Cstr, self.Kstr Ass = np.zeros((2 * num_dof, 2 * num_dof)) Bss = np.zeros((2 * num_dof, num_dof)) Css = np.eye(2 * num_dof) Dss = np.zeros((2 * num_dof, num_dof)) Minv_neg = -np.linalg.inv(self.Mstr) Ass[range(num_dof), range(num_dof, 2 * num_dof)] = 1. Ass[num_dof:, :num_dof] = np.dot(Minv_neg, Kstr) Ass[num_dof:, num_dof:] = np.dot(Minv_neg, Cstr) Bss[num_dof:, :] = -Minv_neg self.Kin = None self.Kout = None self.SScont = libss.StateSpace(Ass, Bss, Css, Dss) input_variables = LinearVector([InputVariable('forces_n', size=self.Mstr.shape[0], index=0)]) output_variables = LinearVector([OutputVariable('eta', size=self.num_dof_flex, index=0), OutputVariable('eta_dot', size=self.num_dof_flex, index=1)]) if not self.clamped: output_variables.add('beta_bar', size=self.num_dof_rig, index=0.5) output_variables.append('beta', size=self.num_dof_rig) self.SScont.output_variables = output_variables self.SScont.input_variables = input_variables self.SScont.state_variables = LinearVector.transform(output_variables, to_type=StateVariable) def freqresp(self, wv=None, bode=True): """ Computes the frequency response of the current state-space model. If ``self.modal=True``, the in/out are determined according to ``self.inout_coords`` """ assert wv is not None, 'Frequency range not provided.' if self.dlti: self.Ydisc = libss.freqresp(self.SSdisc, wv, dlti=self.dlti) if bode: self.Ydisc_abs = np.abs(self.Ydisc) self.Ydisc_ph = np.angle(self.Ydisc, deg=True) else: self.Ycont = libss.freqresp(self.SScont, wv, dlti=self.dlti) if bode: self.Ycont_abs = np.abs(self.Ycont) self.Ycont_ph = np.angle(self.Ycont, deg=True) def converge_modal(self, wv=None, tol=None, Yref=None, Print=False): """ Determine number of modes required to achieve a certain convergence of the modal solution in a prescribed frequency range ``wv``. The H-infinity norm of the error w.r.t. ``Yref`` is used for assessing convergence. .. Warning:: if a reference freq. response, Yref, is not provided, the full- state continuous-time frequency response is used as reference. This requires the full-states matrices ``Mstr``, ``Cstr``, ``Kstr`` to be available. """ if wv is None: wv = self.wv assert wv is not None, 'Frequency range not provided.' assert tol is not None, 'Tolerance, tol, not provided' assert self.modal is True, 'Convergence analysis requires modal=True' if Yref is None: # use cont. time. full-states as reference dlti_here = self.dlti self.modal = False self.dlti = False self.assemble() self.freqresp(wv) Yref = self.Ycont.copy() self.dlti = dlti_here self.modal = True if Print: print('No. modes\tError\tTolerance') for nn in range(1, self.Nmodes + 1): self.assemble(Nmodes=nn) self.freqresp(wv, bode=False) Yhere = self.Ycont if self.dlti: Yhere = self.Ydisc er = np.max(np.abs(Yhere - Yref)) if Print: print('%.3d\t%.2e\t%.2e' % (nn, er, tol)) if er < tol: if Print: print('Converged!') self.Nmodes = nn break def tune_newmark_damp(self, amplification_factor=0.999): """ Tune artifical damping to achieve a percent reduction of the lower frequency (lower damped) mode """ assert self.discr_method == 'newmark' and self.dlti, \ "select self.discr_method='newmark' and self.dlti=True" newmark_damp = self.newmark_damp import scipy.optimize as scopt def get_res(newmark_damp_log10): self.newmark_damp = 10. ** (newmark_damp_log10) self.assemble() eigsabs = np.abs(np.linalg.eigvals(self.SSdisc.A)) return np.max(eigsabs) - amplification_factor exp_opt = scopt.fsolve(get_res, x0=-3)[0] self.newmark_damp = 10. ** exp_opt print('artificial viscosity: %.4e' % self.newmark_damp) def update_modal(self): r""" Re-projects the full-states continuous-time structural dynamics equations .. math:: \mathbf{M}\,\mathbf{\ddot{x}} +\mathbf{C}\,\mathbf{\dot{x}} + \mathbf{K\,x} = \mathbf{F} onto modal space. The modes used to project are controlled through the ``self.proj_modes={damped or undamped}`` attribute. .. Warning:: This method overrides SHARPy ``timestep_info`` results and requires ``Mstr``, ``Cstr``, ``Kstr`` to be available. """ if self.proj_modes == 'undamped': if self.Cstr is not None: if self.settings['print_info']: cout.cout_wrap('Warning, projecting system with damping onto undamped modes') # Eigenvalues are purely complex - only the complex part is calculated eigenvalues, eigenvectors = np.linalg.eig(np.linalg.solve(self.Mstr, self.Kstr)) omega = np.sqrt(eigenvalues) order = np.argsort(omega)[:self.Nmodes] self.freq_natural = omega[order] phi = eigenvectors[:, order] phi = modalutils.mode_sign_convention(self.structure.boundary_conditions, phi, not self.clamped, self.use_euler) if not self.clamped and self.use_principal_axes: phi = modalutils.free_modes_principal_axes(phi, self.Mstr, use_euler=self.use_euler) # Scale modes to have an identity mass matrix phi = modalutils.scale_mass_normalised_modes(phi, self.Mstr) self.U = phi # Update self.eigs = eigenvalues[order] if not self.use_principal_axes: # in the case of use_principal_axes modes are already ordered self.U = self.sort_repeated_evecs(self.U, self.eigs) # To do: update SHARPy's timestep info modal results else: raise NotImplementedError('Projection update for damped systems not yet implemented ') def update_truncated_modes(self, nmodes): r""" Updates the system to the specified number of modes Args: nmodes: Returns: """ # Verify that the new number of modes is less than the current value assert nmodes <= self.Nmodes, 'Unable to truncate to %g modes since only %g are available' %(nmodes, self.Nmodes) self.Nmodes = nmodes self.eigs = self.eigs[:nmodes] self.U = self.U[:,:nmodes] self.freq_natural = self.freq_natural[:nmodes] try: self.freq_damp[:nmodes] except TypeError: pass # Update Ccut matrix if self.modal: self.Ccut = np.dot(self.U.T, np.dot(self.Cstr, self.U)) def scale_system_normalised_time(self, time_ref): r""" Scale the system with a normalised time step. The resulting time step is :math:`\Delta t = \Delta \bar{t}/t_{ref}`, where the over bar denotes dimensional time. The structural equations of motion are rescaled as: .. math:: \mathbf{M}\ddot{\boldsymbol{\eta}} + \mathbf{C} t_{ref} \dot{\boldsymbol{\eta}} + \mathbf{K} t_{ref}^2 \boldsymbol{\eta} = t_{ref}^2 \mathbf{N} For aeroelastic applications, the reference time is usually defined using the semi-chord, :math:`b`, and the free stream velocity, :math:`U_\infty`. .. math:: t_{ref,ae} = \frac{b}{U_\infty} Args: time_ref (float): Normalisation factor such that :math:`t/\bar{t}` is non-dimensional. """ if self.scaled_reference_matrices: raise UserWarning('System already time scaled. System may just need an update.' ' See update_matrices_time_scale') # if time_ref != 1.0 and time_ref is not None: if self.num_rig_dof != 0: warnings.warn('Time normalisation not yet implemented with rigid body motion.') if self.dlti: self.scaled_reference_matrices['dt'] = self.dt self.dt /= time_ref if self.settings['print_info']: cout.cout_wrap('Scaling beam according to reduced time...', 0) if self.dlti: cout.cout_wrap('\tSetting the beam time step to (%.4f)' % self.dt, 1) self.scaled_reference_matrices['C'] = self.Cstr.copy() self.scaled_reference_matrices['K'] = self.Kstr.copy() self.update_matrices_time_scale(time_ref) def update_matrices_time_scale(self, time_ref): try: cout.cout_wrap('Updating C and K matrices and natural frequencies with new normalised time...', 1) except ValueError: pass try: self.Kstr = self.scaled_reference_matrices['K'] * time_ref ** 2 self.Cstr = self.scaled_reference_matrices['C'] * time_ref self.freq_natural *= time_ref except KeyError: raise KeyError('The scaled reference matrices have not been set, most likely because you are trying to ' 'rescale a dimensional system. Make sure your system is normalised.') def cont2disc(self, dt=None): """Convert continuous-time SS model into """ assert self.discr_method != 'newmark', \ 'For Newmark-beta discretisation, use assemble method directly.' if dt is not None: self.dt = dt else: assert self.dt is not None, \ 'Provide time-step for conversion to discrete-time' SScont = self.SScont tpl = scsig.cont2discrete( (SScont.A, SScont.B, SScont.C, SScont.D), dt=self.dt, method=self.discr_method) self.SSdisc = libss.StateSpace(*tpl[:-1], dt=tpl[-1]) self.dlti = True def newmark_ss(M, C, K, dt, num_damp=1e-4, M_is_SPD=False): r""" Produces a discrete-time state-space model of the 2nd order ordinary differential equation (ODE) given by: .. math:: \mathbf{M}\mathbf{\ddot{q}} + \mathbf{C}\mathbf{\dot{q}} + \mathbf{K}\mathbf{q} = \mathbf{f(t)} This ODE is discretized based on the Newmark-:math:`\beta` integration scheme. The output state-space model has the form: .. math:: \mathbf{x}_{n+1} &= \mathbf{A_{ss}}\mathbf{x}_n + \mathbf{B_{ss}}\mathbf{f}_n \\ \mathbf{y}_n &= \mathbf{C_{ss}}\mathbf{x}_n + \mathbf{D_{ss}}\mathbf{f}_n where :math:`\mathbf{y} = \begin{Bmatrix} \mathbf{q} \\ \mathbf{\dot{q}} \end{Bmatrix}` Note that as the state-space representation only requires the input force :math:`\mathbf{f}` to be evaluated at time-step :math:`n`, thus the pass-through matrix :math:`\mathbf{D_{ss}}` is not zero. This function retuns a tuple with the discrete state-space matrices :math:`(\mathbf{A_{ss},B_{ss},C_{ss},D_{ss}})`. Theory ------ The following steps describe how to apply the Newmark-:math:`\beta` scheme to the ODE in order to generate the discrete time-state space-model. It folows the development of [1]. .. admonition:: Notation Bold upper case letters represent matrices, bold lower case letters represent vectors. Non-bold symbols are scalars. Curly brackets indicate (block) vectors and square brackets indicate (block) matrices. Evaluating the ODE to the time steps :math:`t_n` and :math:`t_{n+1}` and isolating the acceleration term: .. math:: \mathbf{\ddot q}_{n} &= - \mathbf{M}^{-1}\mathbf{C}\mathbf{\dot{q}}_{n} - \mathbf{M}^{-1}\mathbf{K}\mathbf{q}_{n} + \mathbf{M}^{-1}\mathbf{f}_{n} \\ \mathbf{\ddot q}_{n+1} &= - \mathbf{M}^{-1}\mathbf{C}\mathbf{\dot{q}}_{n+1} - \mathbf{M}^{-1}\mathbf{K}\mathbf{q}_{n+1} + \mathbf{M}^{-1}\mathbf{f}_{n+1} \\ The update equations of the Newmark-beta scheme are [1]: .. math:: \mathbf{q}_{n+1} &= \mathbf{q}_n + \mathbf{\dot q}_n\Delta t + (1/2-\beta)\mathbf{\ddot q}_n \Delta t^2 + \beta \mathbf{\ddot q}_{n+1} \Delta t^2 + O(\Delta t^3) \\ \mathbf{\dot q}_{n+1} &= \mathbf{\dot q}_n + (1-\gamma)\mathbf{\ddot q}_n \Delta t + \gamma \mathbf{\ddot q}_{n+1} \Delta t + O(\Delta t^3) where :math:`\Delta t = t_{n+1} - t_n`. The stencil is unconditionally stable if the tuning parameters :math:`\gamma` and :math:`\beta` are chosen as: .. math:: \gamma &= \frac{1}{2} + \alpha \\ \beta &= \frac{(1+\alpha)^2}{4} = \frac{(1/2+\gamma)^2}{4} = \frac{1}{16} + \frac{1}{4}(\gamma + \gamma^2) where :math:`\alpha>0` accounts for small positive algorithmic damping (:math:`\alpha` is ``num_damp`` in the code). Substituting the former relations onto the later ones, rearranging terms, and writing it in state-space form: .. math:: \mathbf{A_{ss1}} \begin{Bmatrix} \mathbf{q}_{n+1} \\ \mathbf{\dot q}_{n+1} \end{Bmatrix} = \mathbf{A_{ss0}} \begin{Bmatrix} \mathbf{q}_{n} \\ \mathbf{\dot q}_{n} \end{Bmatrix} + \mathbf{B_{ss0}} \mathbf{f}_n + \mathbf{B_{ss1}} \mathbf{f}_{n+1} where .. math:: \mathbf{A_{ss1}} &= \begin{bmatrix} \mathbf{I} + \beta\Delta t^2 \mathbf{M}^{-1}\mathbf{K} & \beta\Delta t^2 \mathbf{M}^{-1}\mathbf{C} \\ \gamma \Delta t \mathbf{M}^{-1}\mathbf{K} & \mathbf{I}+ \gamma \Delta t \mathbf{M}^{-1}\mathbf{C} \end{bmatrix} \\ \mathbf{A_{ss0}} &= \begin{bmatrix} \mathbf{I} - \Delta t^2(1/2-\beta)\mathbf{M}^{-1}\mathbf{K} & \Delta t \mathbf{I} - (1/2-\beta)\Delta t^2 \mathbf{M}^{-1}\mathbf{C} \\ -(1-\gamma)\Delta t \mathbf{M}^{-1}\mathbf{K} & \mathbf{I} - (1-\gamma)\Delta t \mathbf{M}^{-1}\mathbf{C} \end{bmatrix} \\ \mathbf{B_{ss0}} &= \begin{bmatrix} (\Delta t^2(1/2-\beta) \mathbf{M}^{-1} \\ (1-\gamma)\Delta t \mathbf{M}^{-1} \end{bmatrix} \\ \mathbf{B_{ss1}} &= \begin{bmatrix} (\beta \Delta t^2) \mathbf{M}^{-1}\\ (\gamma \Delta t) \mathbf{M}^{-1} \end{bmatrix} This is not in standard space-state form because the state update equation depends of the input at :math:`t_{n+1}`. This term can be eliminated by defining the state .. math:: \mathbf{x}_n = \begin{Bmatrix} \mathbf{q}_{n} \\ \mathbf{\dot q}_{n} \end{Bmatrix} - \mathbf{A}_{\mathbf{ss1}}^{-1}\mathbf{B_{ss1}} \mathbf{f}_n Then .. math:: \mathbf{x}_{n+1} &= \mathbf{A}_{\mathbf{ss1}}^{-1}[ \mathbf{A_{ss0}} \mathbf{x}_n + ( \mathbf{A_{ss0}}\mathbf{A}_{\mathbf{ss1}}^{-1}\mathbf{B_{ss1}} + \mathbf{B_{ss0}} ) \mathbf{f}_n ] \\ \begin{Bmatrix} \mathbf{q}_{n} \\ \mathbf{\dot q}_{n} \end{Bmatrix} &= \mathbf{x}_n + \mathbf{A}_{\mathbf{ss1}}^{-1}\mathbf{B_{ss1}} \mathbf{f}_n See also :func:`sharpy.linear.src.libss.SSconv` for more details on the elimination of the term multiplying :math:`\mathbf{f}_{n+1}` in the state equation. This system is identified with a standard discrete-time state-space model .. math:: \mathbf{x}_{n+1} &= \mathbf{A_{ss}} \mathbf{x}_n + \mathbf{B_{ss}} \mathbf{f}_n \\ \mathbf{y_n} &= \mathbf{C_{ss}} \mathbf{x}_n + \mathbf{D_{ss}} \mathbf{f}_n where .. math:: \mathbf{A_{ss}} &= \mathbf{A}_{\mathbf{ss1}}^{-1}\mathbf{A_{ss0}} \\ \mathbf{B_{ss}} &= \mathbf{A_{\mathbf{ss1}}^{-1}(\mathbf{B_{ss0}} + \mathbf{A_{ss0}}\mathbf{A_{\mathbf{ss1}}^{-1}\mathbf{B_{ss1}}) \\ \mathbf{C_{ss}} &= \mathbf{I} \\ \mathbf{D_{ss}} &= \mathbf{A_{\mathbf{ss1}}^{-1}\mathbf{B_{ss1}} .. admonition:: Notation is used in the code .. math:: \texttt{th1} &= \gamma \\ \texttt{th2} &= \beta \\ \texttt{a0} &= (1/2 -\beta) \Delta t^2 \\ \texttt{b0} &= (1 -\gamma) \Delta t \\ \texttt{a1} &= \beta \Delta t^2 \\ \texttt{b1} &= \gamma \Delta t \\ Args: M (np.array): Mass matrix :math:`\mathbf{M}` C (np.array): Damping matrix :math:`\mathbf{C}` K (np.array): Stiffness matrix :math:`\mathbf{K}` dt (float): Timestep increment num_damp (float): Numerical damping. Default ``1e-4`` M_is_SPD (bool): whether to factorized M using Cholesky (only works for SPD matrices) or LU decomposition. Default: ``False`` Returns: tuple: with the :math:`(\mathbf{A_{ss},B_{ss},C_{ss},D_{ss}})` matrices of the discrete-time state-space representation References: [1] - Geradin M., Rixen D. - Mechanical Vibrations: Theory and application to structural dynamics """ # weights th1 = 0.5 + num_damp # th2=0.25*(th1+.5)**2 th2 = 0.0625 + 0.25 * (th1 + th1 ** 2) dt2 = dt ** 2 a1 = th2 * dt2 a0 = 0.5 * dt2 - a1 b1 = th1 * dt b0 = dt - b1 # Identity matrix N = K.shape[0] Imat = np.eye(N) # Factorize M and obtain relevant matrices # Even though the inverse needs to be explicitly calculated, using the matrix # factorization is more numerically stable and faster than multiplying by the # inverse M_factors = sc.linalg.cho_factor(M) if M_is_SPD else sc.linalg.lu_factor(M) def M_solve(b): return sc.linalg.cho_solve(M_factors, b) if M_is_SPD else sc.linalg.lu_solve(M_factors, b) Minv = M_solve(Imat) MinvK = M_solve(K) MinvC = M_solve(C) # build StateSpace Ass0 = np.block([[Imat - a0 * MinvK, dt * Imat - a0 * MinvC], [-b0 * MinvK, Imat - b0 * MinvC]]) Ass1 = np.block([[Imat + a1 * MinvK, a1 * MinvC], [b1 * MinvK, Imat + b1 * MinvC]]) # Factorize Ass1 once, solve multiple times Ass1_factors = sc.linalg.lu_factor(Ass1) Ass = sc.linalg.lu_solve(Ass1_factors, Ass0) Bss0 = sc.linalg.lu_solve(Ass1_factors, np.block([[a0 * Minv], [b0 * Minv]])) Bss1 = sc.linalg.lu_solve(Ass1_factors, np.block([[a1 * Minv], [b1 * Minv]])) # eliminate predictior term Bss1 return libss.SSconv(Ass, Bss0, Bss1, C=np.eye(2 * N), D=np.zeros((2 * N, N))) def sort_eigvals(eigv, eigabsv, tol=1e-6): """ sort by magnitude (frequency) and imaginary part if complex conj """ order = np.argsort(np.abs(eigv)) eigv = eigv[order] for ii in range(len(eigv) - 1): # check if ii and ii+1 are the same eigenvalue if np.abs(eigv[ii].imag + eigv[ii + 1].imag) / eigabsv[ii] < tol: if np.abs(eigv[ii].real - eigv[ii + 1].real) / eigabsv[ii] < tol: # swap if required if eigv[ii].imag > eigv[ii + 1].imag: temp = eigv[ii] eigv[ii] = eigv[ii + 1] eigv[ii + 1] = temp temp = order[ii] order[ii] = order[ii + 1] order[ii + 1] = temp return order, eigv ================================================ FILE: sharpy/linear/src/linuvlm.py ================================================ """ Linear UVLM solver classes Contains classes to assemble a linear UVLM system. The three main classes are: * :class:`~sharpy.linear.src.linuvlm.Static`: : for static VLM solutions. * :class:`~sharpy.linear.src.linuvlm.Dynamic`: for dynamic UVLM solutions. * :class:`~sharpy.linear.src.linuvlm.DynamicBlock`: a more efficient representation of ``Dynamic`` using lists for the different blocks in the UVLM equations References: Maraniello, S., & Palacios, R.. State-Space Realizations and Internal Balancing in Potential-Flow Aerodynamics with Arbitrary Kinematics. AIAA Journal, 57(6), 1–14. 2019. https://doi.org/10.2514/1.J058153 """ import time import warnings import numpy as np import scipy.linalg as scalg import scipy.sparse as sparse import sharpy.linear.src.interp as interp import sharpy.linear.src.multisurfaces as multisurfaces import sharpy.linear.src.assembly as ass import sharpy.linear.src.libss as libss import sharpy.linear.src.libsparse as libsp import sharpy.rom.utils.librom as librom import sharpy.utils.algebra as algebra import sharpy.utils.settings as settings import sharpy.utils.cout_utils as cout import sharpy.utils.exceptions as exceptions from sharpy.utils.constants import vortex_radius_def from sharpy.linear.utils.ss_interface import LinearVector, StateVariable, InputVariable, OutputVariable settings_types_static = dict() settings_default_static = dict() settings_types_static['vortex_radius'] = 'float' settings_default_static['vortex_radius'] = vortex_radius_def settings_types_static['cfl1'] = 'bool' settings_default_static['cfl1'] = True settings_types_dynamic = dict() settings_default_dynamic = dict() settings_types_dynamic['dt'] = 'float' settings_default_dynamic['dt'] = 0.1 settings_types_dynamic['integr_order'] = 'int' settings_default_dynamic['integr_order'] = 2 settings_types_dynamic['density'] = 'float' settings_default_dynamic['density'] = 1.225 settings_types_dynamic['ScalingDict'] = 'dict' settings_default_dynamic['ScalingDict'] = {'length': 1.0, 'speed': 1.0, 'density': 1.0} settings_types_dynamic['remove_predictor'] = 'bool' settings_default_dynamic['remove_predictor'] = True settings_types_dynamic['use_sparse'] = 'bool' settings_default_dynamic['use_sparse'] = True settings_types_dynamic['vortex_radius'] = 'float' settings_default_dynamic['vortex_radius'] = vortex_radius_def settings_types_dynamic['cfl1'] = 'bool' settings_default_dynamic['cfl1'] = True class Static(): """ Static linear solver """ def __init__(self, tsdata, custom_settings=None, for_vel=np.zeros((6,))): cout.cout_wrap('Initialising Static linear UVLM solver class...') t0 = time.time() if custom_settings is None: settings_here = settings_default_static else: settings_here = custom_settings settings.to_custom_types(settings_here, settings_types_static, settings_default_static, no_ctype=True) self.vortex_radius = settings_here['vortex_radius'] self.cfl1 = settings_here['cfl1'] MS = multisurfaces.MultiAeroGridSurfaces(tsdata, self.vortex_radius, for_vel=for_vel) MS.get_ind_velocities_at_collocation_points() MS.get_input_velocities_at_collocation_points() MS.get_ind_velocities_at_segments() MS.get_input_velocities_at_segments() # define total sizes self.K = sum(MS.KK) self.K_star = sum(MS.KK_star) self.Kzeta = sum(MS.KKzeta) self.Kzeta_star = sum(MS.KKzeta_star) self.MS = MS # define input perturbation self.zeta = np.zeros((3 * self.Kzeta)) self.zeta_dot = np.zeros((3 * self.Kzeta)) self.u_ext = np.zeros((3 * self.Kzeta)) self.input_variables_list = [InputVariable('zeta', size=3 * self.Kzeta, index=0), InputVariable('zeta_dot', size=3 * self.Kzeta, index=1), InputVariable('u_gust', size=3 * self.Kzeta, index=2)] self.state_variables_list = [StateVariable('gamma', size=self.K, index=0), StateVariable('gamma_w', size=self.K_star, index=1), StateVariable('gamma_m1', size=self.K, index=2)] self.output_variables_list = [OutputVariable('forces_v', size=3 * self.Kzeta, index=0)] # profiling output self.prof_out = './asbly.prof' self.time_init_sta = time.time() - t0 cout.cout_wrap('\t\t\t...done in %.2f sec' % self.time_init_sta) def assemble_profiling(self): """ Generate profiling report for assembly and save it in self.prof_out. To read the report: import pstats p=pstats.Stats(self.prof_out) """ import cProfile cProfile.runctx('self.assemble()', globals(), locals(), filename=self.prof_out) def assemble(self): """ Assemble global matrices """ cout.cout_wrap('\tAssembly of static linear UVLM equations started...', 1) MS = self.MS t0 = time.time() # ----------------------------------------------------------- state eq. List_uc_dncdzeta = ass.uc_dncdzeta(MS.Surfs) List_nc_dqcdzeta_coll, List_nc_dqcdzeta_vert = \ ass.nc_dqcdzeta(MS.Surfs, MS.Surfs_star) List_AICs, List_AICs_star = ass.AICs(MS.Surfs, MS.Surfs_star, target='collocation', Project=True) List_Wnv = [] for ss in range(MS.n_surf): List_Wnv.append( interp.get_Wnv_vector(MS.Surfs[ss], MS.Surfs[ss].aM, MS.Surfs[ss].aN)) ### zeta derivatives self.Ducdzeta = np.block(List_nc_dqcdzeta_vert) del List_nc_dqcdzeta_vert self.Ducdzeta += scalg.block_diag(*List_nc_dqcdzeta_coll) del List_nc_dqcdzeta_coll self.Ducdzeta += scalg.block_diag(*List_uc_dncdzeta) del List_uc_dncdzeta # # omega x zeta terms List_nc_domegazetadzeta_vert = ass.nc_domegazetadzeta(MS.Surfs, MS.Surfs_star) self.Ducdzeta += scalg.block_diag(*List_nc_domegazetadzeta_vert) del List_nc_domegazetadzeta_vert ### input velocity derivatives self.Ducdu_ext = scalg.block_diag(*List_Wnv) del List_Wnv ### Condense Gammaw terms for ss_out in range(MS.n_surf): K = MS.KK[ss_out] for ss_in in range(MS.n_surf): N_star = MS.NN_star[ss_in] aic = List_AICs[ss_out][ss_in] # bound aic_star = List_AICs_star[ss_out][ss_in] # wake # fold aic_star: sum along chord at each span-coordinate aic_star_fold = np.zeros((K, N_star)) for jj in range(N_star): aic_star_fold[:, jj] += np.sum(aic_star[:, jj::N_star], axis=1) aic[:, -N_star:] += aic_star_fold self.AIC = np.block(List_AICs) # ---------------------------------------------------------- output eq. ### Zeta derivatives # ... at constant relative velocity self.Dfqsdzeta = scalg.block_diag( *ass.dfqsdzeta_vrel0(MS.Surfs, MS.Surfs_star)) # ... induced velocity contrib. List_coll, List_vert = ass.dfqsdvind_zeta(MS.Surfs, MS.Surfs_star) for ss in range(MS.n_surf): List_vert[ss][ss] += List_coll[ss] self.Dfqsdzeta += np.block(List_vert) del List_vert, List_coll ### Input velocities self.Dfqsdu_ext = scalg.block_diag( *ass.dfqsduinput(MS.Surfs, MS.Surfs_star)) ### Gamma derivatives # ... at constant relative velocity List_dfqsdgamma_vrel0, List_dfqsdgamma_star_vrel0 = \ ass.dfqsdgamma_vrel0(MS.Surfs, MS.Surfs_star) self.Dfqsdgamma = scalg.block_diag(*List_dfqsdgamma_vrel0) self.Dfqsdgamma_star = scalg.block_diag(*List_dfqsdgamma_star_vrel0) del List_dfqsdgamma_vrel0, List_dfqsdgamma_star_vrel0 # ... induced velocity contrib. List_dfqsdvind_gamma, List_dfqsdvind_gamma_star = \ ass.dfqsdvind_gamma(MS.Surfs, MS.Surfs_star) self.Dfqsdgamma += np.block(List_dfqsdvind_gamma) self.Dfqsdgamma_star += np.block(List_dfqsdvind_gamma_star) del List_dfqsdvind_gamma, List_dfqsdvind_gamma_star self.time_asbly = time.time() - t0 cout.cout_wrap('\t\t\t...done in %.2f sec' % self.time_asbly, 1) def solve(self): r""" Solve for bound :math:`\\Gamma` using the equation; .. math:: \\mathcal{A}(\\Gamma^n) = u^n # ... at constant rotation speed ``self.Dfqsdzeta+=scalg.block_diag(*ass.dfqsdzeta_omega(MS.Surfs,MS.Surfs_star))`` """ MS = self.MS t0 = time.time() ### state bv = np.dot(self.Ducdu_ext, self.u_ext - self.zeta_dot) + \ np.dot(self.Ducdzeta, self.zeta) self.gamma = np.linalg.solve(self.AIC, -bv) ### retrieve gamma over wake gamma_star = [] Ktot = 0 for ss in range(MS.n_surf): Ktot += MS.Surfs[ss].maps.K N = MS.Surfs[ss].maps.N Mstar = MS.Surfs_star[ss].maps.M gamma_star.append(np.concatenate(Mstar * [self.gamma[Ktot - N:Ktot]])) gamma_star = np.concatenate(gamma_star) ### compute steady force self.fqs = np.dot(self.Dfqsdgamma, self.gamma) + \ np.dot(self.Dfqsdgamma_star, gamma_star) + \ np.dot(self.Dfqsdzeta, self.zeta) + \ np.dot(self.Dfqsdu_ext, self.u_ext - self.zeta_dot) self.time_sol = time.time() - t0 cout.cout_wrap('\tSolution done in %.2f sec' % self.time_sol, 1) def reshape(self): """ Reshapes state/output according to SHARPy format """ MS = self.MS if not hasattr(self, 'gamma') or not hasattr(self, 'fqs'): raise NameError('State and output not found') self.Gamma = [] self.Fqs = [] Ktot, Kzeta_tot = 0, 0 for ss in range(MS.n_surf): M, N = MS.Surfs[ss].maps.M, MS.Surfs[ss].maps.N K, Kzeta = MS.Surfs[ss].maps.K, MS.Surfs[ss].maps.Kzeta iivec = range(Ktot, Ktot + K) self.Gamma.append(self.gamma[iivec].reshape((M, N))) iivec = range(Kzeta_tot, Kzeta_tot + 3 * Kzeta) self.Fqs.append(self.fqs[iivec].reshape((3, M + 1, N + 1))) Ktot += K Kzeta_tot += 3 * Kzeta def total_forces(self, zeta_pole=np.zeros((3,))): """ Calculates total force (Ftot) and moment (Mtot) (about pole zeta_pole). """ if not hasattr(self, 'Gamma') or not hasattr(self, 'Fqs'): self.reshape() self.Ftot = np.zeros((3,)) self.Mtot = np.zeros((3,)) for ss in range(self.MS.n_surf): M, N = self.MS.Surfs[ss].maps.M, self.MS.Surfs[ss].maps.N for nn in range(N + 1): for mm in range(M + 1): zeta_node = self.MS.Surfs[ss].zeta[:, mm, nn] fnode = self.Fqs[ss][:, mm, nn] self.Ftot += fnode self.Mtot += np.cross(zeta_node - zeta_pole, fnode) # for cc in range(3): # self.Ftot[cc]+=np.sum(self.Fqs[ss][cc,:,:]) def get_total_forces_gain(self, zeta_pole=np.zeros((3,))): r""" Calculates gain matrices to calculate the total force (Kftot) and moment (Kmtot, Kmtot_disp) about the pole zeta_pole. Being :math:`f` and :math:`\zeta` the force and position at the vertex (m,n) of the lattice these are produced as: * ``ftot=sum(f) -> dftot += df`` * ``mtot-sum((zeta-zeta_pole) x f) -> dmtot += cross(zeta0-zeta_pole) df - cross(f0) dzeta`` """ self.Kftot = np.zeros((3, 3 * self.Kzeta)) self.Kmtot = np.zeros((3, 3 * self.Kzeta)) self.Kmtot_disp = np.zeros((3, 3 * self.Kzeta)) Kzeta_start = 0 for ss in range(self.MS.n_surf): M, N = self.MS.Surfs[ss].maps.M, self.MS.Surfs[ss].maps.N for nn in range(N + 1): for mm in range(M + 1): jjvec = [Kzeta_start + np.ravel_multi_index((cc, mm, nn), (3, M + 1, N + 1)) for cc in range(3)] self.Kftot[[0, 1, 2], jjvec] = 1. self.Kmtot[np.ix_([0, 1, 2], jjvec)] = algebra.skew( self.MS.Surfs[ss].zeta[:, mm, nn] - zeta_pole) self.Kmtot_disp[np.ix_([0, 1, 2], jjvec)] = algebra.skew( -self.MS.Surfs[ss].fqs[:, mm, nn]) Kzeta_start += 3 * self.MS.KKzeta[ss] def get_sect_forces_gain(self): """ Gains to computes sectional forces. Moments are computed w.r.t. mid-vertex (chord-wise index M/2) of each section. """ Ntot = 0 for ss in range(self.MS.n_surf): Ntot += self.MS.NN[ss] + 1 self.Kfsec = np.zeros((3 * Ntot, 3 * self.Kzeta)) self.Kmsec = np.zeros((3 * Ntot, 3 * self.Kzeta)) Kzeta_start = 0 II_start = 0 for ss in range(self.MS.n_surf): M, N = self.MS.MM[ss], self.MS.NN[ss] for nn in range(N + 1): zeta_sec = self.MS.Surfs[ss].zeta[:, :, nn] # section indices iivec = [II_start + np.ravel_multi_index((cc, nn), (3, N + 1)) for cc in range(3)] # iivec = [II_start + cc+6*nn for cc in range(6)] # iivec = [II_start + cc*(N+1) + nn for cc in range(6)] for mm in range(M + 1): # vertex indices jjvec = [Kzeta_start + np.ravel_multi_index((cc, mm, nn), (3, M + 1, N + 1)) for cc in range(3)] # sectional force self.Kfsec[iivec, jjvec] = 1.0 # sectional moment dx, dy, dz = zeta_sec[:, mm] - zeta_sec[:, M // 2] self.Kmsec[np.ix_(iivec, jjvec)] = np.array([[0, -dz, dy], [dz, 0, -dx], [-dy, dx, 0]]) Kzeta_start += 3 * self.MS.KKzeta[ss] II_start += 3 * (N + 1) def get_rigid_motion_gains(self, zeta_rotation=np.zeros((3,))): """ Gains to reproduce rigid-body motion such that grid displacements and velocities are given by: * ``dzeta = Ktra*u_tra + Krot*u_rot`` * ``dzeta_dot = Ktra_vel*u_tra_dot + Krot*u_rot_dot`` Rotations are assumed to happen independently with respect to the zeta_rotation point and about the x,y and z axes of the inertial frame. """ # warnings.warn('Rigid rotation matrix not implemented!') Ntot = 0 for ss in range(self.MS.n_surf): Ntot += self.MS.NN[ss] + 1 self.Ktra = np.zeros((3 * self.Kzeta, 3)) self.Ktra_dot = np.zeros((3 * self.Kzeta, 3)) self.Krot = np.zeros((3 * self.Kzeta, 3)) self.Krot_dot = np.zeros((3 * self.Kzeta, 3)) Kzeta_start = 0 for ss in range(self.MS.n_surf): M, N = self.MS.MM[ss], self.MS.NN[ss] zeta = self.MS.Surfs[ss].zeta for nn in range(N + 1): for mm in range(M + 1): # vertex indices iivec = [Kzeta_start + np.ravel_multi_index((cc, mm, nn), (3, M + 1, N + 1)) for cc in range(3)] self.Ktra[iivec, [0, 1, 2]] += 1. self.Ktra_dot[iivec, [0, 1, 2]] += 1. # sectional moment dx, dy, dz = zeta[:, mm, nn] - zeta_rotation Dskew = np.array([[0, -dz, dy], [dz, 0, -dx], [-dy, dx, 0]]) self.Krot[iivec, :] = Dskew self.Krot_dot[iivec, :] = Dskew Kzeta_start += 3 * self.MS.KKzeta[ss] # # ------------------------------------------------------------------------------ # # utilities for Dynamic.balfreq method # def get_trapz_weights(k0,kend,Nk,knyq=False): # """ # Returns uniform frequency grid (kv of length Nk) and weights (wv) for # Gramians integration using trapezoidal rule. If knyq is True, it is assumed # that kend is also the Nyquist frequency. # """ # assert k0>=0. and kend>=0., 'Frequencies must be positive!' # dk=(kend-k0)/(Nk-1.) # kv=np.linspace(k0,kend,Nk) # wv=np.ones((Nk,))*dk*np.sqrt(2) # if k0/(kend-k0)<1e-10: # wv[0]=.5*dk # else: # wv[0]=dk/np.sqrt(2) # if knyq: # wv[-1]=.5*dk # else: # wv[-1]=dk/np.sqrt(2) # return kv,wv # def get_gauss_weights(k0,kend,Npart,order): # """ # Returns gauss-legendre frequency grid (kv of length Npart*order) and # weights (wv) for Gramians integration. # The integration grid is divided into Npart partitions, and in each of # them integration is performed using a Gauss-Legendre quadrature of # order order. # Note: integration points are never located at k0 or kend, hence there # is no need for special treatment as in (for e.g.) a uniform grid case # (see get_unif_weights) # """ # if Npart==1: # # get gauss normalised coords and weights # xad,wad=np.polynomial.legendre.leggauss(order) # krange=kend-k0 # kv=.5*(k0+kend) + .5*krange*xad # wv=wad*(.5*krange)*np.sqrt(2) # print('partitioning: %.3f to %.3f' %(k0,kend) ) # else: # kv=np.zeros((Npart*order,)) # wv=np.zeros((Npart*order,)) # dk_part=(kend-k0)/Npart # for ii in range(Npart): # k0_part=k0+ii*dk_part # kend_part=k0_part+dk_part # iivec=range(order*ii, order*(ii+1)) # kv[iivec],wv[iivec]=get_gauss_weights(k0_part,kend_part,Npart=1,order=order) # return kv,wv # ------------------------------------------------------------------------------ class Dynamic(Static): r""" Class for dynamic linearised UVLM solution. Linearisation around steady-state are only supported. The class is built upon Static, and inherits all the methods contained there. Input: - tsdata: aero timestep data from SHARPy solution - dt: time-step - integr_order=2: integration order for UVLM unsteady aerodynamic force - RemovePredictor=True: if true, the state-space model is modified so as to accept in input perturbations, u, evaluated at time-step n rather than n+1. - ScalingDict=None: disctionary containing fundamental reference units: .. code-block:: python {'length': reference_length, 'speed': reference_speed, 'density': reference density} used to derive scaling quantities for the state-space model variables. The scaling factors are stored in ``self.ScalingFact``. Note that while time, circulation, angular speeds) are scaled accordingly, FORCES ARE NOT. These scale by :math:`q_\infty b^2`, where :math:`b` is the reference length and :math:`q_\infty` is the dynamic pressure. - UseSparse=False: builds the A and B matrices in sparse form. C and D are dense anyway so the sparse format cannot be applied to them. Methods: - nondimss: normalises a dimensional state-space model based on the scaling factors in self.ScalingFact. - dimss: inverse of nondimss. - assemble_ss: builds state-space model. See function for more details. - assemble_ss_profiling: generate profiling report of the assembly and saves it into self.prof_out. To read the report: .. code-block:: python import pstats p = pstats.Stats(self.prof_out) - solve_steady: solves for the steady state. Several methods available. - solve_step: solves one time-step - freqresp: ad-hoc method for fast frequency response (only implemented) for ``remove_predictor=False`` Attributes: Nx (int): Number of states Nu (int): Number of inputs Ny (int): Number of outputs K (int): Number of paneles :math:`K = MN` K_star (int): Number of wake panels :math:`K^*=M^*N` Kzeta (int): Number of panel vertices :math:`K_\zeta=(M+1)(N+1)` Kzeta_star (int): Number of wake panel vertices :math:`K_{\zeta,w} = (M^*+1)(N+1)` To do: Upgrade to linearise around unsteady snapshot (adjoint) """ def __init__(self, tsdata, dt=None, dynamic_settings=None, integr_order=2, RemovePredictor=True, ScalingDict=None, UseSparse=True, for_vel=np.zeros((6,))): # Transform settings dictionary - in the future remove remaining inputs self.settings = dict() if dynamic_settings: self.settings = dynamic_settings settings.to_custom_types(self.settings, settings_types_dynamic, settings_default_dynamic, no_ctype=True) else: warnings.warn('No settings dictionary found. Using default. Individual parsing of settings is deprecated', DeprecationWarning) # Future: remove deprecation warning and make settings the only argument settings.to_custom_types(self.settings, settings_types_dynamic, settings_default_dynamic, no_ctype=True) self.settings['dt'] = dt self.settings['integr_order'] = integr_order self.settings['remove_predictor'] = RemovePredictor self.settings['use_sparse'] = UseSparse self.settings['ScalingDict'] = ScalingDict static_dict = {'vortex_radius': self.settings['vortex_radius'], 'cfl1': self.settings['cfl1']} super().__init__(tsdata, custom_settings=static_dict, for_vel=for_vel) self.dt = self.settings['dt'] self.integr_order = self.settings['integr_order'] self.vortex_radius = self.settings['vortex_radius'] self.cfl1 = self.settings['cfl1'] if self.integr_order == 1: Nx = 2 * self.K + self.K_star elif self.integr_order == 2: Nx = 3 * self.K + self.K_star # b0, bm1, bp1 = -2., 0.5, 1.5 else: raise NameError('Only integration orders 1 and 2 are supported') Ny = 3 * self.Kzeta Nu = 3 * Ny self.Nx = Nx self.Nu = Nu self.Ny = Ny self.remove_predictor = self.settings['remove_predictor'] # Stores original B matrix for state recovery later self.B_predictor = None self.D_predictor = None self.include_added_mass = True self.use_sparse = self.settings['use_sparse'] ScalingFacts = self.settings['ScalingDict'] ScalingFacts['time'] = ScalingFacts['length'] / ScalingFacts['speed'] ScalingFacts['circulation'] = ScalingFacts['speed'] * ScalingFacts['length'] ScalingFacts['dyn_pressure'] = 0.5 * ScalingFacts['density'] * ScalingFacts['speed'] ** 2 ScalingFacts['force'] = ScalingFacts['dyn_pressure'] * ScalingFacts['length'] ** 2 self.ScalingFacts = ScalingFacts self.input_variables_list = [InputVariable('zeta', size=3 * self.Kzeta, index=0), InputVariable('zeta_dot', size=3 * self.Kzeta, index=1), InputVariable('u_gust', size=3 * self.Kzeta, index=2)] self.state_variables_list = [StateVariable('gamma', size=self.K, index=0), StateVariable('gamma_w', size=self.K_star, index=1), StateVariable('dtgamma_dot', size=self.K, index=2), StateVariable('gamma_m1', size=self.K, index=3)] self.output_variables_list = [OutputVariable('forces_v', size=3 * self.Kzeta, index=0)] if self.integr_order == 1: self.state_variables_list.pop(2) # remove time derivative state ### collect statistics self.cpu_summary = {'dim': 0., 'nondim': 0., 'assemble': 0.} # Initialise State Space self.SS = None @property def Nu(self): """Number of inputs :math:`m` to the system.""" if self.SS is not None: if self.SS.B.shape.__len__() == 1: self.Nu = 1 else: self.Nu = self.SS.B.shape[1] return self._Nu @Nu.setter def Nu(self, value): self._Nu = value @property def Nx(self): """Number of states :math:`n` of the system.""" if self.SS is not None: self.Nx = self.SS.B.shape[0] return self._Nx @Nx.setter def Nx(self, value): self._Nx = value @property def Ny(self): """Number of outputs :math:`p` of the system.""" if self.SS is not None: self.Ny = self.SS.C.shape[0] return self._Ny @Ny.setter def Ny(self, value): self._Ny = value def nondimss(self): """ Scale state-space model based of self.ScalingFacts """ cout.cout_wrap('Scaling UVLM system with reference time %fs' % self.ScalingFacts['time']) t0 = time.time() Kzeta = self.Kzeta self.SS.B[:, :3 * Kzeta] *= (self.ScalingFacts['length'] / self.ScalingFacts['circulation']) self.SS.B[:, 3 * Kzeta:] *= (self.ScalingFacts['speed'] / self.ScalingFacts['circulation']) if self.remove_predictor: self.B_predictor[:, :3 * Kzeta] *= (self.ScalingFacts['length'] / self.ScalingFacts['circulation']) self.B_predictor[:, 3 * Kzeta:] *= (self.ScalingFacts['speed'] / self.ScalingFacts['circulation']) self.SS.C *= (self.ScalingFacts['circulation'] / self.ScalingFacts['force']) self.SS.D[:, :3 * Kzeta] *= (self.ScalingFacts['length'] / self.ScalingFacts['force']) self.SS.D[:, 3 * Kzeta:] *= (self.ScalingFacts['speed'] / self.ScalingFacts['force']) if self.remove_predictor: self.D_predictor[:, :3 * Kzeta] *= (self.ScalingFacts['length'] / self.ScalingFacts['force']) self.D_predictor[:, 3 * Kzeta:] *= (self.ScalingFacts['speed'] / self.ScalingFacts['force']) self.SS.dt = self.SS.dt / self.ScalingFacts['time'] self.cpu_summary['nondim'] = time.time() - t0 cout.cout_wrap('Non-dimensional time step set (%f)' % self.SS.dt, 1) cout.cout_wrap('System scaled in %fs' % self.cpu_summary['nondim']) def dimss(self): t0 = time.time() Kzeta = self.Kzeta self.SS.B[:, :3 * Kzeta] /= (self.ScalingFacts['length'] / self.ScalingFacts['circulation']) self.SS.B[:, 3 * Kzeta:] /= (self.ScalingFacts['speed'] / self.ScalingFacts['circulation']) if self.remove_predictor: self.B_predictor[:, :3 * Kzeta] /= (self.ScalingFacts['length'] / self.ScalingFacts['circulation']) self.B_predictor[:, 3 * Kzeta:] /= (self.ScalingFacts['speed'] / self.ScalingFacts['circulation']) self.SS.C /= (self.ScalingFacts['circulation'] / self.ScalingFacts['force']) self.SS.D[:, :3 * Kzeta] /= (self.ScalingFacts['length'] / self.ScalingFacts['force']) self.SS.D[:, 3 * Kzeta:] /= (self.ScalingFacts['speed'] / self.ScalingFacts['force']) if self.remove_predictor: self.D_predictor[:, :3 * Kzeta] /= (self.ScalingFacts['length'] / self.ScalingFacts['force']) self.D_predictor[:, 3 * Kzeta:] /= (self.ScalingFacts['speed'] / self.ScalingFacts['force']) self.SS.dt = self.SS.dt * self.ScalingFacts['time'] self.cpu_summary['dim'] = time.time() - t0 def assemble_ss(self, wake_prop_settings=None): r""" Produces state-space model of the form .. math:: \mathbf{x}_{n+1} &= \mathbf{A}\,\mathbf{x}_n + \mathbf{B} \mathbf{u}_{n+1} \\ \mathbf{y}_n &= \mathbf{C}\,\mathbf{x}_n + \mathbf{D} \mathbf{u}_n where the state, inputs and outputs are: .. math:: \mathbf{x}_n = \{ \delta \mathbf{\Gamma}_n,\, \delta \mathbf{\Gamma_{w_n}},\, \Delta t\,\delta\mathbf{\Gamma}'_n,\, \delta\mathbf{\Gamma}_{n-1} \} .. math:: \mathbf{u}_n = \{ \delta\mathbf{\zeta}_n,\, \delta\mathbf{\zeta}'_n,\, \delta\mathbf{u}_{ext,n} \} .. math:: \mathbf{y} = \{\delta\mathbf{f}\} with :math:`\mathbf{\Gamma}\in\mathbb{R}^{MN}` being the vector of vortex circulations, :math:`\mathbf{\zeta}\in\mathbb{R}^{3(M+1)(N+1)}` the vector of vortex lattice coordinates and :math:`\mathbf{f}\in\mathbb{R}^{3(M+1)(N+1)}` the vector of aerodynamic forces and moments. Note that :math:`(\bullet)'` denotes a derivative with respect to time. Note that the input is atypically defined at time ``n+1``, therefore by default ``self.remove_predictor = True`` and the predictor term ``u_{n+1}`` is eliminated through the change of state[1]: .. math:: \mathbf{h}_n &= \mathbf{x}_n - \mathbf{B}\,\mathbf{u}_n \\ such that: .. math:: \mathbf{h}_{n+1} &= \mathbf{A}\,\mathbf{h}_n + \mathbf{A\,B}\,\mathbf{u}_n \\ \mathbf{y}_n &= \mathbf{C\,h}_n + (\mathbf{C\,B}+\mathbf{D})\,\mathbf{u}_n which only modifies the equivalent :math:`\mathbf{B}` and :math:`\mathbf{D}` matrices. References: [1] Franklin, GF and Powell, JD. Digital Control of Dynamic Systems, Addison-Wesley Publishing Company, 1980 To do: - remove all calls to scipy.linalg.block_diag """ cout.cout_wrap('State-space realisation of UVLM equations started...') t0 = time.time() MS = self.MS K, K_star = self.K, self.K_star Kzeta = self.Kzeta # ------------------------------------------------------ determine size Nx = self.Nx Nu = self.Nu Ny = self.Ny if self.integr_order == 2: # Second order differencing scheme coefficients b0, bm1, bp1 = -2., 0.5, 1.5 # ----------------------------------------------------------- state eq. ### state terms (A matrix) # - choice of sparse matrices format is optimised to reduce memory load # Aero influence coeffs List_AICs, List_AICs_star = ass.AICs(MS.Surfs, MS.Surfs_star, target='collocation', Project=True) A0 = np.block(List_AICs) A0W = np.block(List_AICs_star) List_AICs, List_AICs_star = None, None LU, P = scalg.lu_factor(A0) AinvAW = scalg.lu_solve((LU, P), A0W) A0, A0W = None, None # self.A0,self.A0W=A0,A0W ### propagation of circ # fast and memory efficient with both dense and sparse matrices List_C, List_Cstar = ass.wake_prop(MS, self.use_sparse, sparse_format='csc', settings=wake_prop_settings) if self.use_sparse: Cgamma = libsp.csc_matrix(sparse.block_diag(List_C, format='csc')) CgammaW = libsp.csc_matrix(sparse.block_diag(List_Cstar, format='csc')) else: Cgamma = scalg.block_diag(*List_C) CgammaW = scalg.block_diag(*List_Cstar) List_C, List_Cstar = None, None # recurrent dense terms stored as numpy.ndarrays AinvAWCgamma = -libsp.dot(AinvAW, Cgamma) AinvAWCgammaW = -libsp.dot(AinvAW, CgammaW) ### A matrix assembly if self.use_sparse: # lil format allows fast assembly Ass = sparse.lil_matrix((Nx, Nx)) else: Ass = np.zeros((Nx, Nx)) Ass[:K, :K] = AinvAWCgamma Ass[:K, K:K + K_star] = AinvAWCgammaW Ass[K:K + K_star, :K] = Cgamma Ass[K:K + K_star, K:K + K_star] = CgammaW Cgamma, CgammaW = None, None # delta eq. iivec = range(K + K_star, 2 * K + K_star) ones = np.ones((K,)) if self.integr_order == 1: Ass[iivec, :K] = AinvAWCgamma Ass[iivec, range(K)] -= ones Ass[iivec, K:K + K_star] = AinvAWCgammaW if self.integr_order == 2: Ass[iivec, :K] = bp1 * AinvAWCgamma AinvAWCgamma = None Ass[iivec, range(K)] += b0 * ones Ass[iivec, K:K + K_star] = bp1 * AinvAWCgammaW AinvAWCgammaW = None Ass[iivec, range(2 * K + K_star, 3 * K + K_star)] = bm1 * ones # identity eq. Ass[range(2 * K + K_star, 3 * K + K_star), range(K)] = ones if self.use_sparse: # conversion to csc occupies less memory and allows fast algebra Ass = libsp.csc_matrix(Ass) # zeta derivs List_nc_dqcdzeta = ass.nc_dqcdzeta(MS.Surfs, MS.Surfs_star, Merge=True) List_uc_dncdzeta = ass.uc_dncdzeta(MS.Surfs) List_nc_domegazetadzeta_vert = ass.nc_domegazetadzeta(MS.Surfs, MS.Surfs_star) for ss in range(MS.n_surf): List_nc_dqcdzeta[ss][ss] += \ (List_uc_dncdzeta[ss] + List_nc_domegazetadzeta_vert[ss]) Ducdzeta = np.block(List_nc_dqcdzeta) # dense matrix List_nc_dqcdzeta = None List_uc_dncdzeta = None List_nc_domegazetadzeta_vert = None # ext velocity derivs (Wnv0) List_Wnv = [] for ss in range(MS.n_surf): List_Wnv.append( interp.get_Wnv_vector(MS.Surfs[ss], MS.Surfs[ss].aM, MS.Surfs[ss].aN)) AinvWnv0 = scalg.lu_solve((LU, P), scalg.block_diag(*List_Wnv)) List_Wnv = None ### B matrix assembly if self.use_sparse: Bss = sparse.lil_matrix((Nx, Nu)) else: Bss = np.zeros((Nx, Nu)) Bup = np.block([-scalg.lu_solve((LU, P), Ducdzeta), AinvWnv0, -AinvWnv0]) AinvWnv0 = None Bss[:K, :] = Bup if self.integr_order == 1: Bss[K + K_star:2 * K + K_star, :] = Bup if self.integr_order == 2: Bss[K + K_star:2 * K + K_star, :] = bp1 * Bup Bup = None if self.use_sparse: Bss = libsp.csc_matrix(Bss) LU, P = None, None # ---------------------------------------------------------- output eq. ### state terms (C matrix) # gamma (induced velocity contrib.) List_dfqsdvind_gamma, List_dfqsdvind_gamma_star = \ ass.dfqsdvind_gamma(MS.Surfs, MS.Surfs_star) # gamma (at constant relative velocity) List_dfqsdgamma_vrel0, List_dfqsdgamma_star_vrel0 = \ ass.dfqsdgamma_vrel0(MS.Surfs, MS.Surfs_star) for ss in range(MS.n_surf): List_dfqsdvind_gamma[ss][ss] += List_dfqsdgamma_vrel0[ss] List_dfqsdvind_gamma_star[ss][ss] += List_dfqsdgamma_star_vrel0[ss] Dfqsdgamma = np.block(List_dfqsdvind_gamma) Dfqsdgamma_star = np.block(List_dfqsdvind_gamma_star) List_dfqsdvind_gamma, List_dfqsdvind_gamma_star = None, None List_dfqsdgamma_vrel0, List_dfqsdgamma_star_vrel0 = None, None # gamma_dot Dfunstdgamma_dot = scalg.block_diag(*ass.dfunstdgamma_dot(MS.Surfs)) # C matrix assembly Css = np.zeros((Ny, Nx)) Css[:, :K] = Dfqsdgamma Css[:, K:K + K_star] = Dfqsdgamma_star if self.include_added_mass: Css[:, K + K_star:2 * K + K_star] = Dfunstdgamma_dot / self.dt ### input terms (D matrix) Dss = np.zeros((Ny, Nu)) # zeta (at constant relative velocity) Dss[:, :3 * Kzeta] = scalg.block_diag( *ass.dfqsdzeta_vrel0(MS.Surfs, MS.Surfs_star)) # zeta (induced velocity contrib) List_coll, List_vert = ass.dfqsdvind_zeta(MS.Surfs, MS.Surfs_star) for ss in range(MS.n_surf): List_vert[ss][ss] += List_coll[ss] Dss[:, :3 * Kzeta] += np.block(List_vert) del List_vert, List_coll # input velocities (external) Dss[:, 6 * Kzeta:9 * Kzeta] = scalg.block_diag( *ass.dfqsduinput(MS.Surfs, MS.Surfs_star)) # input velocities (body movement) if self.include_added_mass: Dss[:, 3 * Kzeta:6 * Kzeta] = -Dss[:, 6 * Kzeta:9 * Kzeta] if self.remove_predictor: Ass, Bmod, Css, Dmod = \ libss.SSconv(Ass, None, Bss, Css, Dss, Bm1=None) self.SS = libss.StateSpace(Ass, Bmod, Css, Dmod, dt=self.dt) # Store original B matrix for state unpacking self.B_predictor = Bss self.D_predictor = Dss cout.cout_wrap('\tstate-space model produced in form:\n\t' \ '\t\th_{n+1} = A h_{n} + B u_{n}\n\t' \ '\t\twith:\n\tx_n = h_n + Bp u_n', 1) else: self.SS = libss.StateSpace(Ass, Bss, Css, Dss, dt=self.dt) cout.cout_wrap('\tstate-space model produced in form:\n\t' \ 'x_{n+1} = A x_{n} + Bp u_{n+1}', 1) # add variable tracker self.SS.input_variables = LinearVector(self.input_variables_list) self.SS.state_variables = LinearVector(self.state_variables_list) self.SS.output_variables = LinearVector(self.output_variables_list) self.cpu_summary['assemble'] = time.time() - t0 cout.cout_wrap('\t\t\t...done in %.2f sec' % self.cpu_summary['assemble']) def freqresp(self, kv, wake_prop_settings=None): """ Ad-hoc method for fast UVLM frequency response over the frequencies kv. The method, only requires inversion of a K x K matrix at each frequency as the equation for propagation of wake circulation are solved exactly. The algorithm implemented here can be used also upon projection of the state-space model. Note: This method is very similar to the "minsize" solution option is the steady_solve. """ if self.remove_predictor: # raise NameError('Option "remove_predictor=True" not implemented yet. '+ # 'Refer to Frequency class implementation.') assert self.B_predictor.shape == self.SS.B.shape, \ ('In order to use "freqresp" with "remove_predictor=True", project ' + '"self.B_predictor" as per "self.SS.B"!') assert self.D_predictor.shape == self.SS.D.shape, \ ('In order to use "freqresp" with "remove_predictor=True", project ' + '"self.D_predictor" as per "self.SS.D"!') MS = self.MS K = self.K K_star = self.K_star Eye = np.eye(K) if self.remove_predictor: Bup = self.B_predictor[:K, :] else: Bup = self.SS.B[:K, :] if self.use_sparse: # warning: behaviour may change in future numpy release. # Ensure P,Pw,Bup are np.ndarray P = np.array(self.SS.A[:K, :K].todense()) Pw = np.array(self.SS.A[:K, K:K + K_star].todense()) if type(Bup) not in [np.ndarray, libsp.csc_matrix]: Bup = Bup.toarray() else: P = self.SS.A[:K, :K] Pw = self.SS.A[:K, K:K + K_star] Nk = len(kv) kvdt = kv * self.SS.dt zv = np.cos(kvdt) + 1.j * np.sin(kvdt) Yfreq = np.empty((self.SS.outputs, self.SS.inputs, Nk,), dtype=np.complex_) for kk in range(Nk): ### build Cw complex Cw_cpx = self.get_Cw_cpx(zv[kk], settings=wake_prop_settings) if self.remove_predictor: Ygamma = zv[kk] * \ libsp.solve(zv[kk] * Eye - P - libsp.dot(Pw, Cw_cpx, type_out=libsp.csc_matrix), Bup) else: Ygamma = libsp.solve(zv[kk] * Eye - P - libsp.dot(Pw, Cw_cpx, type_out=libsp.csc_matrix), Bup) Ygamma_star = Cw_cpx.dot(Ygamma) if self.integr_order == 1: dfact = (1. - 1. / zv[kk]) elif self.integr_order == 2: dfact = .5 * (3. - 4. / zv[kk] + 1. / zv[kk] ** 2) else: raise NameError('Specify valid integration order') # calculate solution if self.remove_predictor: Yfreq[:, :, kk] = np.dot(self.SS.C[:, :K], Ygamma) + \ np.dot(self.SS.C[:, K:K + K_star], Ygamma_star) + \ np.dot(self.SS.C[:, K + K_star:2 * K + K_star], dfact * Ygamma) + \ self.D_predictor else: Yfreq[:, :, kk] = np.dot(self.SS.C[:, :K], Ygamma) + \ np.dot(self.SS.C[:, K:K + K_star], Ygamma_star) + \ np.dot(self.SS.C[:, K + K_star:2 * K + K_star], dfact * Ygamma) + \ self.SS.D return Yfreq def get_Cw_cpx(self, zval, settings=None): r""" Produces a sparse matrix .. math:: \bar{\mathbf{C}}(z) where .. math:: z = e^{k \Delta t} such that the wake circulation frequency response at :math:`z` is .. math:: \bar{\boldsymbol{\Gamma}}_w = \bar{\mathbf{C}}(z) \bar{\mathbf{\Gamma}} """ return get_Cw_cpx(self.MS, self.K, self.K_star, zval, settings=settings) def balfreq(self, DictBalFreq, wake_prop_settings=None): """ Low-rank method for frequency limited balancing. The Observability ad controllability Gramians over the frequencies kv are solved in factorised form. Balancd modes are then obtained with a square-root method. Details: Observability and controllability Gramians are solved in factorised form through explicit integration. The number of integration points determines both the accuracy and the maximum size of the balanced model. Stability over all (Nb) balanced states is achieved if: a. one of the Gramian is integrated through the full Nyquist range b. the integration points are enough. Note, however, that even when stability is not achieved over the full balanced states, stability of the balanced truncated model with Ns<=Nb states is normally observed even when a low number of integration points is used. Two integration methods (trapezoidal rule on uniform grid and Gauss-Legendre quadrature) are provided. Input: - DictBalFreq: dictionary specifying integration method with keys: - ``frequency``: defines limit frequencies for balancing. The balanced model will be accurate in the range [0,F], where F is the value of this key. Note that F units must be consistent with the units specified in the self.ScalingFacts dictionary. - ``method_low``: ['gauss','trapz'] specifies whether to use gauss quadrature or trapezoidal rule in the low-frequency range [0,F] - ``options_low``: options to use for integration in the low-frequencies. These depend on the integration scheme (See below). - ``method_high``: method to use for integration in the range [F,F_N], where F_N is the Nyquist frequency. See 'method_low'. - ``options_high``: options to use for integration in the high-frequencies. - ``check_stability``: if True, the balanced model is truncated to eliminate unstable modes - if any is found. Note that very accurate balanced model can still be obtained, even if high order modes are unstable. Note that this option is overridden if "" - ``get_frequency_response``: if True, the function also returns the frequency response evaluated at the low-frequency range integration points. If True, this option also allows to automatically tune the balanced model. Future options: - ``truncation_tolerance``: if ``get_frequency_response`` is True, allows to truncate the balanced model so as to achieved a prescribed tolerance in the low-frequwncy range. - ``Ncpu``: for parallel run The following integration schemes are available: - ``trapz``: performs integration over equally spaced points using trapezoidal rule. It accepts options dictionaries with keys: - ``points``: number of integration points to use (including domain boundary) - ``gauss`` performs gauss-lobotto quadrature. The domain can be partitioned in Npart sub-domain in which the gauss-lobotto quadrature of order Ord can be applied. A total number of Npart*Ord points is required. It accepts options dictionaries of the form: - ``partitions``: number of partitions - ``order``: quadrature order. Example: The following dictionary .. code-block:: python DictBalFreq={ 'frequency': 1.2, 'method_low': 'trapz', 'options_low': {'points': 12}, 'method_high': 'gauss', 'options_high': {'partitions': 2, 'order': 8}, 'check_stability': True } balances the state-space model self.SS in the frequency range [0, 1.2] using (a) 12 equally-spaced points integration of the Gramians in the low-frequency range [0,1.2] and (b) a 2 Gauss-Lobotto 8-th order quadratures of the controllability Gramian in the high-frequency range. A total number of 28 integration points will be required, which will result into a balanced model with number of states ``min{2*28* number_inputs, 2*28* number_outputs}`` The model is finally truncated so as to retain only the first Ns stable modes. """ ### check input dictionary if 'frequency' not in DictBalFreq: raise NameError('Solution dictionary must include the "frequency" key') if 'method_low' not in DictBalFreq: warnings.warn('Setting default options for low-frequency integration') DictBalFreq['method_low'] = 'trapz' DictBalFreq['options_low'] = {'points': 12} if 'method_high' not in DictBalFreq: warnings.warn('Setting default options for high-frequency integration') DictBalFreq['method_high'] = 'gauss' DictBalFreq['options_high'] = {'partitions': 2, 'order': 8} if 'check_stability' not in DictBalFreq: DictBalFreq['check_stability'] = True if 'output_modes' not in DictBalFreq: DictBalFreq['output_modes'] = True if 'get_frequency_response' not in DictBalFreq: DictBalFreq['get_frequency_response'] = False ### get integration points and weights # Nyquist frequency kn = np.pi / self.SS.dt Opt = DictBalFreq['options_low'] if DictBalFreq['method_low'] == 'trapz': kv_low, wv_low = librom.get_trapz_weights(0., DictBalFreq['frequency'], Opt['points'], False) elif DictBalFreq['method_low'] == 'gauss': kv_low, wv_low = librom.get_gauss_weights(0., DictBalFreq['frequency'], Opt['partitions'], Opt['order']) else: raise NameError( 'Invalid value %s for key "method_low"' % DictBalFreq['method_low']) Opt = DictBalFreq['options_high'] if DictBalFreq['method_high'] == 'trapz': kv_high, wv_high = librom.get_trapz_weights(DictBalFreq['frequency'], kn, Opt['points'], True) elif DictBalFreq['method_high'] == 'gauss': kv_high, wv_high = librom.get_gauss_weights(DictBalFreq['frequency'], kn, Opt['partitions'], Opt['order']) else: raise NameError( 'Invalid value %s for key "method_high"' % DictBalFreq['method_high']) ### get useful terms K = self.K K_star = self.K_star Eye = np.eye(K) if self.remove_predictor: Bup = self.B_predictor[:K, :] else: Bup = self.SS.B[:K, :] if self.use_sparse: # warning: behaviour may change in future numpy release. # Ensure P,Pw,Bup are np.ndarray P = np.array(self.SS.A[:K, :K].todense()) Pw = np.array(self.SS.A[:K, K:K + K_star].todense()) if type(Bup) not in [np.ndarray, libsp.csc_matrix]: Bup = Bup.toarray() else: P = self.SS.A[:K, :K] Pw = self.SS.A[:K, K:K + K_star] # indices to manipulate obs solution ii00 = range(0, self.K) ii01 = range(self.K, self.K + self.K_star) ii02 = range(self.K + self.K_star, 2 * self.K + self.K_star) ii03 = range(2 * self.K + self.K_star, 3 * self.K + self.K_star) # integration factors if self.integr_order == 2: b0, bm1, bp1 = -2., 0.5, 1.5 else: b0, bp1 = -1., 1. raise NameError('Method not implemented for integration order 1') ### -------------------------------------------------- loop frequencies ### merge vectors Nk_low = len(kv_low) kvdt = np.concatenate((kv_low, kv_high)) * self.SS.dt wv = np.concatenate((wv_low, wv_high)) * self.SS.dt zv = np.cos(kvdt) + 1.j * np.sin(kvdt) Qobs = np.zeros((self.SS.states, self.SS.outputs), dtype=np.complex_) Zc = np.zeros((self.SS.states, 2 * self.SS.inputs * len(kvdt)), ) Zo = np.zeros((self.SS.states, 2 * self.SS.outputs * Nk_low), ) if DictBalFreq['get_frequency_response']: self.Yfreq = np.empty((self.SS.outputs, self.SS.inputs, Nk_low,), dtype=np.complex_) self.kv = kv_low for kk in range(len(kvdt)): zval = zv[kk] Intfact = wv[kk] # integration factor # build terms that will be recycled Cw_cpx = self.get_Cw_cpx(zval, settings=wake_prop_settings) PwCw_T = Cw_cpx.T.dot(Pw.T) Kernel = np.linalg.inv(zval * Eye - P - PwCw_T.T) ### ----- controllability Ygamma = Intfact * np.dot(Kernel, Bup) if self.remove_predictor: Ygamma *= zval Ygamma_star = Cw_cpx.dot(Ygamma) if self.integr_order == 1: dfact = (bp1 + b0 / zval) Qctrl = np.vstack([Ygamma, Ygamma_star, dfact * Ygamma]) elif self.integr_order == 2: dfact = bp1 + b0 / zval + bm1 / zval ** 2 Qctrl = np.vstack( [Ygamma, Ygamma_star, dfact * Ygamma, (1. / zval) * Ygamma]) else: raise NameError('Specify valid integration order') kkvec = range(2 * kk * self.SS.inputs, 2 * (kk + 1) * self.SS.inputs) Zc[:, kkvec[:self.SS.inputs]] = Qctrl.real # *Intfact Zc[:, kkvec[self.SS.inputs:]] = Qctrl.imag # *Intfact ### ----- frequency response if DictBalFreq['get_frequency_response'] and kk < Nk_low: self.Yfreq[:, :, kk] = np.dot(self.SS.C, Qctrl) / Intfact + self.SS.D ### ----- frequency response if DictBalFreq['get_frequency_response'] and kk < Nk_low: self.Yfreq[:, :, kk] = np.dot(self.SS.C, Qctrl) / Intfact + self.SS.D ### ----- observability # solve (1./zval*I - A.T)^{-1} C^T (in low-frequency only) if kk >= Nk_low: continue zinv = 1. / zval Cw_cpx_H = Cw_cpx.conjugate().T Qobs[ii02, :] = zval * self.SS.C[:, ii02].T if self.integr_order == 2: Qobs[ii03, :] = bm1 * zval ** 2 * self.SS.C[:, ii02].T rhs = self.SS.C[:, ii00].T + \ Cw_cpx_H.dot(self.SS.C[:, ii01].T) + \ libsp.dot( (bp1 * zval) * (PwCw_T.conj() + P.T) + \ (b0 * zval + bm1 * zval ** 2) * Eye, self.SS.C[:, ii02].T) Qobs[ii00, :] = np.dot(Kernel.conj().T, rhs) Eye_star = libsp.csc_matrix( (zinv * np.ones((K_star,)), (range(K_star), range(K_star))), shape=(K_star, K_star), dtype=np.complex_) Qobs[ii01, :] = libsp.solve( Eye_star - self.SS.A[K:K + K_star, K:K + K_star].T, np.dot(Pw.T, Qobs[ii00, :] + \ (bp1 * zval) * self.SS.C[:, ii02].T) + \ self.SS.C[:, ii01].T) kkvec = range(2 * kk * self.SS.outputs, 2 * (kk + 1) * self.SS.outputs) Zo[:, kkvec[:self.SS.outputs]] = Intfact * Qobs.real Zo[:, kkvec[self.SS.outputs:]] = Intfact * Qobs.imag # delete full matrices Kernel = None Qctrl = None Qobs = None # self.Zc=Zc # self.Zo=Zo # LRSQM (optimised) U, hsv, Vh = scalg.svd(np.dot(Zo.T, Zc), full_matrices=False) sinv = hsv ** (-0.5) T = np.dot(Zc, Vh.T * sinv) Ti = np.dot((U * sinv).T, Zo.T) # Zc,Zo=None,None ### build frequency balanced model Ab = libsp.dot(Ti, libsp.dot(self.SS.A, T)) Bb = libsp.dot(Ti, self.SS.B) Cb = libsp.dot(self.SS.C, T) SSb = libss.StateSpace(Ab, Bb, Cb, self.SS.D, dt=self.SS.dt) ### Eliminate unstable modes - if any: if DictBalFreq['check_stability']: for nn in range(1, len(hsv) + 1): eigs_trunc = scalg.eigvals(SSb.A[:nn, :nn]) eigs_trunc_max = np.max(np.abs(eigs_trunc)) if eigs_trunc_max > 1. - 1e-16: SSb.truncate(nn - 1) hsv = hsv[:nn - 1] T = T[:, :nn - 1] Ti = Ti[:nn - 1, :] break self.SSb = SSb self.hsv = hsv if DictBalFreq['output_modes']: self.T = T self.Ti = Ti self.Zc = Zc self.Zo = Zo def balfreq_profiling(self, wake_prop_settings=None): """ Generate profiling report for balfreq function and saves it into ``self.prof_out.`` The function also returns a ``pstats.Stats`` object. To read the report: .. code-block:: python import pstats p=pstats.Stats(self.prof_out).sort_stats('cumtime') p.print_stats(20) """ import pstats import cProfile def wrap(): DictBalFreq = {'frequency': 0.5, 'check_stability': False} self.balfreq(DictBalFreq, wake_prop_settings=wake_prop_settings) cProfile.runctx('wrap()', globals(), locals(), filename=self.prof_out) cProfile.runctx('wrap()', globals(), locals(), filename=self.prof_out) return pstats.Stats(self.prof_out).sort_stats('cumtime') def assemble_ss_profiling(self): """ Generate profiling report for assembly and save it in self.prof_out. To read the report: import pstats p=pstats.Stats(self.prof_out) """ import cProfile cProfile.runctx('self.assemble_ss()', globals(), locals(), filename=self.prof_out) def solve_steady(self, usta, method='direct'): """ Steady state solution from state-space model. Warning: these methods are less efficient than the solver in Static class, Static.solve, and should be used only for verification purposes. The "minsize" method, however, guarantees the inversion of a K x K matrix only, similarly to what is done in Static.solve. """ if self.remove_predictor is True and method != 'direct': raise NameError('Only direct solution is available if predictor ' \ 'term has been removed from the state-space equations (see assembly_ss)') if self.use_sparse is True and method != 'direct': raise NameError('Only direct solution is available if use_sparse is True. ' \ '(see assembly_ss)') Ass, Bss, Css, Dss = self.SS.A, self.SS.B, self.SS.C, self.SS.D if method == 'minsize': # as opposed to linuvlm.Static, this solves for the bound circulation # starting from # Gamma = [P, Pw] * {Gamma,Gamma_w} + B u_ # Gamma_w = [C, Cw] * {Gamma,Gamma_w} (eq. 1a,1b) # where time indices have been dropped. Transforming into # Gamma = [P+PwC, PwCw] * {Gamma,Gamma_w} + B u (eq. 2) # and enforcing Gamma_w = Gamma_TE, this reduces to the KxK system: # AIC Gamma = B u (eq. 3) MS = self.MS K = self.K K_star = self.K_star ### build eq. 2: P = Ass[:K, :K] Pw = Ass[:K, K:K + K_star] C = Ass[K:K + K_star, :K] Cw = Ass[K:K + K_star, K:K + K_star] PwCw = np.dot(Pw, Cw) ### build eq. 3 AIC = np.eye(K) - P - np.dot(Pw, C) # condense Gammaw terms K0out, K0out_star = 0, 0 for ss_out in range(MS.n_surf): Kout = MS.KK[ss_out] Kout_star = MS.KK_star[ss_out] K0in, K0in_star = 0, 0 for ss_in in range(MS.n_surf): Kin = MS.KK[ss_in] Kin_star = MS.KK_star[ss_in] # link to sub-matrices aic = AIC[K0out:K0out + Kout, K0in:K0in + Kin] aic_star = PwCw[K0out:K0out + Kout, K0in_star:K0in_star + Kin_star] # fold aic_star: sum along chord at each span-coordinate N_star = MS.NN_star[ss_in] aic_star_fold = np.zeros((Kout, N_star)) for jj in range(N_star): aic_star_fold[:, jj] += np.sum(aic_star[:, jj::N_star], axis=1) aic[:, -N_star:] -= aic_star_fold K0in += Kin K0in_star += Kin_star K0out += Kout K0out_star += Kout_star ### solve # gamma gamma = np.linalg.solve(AIC, np.dot(Bss[:K, :], usta)) # retrieve gamma over wake gamma_star = [] Ktot = 0 for ss in range(MS.n_surf): Ktot += MS.Surfs[ss].maps.K N = MS.Surfs[ss].maps.N Mstar = MS.Surfs_star[ss].maps.M gamma_star.append(np.concatenate(Mstar * [gamma[Ktot - N:Ktot]])) gamma_star = np.concatenate(gamma_star) if self.integr_order == 1: xsta = np.concatenate((gamma, gamma_star, np.zeros_like(gamma))) if self.integr_order == 2: xsta = np.concatenate((gamma, gamma_star, np.zeros_like(gamma), gamma)) ysta = np.dot(Css, xsta) + np.dot(Dss, usta) elif method == 'direct': """ Solves (I - A) x = B u with direct method""" # if self.use_sparse: # xsta=libsp.solve(libsp.eye_as(Ass)-Ass,Bss.dot(usta)) # else: # Ass_steady = np.eye(*Ass.shape) - Ass # xsta = np.linalg.solve(Ass_steady, np.dot(Bss, usta)) xsta = libsp.solve(libsp.eye_as(Ass) - Ass, Bss.dot(usta)) ysta = np.dot(Css, xsta) + np.dot(Dss, usta) elif method == 'recursive': """ Provides steady-state solution solving for impulsive response """ tol, er = 1e-4, 1.0 Ftot0 = np.zeros((3,)) nn = 0 xsta = np.zeros((self.Nx)) while er > tol and nn < 1000: xsta = np.dot(Ass, xsta) + np.dot(Bss, usta) ysta = np.dot(Css, xsta) + np.dot(Dss, usta) Ftot = np.array( [np.sum(ysta[cc * self.Kzeta:(cc + 1) * self.Kzeta]) for cc in range(3)]) er = np.linalg.norm(Ftot - Ftot0) Ftot0 = Ftot.copy() nn += 1 if er < tol: pass # print('Recursive solution found in %.3d iterations'%nn) else: raise exceptions.NotConvergedSolver('Solution not found! Max. iterations reached with error: %.3e' % er) elif method == 'subsystem': """ Solves sub-system related to Gamma, Gamma_w states only """ Nxsub = self.K + self.K_star Asub_steady = np.eye(Nxsub) - Ass[:Nxsub, :Nxsub] xsub = np.linalg.solve(Asub_steady, np.dot(Bss[:Nxsub, :], usta)) if self.integr_order == 1: xsta = np.concatenate((xsub, np.zeros((self.K,)))) if self.integr_order == 2: xsta = np.concatenate((xsub, np.zeros((2 * self.K,)))) ysta = np.dot(Css, xsta) + np.dot(Dss, usta) else: raise NameError('Method %s not recognised!' % method) return xsta, ysta def solve_step(self, x_n, u_n, u_n1=None, transform_state=False): r""" Solve step. If the predictor term has not been removed (``remove_predictor = False``) then the system is solved as: .. math:: \mathbf{x}^{n+1} &= \mathbf{A\,x}^n + \mathbf{B\,u}^n \\ \mathbf{y}^{n+1} &= \mathbf{C\,x}^{n+1} + \mathbf{D\,u}^n Else, if ``remove_predictor = True``, the state is modified as .. math:: \mathbf{h}^n = \mathbf{x}^n - \mathbf{B\,u}^n And the system solved by: .. math:: \mathbf{h}^{n+1} &= \mathbf{A\,h}^n + \mathbf{B_{mod}\,u}^{n} \\ \mathbf{y}^{n+1} &= \mathbf{C\,h}^{n+1} + \mathbf{D_{mod}\,u}^{n+1} Finally, the original state is recovered using the reverse transformation: .. math:: \mathbf{x}^{n+1} = \mathbf{h}^{n+1} + \mathbf{B\,u}^{n+1} where the modifications to the :math:`\mathbf{B}_{mod}` and :math:`\mathbf{D}_{mod}` are detailed in :func:`Dynamic.assemble_ss`. Notes: Although the original equations include the term :math:`\mathbf{u}_{n+1}`, it is a reasonable approximation to take :math:`\mathbf{u}_{n+1}\approx\mathbf{u}_n` given a sufficiently small time step, hence if the input at time ``n+1`` is not parsed, it is estimated from :math:`u^n`. Args: x_n (np.array): State vector at the current time step :math:`\mathbf{x}^n` u_n (np.array): Input vector at time step :math:`\mathbf{u}^n` u_n1 (np.array): Input vector at time step :math:`\mathbf{u}^{n+1}` transform_state (bool): When the predictor term is removed, if true it will transform the state vector. If false it will be assumed that the state vector that is parsed is already transformed i.e. it is :math:`\mathbf{h}`. Returns: Tuple: Updated state and output vector packed in a tuple :math:`(\mathbf{x}^{n+1},\,\mathbf{y}^{n+1})` Notes: To speed-up the solution and use minimal memory: - solve for bound vorticity (and) - propagate the wake - compute the output separately. """ if u_n1 is None: u_n1 = u_n.copy() if self.remove_predictor: # Transform state vector # TODO: Agree on a way to do this. Either always transform here or transform prior to using the method. if transform_state: h_n = x_n - self.B_predictor.dot(u_n) else: h_n = x_n h_n1 = self.SS.A.dot(h_n) + self.SS.B.dot(u_n) y_n1 = np.dot(self.SS.C, h_n1) + np.dot(self.SS.D, u_n1) # Recover state vector if transform_state: x_n1 = h_n1 + self.B_predictor.dot(u_n1) else: x_n1 = h_n1 else: x_n1 = self.SS.A.dot(x_n) + self.SS.B.dot(u_n1) y_n1 = np.dot(self.SS.C, x_n1) + np.dot(self.SS.D, u_n1) return x_n1, y_n1 def unpack_state(self, xvec): r""" Unpacks the state vector into physical constituents for full order models. The state vector :math:`\mathbf{x}` of the form .. math:: \mathbf{x}_n = \{ \delta \mathbf{\Gamma}_n,\, \delta \mathbf{\Gamma_{w_n}},\, \Delta t\,\delta\mathbf{\Gamma}'_n,\, \delta\mathbf{\Gamma}_{n-1} \} Is unpacked into: .. math:: {\delta \mathbf{\Gamma}_n,\, \delta \mathbf{\Gamma_{w_n}},\, \,\delta\mathbf{\Gamma}'_n} Args: xvec (np.ndarray): State vector Returns: tuple: Column vectors for bound circulation, wake circulation and circulation derivative packed in a tuple. """ K, K_star = self.K, self.K_star gamma_vec = xvec[:K] gamma_star_vec = xvec[K:K + K_star] gamma_dot_vec = xvec[K + K_star:2 * K + K_star] / self.dt return gamma_vec, gamma_star_vec, gamma_dot_vec ################################################################################ ################################################################################ class DynamicBlock(Dynamic): """ Class for dynamic linearised UVLM solution. Linearisation around steady-state are only supported. The class is a low-memory implementation of Dynamic, and inherits most of the methods contained there. State-space models are allocated in list-block form (as per numpy.block) to minimise memory usage. This class provides lower memory / computational time assembly, frequency response and frequency limited balancing. Input: - tsdata: aero timestep data from SHARPy solution - dt: time-step - integr_order=2: integration order for UVLM unsteady aerodynamic force - RemovePredictor=True: if true, the state-space model is modified so as to accept in input perturbations, u, evaluated at time-step n rather than n+1. - ScalingDict=None: disctionary containing fundamental reference units >>> {'length': reference_length, 'speed': reference_speed, 'density': reference density} used to derive scaling quantities for the state-space model variables. The scaling factors are stores in ``self.ScalingFact``. Note that while time, circulation, angular speeds) are scaled accordingly, FORCES ARE NOT. These scale by qinf*b**2, where b is the reference length and qinf is the dinamic pressure. - UseSparse=False: builds the A and B matrices in sparse form. C and D are dense, hence the sparce format is not used. Methods: - nondimss: normalises a dimensional state-space model based on the scaling factors in self.ScalingFact. - dimss: inverse of nondimss. - assemble_ss: builds state-space model. See function for more details. - assemble_ss_profiling: generate profiling report of the assembly and saves it into self.prof_out. To read the report: >>> import pstats p=pstats.Stats(self.prof_out) - freqresp: ad-hoc method for fast frequency response (only implemented) for remove_predictor=False To do: upgrade to linearise around unsteady snapshot (adjoint) """ def __init__(self, tsdata, dt=None, dynamic_settings=None, integr_order=2, RemovePredictor=True, ScalingDict=None, UseSparse=True, for_vel=np.zeros((6), )): if dynamic_settings is None: warnings.warn('Individual parsing of settings is deprecated. Please use the settings dictionary', DeprecationWarning) super().__init__(tsdata, dt, dynamic_settings=dynamic_settings, integr_order=integr_order, RemovePredictor=RemovePredictor, ScalingDict=ScalingDict, UseSparse=UseSparse, for_vel=for_vel) # number of blocks self.nblock_x = self.integr_order + 2 self.nblock_u = 3 self.nblock_y = 1 # sizes in blocks self.S_x = [self.K, self.K_star, self.K] if self.integr_order == 2: self.S_x += [self.K] self.S_u = 3 * [3 * self.Kzeta] self.S_y = [3 * self.Kzeta] def nondimss(self): """ Scale state-space model based of self.ScalingFacts. """ t0 = time.time() B_facts = [self.ScalingFacts['length'] / self.ScalingFacts['circulation'], self.ScalingFacts['speed'] / self.ScalingFacts['circulation'], self.ScalingFacts['speed'] / self.ScalingFacts['circulation']] D_facts = [self.ScalingFacts['length'] / self.ScalingFacts['force'], self.ScalingFacts['speed'] / self.ScalingFacts['force'], self.ScalingFacts['speed'] / self.ScalingFacts['force']] C_facts = self.nblock_x * \ [self.ScalingFacts['circulation'] / self.ScalingFacts['force']] for ii in range(self.nblock_x): for jj in range(self.nblock_u): if self.SS.B[ii][jj] is not None: self.SS.B[ii][jj] *= B_facts[jj] for ii in range(self.nblock_y): for jj in range(self.nblock_x): if self.SS.C[ii][jj] is not None: self.SS.C[ii][jj] *= C_facts[jj] for ii in range(self.nblock_y): for jj in range(self.nblock_u): if self.SS.D[ii][jj] is not None: self.SS.D[ii][jj] *= D_facts[jj] self.SS.dt = self.SS.dt / self.ScalingFacts['time'] self.cpu_summary['nondim'] = time.time() - t0 def dimss(self): t0 = time.time() B_facts = [self.ScalingFacts['length'] / self.ScalingFacts['circulation'], self.ScalingFacts['speed'] / self.ScalingFacts['circulation'], self.ScalingFacts['speed'] / self.ScalingFacts['circulation']] D_facts = [self.ScalingFacts['length'] / self.ScalingFacts['force'], self.ScalingFacts['speed'] / self.ScalingFacts['force'], self.ScalingFacts['speed'] / self.ScalingFacts['force']] D_facts = [self.ScalingFacts['length'] / self.ScalingFacts['force'], self.ScalingFacts['speed'] / self.ScalingFacts['force'], self.ScalingFacts['speed'] / self.ScalingFacts['force']] C_facts = self.nblock_x * \ [self.ScalingFacts['circulation'] / self.ScalingFacts['force']] for ii in range(self.nblock_x): for jj in range(self.nblock_u): if self.SS.B[ii][jj] is not None: self.SS.B[ii][jj] /= B_facts[jj] for ii in range(self.nblock_y): for jj in range(self.nblock_x): if self.SS.C[ii][jj] is not None: self.SS.C[ii][jj] /= C_facts[jj] for ii in range(self.nblock_y): for jj in range(self.nblock_u): if self.SS.D[ii][jj] is not None: self.SS.D[ii][jj] /= D_facts[jj] self.SS.dt = self.SS.dt * self.ScalingFacts['time'] self.cpu_summary['dim'] = time.time() - t0 def assemble_ss(self, wake_prop_settings=None): r""" Produces block-form of state-space model .. math:: \mathbf{x}_{n+1} &= \mathbf{A}\,\mathbf{x}_n + \mathbf{B} \mathbf{u}_{n+1} \\ \mathbf{y}_n &= \mathbf{C}\,\mathbf{x}_n + \mathbf{D} \mathbf{u}_n where the state, inputs and outputs are: .. math:: \mathbf{x}_n = \{ \delta \mathbf{\Gamma}_n,\, \delta \mathbf{\Gamma_{w_n}},\, \Delta t\,\delta\mathbf{\Gamma}'_n,\, \delta\mathbf{\Gamma}_{n-1} \} .. math:: \mathbf{u}_n = \{ \delta\mathbf{\zeta}_n,\, \delta\mathbf{\zeta}'_n,\, \delta\mathbf{u}_{ext,n} \} .. math:: \mathbf{y} = \{\delta\mathbf{f}\} with :math:`\mathbf{\Gamma}` being the vector of vortex circulations, :math:`\mathbf{\zeta}` the vector of vortex lattice coordinates and :math:`\mathbf{f}` the vector of aerodynamic forces and moments. Note that :math:`(\bullet)'` denotes a derivative with respect to time. Note that the input is atypically defined at time ``n+1``, therefore by default ``self.remove_predictor = True`` and the predictor term ``u_{n+1}`` is eliminated through the change of state[1]: .. math:: \mathbf{h}_n &= \mathbf{x}_n - \mathbf{B}\,\mathbf{u}_n \\ such that: .. math:: \mathbf{h}_{n+1} &= \mathbf{A}\,\mathbf{h}_n + \mathbf{A\,B}\,\mathbf{u}_n \\ \mathbf{y}_n &= \mathbf{C\,h}_n + (\mathbf{C\,B}+\mathbf{D})\,\mathbf{u}_n which only modifies the equivalent :math:`\mathbf{B}` and :math:`\mathbf{D}` matrices. References: [1] Franklin, GF and Powell, JD. Digital Control of Dynamic Systems, Addison-Wesley Publishing Company, 1980 To do: - remove all calls to scipy.linalg.block_diag """ cout.cout_wrap('\tBlock form state-space realisation of UVLM equations started...', 1) t0 = time.time() MS = self.MS K, K_star = self.K, self.K_star Kzeta = self.Kzeta # ------------------------------------------------------ determine size Nx = self.Nx Nu = self.Nu Ny = self.Ny nblock_x = self.nblock_x nblock_u = self.nblock_u nblock_y = self.nblock_y if self.integr_order == 2: # Second order differencing scheme coefficients b0, bm1, bp1 = -2., 0.5, 1.5 # ----------------------------------------------------------- state eq. ### state terms (A matrix) # - choice of sparse matrices format is optimised to reduce memory load # Aero influence coeffs List_AICs, List_AICs_star = ass.AICs(MS.Surfs, MS.Surfs_star, target='collocation', Project=True) A0 = np.block(List_AICs) A0W = np.block(List_AICs_star) List_AICs, List_AICs_star = None, None LU, P = scalg.lu_factor(A0) AinvAW = scalg.lu_solve((LU, P), A0W) A0, A0W = None, None ### propagation of circ # fast and memory efficient with both dense and sparse matrices List_C, List_Cstar = ass.wake_prop(MS, self.use_sparse, sparse_format='csc', settings=wake_prop_settings) if self.use_sparse: Cgamma = libsp.csc_matrix(sparse.block_diag(List_C, format='csc')) CgammaW = libsp.csc_matrix(sparse.block_diag(List_Cstar, format='csc')) else: Cgamma = scalg.block_diag(*List_C) CgammaW = scalg.block_diag(*List_Cstar) List_C, List_Cstar = None, None # recurrent dense terms stored as numpy.ndarrays AinvAWCgamma = -libsp.dot(AinvAW, Cgamma) AinvAWCgammaW = -libsp.dot(AinvAW, CgammaW) ### A matrix assembly Ass = [] # non-penetration condition Ass.append([AinvAWCgamma, AinvAWCgammaW, None, ]) if self.integr_order == 2: Ass[0].append(None) # circ. proparagation Ass.append([Cgamma, CgammaW, None, ]) if self.integr_order == 2: Ass[1].append(None) Cgamma = None CgammaW = None # delta eq. if self.use_sparse: ones = libsp.csc_matrix( (np.ones((K,)), (range(K), range(K))), shape=(K, K)) else: ones = np.eye(K) if self.integr_order == 1: Ass.append([AinvAWCgamma - ones, AinvAWCgammaW.copy(), None]) elif self.integr_order == 2: Ass.append([bp1 * AinvAWCgamma + b0 * ones, bp1 * AinvAWCgammaW, None, bm1 * ones]) # identity eq. Ass.append([ones, None, None, None]) AinvAWCgamma = None AinvAWCgammaW = None # zeta derivs List_nc_dqcdzeta = ass.nc_dqcdzeta(MS.Surfs, MS.Surfs_star, Merge=True) List_uc_dncdzeta = ass.uc_dncdzeta(MS.Surfs) List_nc_domegazetadzeta_vert = ass.nc_domegazetadzeta(MS.Surfs, MS.Surfs_star) for ss in range(MS.n_surf): List_nc_dqcdzeta[ss][ss] += \ (List_uc_dncdzeta[ss] + List_nc_domegazetadzeta_vert[ss]) Ducdzeta = np.block(List_nc_dqcdzeta) # dense matrix List_nc_dqcdzeta = None List_uc_dncdzeta = None List_nc_domegazetadzeta_vert = None # ext velocity derivs (Wnv0) List_Wnv = [] for ss in range(MS.n_surf): List_Wnv.append( interp.get_Wnv_vector(MS.Surfs[ss], MS.Surfs[ss].aM, MS.Surfs[ss].aN)) AinvWnv0 = scalg.lu_solve((LU, P), scalg.block_diag(*List_Wnv)) List_Wnv = None ### B matrix assembly Bss = [] # non-penetration condition Bss.append([-scalg.lu_solve((LU, P), Ducdzeta), AinvWnv0, -AinvWnv0]) AinvWnv0 = None # circulation eq. Bss.append([None, None, None]) # delta eq. if self.integr_order == 1: Bss.append([bb.copy() for bb in Bss[0]]) if self.integr_order == 2: Bss.append([bp1 * bb for bb in Bss[0]]) # indentity eq if self.integr_order == 2: Bss.append([None, None, None]) LU, P = None, None # ---------------------------------------------------------- output eq. ### state terms (C matrix) # gamma (induced velocity contrib.) List_dfqsdvind_gamma, List_dfqsdvind_gamma_star = \ ass.dfqsdvind_gamma(MS.Surfs, MS.Surfs_star) # gamma (at constant relative velocity) List_dfqsdgamma_vrel0, List_dfqsdgamma_star_vrel0 = \ ass.dfqsdgamma_vrel0(MS.Surfs, MS.Surfs_star) for ss in range(MS.n_surf): List_dfqsdvind_gamma[ss][ss] += List_dfqsdgamma_vrel0[ss] List_dfqsdvind_gamma_star[ss][ss] += List_dfqsdgamma_star_vrel0[ss] Dfqsdgamma = np.block(List_dfqsdvind_gamma) Dfqsdgamma_star = np.block(List_dfqsdvind_gamma_star) List_dfqsdvind_gamma, List_dfqsdvind_gamma_star = None, None List_dfqsdgamma_vrel0, List_dfqsdgamma_star_vrel0 = None, None # gamma_dot Dfunstdgamma_dot = scalg.block_diag(*ass.dfunstdgamma_dot(MS.Surfs)) ### C matrix assembly Css = [] Css.append([Dfqsdgamma, Dfqsdgamma_star, Dfunstdgamma_dot / self.dt]) if self.integr_order == 2: Css[0].append(None) ### input terms (D matrix) Dss = [] Dss.append( [scalg.block_diag(*ass.dfqsdzeta_vrel0(MS.Surfs, MS.Surfs_star))]) # zeta (induced velocity contrib) List_coll, List_vert = ass.dfqsdvind_zeta(MS.Surfs, MS.Surfs_star) for ss in range(MS.n_surf): List_vert[ss][ss] += List_coll[ss] Dss[0][0] += np.block(List_vert) del List_vert, List_coll Dss[0].append(-scalg.block_diag(*ass.dfqsduinput(MS.Surfs, MS.Surfs_star))) Dss[0].append(-Dss[0][1]) if self.remove_predictor: cout.cout_wrap("\t\tPredictor not be removed! " + "(Though this is accounted for in all methods)", 1) self.SS = libss.ss_block(Ass, Bss, Css, Dss, self.S_x, self.S_u, self.S_y, dt=self.dt) cout.cout_wrap('\tstate-space model produced in form:\n\t' \ 'x_{n+1} = A x_{n} + Bp u_{n+1}', 1) # add variable tracker self.SS.input_variables = LinearVector(self.input_variables_list) self.SS.state_variables = LinearVector(self.state_variables_list) self.SS.output_variables = LinearVector(self.output_variables_list) self.cpu_summary['assemble'] = time.time() - t0 cout.cout_wrap('\t\t\t...done in %.2f sec' % self.cpu_summary['assemble'], 1) def freqresp(self, kv, wake_prop_settings=None): """ Ad-hoc method for fast UVLM frequency response over the frequencies kv. The method, only requires inversion of a K x K matrix at each frequency as the equation for propagation of wake circulation are solved exactly. The algorithm implemented here can be used also upon projection of the state-space model. Note: This method is very similar to the "minsize" solution option is the steady_solve. """ MS = self.MS K = self.K K_star = self.K_star Eye = np.eye(K) Bup = np.hstack(self.SS.B[0]) P = self.SS.A[0][0] Pw = self.SS.A[0][1] Nk = len(kv) kvdt = kv * self.SS.dt zv = np.cos(kvdt) + 1.j * np.sin(kvdt) Yfreq = np.empty((self.SS.outputs, self.SS.inputs, Nk,), dtype=np.complex_) for kk in range(Nk): ### build Cw complex Cw_cpx = self.get_Cw_cpx(zv[kk], settings=wake_prop_settings) Ygamma = libsp.solve(zv[kk] * Eye - P - libsp.dot(Pw, Cw_cpx, type_out=libsp.csc_matrix), Bup) if self.remove_predictor: Ygamma *= zv[kk] Ygamma_star = Cw_cpx.dot(Ygamma) if self.integr_order == 1: dfact = (1. - 1. / zv[kk]) elif self.integr_order == 2: dfact = .5 * (3. - 4. / zv[kk] + 1. / zv[kk] ** 2) else: raise NameError('Specify valid integration order') # calculate solution Yfreq[:, :, kk] = np.dot(self.SS.C[0][0], Ygamma) + \ np.dot(self.SS.C[0][1], Ygamma_star) + \ np.dot(self.SS.C[0][2], dfact * Ygamma) + \ np.hstack(self.SS.D[0]) return Yfreq def balfreq(self, DictBalFreq, wake_prop_settings=None): """ Low-rank method for frequency limited balancing. The Observability ad controllability Gramians over the frequencies kv are solved in factorised form. Balancd modes are then obtained with a square-root method. Details: Observability and controllability Gramians are solved in factorised form through explicit integration. The number of integration points determines both the accuracy and the maximum size of the balanced model. Stability over all (Nb) balanced states is achieved if: a. one of the Gramian is integrated through the full Nyquist range b. the integration points are enough. Note, however, that even when stability is not achieved over the full balanced states, stability of the balanced truncated model with Ns<=Nb states is normally observed even when a low number of integration points is used. Two integration methods (trapezoidal rule on uniform grid and Gauss-Legendre quadrature) are provided. Input: - DictBalFreq: dictionary specifying integration method with keys: - 'frequency': defines limit frequencies for balancing. The balanced model will be accurate in the range [0,F], where F is the value of this key. Note that F units must be consistent with the units specified in the self.ScalingFacts dictionary. - 'method_low': ['gauss','trapz'] specifies whether to use gauss quadrature or trapezoidal rule in the low-frequency range [0,F] - 'options_low': options to use for integration in the low-frequencies. These depend on the integration scheme (See below). - 'method_high': method to use for integration in the range [F,F_N], where F_N is the Nyquist frequency. See 'method_low'. - 'options_high': options to use for integration in the high-frequencies. - 'check_stability': if True, the balanced model is truncated to eliminate unstable modes - if any is found. Note that very accurate balanced model can still be obtained, even if high order modes are unstable. Note that this option is overridden if "" - 'get_frequency_response': if True, the function also returns the frequency response evaluated at the low-frequency range integration points. If True, this option also allows to automatically tune the balanced model. Future options: - 'truncation_tolerance': if 'get_frequency_response' is True, allows to truncatethe balanced model so as to achieved a prescribed tolerance in the low-frequwncy range. - Ncpu: for parallel run The following integration schemes are available: - 'trapz': performs integration over equally spaced points using trapezoidal rule. It accepts options dictionaries with keys: - 'points': number of integration points to use (including domain boundary) - 'gauss' performs gauss-lobotto quadrature. The domain can be partitioned in Npart sub-domain in which the gauss-lobotto quadrature of order Ord can be applied. A total number of Npart*Ord points is required. It accepts options dictionaries of the form: - 'partitions': number of partitions - 'order': quadrature order. Example: The following dictionary DictBalFreq={ 'frequency': 1.2, 'method_low': 'trapz', 'options_low': {'points': 12}, 'method_high': 'gauss', 'options_high': {'partitions': 2, 'order': 8}, 'check_stability': True } balances the state-space model self.SS in the frequency range [0, 1.2] using (a) 12 equally-spaced points integration of the Gramians in the low-frequency range [0,1.2] and (b) a 2 Gauss-Lobotto 8-th order quadratures of the controllability Gramian in the high-frequency range. A total number of 28 integration points will be required, which will result into a balanced model with number of states min{ 2*28* number_inputs, 2*28* number_outputs } The model is finally truncated so as to retain only the first Ns stable modes. """ ### check input dictionary if 'frequency' not in DictBalFreq: raise NameError('Solution dictionary must include the "frequency" key') if 'method_low' not in DictBalFreq: warnings.warn('Setting default options for low-frequency integration') DictBalFreq['method_low'] = 'trapz' DictBalFreq['options_low'] = {'points': 12} if 'method_high' not in DictBalFreq: warnings.warn('Setting default options for high-frequency integration') DictBalFreq['method_high'] = 'gauss' DictBalFreq['options_high'] = {'partitions': 2, 'order': 8} if 'check_stability' not in DictBalFreq: DictBalFreq['check_stability'] = True if 'output_modes' not in DictBalFreq: DictBalFreq['output_modes'] = True if 'get_frequency_response' not in DictBalFreq: DictBalFreq['get_frequency_response'] = False ### get integration points and weights # Nyquist frequency kn = np.pi / self.SS.dt Opt = DictBalFreq['options_low'] if DictBalFreq['method_low'] == 'trapz': kv_low, wv_low = librom.get_trapz_weights(0., DictBalFreq['frequency'], Opt['points'], False) elif DictBalFreq['method_low'] == 'gauss': kv_low, wv_low = librom.get_gauss_weights(0., DictBalFreq['frequency'], Opt['partitions'], Opt['order']) else: raise NameError( 'Invalid value %s for key "method_low"' % DictBalFreq['method_low']) Opt = DictBalFreq['options_high'] if DictBalFreq['method_high'] == 'trapz': if Opt['points'] == 0: warnings.warn('You have chosen no points in high frequency range!') kv_high, wv_high = [], [] else: kv_high, wv_high = librom.get_trapz_weights(DictBalFreq['frequency'], kn, Opt['points'], True) elif DictBalFreq['method_high'] == 'gauss': if Opt['order'] * Opt['partitions'] == 0: warnings.warn('You have chosen no points in high frequency range!') kv_high, wv_high = [], [] else: kv_high, wv_high = librom.get_gauss_weights(DictBalFreq['frequency'], kn, Opt['partitions'], Opt['order']) else: raise NameError( 'Invalid value %s for key "method_high"' % DictBalFreq['method_high']) ### get useful terms K = self.K K_star = self.K_star Eye = np.eye(K) Bup = np.hstack(self.SS.B[0]) P = self.SS.A[0][0] Pw = self.SS.A[0][1] # indices to manipulate obs solution ii00 = range(0, self.K) ii01 = range(self.K, self.K + self.K_star) ii02 = range(self.K + self.K_star, 2 * self.K + self.K_star) ii03 = range(2 * self.K + self.K_star, 3 * self.K + self.K_star) # integration factors if self.integr_order == 2: b0, bm1, bp1 = -2., 0.5, 1.5 else: b0, bp1 = -1., 1. raise NameError('Method not implemented for integration order 1') ### -------------------------------------------------- loop frequencies ### merge vectors Nk_low = len(kv_low) kvdt = np.concatenate((kv_low, kv_high)) * self.SS.dt wv = np.concatenate((wv_low, wv_high)) * self.SS.dt zv = np.cos(kvdt) + 1.j * np.sin(kvdt) Qobs = np.zeros((self.SS.states, self.SS.outputs), dtype=np.complex_) Zc = np.zeros((self.SS.states, 2 * self.SS.inputs * len(kvdt)), ) Zo = np.zeros((self.SS.states, 2 * self.SS.outputs * Nk_low), ) if DictBalFreq['get_frequency_response']: self.Yfreq = np.empty((self.SS.outputs, self.SS.inputs, Nk_low,), dtype=np.complex_) self.kv = kv_low for kk in range(len(kvdt)): zval = zv[kk] Intfact = wv[kk] # integration factor # build terms that will be recycled Cw_cpx = self.get_Cw_cpx(zval, settings=wake_prop_settings) P_PwCw = P + Cw_cpx.T.dot(Pw.T).T Kernel = np.linalg.inv(zval * Eye - P_PwCw) ### ----- controllability Ygamma = Intfact * np.dot(Kernel, Bup) if self.remove_predictor: Ygamma *= zval Ygamma_star = Cw_cpx.dot(Ygamma) if self.integr_order == 1: dfact = (bp1 + bp0 / zval) Qctrl = np.vstack([Ygamma, Ygamma_star, dfact * Ygamma]) elif self.integr_order == 2: dfact = bp1 + b0 / zval + bm1 / zval ** 2 Qctrl = np.vstack( [Ygamma, Ygamma_star, dfact * Ygamma, (1. / zval) * Ygamma]) else: raise NameError('Specify valid integration order') kkvec = range(2 * kk * self.SS.inputs, 2 * (kk + 1) * self.SS.inputs) Zc[:, kkvec[:self.SS.inputs]] = Qctrl.real # *Intfact Zc[:, kkvec[self.SS.inputs:]] = Qctrl.imag # *Intfact ### ----- frequency response if DictBalFreq['get_frequency_response'] and kk < Nk_low: self.Yfreq[:, :, kk] = (1. / Intfact) * \ (np.dot(self.SS.C[0][0], Ygamma) + \ np.dot(self.SS.C[0][1], Ygamma_star) + \ dfact * np.dot(self.SS.C[0][2], Ygamma)) + \ np.hstack(self.SS.D[0]) ### ----- frequency response if DictBalFreq['get_frequency_response'] and kk < Nk_low: self.Yfreq[:, :, kk] = (1. / Intfact) * \ (np.dot(self.SS.C[0][0], Ygamma) + \ np.dot(self.SS.C[0][1], Ygamma_star) + \ dfact * np.dot(self.SS.C[0][2], Ygamma)) + \ np.hstack(self.SS.D[0]) ### ----- observability # solve (1./zval*I - A.T)^{-1} C^T (in low-frequency only) if kk >= Nk_low: continue zinv = 1. / zval Qobs[ii02, :] = zinv * self.SS.C[0][2].T if self.integr_order == 1: raise NameError('Obs Gramian Integr not implemented') elif self.integr_order == 2: Qobs[ii03, :] = (bm1 * zinv) * Qobs[ii02, :] # solve bound circulation rhs = Cw_cpx.T.dot(self.SS.C[0][1].T) + self.SS.C[0][0].T + \ Qobs[ii02, :] * (b0 + zinv * bm1) + \ np.dot(P_PwCw.T, bp1 * Qobs[ii02, :]) Qobs[ii00, :] = np.dot(Kernel.T, rhs) # solve wake Eye_star = libsp.csc_matrix( (zval * np.ones((K_star,)), (range(K_star), range(K_star))), shape=(K_star, K_star), dtype=np.complex_) Qobs[ii01, :] = libsp.solve( Eye_star - self.SS.A[1][1].T, self.SS.C[0][1].T + np.dot(Pw.T, Qobs[ii00, :] + bp1 * Qobs[ii02, :])) kkvec = range(2 * kk * self.SS.outputs, 2 * (kk + 1) * self.SS.outputs) Zo[:, kkvec[:self.SS.outputs]] = Intfact * Qobs.real Zo[:, kkvec[self.SS.outputs:]] = Intfact * Qobs.imag # delete full matrices Kernel = None Qctrl = None Qobs = None # LRSQM (optimised) U, hsv, Vh = scalg.svd(np.dot(Zo.T, Zc), full_matrices=False) sinv = hsv ** (-0.5) T = np.dot(Zc, Vh.T * sinv) Ti = np.dot((U * sinv).T, Zo.T) ### build frequency balanced model Ab, Bb, Cb = self.SS.project(Ti, T, by_arrays=True, overwrite=False) if self.remove_predictor: Ab, Bb, Cb, Db = \ libss.SSconv(np.block(Ab), None, np.block(Bb), np.block(Cb), np.block(self.SS.D), Bm1=None) SSb = libss.StateSpace(Ab, Bb, Cb, Db, dt=self.dt) else: SSb = libss.StateSpace(np.block(Ab), np.block(Bb), np.block(Cb), np.block(self.SS.D), dt=self.SS.dt) ### Eliminate unstable modes - if any: if DictBalFreq['check_stability']: for nn in range(1, len(hsv) + 1): eigs_trunc = scalg.eigvals(SSb.A[:nn, :nn]) eigs_trunc_max = np.max(np.abs(eigs_trunc)) if eigs_trunc_max > 1. - 1e-16: SSb.truncate(nn - 1) hsv = hsv[:nn - 1] T = T[:, :nn - 1] Ti = Ti[:nn - 1, :] break self.SSb = SSb self.hsv = hsv if DictBalFreq['output_modes']: self.T = T self.Ti = Ti self.Zc = Zc self.Zo = Zo self.svd_res = {'U': U, 'hsv': hsv, 'Vh': Vh} def solve_step(self, x_n, u_n, u_n1=None, transform_state=False): r""" Solve step. If the predictor term has not been removed (``remove_predictor = False``) then the system is solved as: .. math:: \mathbf{x}^{n+1} &= \mathbf{A\,x}^n + \mathbf{B\,u}^n \\ \mathbf{y}^{n+1} &= \mathbf{C\,x}^{n+1} + \mathbf{D\,u}^n Else, if ``remove_predictor = True``, the state is modified as .. math:: \mathbf{h}^n = \mathbf{x}^n - \mathbf{B\,u}^n And the system solved by: .. math:: \mathbf{h}^{n+1} &= \mathbf{A\,h}^n + \mathbf{B_{mod}\,u}^{n} \\ \mathbf{y}^{n+1} &= \mathbf{C\,h}^{n+1} + \mathbf{D_{mod}\,u}^{n+1} Finally, the original state is recovered using the reverse transformation: .. math:: \mathbf{x}^{n+1} = \mathbf{h}^{n+1} + \mathbf{B\,u}^{n+1} where the modifications to the :math:`\mathbf{B}_{mod}` and :math:`\mathbf{D}_{mod}` are detailed in :func:`Dynamic.assemble_ss`. Notes: Although the original equations include the term :math:`\mathbf{u}_{n+1}`, it is a reasonable approximation to take :math:`\mathbf{u}_{n+1}\approx\mathbf{u}_n` given a sufficiently small time step, hence if the input at time ``n+1`` is not parsed, it is estimated from :math:`u^n`. Args: x_n (np.array): State vector at the current time step :math:`\mathbf{x}^n` u_n (np.array): Input vector at time step :math:`\mathbf{u}^n` u_n1 (np.array): Input vector at time step :math:`\mathbf{u}^{n+1}` transform_state (bool): When the predictor term is removed, if true it will transform the state vector. If false it will be assumed that the state vector that is parsed is already transformed i.e. it is :math:`\mathbf{h}`. Returns: Tuple: Updated state and output vector packed in a tuple :math:`(\mathbf{x}^{n+1},\,\mathbf{y}^{n+1})` Notes: Because in BlockDynamics the predictor is never removed when building 'self.SS', the implementation change with respect to Dynamic. However, formulas are consistent. """ if u_n1 is None: u_n1 = u_n.copy() if self.remove_predictor and not hasattr(self, 'CBplusD'): self.CBplusD = libsp.block_sum(libsp.block_dot(self.SS.C, self.SS.B), self.SS.D) ### transform input in block matrices X_n = [] II0 = 0 for ii in range(self.SS.blocks_x): IIend = II0 + self.SS.S_x[ii] X_n.append([x_n[II0:IIend]]) II0 = IIend U_n1 = [] II0 = 0 for ii in range(self.SS.blocks_u): IIend = II0 + self.SS.S_u[ii] U_n1.append([u_n1[II0:IIend]]) II0 = IIend if u_n is not None: U_n = [] for ii in range(self.SS.blocks_u): IIend = II0 + self.SS.S_u[ii] U_n.append([u_n[II0:IIend]]) II0 = IIend if self.remove_predictor: # Transform state vector # TODO: Agree on a way to do this. Either always transform here or transform prior to using the method. if transform_state: H_n1 = libsp.block_dot(self.SS.A, X_n) else: H_n = X_n H_n1 = libsp.block_dot(self.SS.A, libsp.block_sum(H_n, libsp.block_dot(self.SS.B, U_n))) Y_n1 = libsp.block_sum(libsp.block_dot(self.SS.C, H_n1), libsp.block_dot(self.CBplusD, U_n1)) Y_n1 = libsp.block_sum(libsp.block_dot(self.SS.C, H_n1), libsp.block_dot(self.CBplusD, U_n1)) # Recover state vector if transform_state: X_n1 = libsp.block_sum(H_n1, libsp.block_dot(self.SS.B, U_n1)) else: X_n1 = H_n1 else: X_n1 = libsp.block_sum(libsp.block_dot(self.SS.A, X_n), libsp.block_dot(self.SS.B, U_n1)) Y_n1 = libsp.block_sum(libsp.block_dot(self.SS.C, X_n1), libsp.block_dot(self.SS.D, U_n1)) x_n1 = np.concatenate([X_n1[ii][0] for ii in range(self.SS.blocks_x)]) return x_n1, np.block(Y_n1).reshape(-1) ################################################################################ ################################################################################ class Frequency(Static): """ Class for frequency description of linearised UVLM solution. Linearisation around steady-state are only supported. The class is built upon Static, and inherits all the methods contained there. The class supports most of the features of Dynamics but has lower memory requirements of Dynamic, and should be preferred for: a. producing memory and computationally cheap frequency responses b. building reduced order models using RFA/polynomial fitting Usage: Upon initialisation, the assemble method produces all the matrices required for the frequency description of the UVLM (see assemble for details). A state-space model is not allocated but: - Time stepping is also possible (but not implemented yet) as all the fundamental terms describing the UVLM equations are still produced (except the propagation of wake circulation) - ad-hoc methods for scaling, unscaling and frequency response are provided. Input: - tsdata: aero timestep data from SHARPy solution - dt: time-step - integr_order=0,1,2: integration order for UVLM unsteady aerodynamic force. If 0, the derivative is computed exactly. - RemovePredictor=True: This flag is only used for the frequency response calculation. The frequency description, in fact, naturally arises without the predictor, but lags can be included during the frequency response calculation. See Dynamic documentation for more details. - ScalingDict=None: disctionary containing fundamental reference units .. code-block:: python {'length': reference_length, 'speed': reference_speed, 'density': reference density} used to derive scaling quantities for the state-space model variables. The scaling factors are stores in ``self.ScalingFact.`` Note that while time, circulation, angular speeds) are scaled accordingly, FORCES ARE NOT. These scale by qinf*b**2, where b is the reference length and qinf is the dinamic pressure. - UseSparse=False: builds the A and B matrices in sparse form. C and D are dense, hence the sparce format is not used. Methods: - nondimss: normalises matrices produced by the assemble method based on the scaling factors in self.ScalingFact. - dimss: inverse of nondimss. - assemble: builds matrices for UVLM minimal size description. - assemble_profiling: generate profiling report of the assembly and saves it into self.prof_out. To read the report: .. code-block:: python import pstats p=pstats.Stats(self.prof_out) - freqresp: fast algorithm for frequency response. Methods to implement: - solve_steady: runs freqresp at 0 frequency. - solve_step: solves one time-step """ def __init__(self, tsdata, dt, integr_order=2, RemovePredictor=True, ScalingDict=None, UseSparse=True): super().__init__(tsdata) self.dt = dt self.integr_order = integr_order assert self.integr_order in [1, 2, 0], 'integr_order must be in [0,1,2]' self.inputs = 9 * self.Kzeta self.outputs = 3 * self.Kzeta self.remove_predictor = RemovePredictor self.use_sparse = UseSparse # create scaling quantities if ScalingDict is None: ScalingFacts = {'length': 1., 'speed': 1., 'density': 1.} else: ScalingFacts = ScalingDict for key in ScalingFacts: ScalingFacts[key] = np.float(ScalingFacts[key]) ScalingFacts['time'] = ScalingFacts['length'] / ScalingFacts['speed'] ScalingFacts['circulation'] = ScalingFacts['speed'] * ScalingFacts['length'] ScalingFacts['dyn_pressure'] = 0.5 * ScalingFacts['density'] * ScalingFacts['speed'] ** 2 ScalingFacts['force'] = ScalingFacts['dyn_pressure'] * ScalingFacts['length'] ** 2 self.ScalingFacts = ScalingFacts ### collect statistics self.cpu_summary = {'dim': 0., 'nondim': 0., 'assemble': 0.} def nondimss(self): """ Scale state-space model based of self.ScalingFacts """ t0 = time.time() Kzeta = self.Kzeta self.Bss[:, :3 * Kzeta] *= (self.ScalingFacts['length'] / self.ScalingFacts['circulation']) self.Bss[:, 3 * Kzeta:] *= (self.ScalingFacts['speed'] / self.ScalingFacts['circulation']) self.Css *= (self.ScalingFacts['circulation'] / self.ScalingFacts['force']) self.Dss[:, :3 * Kzeta] *= (self.ScalingFacts['length'] / self.ScalingFacts['force']) self.Dss[:, 3 * Kzeta:] *= (self.ScalingFacts['speed'] / self.ScalingFacts['force']) self.dt = self.dt / self.ScalingFacts['time'] self.cpu_summary['nondim'] = time.time() - t0 def dimss(self): t0 = time.time() Kzeta = self.Kzeta self.Bss[:, :3 * Kzeta] /= (self.ScalingFacts['length'] / self.ScalingFacts['circulation']) self.Bss[:, 3 * Kzeta:] /= (self.ScalingFacts['speed'] / self.ScalingFacts['circulation']) self.Css /= (self.ScalingFacts['circulation'] / self.ScalingFacts['force']) self.Dss[:, :3 * Kzeta] /= (self.ScalingFacts['length'] / self.ScalingFacts['force']) self.Dss[:, 3 * Kzeta:] /= (self.ScalingFacts['speed'] / self.ScalingFacts['force']) self.dt = self.dt * self.ScalingFacts['time'] self.cpu_summary['dim'] = time.time() - t0 def assemble(self): r""" Assembles matrices for minumal size frequency description of UVLM. The state equation is represented in the form: .. math:: \mathbf{A_0} \mathbf{\Gamma} + \mathbf{A_{w_0}} \mathbf{\Gamma_w} = \mathbf{B_0} \mathbf{u} While the output equation is as per the Dynamic class, namely: .. math:: \mathbf{y} = \mathbf{C} \mathbf{x} + \mathbf{D} \mathbf{u} where .. math:: \mathbf{x} = [\mathbf{\Gamma}; \mathbf{\Gamma_w}; \Delta\mathbf(\Gamma)] The propagation of wake circulation matrices are not produced as these are not required for frequency response analysis. """ cout.cout_wrap('\tAssembly of frequency description of UVLM started...', 1) t0 = time.time() MS = self.MS K, K_star = self.K, self.K_star Kzeta = self.Kzeta Nu = self.inputs Ny = self.outputs # ----------------------------------------------------------- state eq. # Aero influence coeffs List_AICs, List_AICs_star = ass.AICs(MS.Surfs, MS.Surfs_star, target='collocation', Project=True) A0 = np.block(List_AICs) A0W = np.block(List_AICs_star) List_AICs, List_AICs_star = None, None # zeta derivs List_nc_dqcdzeta = ass.nc_dqcdzeta(MS.Surfs, MS.Surfs_star, Merge=True) List_uc_dncdzeta = ass.uc_dncdzeta(MS.Surfs) List_nc_domegazetadzeta_vert = ass.nc_domegazetadzeta(MS.Surfs, MS.Surfs_star) for ss in range(MS.n_surf): List_nc_dqcdzeta[ss][ss] += \ (List_uc_dncdzeta[ss] + List_nc_domegazetadzeta_vert[ss]) Ducdzeta = np.block(List_nc_dqcdzeta) # dense matrix List_nc_dqcdzeta = None List_uc_dncdzeta = None List_nc_domegazetadzeta_vert = None # ext velocity derivs (Wnv0) List_Wnv = [] for ss in range(MS.n_surf): List_Wnv.append( interp.get_Wnv_vector(MS.Surfs[ss], MS.Surfs[ss].aM, MS.Surfs[ss].aN)) Wnv0 = scalg.block_diag(*List_Wnv) List_Wnv = None ### B matrix assembly # this could be also sparse... Bss = np.block([-Ducdzeta, Wnv0, -Wnv0]) # ---------------------------------------------------------- output eq. ### state terms (C matrix) # gamma (induced velocity contrib.) List_dfqsdvind_gamma, List_dfqsdvind_gamma_star = \ ass.dfqsdvind_gamma(MS.Surfs, MS.Surfs_star) # gamma (at constant relative velocity) List_dfqsdgamma_vrel0, List_dfqsdgamma_star_vrel0 = \ ass.dfqsdgamma_vrel0(MS.Surfs, MS.Surfs_star) for ss in range(MS.n_surf): List_dfqsdvind_gamma[ss][ss] += List_dfqsdgamma_vrel0[ss] List_dfqsdvind_gamma_star[ss][ss] += List_dfqsdgamma_star_vrel0[ss] Dfqsdgamma = np.block(List_dfqsdvind_gamma) Dfqsdgamma_star = np.block(List_dfqsdvind_gamma_star) List_dfqsdvind_gamma, List_dfqsdvind_gamma_star = None, None List_dfqsdgamma_vrel0, List_dfqsdgamma_star_vrel0 = None, None # gamma_dot Dfunstdgamma_dot = scalg.block_diag(*ass.dfunstdgamma_dot(MS.Surfs)) # C matrix assembly Css = np.zeros((Ny, 2 * K + K_star)) Css[:, :K] = Dfqsdgamma Css[:, K:K + K_star] = Dfqsdgamma_star # added mass Css[:, K + K_star:2 * K + K_star] = Dfunstdgamma_dot / self.dt ### input terms (D matrix) Dss = np.zeros((Ny, Nu)) # zeta (at constant relative velocity) Dss[:, :3 * Kzeta] = scalg.block_diag( *ass.dfqsdzeta_vrel0(MS.Surfs, MS.Surfs_star)) # zeta (induced velocity contrib) List_coll, List_vert = ass.dfqsdvind_zeta(MS.Surfs, MS.Surfs_star) for ss in range(MS.n_surf): List_vert[ss][ss] += List_coll[ss] Dss[:, :3 * Kzeta] += np.block(List_vert) del List_vert, List_coll # input velocities (external) Dss[:, 6 * Kzeta:9 * Kzeta] = scalg.block_diag( *ass.dfqsduinput(MS.Surfs, MS.Surfs_star)) # input velocities (body movement) Dss[:, 3 * Kzeta:6 * Kzeta] = -Dss[:, 6 * Kzeta:9 * Kzeta] ### store matrices self.A0 = A0 self.A0W = A0W self.Bss = Bss self.Css = Css self.Dss = Dss self.inputs = 9 * Kzeta self.outputs = 3 * Kzeta self.cpu_summary['assemble'] = time.time() - t0 cout.cout_wrap('\t\t\t...done in %.2f sec' % self.cpu_summary['assemble'], 1) def addGain(self, K, where): assert where in ['in', 'out'], \ 'Specify whether gains are added to input or output' if where == 'in': self.Bss = libsp.dot(self.Bss, K) self.Dss = libsp.dot(self.Dss, K) self.inputs = K.shape[1] if where == 'out': self.Css = libsp.dot(K, self.Css) self.Dss = libsp.dot(K, self.Dss) self.outputs = K.shape[0] if where == 'out': self.Css = libsp.dot(K, self.Css) self.Dss = libsp.dot(K, self.Dss) self.outputs = K.shape[0] def freqresp(self, kv, wake_prop_settings=None): """ Ad-hoc method for fast UVLM frequency response over the frequencies kv. The method, only requires inversion of a K x K matrix at each frequency as the equation for propagation of wake circulation are solved exactly. """ MS = self.MS K = self.K K_star = self.K_star Nk = len(kv) kvdt = kv * self.dt zv = np.cos(kvdt) + 1.j * np.sin(kvdt) Yfreq = np.empty((self.outputs, self.inputs, Nk,), dtype=np.complex_) ### loop frequencies for kk in range(Nk): ### build Cw complex Cw_cpx = self.get_Cw_cpx(zv[kk], settings=wake_prop_settings) # get bound state freq response if self.remove_predictor: Ygamma = np.linalg.solve( self.A0 + libsp.dot( self.A0W, Cw_cpx, type_out=libsp.csc_matrix), self.Bss) else: Ygamma = zv[kk] ** (-1) * \ np.linalg.solve( self.A0 + libsp.dot( self.A0W, Cw_cpx, type_out=libsp.csc_matrix), self.Bss) Ygamma_star = Cw_cpx.dot(Ygamma) # determine factor for delta of bound circulation if self.integr_order == 0: dfact = (1.j * kv[kk]) * self.dt elif self.integr_order == 1: dfact = (1. - 1. / zv[kk]) elif self.integr_order == 2: dfact = .5 * (3. - 4. / zv[kk] + 1. / zv[kk] ** 2) else: raise NameError('Specify valid integration order') Yfreq[:, :, kk] = np.dot(self.Css[:, :K], Ygamma) + \ np.dot(self.Css[:, K:K + K_star], Ygamma_star) + \ np.dot(self.Css[:, -K:], dfact * Ygamma) + \ self.Dss return Yfreq def get_Cw_cpx(self, zval, settings=None): r""" Produces a sparse matrix .. math:: \bar{\mathbf{C}}(z) where .. math:: z = e^{k \Delta t} such that the wake circulation frequency response at :math:`z` is .. math:: \bar{\goldsymbol{\Gamma}}_w = \bar{\mathbf{C}}(z) \bar{\boldsymbol{\Gamma}} """ return get_Cw_cpx(self.MS, self.K, self.K_star, zval, settings=settings) def assemble_profiling(self): """ Generate profiling report for assembly and save it in self.prof_out. To read the report: import pstats p=pstats.Stats(self.prof_out) """ import cProfile cProfile.runctx('self.assemble()', globals(), locals(), filename=self.prof_out) def get_Cw_cpx(MS, K, K_star, zval, settings=None): r""" Produces a sparse matrix .. math:: \bar{\mathbf{C}}(z) where .. math:: z = e^{k \Delta t} such that the wake circulation frequency response at :math:`z` is .. math:: \bar{\boldsymbol{\Gamma}}_w = \bar{\mathbf{C}}(z) \bar{\mathbf{\Gamma}} """ try: cfl1 = settings['cfl1'] except (KeyError, TypeError): # In case the key does not exist or settings=None cfl1 = True cout.cout_wrap("Computing wake propagation solution matrix if frequency domain with CFL1=%s" % cfl1, 1) # print("Computing wake propagation solution matrix if frequency domain with CFL1=%s" % cfl1) if cfl1: jjvec = [] iivec = [] valvec = [] K0tot, K0totstar = 0, 0 for ss in range(MS.n_surf): M, N = MS.dimensions[ss] Mstar, N = MS.dimensions_star[ss] for mm in range(Mstar): jjvec += range(K0tot + N * (M - 1), K0tot + N * M) iivec += range(K0totstar + mm * N, K0totstar + (mm + 1) * N) valvec += N * [zval ** (-mm - 1)] K0tot += MS.KK[ss] K0totstar += MS.KK_star[ss] else: # sum_m_n = 0 sum_mstar_n = 0 for ss in range(MS.n_surf): # M, N = MS.dimensions[ss] Mstar, N = MS.dimensions_star[ss] # sum_m_n += M*N sum_mstar_n += Mstar*N try: MS.Surfs_star[ss].zetac except AttributeError: MS.Surfs_star[ss].zetac.generate_collocations() jjvec = [None]*sum_mstar_n iivec = [None]*sum_mstar_n valvec = [None]*sum_mstar_n K0tot, K0totstar = 0, 0 ipoint = 0 for ss in range(MS.n_surf): M, N = MS.dimensions[ss] Mstar, N = MS.dimensions_star[ss] Surf = MS.Surfs[ss] Surf_star = MS.Surfs_star[ss] for iin in range(N): for mm in range(Mstar): # Value location in the sparse array ipoint = K0totstar + mm * N + iin # Compute CFL if mm == 0: conv_vec = Surf_star.zetac[:, 0, iin] - Surf.zetac[:, -1, iin] dist = np.linalg.norm(conv_vec) conv_dir_te = conv_vec/dist vel = Surf.u_input_coll[:, -1, iin] vel_value = np.dot(vel, conv_dir_te) cfl = settings['dt']*vel_value/dist else: conv_vec = Surf_star.zetac[:, mm, iin] - Surf_star.zetac[:, mm - 1, iin] dist = np.linalg.norm(conv_vec) conv_dir = conv_vec/dist vel = Surf.u_input_coll[:, -1, iin] vel_value = np.dot(vel, conv_dir_te) cfl = settings['dt']*vel_value/dist # Compute coefficient coef = get_Cw_cpx_coef_cfl_n1(cfl, zval) # Assign values jjvec[ipoint] = K0tot + N * (M - 1) + iin iivec[ipoint] = K0totstar + mm * N + iin if mm == 0: # First row valvec[ipoint] = coef else: ipoint_prev = K0totstar + (mm - 1) * N + iin valvec[ipoint] = coef*valvec[ipoint_prev] K0tot += MS.KK[ss] K0totstar += MS.KK_star[ss] return libsp.csc_matrix((valvec, (iivec, jjvec)), shape=(K_star, K), dtype=np.complex_) def get_Cw_cpx_coef_cfl_n1(cfl, zval): # Convergence loop end criteria tol = 1e-12 rmax = 100 # Initial values coef = 0. r = 0 # Loop error = 2*tol while ((error > tol) and (r < rmax)): delta_coef = ((1 - cfl)**r)*cfl*(zval**(-r-1)) coef += delta_coef error = np.abs(delta_coef/coef) r += 1 coef /= (1 - (1-cfl)**rmax*(zval**(-1))) if (error > tol): cout.cout_wrap(("WARNING computation of Cw_cpx did not reach desired accuracy. r: %d. error: %d" % (r, error)), 2) return coef ################################################################################ if __name__ == '__main__': import unittest from sharpy.utils.sharpydir import SharpyDir import sharpy.utils.h5utils as h5 class Test_linuvlm_Sta_vs_Dyn(unittest.TestCase): """ Test methods into this module """ def setUp(self): fname = SharpyDir + '/sharpy/linear/test/h5input/' + \ 'goland_mod_Nsurf02_M003_N004_a040.aero_state.h5' haero = h5.readh5(fname) tsdata = haero.ts00000 # Static solver Sta = Static(tsdata) Sta.assemble_profiling() Sta.assemble() Sta.get_total_forces_gain() # random input Sta.u_ext = 1.0 + 0.30 * np.random.rand(3 * Sta.Kzeta) Sta.zeta_dot = 0.2 + 0.10 * np.random.rand(3 * Sta.Kzeta) Sta.zeta = 0.05 * (np.random.rand(3 * Sta.Kzeta) - 1.0) Sta.solve() Sta.reshape() Sta.total_forces() self.Sta = Sta self.tsdata = tsdata def test_force_gains(self): """ to do: add check on moments gain """ Sta = self.Sta Ftot02 = libsp.dot(Sta.Kftot, Sta.fqs) assert np.max(np.abs(Ftot02 - Sta.Ftot)) < 1e-10, 'Total force gain matrix wrong!' def test_Dyn_steady_state(self): """ Test steady state predicted by Dynamic and Static classes are the same. """ Sta = self.Sta Order = [2, 1] RemPred = [True, False] UseSparse = [True, False] for order in Order: for rem_pred in RemPred: for use_sparse in UseSparse: # Dynamic solver Dyn = Dynamic(self.tsdata, dt=0.05, integr_order=order, RemovePredictor=rem_pred, UseSparse=use_sparse) Dyn.assemble_ss() # steady state solution usta = np.concatenate((Sta.zeta, Sta.zeta_dot, Sta.u_ext)) xsta, ysta = Dyn.solve_steady(usta, method='direct') if use_sparse is False and rem_pred is False: xmin, ymin = Dyn.solve_steady(usta, method='minsize') xrec, yrec = Dyn.solve_steady(usta, method='recursive') xsub, ysub = Dyn.solve_steady(usta, method='subsystem') # assert all solutions are matching assert max(np.linalg.norm(xsta - xmin), np.linalg.norm(ysta - ymin)), \ 'Direct and min. size solutions not matching!' assert max(np.linalg.norm(xsta - xrec), np.linalg.norm(ysta - yrec)), \ 'Direct and recursive solutions not matching!' assert max(np.linalg.norm(xsta - xsub), np.linalg.norm(ysta - ysub)), \ 'Direct and sub-system solutions not matching!' # compare against Static solver solution er = np.max(np.abs(ysta - Sta.fqs) / np.linalg.norm(Sta.Ftot)) print('Error force distribution: %.3e' % er) assert er < 1e-12, \ 'Steady-state force not matching (error: %.2e)!' % er if rem_pred is False: # compare state er = np.max(np.abs(xsta[:Dyn.K] - Sta.gamma)) print('Error bound circulation: %.3e' % er) assert er < 1e-13, \ 'Steady-state gamma not matching (error: %.2e)!' % er gammaw_ref = np.zeros((Dyn.K_star,)) kk = 0 for ss in range(Dyn.MS.n_surf): Mstar = Dyn.MS.MM_star[ss] Nstar = Dyn.MS.NN_star[ss] for mm in range(Mstar): gammaw_ref[kk:kk + Nstar] = Sta.Gamma[ss][-1, :] kk += Nstar er = np.max(np.abs(xsta[Dyn.K:Dyn.K + Dyn.K_star] - gammaw_ref)) print('Error wake circulation: %.3e' % er) assert er < 1e-13, 'Steady-state gamma_star not matching!' er = np.max(np.abs(xsta[Dyn.K + Dyn.K_star:2 * Dyn.K + Dyn.K_star])) print('Error bound derivative: %.3e' % er) assert er < 1e-13, 'Non-zero derivative of circulation at steady state!' if Dyn.integr_order == 2: er = np.max(np.abs(xsta[:Dyn.K] - xsta[-Dyn.K:])) print('Error bound circulation previous vs current time-step: %.3e' % er) assert er < 1e-13, \ 'Circulation at previous and current time-step not matching' ### Verify gains Dyn.get_total_forces_gain() Dyn.get_sect_forces_gain() # sectional forces - algorithm for surfaces with equal M n_surf = Dyn.MS.n_surf M, N = Dyn.MS.MM[0], Dyn.MS.NN[0] fnodes = ysta.reshape((n_surf, 3, M + 1, N + 1)) Fsect_ref = np.zeros((n_surf, 3, N + 1)) Msect_ref = np.zeros((n_surf, 3, N + 1)) for ss in range(n_surf): for nn in range(N + 1): for mm in range(M + 1): Fsect_ref[ss, :, nn] += fnodes[ss, :, mm, nn] arm = Dyn.MS.Surfs[ss].zeta[:, mm, nn] - Dyn.MS.Surfs[ss].zeta[:, M // 2, nn] Msect_ref[ss, :, nn] += np.cross(arm, fnodes[ss, :, mm, nn]) Fsect = np.dot(Dyn.Kfsec, ysta).reshape((n_surf, 3, N + 1)) assert np.max(np.abs(Fsect - Fsect_ref)) < 1e-12, \ 'Error in gains for cross-sectional forces' Msect = np.dot(Dyn.Kmsec, ysta).reshape((n_surf, 3, N + 1)) assert np.max(np.abs(Msect - Msect_ref)) < 1e-12, \ 'Error in gains for cross-sectional forces' # total forces Ftot_ref = np.zeros((3,)) for cc in range(3): Ftot_ref[cc] = np.sum(Fsect_ref[:, cc, :]) Ftot = np.dot(Dyn.Kftot, ysta) assert np.max(np.abs(Ftot - Ftot_ref)) < 1e-11, \ 'Error in gains for total forces' def test_nondimss_dimss(self): """ Test scaling and unscaling of UVLM """ Sta = self.Sta # estimate reference quantities Uinf = np.linalg.norm(self.tsdata.u_ext[0][:, 0, 0]) chord = np.linalg.norm(self.tsdata.zeta[0][:, -1, 0] - self.tsdata.zeta[0][:, 0, 0]) rho = self.tsdata.rho ScalingDict = {'length': .5 * chord, 'speed': Uinf, 'density': rho} # reference Dyn0 = Dynamic(self.tsdata, dt=0.05, integr_order=2, RemovePredictor=True, UseSparse=True) Dyn0.assemble_ss() # scale/unscale Dyn1 = Dynamic(self.tsdata, dt=0.05, integr_order=2, RemovePredictor=True, UseSparse=True, ScalingDict=ScalingDict) Dyn1.assemble_ss() Dyn1.nondimss() Dyn1.dimss() libss.compare_ss(Dyn0.SS, Dyn1.SS, tol=1e-10) assert np.max(np.abs(Dyn0.SS.dt - Dyn1.SS.dt)) < 1e-12 * Dyn0.SS.dt, \ 'Scaling/unscaling of time-step not correct' def test_freqresp(self): Sta = self.Sta # estimate reference quantities Uinf = np.linalg.norm(self.tsdata.u_ext[0][:, 0, 0]) chord = np.linalg.norm( self.tsdata.zeta[0][:, -1, 0] - self.tsdata.zeta[0][:, 0, 0]) rho = self.tsdata.rho ScalingDict = {'length': .5 * chord, 'speed': Uinf, 'density': rho} kv = np.linspace(0, .5, 3) for use_sparse in [False, True]: for remove_predictor in [True, False]: ### ----- Dynamic class Dyn = Dynamic(self.tsdata, dt=0.05, ScalingDict=ScalingDict, integr_order=2, RemovePredictor=remove_predictor, UseSparse=use_sparse) Dyn.assemble_ss() Dyn.nondimss() Yref = libss.freqresp(Dyn.SS, kv) Ydyn = Dyn.freqresp(kv) ermax = np.max(np.abs(Ydyn - Yref)) assert ermax < 1e-13, \ 'Dynamic.freqresp produces too large error (%.2e)!' % ermax ### ----- BlockDynamic class BlockDyn = DynamicBlock(self.tsdata, dt=0.05, ScalingDict=ScalingDict, integr_order=2, RemovePredictor=remove_predictor, UseSparse=use_sparse) BlockDyn.assemble_ss() BlockDyn.nondimss() Ydyn_block = BlockDyn.freqresp(kv) ermax = np.max(np.abs(Ydyn_block - Yref)) assert ermax < 1e-13, \ 'Dynamic.freqresp produces too large error (%.2e)!' % ermax ### ----- Frequency class Freq = Frequency(self.tsdata, dt=0.05, ScalingDict=ScalingDict, integr_order=2, RemovePredictor=remove_predictor, UseSparse=use_sparse) Freq.assemble() Freq.nondimss() Yfreq = Freq.freqresp(kv) ermax = np.max(np.abs(Yfreq - Yref)) assert ermax < 1e-13, \ 'Frequency.freqresp produces too large error (%.2e)!' % ermax def test_solve_step(self): Sta = self.Sta # estimate reference quantities Uinf = np.linalg.norm(self.tsdata.u_ext[0][:, 0, 0]) chord = np.linalg.norm( self.tsdata.zeta[0][:, -1, 0] - self.tsdata.zeta[0][:, 0, 0]) rho = self.tsdata.rho ScalingDict = {'length': .5 * chord, 'speed': Uinf, 'density': rho} ### build an input time history NT = 5 Uin = np.random.rand(9 * Sta.Kzeta, NT) ### get size of output Ny = 3 * Sta.Kzeta Ydyn = np.zeros((Ny, NT)) Yblock = np.zeros((Ny, NT)) for integr_order in [1, 2]: Nx = (1 + integr_order) * Sta.K + Sta.K_star Xdyn = np.zeros((Nx, NT)) Xblock = np.zeros((Nx, NT)) for use_sparse in [True, False]: for remove_predictor in [True, False]: Xdyn *= 0. Xblock *= 0. Ydyn *= 0. Yblock *= 0. ### ----- Dynamic class Dyn = Dynamic(self.tsdata, dt=0.05, ScalingDict=ScalingDict, integr_order=integr_order, Remove=remove_predictor, UseSparse=use_sparse) Dyn.assemble_ss() Dyn.nondimss() for tt in range(1, NT): Xdyn[:, tt], Ydyn[:, tt] = \ Dyn.solve_step(Xdyn[:, tt - 1], Uin[:, tt - 1], transform_state=True) ### ----- BlockDynamic class BlockDyn = DynamicBlock(self.tsdata, dt=0.05, ScalingDict=ScalingDict, integr_order=integr_order, RemovePredictor=remove_predictor, UseSparse=use_sparse) BlockDyn.assemble_ss() BlockDyn.nondimss() for tt in range(1, NT): Xblock[:, tt], Yblock[:, tt] = \ BlockDyn.solve_step(Xdyn[:, tt - 1], Uin[:, tt - 1], transform_state=True) ermax = np.max(np.abs(Xdyn - Xblock)) / np.max(np.abs(Xdyn)) assert ermax < 1e-14, \ ('solve_step methods in Dynamic and BlockDynamic not matching ' + ' (relative error %.2e)!' % ermax) ermax = np.max(np.abs(Ydyn - Yblock)) / np.max(np.abs(Ydyn)) assert ermax < 1e-14, \ ('solve_step methods in Dynamic and BlockDynamic not matching ' + ' (relative error %.2e)!' % ermax) unittest.main() ================================================ FILE: sharpy/linear/src/multisurfaces.py ================================================ """Generation of multiple aerodynamic surfaces S. Maraniello, 25 May 2018 """ import numpy as np import sharpy.linear.src.gridmapping as gridmapping import sharpy.linear.src.surface as surface import sharpy.linear.src.assembly as assembly import sharpy.utils.cout_utils as cout class MultiAeroGridSurfaces(): """ Creates and assembles multiple aerodynamic surfaces from data """ def __init__(self, tsdata, vortex_radius, for_vel=np.zeros((6,))): """ Initialise from data structure at time step. Args: tsdata (sharpy.utils.datastructures.AeroTimeStepInfo): Linearisation time step vortex_radius (np.float): Distance below which induction is not computed for_vel (np.ndarray): Frame of reference velocity in the inertial (G) frame, including the angular velocity. """ self.tsdata0 = tsdata self.n_surf = tsdata.n_surf self.dimensions = tsdata.dimensions self.dimensions_star = tsdata.dimensions_star # allocate surfaces self.Surfs = [] self.Surfs_star = [] # allocate size lists - useful for global assembly self.NN = [] self.MM = [] self.KK = [] self.KKzeta = [] self.NN_star = [] self.MM_star = [] self.KK_star = [] self.KKzeta_star = [] for ss in range(self.n_surf): ### Allocate bound surfaces M, N = tsdata.dimensions[ss] Map = gridmapping.AeroGridMap(M, N) # try: # omega = tsdata.omega[ss] # except AttributeError: # omega = for_vel[3:] Surf = surface.AeroGridSurface( Map, zeta=tsdata.zeta[ss], gamma=tsdata.gamma[ss], vortex_radius=vortex_radius, u_ext=tsdata.u_ext[ss], zeta_dot=tsdata.zeta_dot[ss], gamma_dot=tsdata.gamma_dot[ss], rho=tsdata.rho, for_vel=for_vel) # generate geometry data Surf.generate_areas() Surf.generate_normals() Surf.aM, Surf.aN = 0.5, 0.5 Surf.generate_collocations() self.Surfs.append(Surf) # store size self.MM.append(M) self.NN.append(N) self.KK.append(Map.K) self.KKzeta.append(Map.Kzeta) ### Allocate wake surfaces M, N = tsdata.dimensions_star[ss] Map = gridmapping.AeroGridMap(M, N) Surf = surface.AeroGridSurface(Map, zeta=tsdata.zeta_star[ss], gamma=tsdata.gamma_star[ss], vortex_radius=vortex_radius, rho=tsdata.rho) self.Surfs_star.append(Surf) # store size self.MM_star.append(M) self.NN_star.append(N) self.KK_star.append(Map.K) self.KKzeta_star.append(Map.Kzeta) def get_ind_velocities_at_target_collocation_points(self, target): """ Computes normal induced velocities at target surface collocation points. """ # Loop input surfaces for ss_in in range(self.n_surf): # Bound Surf_in = self.Surfs[ss_in] target.u_ind_coll += \ Surf_in.get_induced_velocity_over_surface(target, target='collocation', Project=False) # Wake Surf_in = self.Surfs_star[ss_in] target.u_ind_coll += \ Surf_in.get_induced_velocity_over_surface(target, target='collocation', Project=False) def get_ind_velocities_at_collocation_points(self): """ Computes normal induced velocities at collocation points. """ # Loop surfaces (where ind. velocity is computed) for ss_out in range(self.n_surf): # define array Surf_out = self.Surfs[ss_out] M_out, N_out = self.dimensions[ss_out] Surf_out.u_ind_coll = np.zeros((3, M_out, N_out)) self.get_ind_velocities_at_target_collocation_points(Surf_out) def get_normal_ind_velocities_at_collocation_points(self): """ Computes normal induced velocities at collocation points. Note: for state-equation both projected and not projected induced velocities are required at the collocation points. Hence, this method tries to first the u_ind_coll attribute in each surface. """ # Loop surfaces (where ind. velocity is computed) for ss_out in range(self.n_surf): # define array Surf_out = self.Surfs[ss_out] M_out, N_out = self.dimensions[ss_out] # Surf_out.u_ind_coll_norm=np.empty((M_out,N_out)) Surf_out.u_ind_coll_norm = np.zeros((M_out, N_out)) if hasattr(Surf_out, 'u_ind_coll'): Surf_out.u_ind_coll_norm = \ Surf_out.project_coll_to_normal(Surf_out.u_ind_coll) else: # Loop input surfaces for ss_in in range(self.n_surf): # Bound Surf_in = self.Surfs[ss_in] Surf_out.u_ind_coll_norm += \ Surf_in.get_induced_velocity_over_surface(Surf_out, target='collocation', Project=True) # Wake Surf_in = self.Surfs_star[ss_in] Surf_out.u_ind_coll_norm += \ Surf_in.get_induced_velocity_over_surface(Surf_out, target='collocation', Project=True) def get_input_velocities_at_collocation_points(self): for surf in self.Surfs: if surf.u_input_coll is None: surf.get_input_velocities_at_collocation_points() # ------------------------------------------------------------------------- def get_ind_velocities_at_segments(self, overwrite=False): """ Computes induced velocities at mid-segment points. """ # Loop surfaces (where ind. velocity is computed) for ss_out in range(self.n_surf): Surf_out = self.Surfs[ss_out] if hasattr(Surf_out, 'u_ind_seg') and (not overwrite): continue M_out, N_out = self.dimensions[ss_out] Surf_out.u_ind_seg = np.zeros((3, 4, M_out, N_out)) # Loop input surfaces for ss_in in range(self.n_surf): # Buond Surf_in = self.Surfs[ss_in] Surf_out.u_ind_seg += \ Surf_in.get_induced_velocity_over_surface(Surf_out, target='segments', Project=False) # Wake Surf_in = self.Surfs_star[ss_in] Surf_out.u_ind_seg += \ Surf_in.get_induced_velocity_over_surface(Surf_out, target='segments', Project=False) def get_input_velocities_at_segments(self, overwrite=False): for surf in self.Surfs: if (surf.u_input_seg is not None) and (not overwrite): continue surf.get_input_velocities_at_segments() # ------------------------------------------------------------------------- def get_joukovski_qs(self, overwrite=False): """ Returns quasi-steady forces over Warning: forces are stored in a NON-redundant format: (3,4,M,N) where the element (:,ss,mm,nn) is the contribution to the force over the ss-th segment due to the circulation of panel (mm,nn). """ # get input and induced velocities at segments self.get_input_velocities_at_segments(overwrite) self.get_ind_velocities_at_segments(overwrite) for ss in range(self.n_surf): Surf = self.Surfs[ss] Surf.get_joukovski_qs(gammaw_TE=self.Surfs_star[ss].gamma[0, :]) def verify_non_penetration(self, print_info=False): """ Verify state variables fulfill non-penetration condition at bound surfaces """ # verify induced velocities have been computed for ss in range(self.n_surf): if not hasattr(self.Surfs[ss], 'u_ind_coll_norm'): self.get_normal_ind_velocities_at_collocation_points() break if print_info: print('Verifying non-penetration at bound...') for surf in self.Surfs: # project input velocities if surf.u_input_coll_norm is None: surf.get_normal_input_velocities_at_collocation_points() ErMax = np.max(np.abs( surf.u_ind_coll_norm + surf.u_input_coll_norm)) if print_info: print('Surface %.2d max abs error: %.3e' % (ss, ErMax)) assert ErMax < 1e-12 * np.max(np.abs(self.Surfs[0].u_ext)), \ 'Linearisation state does not verify the non-penetration condition!' # For rotating cases: # assert ErMax<1e-10*np.max(np.abs(self.Surfs[0].u_input_coll)),\ # 'Linearisation state does not verify the non-penetration condition! %.3e > %.3e' % (ErMax, 1e-10*np.max(np.abs(self.Surfs[0].u_input_coll))) def verify_aic_coll(self, print_info=False): """ Verify aic at collocation points using non-penetration condition """ AIC_list, AIC_star_list = assembly.AICs( self.Surfs, self.Surfs_star, target='collocation', Project=True) ### Compute iduced velocity for ss_out in range(self.n_surf): Surf_out = self.Surfs[ss_out] Surf_out.u_ind_coll_norm = np.zeros((Surf_out.maps.K,)) for ss_in in range(self.n_surf): # Bound surface Surf_in = self.Surfs[ss_in] aic = AIC_list[ss_out][ss_in] Surf_out.u_ind_coll_norm += np.dot( aic, Surf_in.gamma.reshape(-1, order='C')) # Wakes Surf_in = self.Surfs_star[ss_in] aic = AIC_star_list[ss_out][ss_in] Surf_out.u_ind_coll_norm += np.dot( aic, Surf_in.gamma.reshape(-1, order='C')) Surf_out.u_ind_coll_norm = \ Surf_out.u_ind_coll_norm.reshape((Surf_out.maps.M, Surf_out.maps.N)) if print_info: print('Verifying AICs at collocation points...') i_surf = 0 for surf in self.Surfs: # project input velocities if surf.u_input_coll_norm is None: surf.get_normal_input_velocities_at_collocation_points() ErMax = np.max(np.abs( surf.u_ind_coll_norm + surf.u_input_coll_norm)) if print_info: print('Surface %.2d max abs error: %.3e' % (i_surf, ErMax)) assert ErMax < 1e-12 * np.max(np.abs(self.Surfs[0].u_ext)), \ 'Linearisation state does not verify the non-penetration condition!' i_surf += 1 # For rotating cases: # assert ErMax<1e-10*np.max(np.abs(self.Surfs[0].u_input_coll)),\ # 'Linearisation state does not verify the non-penetration condition! %.3e > %.3e' % (ErMax, 1e-10*np.max(np.abs(self.Surfs[0].u_input_coll))) def verify_joukovski_qs(self, print_info=False): """ Verify quasi-steady contribution for forces matches against SHARPy. """ if print_info: print('Verifying joukovski quasi-steady forces...') self.get_joukovski_qs() for ss in range(self.n_surf): Surf = self.Surfs[ss] Fhere = Surf.fqs.reshape((3, Surf.maps.Kzeta)) Fref = self.tsdata0.forces[ss][0:3].reshape((3, Surf.maps.Kzeta)) # Check if forces and ct_forces_list are the same: # Fref_check=np.array(self.tsdata0.ct_forces_list[6*ss:6*ss+3]) # print('Check forces: ', Fref_check-Fref) ErMax = np.max(np.abs(Fhere - Fref)) if print_info: print('Surface %.2d max abs error: %.3e' % (ss, ErMax)) assert ErMax < 1e-12, 'Wrong quasi-steady force over surface %.2d!' % ss # For rotating cases: # assert ErMax<1e-8 ,'Wrong quasi-steady force over surface %.2d!'%ss if __name__ == '__main__': import read # select test case fname = '../test/h5input/goland_mod_Nsurf01_M003_N004_a040.aero_state.h5' # fname='../test/h5input/goland_mod_Nsurf02_M003_N004_a040.aero_state.h5' haero = read.h5file(fname) tsdata = haero.ts00000 MS = MultiAeroGridSurfaces(tsdata, 1e-6) # vortex_radius # collocation points MS.get_normal_ind_velocities_at_collocation_points() MS.verify_non_penetration() MS.verify_aic_coll() # joukovski MS.verify_joukovski_qs() # embed() ### verify u_induced ================================================ FILE: sharpy/linear/src/surface.py ================================================ """ Geometrical methods for bound surfaces S. Maraniello, 20 May 2018 """ import numpy as np import itertools import sharpy.aero.utils.uvlmlib as uvlmlib # SHARPy's main uvlm interface with cpp import sharpy.linear.src.uvlmutils as uvlmutils # library with UVLM solution methods from sharpy.aero.utils.uvlmlib import get_aic3_cpp import sharpy.utils.cout_utils as cout dmver = np.array([0, 1, 1, 0]) # delta to go from (m,n) panel to (m,n) vertices dnver = np.array([0, 0, 1, 1]) class AeroGridGeo(): r""" Allows retrieving geometrical information of a surface. Requires a gridmapping.AeroGridMap mapping structure in input and the surface vertices coordinates. Indices convention: each panel is characterised through the following indices: - m,n: chord/span-wise indices Methods: - get_*: retrieve information of a panel (e.g. normal, surface area) - generate_*: apply get_* method to each panel and store info into array. Interpolation matrices, W: - these are labelled as 'Wba', where 'a' defines the initial format, b the final. Hence, given the array vb, it holds va=Wab*vb """ def __init__(self, Map: 'gridmapping.AeroGridMap instance', zeta: 'Array of vertex coordinates at each surface', aM: 'chord-wise position of collocation point in panel' = 0.5, aN: 'span-wise position of collocation point in panel' = 0.5): self.maps = Map self.maps.map_all() self.zeta = zeta self.aM = aM self.aN = aN ### Mapping coefficients # self.wvc=self.get_panel_wcv(self.aM,self.aN) # ------------------------------------------------------------------------- def get_panel_vertices_coords(self, m, n): """ Retrieves coordinates of panel (m,n) vertices. """ ### # mpv=self.maps.from_panel_to_vertices(m,n) ### # mpv=self.maps.Mpv[m,n,:,:] # zetav_here_ref=np.zeros((4,3),order='C') # for ii in range(4): # zetav_here_ref[ii,:]=self.zeta[:,mpv[ii,0],mpv[ii,1]] # zetav_here=self.zeta[:,dmver+m,dnver+n].T # assert np.max(np.abs(zetav_here-zetav_here_ref))<1e-16, embed() # return zetav_here ### # return self.zeta[:,dmver+m,dnver+n].T return self.zeta[:, [m + 0, m + 1, m + 1, m + 0], [n + 0, n + 0, n + 1, n + 1]].T # ------------------------------------------------------- get panel normals def generate_normals(self): M, N = self.maps.M, self.maps.N self.normals = np.zeros((3, M, N)) for mm in range(M): for nn in range(N): zetav_here = self.get_panel_vertices_coords(mm, nn) self.normals[:, mm, nn] = uvlmutils.panel_normal(zetav_here) # -------------------------------------------------- get panel surface area def generate_areas(self): M, N = self.maps.M, self.maps.N self.areas = np.zeros((M, N)) for mm in range(M): for nn in range(N): zetav_here = self.get_panel_vertices_coords(mm, nn) self.areas[mm, nn] = uvlmutils.panel_area(zetav_here) # -------------------------------------------------- get collocation points def get_panel_wcv(self): r""" Produces a compact array with weights for bilinear interpolation, where aN,aM in [0,1] are distances in the chordwise and spanwise directions such that: - (aM,aN)=(0,0) --> quantity at vertex 0 - (aM,aN)=(1,0) --> quantity at vertex 1 - (aM,aN)=(1,1) --> quantity at vertex 2 - (aM,aN)=(0,1) --> quantity at vertex 3 """ aM = self.aM aN = self.aN wcv = np.array([(1 - aM) * (1 - aN), aM * (1 - aN), aM * aN, aN * (1 - aM)]) return wcv def get_panel_collocation(self, zetav_here): r""" Using bilinear interpolation, retrieves panel collocation point, where aN,aM in [0,1] are distances in the chordwise and spanwise directions such that: - (aM,aN)=(0,0) --> quantity at vertex 0 - (aM,aN)=(1,0) --> quantity at vertex 1 - (aM,aN)=(1,1) --> quantity at vertex 2 - (aM,aN)=(0,1) --> quantity at vertex 3 """ wcv = self.get_panel_wcv() zetac_here = np.dot(wcv, zetav_here) return zetac_here def generate_collocations(self): M, N = self.maps.M, self.maps.N self.zetac = np.zeros((3, M, N), order='F') # F order avoids the need to copy # when passing self.zetac[:,x,x] to # C written libraries. for mm in range(M): for nn in range(N): zetav_here = self.get_panel_vertices_coords(mm, nn) self.zetac[:, mm, nn] = self.get_panel_collocation(zetav_here) # -------------------------------------------------- get mid-segment points def get_panel_wsv(self): pass def get_panel_midsegments(self, zetav_here): pass def generate_midsegments(): pass def generate_Wsv(): pass # ----------------------------------------------- Interpolations/Projection def interp_vertex_to_coll(self, q_vert): """ Project a quantity q_vert (scalar or vector) defined at vertices to collocation points. """ M, N = self.maps.M, self.maps.N # embed() inshape = q_vert.shape assert inshape[-2] == M + 1 and inshape[-1] == N + 1, 'Unexpected shape of q_vert' # determine weights wcv = self.get_panel_wcv() if len(inshape) == 2: q_coll = np.zeros((M, N)) for mm in range(M): for nn in range(N): # get q_vert at panel corners mpv = self.maps.from_panel_to_vertices(mm, nn) for vv in range(4): q_coll[mm, nn] = q_coll[mm, nn] + \ wcv[vv] * q_vert[mpv[vv, 0], mpv[vv, 1]] elif len(inshape) == 3: q_coll = np.zeros((3, M, N)) for mm in range(M): for nn in range(N): # get q_vert at panel corners mpv = self.maps.from_panel_to_vertices(mm, nn) for vv in range(4): q_coll[:, mm, nn] = q_coll[:, mm, nn] + \ wcv[vv] * q_vert[:, mpv[vv, 0], mpv[vv, 1]] else: raise NameError('Unexpected shape of q_vert') return q_coll def project_coll_to_normal(self, q_coll): """ Project a vector quantity q_coll defined at collocation points to normal. """ M, N = self.maps.M, self.maps.N assert q_coll.shape == (3, M, N), 'Unexpected shape of q_coll' if not hasattr(self, 'normals'): self.generate_normals() q_proj = np.zeros((M, N)) for mm in range(M): for nn in range(N): q_proj[mm, nn] = np.dot(self.normals[:, mm, nn], q_coll[:, mm, nn]) return q_proj # ------------------------------------------------------- visualise surface def plot(self, plot_normals=False): try: import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(111, projection='3d') # Plot vertices grid ax.plot_wireframe(self.zeta[0], self.zeta[1], self.zeta[2]) # rstride=10, cstride=10) # Plot collocation points ax.scatter(self.zetac[0], self.zetac[1], self.zetac[2], zdir='z', s=3, c='r') if plot_normals: ax.quiver(self.zetac[0], self.zetac[1], self.zetac[2], self.normals[0], self.normals[1], self.normals[2], length=0.01 * np.max(self.zeta), ) # normalize=True) self.ax = ax except ModuleNotFoundError: import warnings warnings.warn('Unable to import matplotlib, skipping plots') class AeroGridSurface(AeroGridGeo): r""" Contains geometric and aerodynamic information about bound/wake surface. Compulsory input are those that apply to both bound and wake surfaces: - ``zeta``: defines geometry - ``gamma``: circulation With respect to :class:`.AeroGridGeo`, the class contains methods to: - project prescribed input velocity at nodes (``u_ext``, ``zeta_dot``) over collocation points. - compute induced velocity over ANOTHER surface. - compute AIC induced over ANOTHER surface Args: Map (gridmapping.AeroGridMap): Map of grid. zeta (list(np.ndarray)): Grid vertices coordinates in inertial (G) frame. zeta_dot (list(np.ndarray)): Grid vertices velocities in inertial (G) frame. Default is ``None``. vortex_radius (np.float): Distance below which induction is not computed u_ext (list(np.ndarray)): Grid external velocities in inertial (G) frame. Default is ``None``. gamma_dot (list(np.ndarray)): Panel circulation derivative. Default is ``None``. rho (float): Air density. Default is ``1.`` aM (float): Chordwise position in panel of collocation point. Default is ``0.5`` aN (float): Spanwise position in panel of collocation point. Default is ``0.5`` for_vel (np.ndarray): Frame of reference velocity (including rotational velocity) in the inertial frame. To add: - project prescribed input velocity at nodes (u_ext, zeta_dot) over mid-point segments """ def __init__(self, Map, zeta, gamma, vortex_radius, u_ext=None, zeta_dot=None, gamma_dot=None, rho=1., aM=0.5, aN=0.5, for_vel=np.zeros((6, ))): super().__init__(Map, zeta, aM, aN) self.gamma = gamma self.vortex_radius = vortex_radius self.zeta_dot = zeta_dot self.u_ext = u_ext self.gamma_dot = gamma_dot self.rho = rho self.omega = for_vel[3:] self.for_vel_tra = for_vel[:3] msg_out = 'wrong input shape!' assert self.gamma.shape == (self.maps.M, self.maps.N), \ 'Gamma shape %s not equal to M, N %s' % (str(self.gamma.shape), str((self.maps.M, self.maps.N))) assert self.zeta.shape == (3, self.maps.M + 1, self.maps.N + 1), \ 'Zeta shape %s not equal to 3, M+1, N+1 %s' % (str(self.zeta.shape), str((3, self.maps.M, self.maps.N))) if self.zeta_dot is not None: assert self.zeta_dot.shape == (3, self.maps.M + 1, self.maps.N + 1), \ 'zeta_dot shape %s not equal to 3, M+1, N+1 %s' % (str(self.zeta_dot.shape), str((3, self.maps.M, self.maps.N))) if self.u_ext is not None: assert self.u_ext.shape == (3, self.maps.M + 1, self.maps.N + 1), \ 'u_ext shape %s not equal to 3, M+1, N+1 %s' % (str(self.u_ext.shape), str((3, self.maps.M, self.maps.N))) assert for_vel.shape == (6, ), msg_out assert self.omega.shape == (3, ), msg_out assert self.for_vel_tra.shape == (3, ), msg_out self.u_input_coll = None # input velocities at the collocation points self.u_input_coll_norm = None # normal input velocities at the collocation points self.u_input_seg = None # input velocities at segments # -------------------------------------------------------- input velocities def get_input_velocities_at_collocation_points(self): r""" Returns velocities at collocation points from nodal values ``u_ext`` and ``zeta_dot`` of shape ``(3, M+1, N+1)`` at the collocation points. Notes: .. math:: \boldsymbol{u}_{c} = \mathcal{W}_{cv}(\boldsymbol(\nu)_0 - \boldsymbol{\zeta}_0) is the input velocity at the collocation point, where :math:`\mathcal{W}_{cv}` projects the velocity from the grid points onto the collocation point. This variable is referred to as ``u_input_coll=Wcv*(u_ext-zeta_dot)`` and depends on the coordinates ``zeta`` when the body is rotating. """ # define total velocity if self.zeta_dot is not None: u_tot = self.u_ext - self.zeta_dot else: u_tot = self.u_ext # Include rotation for i_m in range(self.maps.M + 1): for i_n in range(self.maps.N + 1): u_tot[:, i_m, i_n] -= np.cross(self.omega, self.zeta[:, i_m, i_n]) + self.for_vel_tra self.u_input_coll = self.interp_vertex_to_coll(u_tot) def get_normal_input_velocities_at_collocation_points(self): """ From nodal input velocity to normal velocities at collocation points. """ # M,N=self.maps.M,self.maps.N # produce velocities at collocation points self.get_input_velocities_at_collocation_points() self.u_input_coll_norm = self.project_coll_to_normal(self.u_input_coll) def get_input_velocities_at_segments(self): """ Returns velocities at mid-segment points from nodal values u_ext and zeta_dot of shape (3,M+1,N+1). Warning: input velocities at grid segments are stored in a redundant format: (3,4,M,N) where the element (:,ss,mm,nn) is the induced velocity over the ss-th segment of panel (mm,nn). A fast looping is implemented to re-use previously computed velocities 2018/08/24: Include effects due to rotation (omega x zeta). Now it depends on the coordinates zeta """ # define total velocity if self.zeta_dot is not None: u_tot = self.u_ext - self.zeta_dot else: u_tot = self.u_ext # Include rotation for i_m in range(self.maps.M + 1): for i_n in range(self.maps.N + 1): u_tot[:, i_m, i_n] -= np.cross(self.omega, self.zeta[:, i_m, i_n]) + self.for_vel_tra M, N = self.maps.M, self.maps.N self.u_input_seg = np.empty((3, 4, M, N)) # indiced as per self.maps dmver = [0, 1, 1, 0] # delta to go from (m,n) panel to (m,n) vertices dnver = [0, 0, 1, 1] svec = [0, 1, 2, 3] # seg. no. avec = [0, 1, 2, 3] # 1st vertex no. bvec = [1, 2, 3, 0] # 2nd vertex no. ##### panel (0,0): compute all mm, nn = 0, 0 for ss, aa, bb in zip(svec, avec, bvec): uA = u_tot[:, mm + dmver[aa], nn + dnver[aa]] uB = u_tot[:, mm + dmver[bb], nn + dnver[bb]] self.u_input_seg[:, ss, mm, nn] = .5 * (uA + uB) ##### panels n=0: copy seg.3 nn = 0 svec = [0, 1, 2] # seg. no. avec = [0, 1, 2] # 1st vertex no. bvec = [1, 2, 3] # 2nd vertex no. for mm in range(1, M): for ss, aa, bb in zip(svec, avec, bvec): uA = u_tot[:, mm + dmver[aa], nn + dnver[aa]] uB = u_tot[:, mm + dmver[bb], nn + dnver[bb]] self.u_input_seg[:, ss, mm, nn] = .5 * (uA + uB) self.u_input_seg[:, 3, mm, nn] = self.u_input_seg[:, 1, mm - 1, nn] ##### panels m=0: copy seg.0 mm = 0 svec = [1, 2, 3] # seg. number avec = [1, 2, 3] # 1st vertex of seg. bvec = [2, 3, 0] # 2nd vertex of seg. for nn in range(1, N): for ss, aa, bb in zip(svec, avec, bvec): uA = u_tot[:, mm + dmver[aa], nn + dnver[aa]] uB = u_tot[:, mm + dmver[bb], nn + dnver[bb]] self.u_input_seg[:, ss, mm, nn] = .5 * (uA + uB) self.u_input_seg[:, 0, mm, nn] = self.u_input_seg[:, 2, mm, nn - 1] ##### all others: copy seg. 0 and 3 svec = [1, 2] # seg. number avec = [1, 2] # 1st vertex of seg. bvec = [2, 3] # 2nd vertex of seg. for pp in itertools.product(range(1, M), range(1, N)): mm, nn = pp for ss, aa, bb in zip(svec, avec, bvec): uA = u_tot[:, mm + dmver[aa], nn + dnver[aa]] uB = u_tot[:, mm + dmver[bb], nn + dnver[bb]] self.u_input_seg[:, ss, mm, nn] = .5 * (uA + uB) self.u_input_seg[:, 0, mm, nn] = self.u_input_seg[:, 2, mm, nn - 1] self.u_input_seg[:, 3, mm, nn] = self.u_input_seg[:, 1, mm - 1, nn] return self # ------------------------------------------------------ induced velocities def get_induced_velocity(self, zeta_target): """ Computes induced velocity at a point zeta_target. """ M, N = self.maps.M, self.maps.N uind_target = np.zeros((3,), order='C') # uind_ref=np.zeros((3,),order='C') for mm in range(M): for nn in range(N): # panel info zetav_here = self.get_panel_vertices_coords(mm, nn) uind_target += uvlmlib.biot_panel_cpp(zeta_target, zetav_here, self.vortex_radius, self.gamma[mm, nn]) return uind_target def get_aic3(self, zeta_target): """ Produces influence coefficinet matrix to calculate the induced velocity at a target point. The aic3 matrix has shape (3,K) """ K = self.maps.K aic3 = np.zeros((3, K)) for cc in range(K): # define panel mm = self.maps.ind_2d_pan_scal[0][cc] nn = self.maps.ind_2d_pan_scal[1][cc] # get panel coordinates zetav_here = self.get_panel_vertices_coords(mm, nn) aic3[:, cc] = uvlmlib.biot_panel_cpp(zeta_target, zetav_here, self.vortex_radius, gamma=1.0) return aic3 def get_induced_velocity_over_surface(self, Surf_target, target='collocation', Project=False): """ Computes induced velocity over an instance of AeroGridSurface, where target specifies the target grid (collocation or segments). If Project is True, velocities are projected onver panel normal (only available at collocation points). Note: for state-equation, both projected and non-projected velocities at the collocation points are required. Hence, it is suggested to use this method with Projection=False, and project afterwards. Warning: induced velocities at grid segments are stored in a redundant format: (3,4,M,N) where the element (:,ss,mm,nn) is the induced velocity over the ss-th segment of panel (mm,nn). A fast looping is implemented to re-use previously computed velocities """ M_trg = Surf_target.maps.M N_trg = Surf_target.maps.N if target == 'collocation': if not hasattr(Surf_target, 'zetac'): Surf_target.generate_collocations() ZetaTarget = Surf_target.zetac if Project: if not hasattr(Surf_target, 'normals'): Surf_target.generate_normals() Uind = np.empty((M_trg, N_trg)) else: Uind = np.empty((3, M_trg, N_trg)) # loop target points for pp in itertools.product(range(M_trg), range(N_trg)): mm, nn = pp uind = uvlmlib.get_induced_velocity_cpp(self.maps, self.zeta, self.gamma, ZetaTarget[:, mm, nn], self.vortex_radius) if Project: Uind[mm, nn] = np.dot(uind, Surf_target.normals[:, mm, nn]) else: Uind[:, mm, nn] = uind if target == 'segments': if Project: raise NameError('Normal not defined for segment') Uind = np.zeros((3, 4, M_trg, N_trg)) ##### panel (0,0): compute all mm, nn = 0, 0 svec = [0, 1, 2, 3] # seg. number avec = [0, 1, 2, 3] # 1st vertex of seg. bvec = [1, 2, 3, 0] # 2nd vertex of seg. zetav_here = Surf_target.get_panel_vertices_coords(mm, nn) for ss, aa, bb in zip(svec, avec, bvec): zeta_mid = 0.5 * (zetav_here[aa, :] + zetav_here[bb, :]) Uind[:, ss, mm, nn] = uvlmlib.get_induced_velocity_cpp(self.maps, self.zeta, self.gamma, zeta_mid, self.vortex_radius) ##### panels n=0: copy seg.3 nn = 0 svec = [0, 1, 2] # seg. number avec = [0, 1, 2] # 1st vertex of seg. bvec = [1, 2, 3] # 2nd vertex of seg. for mm in range(1, M_trg): zetav_here = Surf_target.get_panel_vertices_coords(mm, nn) for ss, aa, bb in zip(svec, avec, bvec): zeta_mid = 0.5 * (zetav_here[aa, :] + zetav_here[bb, :]) Uind[:, ss, mm, nn] = uvlmlib.get_induced_velocity_cpp(self.maps, self.zeta, self.gamma, zeta_mid, self.vortex_radius) Uind[:, 3, mm, nn] = Uind[:, 1, mm - 1, nn] ##### panels m=0: copy seg.0 mm = 0 svec = [1, 2, 3] # seg. number avec = [1, 2, 3] # 1st vertex of seg. bvec = [2, 3, 0] # 2nd vertex of seg. for nn in range(1, N_trg): zetav_here = Surf_target.get_panel_vertices_coords(mm, nn) for ss, aa, bb in zip(svec, avec, bvec): zeta_mid = 0.5 * (zetav_here[aa, :] + zetav_here[bb, :]) Uind[:, ss, mm, nn] = uvlmlib.get_induced_velocity_cpp(self.maps, self.zeta, self.gamma, zeta_mid, self.vortex_radius) Uind[:, 0, mm, nn] = Uind[:, 2, mm, nn - 1] ##### all others: copy seg. 0 and 3 svec = [1, 2] # seg. number avec = [1, 2] # 1st vertex of seg. bvec = [2, 3] # 2nd vertex of seg. for pp in itertools.product(range(1, M_trg), range(1, N_trg)): mm, nn = pp zetav_here = Surf_target.get_panel_vertices_coords(*pp) for ss, aa, bb in zip(svec, avec, bvec): zeta_mid = 0.5 * (zetav_here[aa, :] + zetav_here[bb, :]) Uind[:, ss, mm, nn] = uvlmlib.get_induced_velocity_cpp(self.maps, self.zeta, self.gamma, zeta_mid, self.vortex_radius) Uind[:, 0, mm, nn] = Uind[:, 2, mm, nn - 1] Uind[:, 3, mm, nn] = Uind[:, 1, mm - 1, nn] return Uind def get_aic_over_surface(self, Surf_target, target='collocation', Project=True): r""" Produces influence coefficient matrices such that the velocity induced over the Surface_target is given by the product: .. code-block:: python if target=='collocation': if Project: u_ind_coll_norm.rehape(-1)=AIC*self.gamma.reshape(-1,order='C') else: u_ind_coll_norm[ii,:,:].rehape(-1)= AIC[ii,:,:]*self.gamma.reshape(-1,order='C') where ``ii=0,1,2``. For the case where ``if target=='segments'``: - AIC has shape (3,self.maps.K,4,Mout,Nout), such that ``AIC[:,:,ss,mm,nn]`` is the influence coefficient matrix associated to the induced velocity at segment ss of panel (mm,nn). """ K_in = self.maps.K if target == 'collocation': K_out = Surf_target.maps.K if not hasattr(Surf_target, 'zetac'): Surf_target.generate_collocations() ZetaTarget = Surf_target.zetac if Project: if not hasattr(Surf_target, 'normals'): Surf_target.generate_normals() AIC = np.empty((K_out, K_in)) else: AIC = np.empty((3, K_out, K_in)) # loop target points for cc in range(K_out): # retrieve panel coords mm = Surf_target.maps.ind_2d_pan_scal[0][cc] nn = Surf_target.maps.ind_2d_pan_scal[1][cc] # retrieve influence coefficients # ref_aic3=self.get_aic3(ZetaTarget[:,mm,nn]) aic3 = get_aic3_cpp(self.maps, self.zeta, ZetaTarget[:, mm, nn], self.vortex_radius) # assert np.max(np.abs(aic3-ref_aic3))<1e-13, embed() if Project: AIC[cc, :] = np.dot(Surf_target.normals[:, mm, nn], aic3) else: AIC[:, cc, :] = aic3 if target == 'segments': if Project: raise NameError('Normal not defined at collocation points') M_trg, N_trg = Surf_target.maps.M, Surf_target.maps.N AIC = np.zeros((3, K_in, 4, M_trg, N_trg)) ##### panel (0,0): compute all mm, nn = 0, 0 svec = [0, 1, 2, 3] # seg. number avec = [0, 1, 2, 3] # 1st vertex of seg. bvec = [1, 2, 3, 0] # 2nd vertex of seg. zetav_here = Surf_target.get_panel_vertices_coords(mm, nn) for ss, aa, bb in zip(svec, avec, bvec): zeta_mid = 0.5 * (zetav_here[aa, :] + zetav_here[bb, :]) AIC[:, :, ss, mm, nn] = get_aic3_cpp(self.maps, self.zeta, zeta_mid, self.vortex_radius) ##### panels n=0: copy seg.3 nn = 0 svec = [0, 1, 2] # seg. number avec = [0, 1, 2] # 1st vertex of seg. bvec = [1, 2, 3] # 2nd vertex of seg. for mm in range(1, M_trg): zetav_here = Surf_target.get_panel_vertices_coords(mm, nn) for ss, aa, bb in zip(svec, avec, bvec): zeta_mid = 0.5 * (zetav_here[aa, :] + zetav_here[bb, :]) AIC[:, :, ss, mm, nn] = get_aic3_cpp(self.maps, self.zeta, zeta_mid, self.vortex_radius) AIC[:, :, 3, mm, nn] = AIC[:, :, 1, mm - 1, nn] ##### panels m=0: copy seg.0 mm = 0 svec = [1, 2, 3] # seg. number avec = [1, 2, 3] # 1st vertex of seg. bvec = [2, 3, 0] # 2nd vertex of seg. for nn in range(1, N_trg): zetav_here = Surf_target.get_panel_vertices_coords(mm, nn) for ss, aa, bb in zip(svec, avec, bvec): zeta_mid = 0.5 * (zetav_here[aa, :] + zetav_here[bb, :]) AIC[:, :, ss, mm, nn] = get_aic3_cpp(self.maps, self.zeta, zeta_mid, self.vortex_radius) AIC[:, :, 0, mm, nn] = AIC[:, :, 2, mm, nn - 1] ##### all others: copy seg. 0 and 3 svec = [1, 2] # seg. number avec = [1, 2] # 1st vertex of seg. bvec = [2, 3] # 2nd vertex of seg. for pp in itertools.product(range(1, M_trg), range(1, N_trg)): mm, nn = pp zetav_here = Surf_target.get_panel_vertices_coords(*pp) for ss, aa, bb in zip(svec, avec, bvec): zeta_mid = 0.5 * (zetav_here[aa, :] + zetav_here[bb, :]) AIC[:, :, ss, mm, nn] = get_aic3_cpp(self.maps, self.zeta, zeta_mid, self.vortex_radius) AIC[:, :, 3, mm, nn] = AIC[:, :, 1, mm - 1, nn] AIC[:, :, 0, mm, nn] = AIC[:, :, 2, mm, nn - 1] return AIC # ------------------------------------------------------------------ forces def get_joukovski_qs(self, gammaw_TE=None, recompute_velocities=True): """ Returns quasi-steady forces evaluated at mid-segment points over the surface. Important: the circulation at the first row of wake panel is required! Hence all Warning: forces are stored in a NON-redundant format: (3,4,M,N) where the element (:,ss,mm,nn) is the contribution to the force over the ss-th segment due to the circulation of panel (mm,nn). """ if self.u_input_seg is None: if not recompute_velocities: print("WARNING: recomputing velocities") self.get_input_velocities_at_segments() if not hasattr(self, 'u_ind_seg'): raise NameError('u_ind_seg not available!') M, N = self.maps.M, self.maps.N self.fqs_seg_unit = np.zeros((3, 4, M, N)) self.fqs = np.zeros((3, M + 1, N + 1)) # indiced as per self.maps dmver = [0, 1, 1, 0] # delta to go from (m,n) panel to (m,n) vertices dnver = [0, 0, 1, 1] svec = [0, 1, 2, 3] # seg. no. avec = [0, 1, 2, 3] # 1st vertex no. bvec = [1, 2, 3, 0] # 2nd vertex no. ### force produced by BOUND panels for pp in itertools.product(range(0, M), range(0, N)): mm, nn = pp zetav_here = self.get_panel_vertices_coords(mm, nn) for ss, aa, bb in zip(svec, avec, bvec): df = uvlmutils.joukovski_qs_segment( zetaA=zetav_here[aa, :], zetaB=zetav_here[bb, :], v_mid=self.u_ind_seg[:, ss, mm, nn] + self.u_input_seg[:, ss, mm, nn], gamma=1.0, fact=self.rho) self.fqs_seg_unit[:, ss, mm, nn] = df # project on vertices self.fqs[:, mm + dmver[aa], nn + dnver[aa]] += 0.5 * self.gamma[mm, nn] * df self.fqs[:, mm + dmver[bb], nn + dnver[bb]] += 0.5 * self.gamma[mm, nn] * df ### force produced by wake T.E. segments # Note: # 1. zetaA & zetaB are ordered such that the wake circulation is # subtracts to the bound circulation over TE segment # 2. the TE segment corresponds to seg.1 of the last row of BOUND panels if gammaw_TE is None: raise NameError('Enter gammaw_TE - option disabled for debugging') gammaw_TE = self.gamma[M - 1, :] self.fqs_wTE_unit = np.zeros((3, N)) for nn in range(N): df = uvlmutils.joukovski_qs_segment( zetaA=self.zeta[:, M, nn + 1], zetaB=self.zeta[:, M, nn], v_mid=self.u_input_seg[:, 1, M - 1, nn] + self.u_ind_seg[:, 1, M - 1, nn], gamma=1.0, fact=self.rho) # record force on TE due to wake and project self.fqs_wTE_unit[:, nn] = df self.fqs[:, M, nn + 1] += 0.5 * gammaw_TE[nn] * df self.fqs[:, M, nn] += 0.5 * gammaw_TE[nn] * df return self def get_joukovski_unsteady(self): """ Returns added mass effects over lattive grid """ if self.gamma_dot is None: raise NameError('circulation derivative not specified!') if not hasattr(self, 'areas'): self.generate_areas() M, N = self.maps.M, self.maps.N self.funst = np.zeros((3, M + 1, N + 1)) wcv = self.get_panel_wcv() for pp in itertools.product(range(0, M), range(0, N)): mm, nn = pp # force at collocation point fcoll = -self.rho * self.areas[mm, nn] * self.normals[:, mm, nn] * self.gamma_dot[mm, nn] # project at vertices for vv in range(4): self.funst[:, mm + dmver[vv], nn + dnver[vv]] += wcv[vv] * fcoll ================================================ FILE: sharpy/linear/src/uvlmutils.py ================================================ """Methods for UVLM solution S. Maraniello, 1 Jun 2018 """ import numpy as np import sharpy.aero.utils.uvlmlib as uvlmlib import sharpy.utils.algebra as algebra from sharpy.utils.constants import cfact_biot # local mapping segment/vertices of a panel svec = [0, 1, 2, 3] # seg. number avec = [0, 1, 2, 3] # 1st vertex of seg. bvec = [1, 2, 3, 0] # 2nd vertex of seg. LoopPanel = [(0, 1), (1, 2), (2, 3), (3, 0)] # used in eval_panel_{exp/comp} def joukovski_qs_segment(zetaA, zetaB, v_mid, gamma=1.0, fact=0.5): """ Joukovski force over vetices A and B produced by the segment A->B. The factor fact allows to compute directly the contribution over the vertices A and B (e.g. 0.5) or include DENSITY. """ rab = zetaB - zetaA fs = algebra.cross3(v_mid, rab) gfact = fact * gamma return gfact * fs def biot_segment(zetaP, zetaA, zetaB, vortex_radius, gamma=1.0): """ Induced velocity of segment A_>B of circulation gamma over point P. """ vortex_radius_sq = vortex_radius*vortex_radius # differences ra = zetaP - zetaA rb = zetaP - zetaB rab = zetaB - zetaA ra_norm, rb_norm = algebra.norm3d(ra), algebra.norm3d(rb) vcross = algebra.cross3(ra, rb) vcross_sq = np.dot(vcross, vcross) # numerical radius if vcross_sq < (vortex_radius_sq * algebra.normsq3d(rab)): return np.zeros((3,)) q = ((cfact_biot * gamma / vcross_sq) * \ (np.dot(rab, ra) / ra_norm - np.dot(rab, rb) / rb_norm)) * vcross return q def biot_panel(zetaC, ZetaPanel, vortex_radius, gamma=1.0): """ Induced velocity over point ZetaC of a panel of vertices coordinates ZetaPanel and circulaiton gamma, where: ZetaPanel.shape=(4,3)=[vertex local number, (x,y,z) component] """ q = np.zeros((3,)) for ss, aa, bb in zip(svec, avec, bvec): q += biot_segment(zetaC, ZetaPanel[aa, :], ZetaPanel[bb, :], vortex_radius, gamma) return q def biot_panel_fast(zetaC, ZetaPanel, vortex_radius, gamma=1.0): """ Induced velocity over point ZetaC of a panel of vertices coordinates ZetaPanel and circulaiton gamma, where: ZetaPanel.shape=(4,3)=[vertex local number, (x,y,z) component] """ vortex_radius_sq = vortex_radius*vortex_radius Cfact = cfact_biot * gamma q = np.zeros((3,)) R_list = zetaC - ZetaPanel Runit_list = [R_list[ii] / algebra.norm3d(R_list[ii]) for ii in svec] for aa, bb in LoopPanel: RAB = ZetaPanel[bb, :] - ZetaPanel[aa, :] # segment vector Vcr = algebra.cross3(R_list[aa], R_list[bb]) vcr2 = np.dot(Vcr, Vcr) if vcr2 < (vortex_radius * algebra.normsq3d(RAB)): continue q += ((Cfact / vcr2) * np.dot(RAB, Runit_list[aa] - Runit_list[bb])) * Vcr return q def panel_normal(ZetaPanel): """ return normal of panel with vertex coordinates ZetaPanel, where: ZetaPanel.shape=(4,3) """ # build cross-vectors r02 = ZetaPanel[2, :] - ZetaPanel[0, :] r13 = ZetaPanel[3, :] - ZetaPanel[1, :] nvec = algebra.cross3(r02, r13) nvec = nvec / algebra.norm3d(nvec) return nvec def panel_area(ZetaPanel): """ return area of panel with vertices coordinates ZetaPanel, where: ZetaPanel.shape=(4,3) using Bretschneider formula - for cyclic or non-cyclic quadrilaters. """ # build cross-vectors r02 = ZetaPanel[2, :] - ZetaPanel[0, :] r13 = ZetaPanel[3, :] - ZetaPanel[1, :] # build side vectors r01 = ZetaPanel[1, :] - ZetaPanel[0, :] r12 = ZetaPanel[2, :] - ZetaPanel[1, :] r23 = ZetaPanel[3, :] - ZetaPanel[2, :] r30 = ZetaPanel[0, :] - ZetaPanel[3, :] # compute distances d02 = algebra.norm3d(r02) d13 = algebra.norm3d(r13) d01 = algebra.norm3d(r01) d12 = algebra.norm3d(r12) d23 = algebra.norm3d(r23) d30 = algebra.norm3d(r30) A = 0.25 * np.sqrt((4. * d02 ** 2 * d13 ** 2) - ((d12 ** 2 + d30 ** 2) - (d01 ** 2 + d23 ** 2)) ** 2) return A if __name__ == '__main__': import cProfile ### verify consistency amongst models gamma = 4. zeta0 = np.array([1.0, 3.0, 0.9]) zeta1 = np.array([5.0, 3.1, 1.9]) zeta2 = np.array([4.8, 8.1, 2.5]) zeta3 = np.array([0.9, 7.9, 1.7]) ZetaPanel = np.array([zeta0, zeta1, zeta2, zeta3]) zetaP = np.array([3.0, 5.5, 2.0]) zetaP = zeta2 * 0.3 + zeta3 * 0.7 ### verify model consistency qref = biot_panel(zetaP, ZetaPanel, 1e-6, gamma=gamma) # vortex_radius qfast = biot_panel_fast(zetaP, ZetaPanel, 1e-6, gamma=gamma) # vortex_radius qcpp = uvlmlib.biot_panel_cpp(zetaP, ZetaPanel, 1e-6, gamma=gamma) # vortex_radius ermax = np.max(np.abs(qref - qfast)) assert ermax < 1e-16, 'biot_panel_fast not matching with biot_panel' ermax = np.max(np.abs(qref - qcpp)) assert ermax < 1e-16, 'biot_panel_cpp not matching with biot_panel' ### profiling def run_biot_panel_cpp(): for ii in range(10000): uvlmlib.biot_panel_cpp(zetaP, ZetaPanel, 1e-6, gamma=3.) # vortex_radius def run_biot_panel_fast(): for ii in range(10000): biot_panel_fast(zetaP, ZetaPanel, 1e-6, gamma=3.) # vortex_radius def run_biot_panel_ref(): for ii in range(10000): biot_panel(zetaP, ZetaPanel, 1e-6, gamma=3.) # vortex_radius print('------------------------------------------ profiling biot_panel_cpp') cProfile.runctx('run_biot_panel_cpp()', globals(), locals()) print('----------------------------------------- profiling biot_panel_fast') cProfile.runctx('run_biot_panel_fast()', globals(), locals()) print('------------------------------------------ profiling biot_panel_ref') cProfile.runctx('run_biot_panel_ref()', globals(), locals()) ================================================ FILE: sharpy/linear/utils/__init__.py ================================================ ================================================ FILE: sharpy/linear/utils/derivatives.py ================================================ import h5py import numpy as np from sharpy.utils import algebra as algebra, cout_utils as cout class Derivatives: """ Class containing the derivatives set for a given state-space system (i.e. aeroelastic or aerodynamic) """ def __init__(self, reference_dimensions, static_state, target_system=None): self.target_system = target_system # type: str # name of target system (aerodynamic/aeroelastic) self.transfer_function = None # type: np.array # matrix of steady-state TF for target system self.static_state = static_state # type: tuple # [fx, fy, fz] at ref state self.reference_dimensions = reference_dimensions # type: dict # name: ref_dimension_value dictionary self.separator = '\n' + 80 * '#' + '\n' self.dict_of_derivatives = {} # type: dict # {name:DerivativeSet} Each of the derivative sets DerivativeSet s_ref = self.reference_dimensions['S_ref'] b_ref = self.reference_dimensions['b_ref'] c_ref = self.reference_dimensions['c_ref'] u_inf = self.reference_dimensions['u_inf'] rho = self.reference_dimensions['rho'] self.dynamic_pressure = 0.5 * rho * u_inf ** 2 self.coefficients = {'force': self.dynamic_pressure * s_ref, 'moment_lon': self.dynamic_pressure * s_ref * c_ref, 'moment_lat': self.dynamic_pressure * s_ref * b_ref, 'force_angular_vel': self.dynamic_pressure * s_ref * c_ref / u_inf, 'moment_lon_angular_vel': self.dynamic_pressure * s_ref * c_ref * c_ref / u_inf} # missing rates self.steady_coefficients = np.array(self.static_state) / self.coefficients['force'] self.filename = 'stability_derivatives.txt' if target_system is not None: self.filename = target_system + '_' + self.filename self.cg = None def initialise_derivatives(self, state_space, steady_forces, quat, v0, phi=None, cg=None, tpa=None): """ Initialises the required class attributes for all derivative calculations/ Args: state_space (sharpy.linear.src.libss.StateSpace): State-space object for the target system steady_forces (np.array): Array of steady forces (at the linearisation) expressed in the beam degrees of freedom and with size equal to the number of structural degrees of freedom quat (np.array): Quaternion at the linearisation reference state v0 (np.array): Free stream velocity vector at the linearisation condition phi (np.array (optional)): Mode shape matrix for modal systems tpa (np.array (optional)): Transformation matrix onto principal axes """ cls = DerivativeSet # explain what is the DerivativeSet class if cls.quat is None: cls.quat = quat cls.cga = algebra.quat2rotation(cls.quat) cls.v0 = v0 cls.coefficients = self.coefficients if phi is not None: cls.modal = True cls.phi = phi[-9:-3, :6] cls.inv_phi_forces = np.linalg.inv(phi[-9:-3, :6].T) cls.inv_phi_vel = np.linalg.inv(phi[-9:-3, :6]) else: cls.modal = False cls.steady_forces = steady_forces H0 = state_space.freqresp(np.array([1e-5]))[:, :, 0].real if cls.modal: vel_inputs_variables = state_space.input_variables.get_variable_from_name('q_dot') output_indices = state_space.output_variables.get_variable_from_name('Q').rows_loc[:6] cls.steady_forces = cls.inv_phi_forces.dot(cls.steady_forces[output_indices]) else: vel_inputs_variables = state_space.input_variables.get_variable_from_name('beta') output_indices = state_space.output_variables.get_variable_from_name('forces_n').rows_loc[-9:-3] cls.steady_forces = cls.steady_forces[output_indices] rbm_indices = vel_inputs_variables.cols_loc[:9] # look for control surfaces try: cs_input_variables = state_space.input_variables.get_variable_from_name('control_surface_deflection') dot_cs_input_variables = state_space.input_variables.get_variable_from_name('dot_control_surface_deflection') except ValueError: cs_indices = np.array([], dtype=int) dot_cs_indices = np.array([], dtype=int) cls.n_control_surfaces = 0 else: cs_indices = cs_input_variables.cols_loc dot_cs_indices = dot_cs_input_variables.cols_loc cls.n_control_surfaces = cs_input_variables.size finally: input_indices = np.concatenate((rbm_indices, cs_indices, dot_cs_indices)) self.transfer_function = H0[np.ix_(output_indices, input_indices)].real self.cg = cg self.tpa = tpa def save(self, output_route): with h5py.File(output_route + '/' + self.filename.replace('.txt', '.h5'), 'w') as f: for k, v in self.dict_of_derivatives.items(): if v.matrix is None: continue f.create_dataset(name=k, data=v.matrix) def savetxt(self, folder): filename = self.filename u_inf = self.reference_dimensions['u_inf'] s_ref = self.reference_dimensions['S_ref'] b_ref = self.reference_dimensions['b_ref'] c_ref = self.reference_dimensions['c_ref'] rho = self.reference_dimensions['rho'] quat = self.reference_dimensions['quat'] euler_orient = algebra.quat2euler(quat) * 180/np.pi labels_out = ['CD', 'CY', 'CL', 'Cl', 'Cm', 'Cn'] separator = '\n' + 80*'#' + '\n' with open(folder + '/' + filename, mode='w') as outfile: outfile.write('SHARPy Stability Derivatives Analysis\n') outfile.write('State:\n') outfile.write('\t{:4f}\t\t\t # Free stream velocity\n'.format(u_inf)) outfile.write('\t{:4f}\t\t\t # Free stream density\n'.format(rho)) outfile.write('\t{:4f}\t\t\t # Alpha [deg]\n'.format(euler_orient[1])) outfile.write('\t{:4f}\t\t\t # Beta [deg]\n'.format(euler_orient[2])) if self.cg is not None: outfile.write(separator) outfile.write('Centre of Gravity:\n') lab = ('x', 'y', 'z') for i in range(3): outfile.write('\t{:s}_A = {:.4f}\t\t\t # [m]\n'.format(lab[i], self.cg[i])) if self.tpa is not None: outfile.write('Principal Axes Directions (expressed in the A frame):\n') for i in range(3): outfile.write('\t{:s}_ppal in A = [{:.4f}, {:.4f}, {:.4f}]\t\t\t\n'.format( lab[i], *self.tpa.dot(np.eye(3)[:, i]))) outfile.write(separator) outfile.write('\nReference Dimensions:\n') outfile.write('\t{:4f}\t\t\t # Reference planform area\n'.format(s_ref)) outfile.write('\t{:4f}\t\t\t # Reference chord\n'.format(c_ref)) outfile.write('\t{:4f}\t\t\t # Reference span\n'.format(b_ref)) outfile.write(separator) outfile.write('\nCoefficients:\n') for ith, coeff in enumerate(self.steady_coefficients): outfile.write('\t{:4e}\t\t\t # {:s}\n'.format(coeff, labels_out[ith])) outfile.write(separator) # this needs to be out of with open as it is done in each of the Derivatives objects for derivative_set in self.dict_of_derivatives.values(): if derivative_set.matrix is None: continue derivative_set.print(derivative_filename=folder + '/' + filename) def new_derivative(self, frame_of_reference, derivative_calculation=None, name=None): """ Returns a DerivativeSet() instance with the appropriate transfer function included for the relevant target system. Args: frame_of_reference (str): Output frame of reference. Body or Stability. (Not Yet Implemented) derivative_calculation (str): Name of function used to create derivative set name (str (optional)): Optional custom name to use as title in output. Returns: DerivativeSet: Instance of class with the relevant transfer function for the current target system. """ new_derivative = DerivativeSet(frame_of_reference, derivative_calculation, name, transfer_function=self.transfer_function) return new_derivative class DerivativeSet: """ Class containing the stability derivative set for each of the input/output combinations. A derivative set may be force/angle or force/control_surface, for example. The class attributes contain the parameters common across all derivative sets. The instance attributes those pertaining to the specific set. """ steady_forces = None coefficients = None quat = None cga = None n_control_surfaces = None v0 = None # Modal cases modal = None phi = None inv_phi_forces = None inv_phi_vel = None def __init__(self, frame_of_reference, derivative_calculation=None, name=None, transfer_function=None): """ Args: frame_of_reference (str): Name of the frame of reference (stability or body axes) derivative_calculation (str): Name of the method to compute derivatives name (str): Name of the derivative set transfer_function (np.array): steady state transfer function for the desired input output channels """ self.transfer_function = transfer_function # type: np.array # steady-state TF for the specific out/in self.matrix = None # type: np.array # matrix of stability derivatives self.labels_in = [] # type: list(str) # strings describing the input channels self.labels_out = [] # type list(str) # strings describing the output channels self.frame_of_reference = frame_of_reference # type: str # name of the FoR (stability or body axes) self.table = None # type: cout.TablePrinter self.name = name # type: str # name of the set # TODO: remove in clean up and make derivative_calculation a position argument if derivative_calculation is not None: self.__getattribute__(derivative_calculation)() def print(self, derivative_filename=None): if self.name is not None: cout.cout_wrap(self.name) with open(derivative_filename, 'a') as f: f.write('Derivative set: {:s}\n'.format(self.name)) f.write('Axes {:s}\n'.format(self.frame_of_reference)) self.table = cout.TablePrinter(n_fields=len(self.labels_in)+1, field_types=['s']+len(self.labels_in) * ['e'], filename=derivative_filename) self.table.print_header(field_names=list(['der'] + self.labels_in)) for i in range(len(self.labels_out)): out_list = [self.labels_out[i]] + list(self.matrix[i, :]) self.table.print_line(out_list) self.table.print_divider_line() self.table.character_return(n_lines=2) def save(self, derivative_name, output_name): with h5py.File(output_name + '.stability.h5', 'w') as f: f.create_dataset(derivative_name, data=self.matrix) def angle_derivatives(self): r""" Stability derivatives against aerodynamic angles (angle of attack and sideslip) expressed in stability axes, i.e forces are lift, drag... Linearised forces in stability axes are expressed as .. math:: F^S = F_0^S + \frac{\partial}{\partial \alpha}\left(C^{GA}(\alpha)F_0^A\right)\delta\alpha + C_0^{GA}\delta F^A Therefore, the stability derivative becomes .. math:: \frac{\partial\F^S}{\partial\alpha} =\frac{\partial}{\partial \alpha}\left(C^{GA}(\alpha)F_0^A\right) + C_0^{GA}\frac{\partial F^A}{\partial\alpha} where .. math:: \frac{\partial F^A}{\partial\alpha} = \frac{\partial F^A}{\partial v^A}\frac{\partial v^A}{\partial\alpha} and .. math:: \frac{\partial v^A}{\partial\alpha} = C^{AG}\frac{\partial}{\partial\alpha}\left(C(0)V_0^G\right). The term :math:`\frac{\partial F^A}{\partial v^A}` is obtained directly from the steady state transfer function of the linear UVLM expressed in the beam degrees of freedoms. """ self.labels_in = ['phi', 'alpha', 'beta'] self.labels_out = ['CD', 'CY', 'CL', 'Cl', 'Cm', 'Cn'] self.matrix = np.zeros((6, 3)) # Get free stream velocity direction v0 = self.v0 f0a = self.steady_forces[:3] m0a = self.steady_forces[-3:] euler0 = algebra.quat2euler(self.quat) cga = self.cga # first term in the stability derivative expression stab_der_trans = algebra.der_Ceuler_by_v(euler0, f0a) stab_der_mom = algebra.der_Ceuler_by_v(euler0, m0a) # second term in the stability derivative expression if self.modal: delta_nodal_vel = np.linalg.inv(self.phi[:3, :3]).dot(cga.T.dot(algebra.der_Peuler_by_v(euler0 * 0, v0))) delta_nodal_forces = self.inv_phi_forces.dot(self.transfer_function[:6, :3].real.dot(delta_nodal_vel)) else: delta_nodal_vel = cga.T.dot(algebra.der_Peuler_by_v(euler0 * 0, v0)) delta_nodal_forces = self.transfer_function[:6, :3].real.dot(delta_nodal_vel) stab_der_trans2 = cga.dot(delta_nodal_forces[:3, :]) stab_der_mom2 = cga.dot(delta_nodal_forces[3:, :]) self.matrix[:3, :] = stab_der_trans + stab_der_trans2 self.matrix[3:6, :] = stab_der_mom + stab_der_mom2 self.apply_coefficients() def angle_derivatives_tb(self): self.name = 'Force/Angle derivatives via angle input' self.labels_in = ['phi', 'alpha', 'beta'] self.labels_out = ['CD', 'CY', 'CL', 'Cl', 'Cm', 'Cn'] self.matrix = np.zeros((6, 3)) # Get free stream velocity direction v0 = self.v0 f0a = self.steady_forces[:3] m0a = self.steady_forces[-3:] euler0 = algebra.quat2euler(self.quat) cga = self.cga # first term in the stability derivative expression stab_der_trans = algebra.der_Ceuler_by_v(euler0, f0a) stab_der_mom = algebra.der_Ceuler_by_v(euler0, m0a) # second term in the stability derivative expression if self.modal: delta_nodal_forces = self.inv_phi_forces.dot(self.transfer_function[:6, 6:9].real) else: delta_nodal_forces = self.transfer_function[:6, 6:9].real stab_der_trans2 = cga.dot(delta_nodal_forces[:3, :]) stab_der_mom2 = cga.dot(delta_nodal_forces[3:, :]) self.matrix[:3, :] = stab_der_trans + stab_der_trans2 self.matrix[3:6, :] = stab_der_mom + stab_der_mom2 self.apply_coefficients() def body_derivatives(self): self.name = 'Force derivatives to rigid body velocities - Body derivatives' self.labels_in = ['uA', 'vA', 'wA', 'pA', 'qA', 'rA'] self.labels_out = ['C_XA', 'C_YA', 'C_ZA', 'C_LA', 'C_MA', 'C_NA'] self.matrix = np.zeros((6, 6)) body_derivatives = self.transfer_function[:6, :6] if self.modal: body_derivatives = self.inv_phi_forces.dot(body_derivatives).dot(self.inv_phi_vel) self.matrix = body_derivatives self.apply_coefficients() def control_surface_derivatives(self): n_control_surfaces = self.n_control_surfaces if n_control_surfaces == 0: return None self.name = 'Force derivatives wrt control surface inputs - Body axes' self.labels_out = ['C_XA', 'C_YA', 'C_ZA', 'C_LA', 'C_MA', 'C_NA'] labels_in_deflection = [] labels_in_rate = [] for i in range(n_control_surfaces): labels_in_deflection.append('delta_{:g}'.format(i)) labels_in_rate.append('dot(delta)_{:g}'.format(i)) self.labels_in = labels_in_deflection + labels_in_rate body_derivatives = self.transfer_function[:6, 9:] assert body_derivatives.shape == (6, 2 * self.n_control_surfaces), 'Incorrect TF shape' if self.modal: self.matrix = self.inv_phi_forces.dot(body_derivatives) else: self.matrix = body_derivatives self.apply_coefficients() def apply_coefficients(self): self.matrix[:3, :] /= self.coefficients['force'] self.matrix[np.ix_([3, 5]), :] /= self.coefficients['moment_lat'] self.matrix[4, :] /= self.coefficients['moment_lon'] ================================================ FILE: sharpy/linear/utils/ss_interface.py ================================================ """State-space modules loading utilities""" import os import h5py import sharpy.utils.cout_utils as cout from abc import ABCMeta, abstractmethod import numpy as np import copy dict_of_systems = dict() systems_dict_import = dict() # Define the system decorator def linear_system(arg): global dict_of_systems try: arg.sys_id except AttributeError: raise AttributeError('Class defined as linear_system with no sys_id') dict_of_systems[arg.sys_id] = arg return arg class BaseElement(metaclass=ABCMeta): @property def sys_id(self): raise NotImplementedError @abstractmethod def initialise(self, data, custom_settings=None): pass @abstractmethod def assemble(self): pass # Some method to connect # Input (from where) - (i.e. generator, system etc) # Output (to display, to system) def sys_list_from_path(cwd): """ Returns the files containing linear system state space elements Args: cwd (str): Current working directory Returns: """ onlyfiles = [f for f in os.listdir(cwd) if os.path.isfile(os.path.join(cwd, f))] for i_file in range(len(onlyfiles)): if onlyfiles[i_file].split('.')[-1] == 'py': # support autosaved files in the folder if onlyfiles[i_file] == "__init__.py": onlyfiles[i_file] = "" continue onlyfiles[i_file] = onlyfiles[i_file].replace('.py', '') else: onlyfiles[i_file] = "" files = [file for file in onlyfiles if not file == ""] return files def sys_from_string(string): return dict_of_systems[string] def initialise_system(sys_id): cout.cout_wrap('Generating an instance of %s' %sys_id, 2) cls_type = sys_from_string(sys_id) sys = cls_type() return sys def dictionary_of_systems(): dictionary = dict() for linear_system in dict_of_systems: init_sys = initialise_system(linear_system) dictionary[linear_system] = init_sys.settins_default return dictionary class VectorVariable: # state variable def __init__(self, name, size, index, var_system=None): self.name = name self.var_system = var_system self.size = size self._index = index self._first_position = 0 self._rows_loc = None self._cols_loc = None @property def cols_loc(self): return np.arange(self.first_position, self.end_position, dtype=int) @property def index(self): return self._index @index.setter def index(self, value): self._index = value @property def first_position(self): return self._first_position @first_position.setter def first_position(self, position): self._first_position = position @property def end_position(self): return self.first_position + self.size @property def rows_loc(self): return np.arange(self.first_position, self.end_position, dtype=int) def __repr__(self): return '({:s}: {:s}, size: {:g}, index: {:g}, starting at: {:g}, finishing at: {:g})'.format( type(self).__name__, self.name, self.size, self.index, self.first_position, self.end_position) def copy(self): return copy.deepcopy(self) def save(self): """ Return: tuple: info to save to h5 """ return type(self).__name__, self.name, self.size, self.index class OutputVariable(VectorVariable): @property def cols_loc(self): return self._cols_loc @cols_loc.setter def cols_loc(self, value=None): if self._cols_loc is None: self._cols_loc = np.arange(self.first_position, self.end_position, dtype=int) # Original location, should not update @property def rows_loc(self): # In the output variables the rows can change, the columns should stay fixed return np.arange(self.first_position, self.end_position, dtype=int) class InputVariable(VectorVariable): @property def rows_loc(self): return self._rows_loc @rows_loc.setter def rows_loc(self, value=None): if self._rows_loc is None: self._rows_loc = np.arange(self.first_position, self.end_position, dtype=int) # Original location, should not update @property def cols_loc(self): return np.arange(self.first_position, self.end_position, dtype=int) class StateVariable(VectorVariable): pass class LinearVector: def __init__(self, list_of_vector_variables): self.vector_variables = list_of_vector_variables # check they are all the same type self.check_sametype_vars_full_vector() # initialise position of variables self.update_indices() self.update_locations() self.variable_class = type(self.vector_variables[0]) # class @property def num_variables(self): return len(self.vector_variables) @property def size(self): return sum([variable.size for variable in self.vector_variables]) def remove(self, *variable_name_list): for variable_name in variable_name_list: list_of_variable_names = [variable.name for variable in self.vector_variables] try: remove_variable_index = list_of_variable_names.index(variable_name) except ValueError: raise ValueError('Trying to remove non-existent {:s} variable'.format(variable_name)) else: self.__remove_variable(remove_variable_index) def __remove_variable(self, index): self.vector_variables.pop(index) self.update_indices() def modify(self, variable_name, **kwargs): """ Modifies the attributes of the desired variable. The new attributes are passed as **kwargs. If an attribute is not recognised a ``NameError`` is returned. Note: If changing the size of the variable, you should then update the linear vector. Args: variable_name (str): Name of the variable to modify **kwargs: Key-word arguments containing the attributes of the variable as keys and the new values as values. """ variable = self.get_variable_from_name(variable_name) for var_attribute, new_var_value in kwargs.items(): if hasattr(variable, var_attribute): variable.__setattr__(var_attribute, new_var_value) else: raise NameError('Unknown variable attribute {:s}'.format(var_attribute)) def add(self, vector_variable, **kwargs): if isinstance(vector_variable, self.variable_class): self.__add_vector_variable(vector_variable) elif type(vector_variable) is str: new_variable = self.variable_class(name=vector_variable, **kwargs) self.__add_vector_variable(new_variable) else: raise TypeError('Only can add a variable from either a {:s} object or with name and kwargs'.format( self.variable_class.__name__ )) def append(self, vector_variable, **kwargs): self.update_indices() appending_index = self.num_variables if isinstance(vector_variable, VectorVariable): vector_variable.index = appending_index elif type(vector_variable) is str: kwargs['index'] = appending_index else: raise TypeError('Only can add a variable from either a {:s} object or with name and kwargs'.format( self.variable_class.__name__ )) self.add(vector_variable, **kwargs) def __add_vector_variable(self, vector_variable): list_of_indices = [variable.index for variable in self.vector_variables] if vector_variable.index in list_of_indices: raise IndexError('New variable index is already in use') self.vector_variables.append(vector_variable) self.update_indices() self.update_locations() def update_indices(self): list_of_indices = [variable.index for variable in self.vector_variables] ordered_list = sorted(list_of_indices) index_variable_dict = {key_index: self.vector_variables[i] for i, key_index in enumerate(list_of_indices)} updated_list = [] for i_var in range(self.num_variables): current_variable_index = ordered_list[i_var] current_variable = index_variable_dict[current_variable_index] current_variable.index = i_var updated_list.append(current_variable) self.vector_variables = updated_list def update_locations(self): for i_var in range(self.num_variables): if i_var == 0: self.vector_variables[i_var].first_position = 0 elif i_var > 0: # since end item is not included in range methods, set the first position to last variable end position self.vector_variables[i_var].first_position = self.vector_variables[i_var - 1].end_position def check_sametype_vars_full_vector(self): variable_class = type(self.vector_variables[0]) for var in self.vector_variables: if not isinstance(var, variable_class): raise TypeError('All variables in the vector are not of the same kind.') @classmethod def merge(cls, vec1, vec2): """ Merges two instances of LinearVectors Args: vec1 (LinearVector): Vector 1 vec2 (LinearVector): Vector 2 Returns: LinearVector: Merged vectors 1 and 2 """ if vec1.variable_class is not vec2.variable_class: raise TypeError('Unable to merge two different kinds of vectors') list_of_variables_1 = [variable.copy() for variable in vec1] list_of_variables_2 = [variable.copy() for variable in vec2] for ith, variable in enumerate(list_of_variables_1 + list_of_variables_2): variable.index = ith merged_vector = cls(list_of_variables_1 + list_of_variables_2) return merged_vector @classmethod def transform(cls, linear_vector, to_type): if not (to_type is InputVariable or to_type is StateVariable or to_type is OutputVariable): raise TypeError('Argument should be the class to which the linear vector should be transformed') list_of_variables = [] for variable in linear_vector.copy(): list_of_variables.append(to_type(name=variable.name, size=variable.size, index=variable.index)) return cls(list_of_variables) @staticmethod def check_connection(output_vector, input_vector): """ Checks an output_vector can be connected to an input channel. """ with_error = False err_log = '' for ith, out_variable in enumerate(output_vector): try: in_variable = input_vector.get_variable_from_name(out_variable.name) except ValueError: err_log += '\nUnable to connect both systems, no input variable named {:s}\n'.format(out_variable.name) with_error = True else: if not out_variable.size == in_variable.size: err_log += 'Variable {:s} and {:s} not the same size' with_error = True if not out_variable.rows_loc[-1] == in_variable.cols_loc[-1]: # checking the last index should be sufficient (and more efficient than checking the whole array) err_log += 'Variable {:s}Output rows not coincident with input columns for variable\n'.format( out_variable.name) err_log += str(out_variable) + '\n' err_log += str(in_variable) + '\n' with_error = True if with_error: raise ValueError(err_log) @staticmethod def check_same_vectors(vec1, vec2): """ Checks that two linear vectors contain the same variables, regardless of type Args: vec1 (LinearVector): Vector 1 vec2 (LinearVector): Vector 2 """ assert vec1.num_variables == vec2.num_variables, 'Number of variables is not equal' assert vec1.size == vec2.size, 'Size is not equal' for i_var in range(vec1.num_variables): assert vec1[i_var].name == vec2[i_var].name, 'Variable name is not the same' assert vec1[i_var].index == vec2[i_var].index, 'Index is not the same' assert vec1[i_var].size == vec2[i_var].size, 'Variable size is not the same' assert vec1[i_var] is not vec2[i_var], 'Variables in LinearVector reference the same object in memory. ' \ 'Careful!' def differentiate(self): pass # going from second order to first order systems def get_variable_from_name(self, name): list_of_variable_names = [variable.name for variable in self.vector_variables] try: variable_index = list_of_variable_names.index(name) except ValueError: raise ValueError('Variable {:s} is non existent'.format(name)) else: return self.vector_variables[variable_index] def __call__(self, variable_name): """ Args: variable_name (str): Variable name Returns: VectorVariable: Vector variable within LinearVector """ return self.get_variable_from_name(variable_name) def copy(self): return copy.deepcopy(self) def add_to_h5_file(self, file_handle): """ Given an h5 handle ``file_handle``, creates and adds a group named with the type of variable and adds the fields ``names`` (encoded in ascii), ``sizes`` and ``indices``. Args: file_handle (h5py.File): file handle of h5py.File Returns: file_handle """ variable_data = [] for variable in self.vector_variables: variable_data.append(variable.save()) types, names, sizes, indices = zip(*variable_data) # encode strings to bytes names = [name.encode('ascii', 'ignore') for name in names] variable_group = file_handle.create_group(types[0]) variable_group.create_dataset(name='names', data=names) variable_group.create_dataset(name='sizes', data=sizes, dtype=int) variable_group.create_dataset(name='indices', data=indices, dtype=int) return file_handle @classmethod def load_from_h5_file(cls, variable_type, variables_data): """ Loads data from the information saved in an h5file Args: variable_type (str): Type of variable (InputVariable, OutputVariable or StateVariable) variables_data (dict): Dictionary containing variable info from h5 with keys: names, sizes and indices Returns: LinearVector: of ``variable_type`` """ var_class_dict = {'InputVariable': InputVariable, 'OutputVariable': OutputVariable, 'StateVariable': StateVariable} n_variables = len(variables_data['names']) list_of_variables = [] try: var_class = var_class_dict[variable_type] except KeyError: raise KeyError(f'Unknown variable type {variable_type}. Must be either InputVariable, OutputVariable ' f'or StateVariable') for ith_variable in range(n_variables): # name = variables_data['names'][ith_variable].astype('U13') # decode to unicode name = str(variables_data['names'][ith_variable], 'utf8') # decode to unicode list_of_variables.append(var_class(name, size=variables_data['sizes'][ith_variable], index=variables_data['indices'][ith_variable]), ) return cls(list_of_variables) def __iter__(self): return SetIterator(self) def __getitem__(self, item): return self.vector_variables[item] def __repr__(self): string_out = '' for var in self.vector_variables: try: out_var = str(repr(var)) except TypeError: print('Error printing var {:s}'.format(var.name)) else: string_out += '\t' + out_var + '\n' return string_out class SetIterator: def __init__(self, linear_vector): self._set_cases = linear_vector self._index = 0 def __next__(self): if self._index < self._set_cases.num_variables: res = self._set_cases.vector_variables[self._index] self._index += 1 return res raise StopIteration ================================================ FILE: sharpy/linear/utils/sselements.py ================================================ """ Linear State Space Element Class """ class Element(object): """ State space member """ def __init__(self): self.sys_id = str() # A string with the name of the element self.sys = None # The actual object self.ss = None # The state space object self.settings = dict() def initialise(self, data, sys_id): self.sys_id = sys_id settings = data.linear.settings[sys_id] # Load settings, the settings should be stored in data.linear.settings # data.linear.settings should be created in the class above containing the entire set up # Get the actual class object (like lingebm) from a dictionary in the same way that it is done for the solvers # in sharpy # sys = sys_from_string(sys_id) # To use the decorator idea we would first need to instantiate the class. Need to see how this is done with NL # SHARPy def assemble(self): pass ================================================ FILE: sharpy/postproc/__init__.py ================================================ import importlib import os import sharpy.utils.solver_interface as solver_interface import sharpy.utils.sharpydir as sharpydir files = solver_interface.solver_list_from_path(os.path.dirname(__file__)) import_path = os.path.realpath(os.path.dirname(__file__)) import_path = import_path.replace(sharpydir.SharpyDir, "") if import_path[0] == "/": import_path = import_path[1:] import_path = import_path.replace("/", ".") for file in files: solver_interface.solvers[file] = importlib.import_module(import_path + "." + file) ================================================ FILE: sharpy/postproc/aeroforcescalculator.py ================================================ import numpy as np import os import sharpy.utils.cout_utils as cout from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils import sharpy.utils.algebra as algebra import sharpy.aero.utils.mapping as mapping import warnings @solver class AeroForcesCalculator(BaseSolver): """AeroForcesCalculator Calculates the total aerodynamic forces and moments on the frame of reference ``A``. """ solver_id = 'AeroForcesCalculator' solver_classification = 'post-processor' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['write_text_file'] = 'bool' settings_default['write_text_file'] = False settings_description['write_text_file'] = 'Write ``txt`` file with results' settings_types['text_file_name'] = 'str' settings_default['text_file_name'] = 'aeroforces.txt' settings_description['text_file_name'] = 'Text file name' settings_types['screen_output'] = 'bool' settings_default['screen_output'] = True settings_description['screen_output'] = 'Show results on screen' settings_default['coefficients'] = False settings_types['coefficients'] = 'bool' settings_description['coefficients'] = 'Calculate aerodynamic coefficients' settings_default['lifting_surfaces'] = True settings_types['lifting_surfaces'] = 'bool' settings_description['lifting_surfaces'] = 'Includes aerodynamic forces from lifting surfaces' settings_default['nonlifting_body'] = False settings_types['nonlifting_body'] = 'bool' settings_description['nonlifting_body'] = 'Includes aerodynamic forces from nonlifting bodies' settings_types['q_ref'] = 'float' settings_default['q_ref'] = 1 settings_description['q_ref'] = 'Reference dynamic pressure' settings_types['S_ref'] = 'float' settings_default['S_ref'] = 1 settings_description['S_ref'] = 'Reference area' settings_types['b_ref'] = 'float' settings_default['b_ref'] = 1 settings_description['b_ref'] = 'Reference span' settings_types['c_ref'] = 'float' settings_default['c_ref'] = 1 settings_description['c_ref'] = 'Reference chord' settings_types['u_inf_dir'] = 'list(float)' settings_default['u_inf_dir'] = [1., 0., 0.] settings_description['u_inf_dir'] = 'Flow direction' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.settings = None self.data = None self.ts_max = 0 self.folder = None self.caller = None self.table = None self.rot = None self.moment_reference_location = np.array([0., 0., 0.]) def initialise(self, data, custom_settings=None, caller=None, restart=False): self.data = data self.settings = data.settings[self.solver_id] self.ts_max = len(self.data.structure.timestep_info) settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) self.caller = caller self.folder = data.output_folder + '/forces/' if not os.path.exists(self.folder): os.makedirs(self.folder) if self.settings['screen_output']: if self.settings['coefficients']: self.table = cout.TablePrinter(7, field_length=12, field_types=['g'] + 6 * ['f']) self.table.print_header(['tstep', 'Cfx_g', 'Cfy_g', 'Cfz_g', 'Cmx_g', 'Cmy_g', 'Cmz_g']) else: self.table = cout.TablePrinter(7, field_length=12, field_types=['g'] + 6 * ['e']) self.table.print_header(['tstep', 'fx_g', 'fy_g', 'fz_g', 'mx_g', 'my_g', 'mz_g']) def run(self, **kwargs): online = settings_utils.set_value_or_default(kwargs, 'online', False) if online: self.ts_max = len(self.data.structure.timestep_info) self.calculate_forces(-1) if self.settings['screen_output']: self.screen_output(-1) else: for ts in range(self.ts_max): self.calculate_forces(ts) if self.settings['screen_output']: self.screen_output(ts) cout.cout_wrap('...Finished', 1) if self.settings['write_text_file']: self.file_output(self.settings['text_file_name']) return self.data def calculate_forces(self, ts): self.rot = algebra.quat2rotation(self.data.structure.timestep_info[ts].quat) # R_GA # flow rotation angle from x and z components of flow direction # WARNING: this will give incorrect results when there is sideslip rot_xy = np.arctan2(-self.settings['u_inf_dir'][2], self.settings['u_inf_dir'][0]) rmat_xy = algebra.euler2rot((0., rot_xy, 0.)) # Forces per surface in A frame relative to the flow force = self.data.aero.timestep_info[ts].forces unsteady_force = self.data.aero.timestep_info[ts].dynamic_forces n_surf = self.data.aero.n_surf rmat = np.block([[self.rot @ rmat_xy.T, np.zeros((3, 3))], [np.zeros((3, 3)), self.rot @ rmat_xy.T]]) force_g = [np.moveaxis(np.squeeze(rmat @ np.expand_dims(np.moveaxis( force[i], (0, 1, 2), (2, 0, 1)), -1)), source=(0, 1, 2), destination=(1, 2, 0)) for i in range(n_surf)] unsteady_force_g = [np.moveaxis(np.squeeze(rmat @ np.expand_dims(np.moveaxis( unsteady_force[i], (0, 1, 2), (2, 0, 1)), -1)), source=(0, 1, 2), destination=(1, 2, 0)) for i in range(n_surf)] for i_surf in range(n_surf): ( self.data.aero.timestep_info[ts].inertial_steady_forces[i_surf, 0:3], self.data.aero.timestep_info[ts].inertial_unsteady_forces[i_surf, 0:3], self.data.aero.timestep_info[ts].body_steady_forces[i_surf, 0:3], self.data.aero.timestep_info[ts].body_unsteady_forces[i_surf, 0:3] ) = self.calculate_forces_for_isurf_in_g_frame(force_g[i_surf], unsteady_force=unsteady_force_g[i_surf]) if self.settings["nonlifting_body"]: for i_surf in range(self.data.nonlifting_body.n_surf): ( self.data.nonlifting_body.timestep_info[ts].inertial_steady_forces[i_surf, 0:3], self.data.nonlifting_body.timestep_info[ts].body_steady_forces[i_surf, 0:3], ) = self.calculate_forces_for_isurf_in_g_frame( self.data.nonlifting_body.timestep_info[ts].forces[i_surf], nonlifting=True) # Convert to forces in B frame try: steady_forces_b = self.data.structure.timestep_info[ts].postproc_node['aero_steady_forces'] except KeyError: if self.settings["nonlifting_body"]: warnings.warn( 'Nonlifting forces are not considered in aero forces calculation since forces cannot not be retrieved from postproc node.') steady_forces_b = self.map_forces_beam_dof(self.data.aero, ts, force_g) try: unsteady_forces_b = self.data.structure.timestep_info[ts].postproc_node['aero_unsteady_forces'] except KeyError: unsteady_forces_b = self.map_forces_beam_dof(self.data.aero, ts, unsteady_force_g) # Convert to forces in A frame steady_forces_a = self.data.structure.timestep_info[ts].nodal_b_for_2_a_for(steady_forces_b, self.data.structure) unsteady_forces_a = self.data.structure.timestep_info[ts].nodal_b_for_2_a_for(unsteady_forces_b, self.data.structure) self.data.aero.timestep_info[ts].total_steady_body_forces = \ rmat @ mapping.total_forces_moments(steady_forces_a, self.data.structure.timestep_info[ts].pos, ref_pos=self.moment_reference_location) self.data.aero.timestep_info[ts].total_unsteady_body_forces = \ rmat @ mapping.total_forces_moments(unsteady_forces_a, self.data.structure.timestep_info[ts].pos, ref_pos=self.moment_reference_location) # Express total forces in G frame self.data.aero.timestep_info[ts].total_steady_inertial_forces = \ np.block([[self.rot, np.zeros((3, 3))], [np.zeros((3, 3)), self.rot]]).dot( self.data.aero.timestep_info[ts].total_steady_body_forces) self.data.aero.timestep_info[ts].total_unsteady_inertial_forces = \ np.block([[self.rot, np.zeros((3, 3))], [np.zeros((3, 3)), self.rot]]).dot( self.data.aero.timestep_info[ts].total_unsteady_body_forces) def calculate_forces_for_isurf_in_g_frame(self, force, unsteady_force=None, nonlifting=False): """ Forces for a surface in G frame """ # Forces per surface in G frame total_steady_force = np.zeros((3,)) total_unsteady_force = np.zeros((3,)) _, n_rows, n_cols = force.shape for i_m in range(n_rows): for i_n in range(n_cols): total_steady_force += force[0:3, i_m, i_n] if not nonlifting: total_unsteady_force += unsteady_force[0:3, i_m, i_n] if not nonlifting: return total_steady_force, total_unsteady_force, np.dot(self.rot.T, total_steady_force), np.dot(self.rot.T, total_unsteady_force) else: return total_steady_force, np.dot(self.rot.T, total_steady_force) def map_forces_beam_dof(self, aero_data, ts, force): struct_tstep = self.data.structure.timestep_info[ts] aero_forces_beam_dof = mapping.aero2struct_force_mapping(force, aero_data.struct2aero_mapping, aero_data.timestep_info[ts].zeta, struct_tstep.pos, struct_tstep.psi, None, self.data.structure.connectivities, struct_tstep.cag()) return aero_forces_beam_dof def calculate_coefficients(self, fx, fy, fz, mx, my, mz): qS = self.settings['q_ref'] * self.settings['S_ref'] return fx / qS, fy / qS, fz / qS, mx / qS / self.settings['b_ref'], my / qS / self.settings['c_ref'], \ mz / qS / self.settings['b_ref'] def screen_output(self, ts): # print time step total aero forces forces = np.zeros(3) moments = np.zeros(3) aero_tstep = self.data.aero.timestep_info[ts] forces += aero_tstep.total_steady_inertial_forces[:3] + aero_tstep.total_unsteady_inertial_forces[:3] moments += aero_tstep.total_steady_inertial_forces[3:] + aero_tstep.total_unsteady_inertial_forces[3:] if self.settings['coefficients']: # TODO: Check if coefficients have to be computed differently for fuselages Cfx, Cfy, Cfz, Cmx, Cmy, Cmz = self.calculate_coefficients(*forces, *moments) self.table.print_line([ts, Cfx, Cfy, Cfz, Cmx, Cmy, Cmz]) else: self.table.print_line([ts, *forces, *moments]) def file_output(self, filename): # assemble forces/moments matrix # (1 timestep) + (3+3 inertial steady+unsteady) + (3+3 body steady+unsteady) force_matrix = np.zeros((self.ts_max, 1 + 3 + 3 + 3 + 3)) moment_matrix = np.zeros((self.ts_max, 1 + 3 + 3 + 3 + 3)) for ts in range(self.ts_max): aero_tstep = self.data.aero.timestep_info[ts] i = 0 force_matrix[ts, i] = ts moment_matrix[ts, i] = ts i += 1 # Steady forces/moments G force_matrix[ts, i:i + 3] = aero_tstep.total_steady_inertial_forces[:3] moment_matrix[ts, i:i + 3] = aero_tstep.total_steady_inertial_forces[3:] i += 3 # Unsteady forces/moments G force_matrix[ts, i:i + 3] = aero_tstep.total_unsteady_inertial_forces[:3] moment_matrix[ts, i:i + 3] = aero_tstep.total_unsteady_inertial_forces[3:] i += 3 # Steady forces/moments A force_matrix[ts, i:i + 3] = aero_tstep.total_steady_body_forces[:3] moment_matrix[ts, i:i + 3] = aero_tstep.total_steady_body_forces[3:] i += 3 # Unsteady forces/moments A force_matrix[ts, i:i + 3] = aero_tstep.total_unsteady_body_forces[:3] moment_matrix[ts, i:i + 3] = aero_tstep.total_unsteady_body_forces[3:] header = '' header += 'tstep, ' header += 'fx_steady_G, fy_steady_G, fz_steady_G, ' header += 'fx_unsteady_G, fy_unsteady_G, fz_unsteady_G, ' header += 'fx_steady_a, fy_steady_a, fz_steady_a, ' header += 'fx_unsteady_a, fy_unsteady_a, fz_unsteady_a' np.savetxt(self.folder + 'forces_' + filename, force_matrix, fmt='%i' + ', %10e' * (np.shape(force_matrix)[1] - 1), delimiter=',', header=header, comments='#') header = '' header += 'tstep, ' header += 'mx_steady_G, my_steady_G, mz_steady_G, ' header += 'mx_unsteady_G, my_unsteady_G, mz_unsteady_G, ' header += 'mx_steady_a, my_steady_a, mz_steady_a, ' header += 'mx_unsteady_a, my_unsteady_a, mz_unsteady_a' np.savetxt(self.folder + 'moments_' + filename, moment_matrix, fmt='%i' + ', %10e' * (np.shape(moment_matrix)[1] - 1), delimiter=',', header=header, comments='#') ================================================ FILE: sharpy/postproc/aerogridplot.py ================================================ import os import numpy as np import sharpy.utils.cout_utils as cout from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as su import sharpy.aero.utils.uvlmlib as uvlmlib from sharpy.utils.constants import vortex_radius_def from sharpy.utils.plotutils import plot_frame_to_vtk @solver class AerogridPlot(BaseSolver): """ Aerodynamic Grid Plotter """ solver_id = 'AerogridPlot' solver_classification = 'post-processor' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['include_rbm'] = 'bool' settings_default['include_rbm'] = True settings_description['include_rbm'] = 'Include rigid body motions' settings_types['include_forward_motion'] = 'bool' settings_default['include_forward_motion'] = False settings_description['include_forward_motion'] = 'Include forward motion' settings_types['include_applied_forces'] = 'bool' settings_default['include_applied_forces'] = True settings_description['include_applied_forces'] = 'Include applied aerodynamic forces at the vertices' settings_types['include_unsteady_applied_forces'] = 'bool' settings_default['include_unsteady_applied_forces'] = False settings_description['include_unsteady_applied_forces'] = 'Include unsteady aerodynamic forces at the vertices' settings_types['minus_m_star'] = 'int' settings_default['minus_m_star'] = 0 settings_description['minus_m_star'] = 'Subtract a number of wake panels that will not be plotted' settings_types['name_prefix'] = 'str' settings_default['name_prefix'] = '' settings_description['name_prefix'] = 'Prefix to add to file name' settings_types['u_inf'] = 'float' settings_default['u_inf'] = 0. settings_description['u_inf'] = 'Free stream velocity' settings_types['dt'] = 'float' settings_default['dt'] = 0. settings_description['dt'] = 'Time step increment' settings_types['include_velocities'] = 'bool' settings_default['include_velocities'] = False settings_description['include_velocities'] = 'Include induced velocities at the vertices' settings_types['include_incidence_angle'] = 'bool' settings_default['include_incidence_angle'] = False settings_description['include_incidence_angle'] = 'Include panel incidence angle' settings_types['plot_nonlifting_surfaces'] = 'bool' settings_default['plot_nonlifting_surfaces'] = False settings_description['plot_nonlifting_surfaces'] = 'Plot nonlifting surfaces' settings_types['plot_lifting_surfaces'] = 'bool' settings_default['plot_lifting_surfaces'] = False settings_description['plot_lifting_surfaces'] = 'Plot nonlifting surfaces' settings_types['num_cores'] = 'int' settings_default['num_cores'] = 1 settings_description['num_cores'] = 'Number of cores used to compute velocities/angles' settings_types['vortex_radius'] = 'float' settings_default['vortex_radius'] = vortex_radius_def settings_description['vortex_radius'] = 'Distance below which inductions are not computed' settings_types['stride'] = 'int' settings_default['stride'] = 1 settings_description['stride'] = 'Number of steps between the execution calls when run online' settings_types['save_wake'] = 'bool' settings_default['save_wake'] = True settings_description['save_wake'] = 'Plot the wake' table = su.SettingsTable() __doc__ += table.generate(settings_types, settings_default, settings_description) def __init__(self): self.settings = None self.data = None self.folder = None self.body_filename = '' self.wake_filename = '' self.nonlifting_filename = '' self.ts_max = 0 self.caller = None def initialise(self, data, custom_settings=None, caller=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings su.to_custom_types(self.settings, self.settings_types, self.settings_default) self.ts_max = self.data.ts + 1 # create folder for containing files if necessary self.folder = data.output_folder + '/aero/' if not os.path.exists(self.folder): os.makedirs(self.folder) self.body_filename = (self.folder + self.settings['name_prefix'] + 'body_' + self.data.settings['SHARPy']['case']) self.wake_filename = (self.folder + self.settings['name_prefix'] + 'wake_' + self.data.settings['SHARPy']['case']) self.nonlifting_filename = (self.folder + self.settings['name_prefix'] + 'nonlifting_' + self.data.settings['SHARPy']['case']) self.caller = caller def run(self, **kwargs): online = kwargs.get('online', False) # TODO: Create a dictionary to plot any variable as in beamplot if not online: for self.ts in range(self.ts_max): if self.data.structure.timestep_info[self.ts] is not None: self.plot_body() self.plot_wake() if self.settings['plot_nonlifting_surfaces']: self.plot_nonlifting_surfaces() cout.cout_wrap('...Finished', 1) elif (self.data.ts % self.settings['stride'] == 0): aero_tsteps = len(self.data.aero.timestep_info) - 1 struct_tsteps = len(self.data.structure.timestep_info) - 1 self.ts = np.max((aero_tsteps, struct_tsteps)) self.plot_body() self.plot_wake() if self.settings['plot_nonlifting_surfaces']: self.plot_nonlifting_surfaces() return self.data def plot_body(self): aero_tstep = self.data.aero.timestep_info[self.ts] struct_tstep = self.data.structure.timestep_info[self.ts] if self.settings['include_incidence_angle']: aero_tstep.postproc_cell['incidence_angle'] = [] for isurf in range(aero_tstep.n_surf): aero_tstep.postproc_cell['incidence_angle'].append( np.zeros_like(aero_tstep.gamma[isurf])) uvlmlib.uvlm_calculate_incidence_angle(aero_tstep, struct_tstep) for i_surf in range(aero_tstep.n_surf): filename = f"{self.body_filename}_{i_surf:02d}_{self.ts:06d}.vtu" zeta = np.moveaxis(aero_tstep.zeta[i_surf], 0, -1).copy() # [m+1, n+1, 3] zeta_dot = np.moveaxis(aero_tstep.zeta_dot[i_surf][:3, ...], 0, -1).copy() # [3, m+1, n+1] gamma = aero_tstep.gamma[i_surf].copy() # [m, n] gamma_dot = aero_tstep.gamma_dot[i_surf].copy() # [m, n] normals = np.moveaxis(aero_tstep.normals[i_surf], 0, -1).copy() # [m, n, 3] surf_id = np.ones(gamma.shape, dtype=int) * i_surf if self.settings['include_rbm']: if struct_tstep.mb_dict is None: zeta += np.expand_dims(struct_tstep.for_pos[:3], (0, 1)) else: zeta += np.expand_dims(self.data.structure.timestep_info[0].cga() @ struct_tstep.for_pos[:3], (0, 1)) if self.settings['include_forward_motion']: zeta[..., 0] -= self.settings['dt'] * self.ts * self.settings['u_inf'] if self.settings['include_incidence_angle']: incidence_angle = aero_tstep.postproc_cell['incidence_angle'][i_surf] else: incidence_angle = np.zeros(gamma.shape) point_struct_id = np.broadcast_to(np.expand_dims(self.data.aero.aero2struct_mapping[i_surf], 0), zeta.shape[:2]) point_cf = np.moveaxis(aero_tstep.forces[i_surf][:3, ...], 0, -1) try: point_unsteady_cf = np.moveaxis(aero_tstep.dynamic_forces[i_surf][:3, ...], 0, -1) except AttributeError: point_unsteady_cf = np.zeros(zeta.shape) try: u_inf = np.moveaxis(aero_tstep.u_ext[i_surf][:3, ...], 0, -1) except AttributeError: u_inf = np.zeros(zeta.shape) if self.settings['include_velocities']: vel = uvlmlib.uvlm_calculate_total_induced_velocity_at_points(aero_tstep, zeta.reshape(-1, 3), self.settings['vortex_radius'], struct_tstep.for_pos, self.settings['num_cores']).reshape(zeta.shape) else: vel = np.zeros(zeta.shape) plot_frame_to_vtk(zeta, filename, node_scalar_data={'point_struct_id': point_struct_id}, node_vector_data={'zeta_dot': zeta_dot, 'point_steady_force': point_cf, 'point_unsteady_force': point_unsteady_cf, 'u_inf': u_inf, 'velocity': vel}, cell_scalar_data={'gamma': gamma, "gamma_dot": gamma_dot, "incidence_angle": incidence_angle, 'surf_id': surf_id}, cell_vector_data={'panel_normal': normals},) def plot_wake(self): for i_surf in range(self.data.aero.timestep_info[self.ts].n_surf): filename = f"{self.wake_filename}_{i_surf:02d}_{self.ts:06d}.vtu" zeta_star = np.moveaxis(self.data.aero.timestep_info[self.ts].zeta_star[i_surf], 0, -1).copy() # [m_star+1, n+1, 3] gamma = self.data.aero.timestep_info[self.ts].gamma_star[i_surf].copy() # [m_star, n] surf_id = np.ones(gamma.shape, dtype=int) * i_surf if self.settings['include_rbm']: if self.data.structure.timestep_info[self.ts].mb_dict is None: zeta_star += np.expand_dims(self.data.structure.timestep_info[self.ts].for_pos[:3], (0, 1)) else: zeta_star += np.expand_dims(self.data.structure.timestep_info[0].cga() @ self.data.structure.timestep_info[self.ts].for_pos[:3], (0, 1)) if self.settings['include_forward_motion']: zeta_star[..., 0] -= self.settings['dt'] * self.ts * self.settings['u_inf'] plot_frame_to_vtk( zeta_star, filename, node_vector_data={}, cell_scalar_data={ "gamma": gamma, "surf_id": surf_id, }, ) def plot_nonlifting_surfaces(self): nonlifting_tstep = self.data.nonlifting_body.timestep_info[self.ts] struct_tstep = self.data.structure.timestep_info[self.ts] for i_surf in range(nonlifting_tstep.n_surf): filename = (self.nonlifting_filename + '_' + '%02u_' % i_surf + '%06u' % self.ts+ '.vtu') dims = nonlifting_tstep.dimensions[i_surf, :] point_data_dim = (dims[0]+1)*(dims[1]+1) # + (dims_star[0]+1)*(dims_star[1]+1) panel_data_dim = (dims[0])*(dims[1]) # + (dims_star[0])*(dims_star[1]) coords = np.zeros((point_data_dim, 3)) conn = [] panel_id = np.zeros((panel_data_dim,), dtype=int) panel_surf_id = np.zeros((panel_data_dim,), dtype=int) panel_sigma = np.zeros((panel_data_dim,)) normal = np.zeros((panel_data_dim, 3)) point_struct_id = np.zeros((point_data_dim,), dtype=int) point_cf = np.zeros((point_data_dim, 3)) u_inf = np.zeros((point_data_dim, 3)) counter = -1 # coordinates of corners for i_n in range(dims[1]+1): for i_m in range(dims[0]+1): counter += 1 coords[counter, :] = nonlifting_tstep.zeta[i_surf][:, i_m, i_n] # TODO: include those for nonlifting body (are they different for nonlifting coordinates?) if self.settings['include_rbm']: coords[counter, :] += struct_tstep.for_pos[0:3] if self.settings['include_forward_motion']: coords[counter, 0] -= self.settings['dt']*self.ts*self.settings['u_inf'] counter = -1 node_counter = -1 for i_n in range(dims[1] + 1): global_counter = self.data.nonlifting_body.aero2struct_mapping[i_surf][i_n] for i_m in range(dims[0] + 1): node_counter += 1 # point data point_struct_id[node_counter] = global_counter point_cf[node_counter, :] = nonlifting_tstep.forces[i_surf][0:3, i_m, i_n] try: u_inf[node_counter, :] = nonlifting_tstep.u_ext[i_surf][0:3, i_m, i_n] except AttributeError: pass if i_n < dims[1] and i_m < dims[0]: counter += 1 else: continue conn.append([node_counter + 0, node_counter + 1, node_counter + dims[0]+2, node_counter + dims[0]+1]) # cell data normal[counter, :] = nonlifting_tstep.normals[i_surf][:, i_m, i_n] panel_id[counter] = counter panel_surf_id[counter] = i_surf panel_sigma[counter] = nonlifting_tstep.sigma[i_surf][i_m, i_n] from tvtk.api import tvtk, write_data ug = tvtk.UnstructuredGrid(points=coords) ug.set_cells(tvtk.Quad().cell_type, conn) ug.cell_data.scalars = panel_id ug.cell_data.scalars.name = 'panel_n_id' ug.cell_data.add_array(panel_surf_id) ug.cell_data.get_array(1).name = 'panel_surface_id' ug.cell_data.add_array(panel_sigma) ug.cell_data.get_array(2).name = 'panel_sigma' ug.cell_data.vectors = normal ug.cell_data.vectors.name = 'panel_normal' ug.point_data.scalars = np.arange(0, coords.shape[0]) ug.point_data.scalars.name = 'n_id' ug.point_data.add_array(point_struct_id) ug.point_data.get_array(1).name = 'point_struct_id' ug.point_data.add_array(point_cf) ug.point_data.get_array(2).name = 'point_steady_force' ug.point_data.add_array(u_inf) ug.point_data.get_array(3).name = 'u_inf' write_data(ug, filename) ================================================ FILE: sharpy/postproc/asymptoticstability.py ================================================ import os import warnings import numpy as np import scipy.linalg as sclalg import sharpy.utils.settings as settings_utils from sharpy.utils.solver_interface import solver, BaseSolver, initialise_solver import sharpy.utils.cout_utils as cout import sharpy.utils.algebra as algebra import sharpy.solvers.lindynamicsim as lindynamicsim import sharpy.structure.utils.modalutils as modalutils import sharpy.utils.frequencyutils as frequencyutils import sharpy.linear.src.libss as libss import h5py @solver class AsymptoticStability(BaseSolver): """ Calculates the asymptotic stability properties of the linearised system by computing the corresponding eigenvalues and eigenvectors. The output of this solver is written to the ``stability`` directory in the case output. The stability of the systems specified in ``target_systems`` is performed. If the system has been previously scaled, a ``reference_velocity`` should be provided to compute the stability at such point. The eigenvalues can be truncated, keeping a minimum ``num_evals`` (sorted by decreasing real part) or by limiting the higher frequency modes through ``frequency_cutoff``. Results can be saved to file using ``export_eigenvalues``. The setting ``display_root_locus`` shows a simple Argand diagram where the continuous time eigenvalues are displayed. The eigenvectors can be displayed (in .vtu format for use in Paraview) with the ``modes_to_plot`` setting, whereby the user specifies the mode indices that are to be plotted (sorted with in the same way as the eigenvalue table, i.e. by decreasing real part). A snapshot of the eigenvector is produced for the 0, 45, 90 and 135 degree phases. This feature currently supports the flexible structural modes and does not show the rigid body contribution. The output is written to the ``stability/modes`` folder and includes the structure at the reference linearisation state. """ solver_id = 'AsymptoticStability' solver_classification = 'post-processor' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = False settings_description['print_info'] = 'Print information and table of eigenvalues' settings_types['reference_velocity'] = 'float' settings_default['reference_velocity'] = 1. settings_description['reference_velocity'] = 'Reference velocity at which to compute eigenvalues for scaled systems' settings_types['frequency_cutoff'] = 'float' settings_default['frequency_cutoff'] = 0 settings_description['frequency_cutoff'] = 'Truncate higher frequency modes. If zero none are truncated' settings_types['export_eigenvalues'] = 'bool' settings_default['export_eigenvalues'] = False settings_description['export_eigenvalues'] = 'Save eigenvalues and eigenvectors to file.' settings_types['output_file_format'] = 'str' settings_default['output_file_format'] = 'dat' settings_description['output_file_format'] = 'Eigenvalue/eigenvector output file format. HDF5 or text (.dat) files.' settings_options['output_file_format'] = ['h5', 'dat'] settings_types['display_root_locus'] = 'bool' settings_default['display_root_locus'] = False settings_description['display_root_locus'] = 'Show plot with eigenvalues on Argand diagram' settings_types['velocity_analysis'] = 'list(float)' settings_default['velocity_analysis'] = [] settings_description['velocity_analysis'] = 'List containing min, max and number ' \ 'of velocities to analyse the system' settings_types['target_system'] = 'list(str)' settings_default['target_system'] = ['aeroelastic'] settings_description['target_system'] = 'System or systems for which to find frequency response.' settings_options['target_system'] = ['aeroelastic', 'aerodynamic', 'structural'] settings_types['num_evals'] = 'int' settings_default['num_evals'] = 200 settings_description['num_evals'] = 'Number of eigenvalues to retain.' settings_types['modes_to_plot'] = 'list(int)' settings_default['modes_to_plot'] = [] settings_description['modes_to_plot'] = 'List of mode numbers to plot. Plots the 0, 45, 90 and 135' \ 'degree phases.' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.settings = None self.data = None self.folder = None self.print_info = False self.frequency_cutoff = np.inf self.num_evals = None self.postprocessors = dict() self.with_postprocessors = False self.caller = None def initialise(self, data, custom_settings=None, caller=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, options=self.settings_options, no_ctype=True) self.num_evals = self.settings['num_evals'] self.folder = data.output_folder + '/stability/' if not os.path.exists(self.folder): os.makedirs(self.folder) if self.settings['print_info']: self.print_info = True else: self.print_info = False try: self.frequency_cutoff = self.settings['frequency_cutoff'] except AttributeError: self.frequency_cutoff = float(self.settings['frequency_cutoff']) if self.frequency_cutoff == 0: self.frequency_cutoff = np.inf self.caller = caller def run(self, **kwargs): """ Computes the eigenvalues and eigenvectors Returns: eigenvalues (np.ndarray): Eigenvalues sorted and frequency truncated eigenvectors (np.ndarray): Corresponding mode shapes """ online = settings_utils.set_value_or_default(kwargs, 'online', False) # if the system is scaled, only one system can be analysed if self.settings['reference_velocity'] != 1. and self.data.linear.linear_system.uvlm.scaled: ss_list = [self.data.linear.linear_system.update(self.settings['reference_velocity'])] not_scaled = False system_name_list = ['aeroelastic'] if len(self.settings['target_system']) > 1: cout.cout_wrap('Warning: the system is scaled thus the only analysis currently supported is' ' for the aeroelastic system', 3) else: ss_list = [frequencyutils.find_target_system(self.data, system_name) for system_name in self.settings['target_system']] not_scaled = True system_name_list = self.settings['target_system'] self.compute_eigenvalues(ss_list, system_name_list, not_scaled) return self.data def compute_eigenvalues(self, ss, system_name_list=None, not_scaled=True): """ Computes the eigenvalues and eigenvectors of the state-space Args: ss (libss.StateSpace or list([libss.StateSpace]): State-space or list of state-spaces system_name_list (list([str]): Names of systems in the case multiple systems are required not_scaled (bool): Flag to indicate whether the systems are assembled in non-dimensional time """ if type(ss) is libss.StateSpace: ss_list = [ss] if system_name_list is None: system_name_list = [''] elif type(ss) is list: ss_list = ss if system_name_list is None: system_name_list = [] for sys, sys_number in enumerate(ss_list): system_name_list.append(f'system{sys_number:g}') if type(sys) is not libss.StateSpace: raise TypeError(f'State-space {sys_number} is not type libss.StateSpace') else: raise TypeError('ss input must be either a libss.StateSpace instance or a list[libss.StateSpace]') for ith, system in enumerate(ss_list): system_name = system_name_list[ith] if self.print_info: cout.cout_wrap('Calculating %s eigenvalues using direct method' % system_name) eigenvalues, eigenvectors = sclalg.eig(system.A) # Convert DT eigenvalues into CT if system.dt: eigenvalues = self.convert_to_continuoustime(system.dt, eigenvalues, not_scaled) num_evals = min(self.num_evals, len(eigenvalues)) eigenvalues, eigenvectors = self.sort_eigenvalues(eigenvalues, eigenvectors, self.frequency_cutoff, number_of_eigenvalues=num_evals) if self.settings['export_eigenvalues']: self.export_eigenvalues(num_evals, eigenvalues, eigenvectors, filename=system_name) if self.settings['print_info']: cout.cout_wrap(f'Dynamical System Eigenvalues - {system_name} system') if system_name != '': eig_table_filename = system_name + '_' else: eig_table_filename = '' eigenvalue_description_file = self.folder + '/{:s}eigenvaluetable.txt'.format(eig_table_filename) eigenvalue_table = modalutils.EigenvalueTable(filename=eigenvalue_description_file) eigenvalue_table.print_header(eigenvalue_table.headers) eigenvalue_table.print_evals(eigenvalues[:self.num_evals]) eigenvalue_table.close_file() if self.settings['display_root_locus']: self.display_root_locus(eigenvalues) if len(self.settings['velocity_analysis']) == 3 and system_name == 'aeroelastic': assert self.data.linear.linear_system.uvlm.scaled, \ 'The UVLM system is unscaled, unable to rescale the structural equations only. Rerun with a ' \ 'normalised UVLM system.' self.velocity_analysis() # Under development if len(self.settings['modes_to_plot']) != 0 and system_name == 'aeroelastic': self.plot_modes(eigenvectors) return eigenvalues, eigenvectors def convert_to_continuoustime(self, dt, discrete_time_eigenvalues, not_scaled=False): r""" Convert eigenvalues to discrete time. The ``not_scaled`` argument can be used to bypass the search from within SHARPy of scaling factors. For instance, when the state-space of choice is not part of a standard SHARPy case but rather an interpolated ROM etc. The eigenvalues are converted to continuous time using .. math:: \lambda_{ct} = \frac{\log (\lambda_{dt})}{\Delta t} If the system is scaled, the dimensional time step is retrieved as .. math:: \Delta t_{dim} = \bar{\Delta t} \frac{l_{ref}}{U_{\infty, actual}} where :math:`l_{ref}` is the reference length and :math:`U_{\infty, actual}` is the free stream velocity at which to calculate the eigenvalues. Args: dt (float): Discrete time increment. discrete_time_eigenvalues (np.ndarray): Array of discrete time eigenvalues. not_scaled (bool): Treat the system as not scaled. No Scaling Factors will be searched in SHARPy. """ if not_scaled: dt = dt else: try: ScalingFacts = self.data.linear.linear_system.uvlm.sys.ScalingFacts if ScalingFacts['length'] != 1.0 and ScalingFacts['time'] != 1.0: dt *= ScalingFacts['length'] / self.settings['reference_velocity'] else: dt = dt except AttributeError: dt = dt return np.log(discrete_time_eigenvalues) / dt def export_eigenvalues(self, num_evals, eigenvalues, eigenvectors, filename=None): """ Saves a ``num_evals`` number of eigenvalues and eigenvectors to file. The files are saved in the output directory and include: * ``{system_name}_eigenvalues.dat``: Array of eigenvalues of shape ``(num_evals, 2)`` where the first column corresponds to the real part and the second column to the imaginary part. * ``{system_name}_stability.h5``: An ``.h5`` file containing the desired number of eigenvalues and eigenvectors of the chosen systems. The units of the eigenvalues are ``rad/s``. Args: num_evals (int): Number of eigenvalues to save. eigenvalues (np.ndarray): Eigenvalue array. eigenvectors (np.ndarray): Matrix of eigenvectors. filename (str (optional)): Optional prefix of the output filenames. See Also: Loading and saving complex arrays: https://stackoverflow.com/questions/6494102/how-to-save-and-load-an-array-of-complex-numbers-using-numpy-savetxt/6522396 """ stability_folder_path = self.folder if filename is None or filename == '': filename = '' else: filename += '_' if self.settings['output_file_format'] == 'dat': np.savetxt(self.folder + f'/{filename:s}eigenvalues.dat', eigenvalues.view(float).reshape(-1, 2)) np.savetxt(self.folder + f'/{filename:s}eigenvectors_r.dat', eigenvectors.real) np.savetxt(self.folder + f'/{filename:s}eigenvectors_i.dat', eigenvectors.imag) elif self.settings['output_file_format'] == 'h5': with h5py.File(stability_folder_path + f'/{filename:s}stability.h5', 'w') as f: f.create_dataset('eigenvalues', data=eigenvalues, dtype=complex) f.create_dataset('eigenvectors', data=eigenvectors, dtype=complex) f.create_dataset('num_eigenvalues', data=num_evals, dtype=int) else: raise TypeError(f'Unrecognised file type saving option {self.settings["output_file_format"]}') # this shouldn't happen as the settings_options should check the validity of the setting def velocity_analysis(self): """ Velocity analysis for scaled systems. Runs the stability analysis for different velocities for aeroelastic systems that have been previously scaled. For every velocity, the linear system is updated. This involves updating the structural matrices and the coupling matrix. The eigenvalues saved are in continuous time. It saves the results to a ``.dat`` file where the first column corresponds to the free stream velocity and the second and third columns to the real and imaginary parts of the eigenvalues. """ ulb, uub, num_u = self.settings['velocity_analysis'] if self.settings['print_info']: cout.cout_wrap('Velocity Asymptotic Stability Analysis', 1) cout.cout_wrap('Initial velocity: {:02f} m/s'.format(ulb), 1) cout.cout_wrap('Final velocity: {:02f} m/s'.format(uub), 1) cout.cout_wrap('Number of evaluations: {:g}'.format(num_u), 1) u_inf_vec = np.linspace(ulb, uub, int(num_u)) real_part_plot = [] imag_part_plot = [] uinf_part_plot = [] for i in range(len(u_inf_vec)): ss_aeroelastic = self.data.linear.linear_system.update(u_inf_vec[i]) eigs, eigenvectors = sclalg.eig(ss_aeroelastic.A) eigs, eigenvectors = self.sort_eigenvalues(eigs, eigenvectors) # Obtain dimensional time dt_dimensional = self.data.linear.linear_system.uvlm.sys.ScalingFacts['length'] / u_inf_vec[i] \ * ss_aeroelastic.dt eigs_cont = np.log(eigs) / dt_dimensional Nunst = np.sum(eigs_cont.real > 0) fn = np.abs(eigs_cont) if self.settings['print_info']: cout.cout_wrap('LTI\tu: %.2f m/2\tmax. CT eig. real: %.6f\t' \ % (u_inf_vec[i], np.max(eigs_cont.real))) cout.cout_wrap('\tN unstab.: %.3d' % (Nunst,)) cout.cout_wrap( '\tUnstable aeroelastic natural frequency CT(rad/s):' + Nunst * '\t%.2f' % tuple(fn[:Nunst])) # Store eigenvalues for plot real_part_plot.append(eigs_cont.real) imag_part_plot.append(eigs_cont.imag) uinf_part_plot.append(np.ones_like(eigs_cont.real) * u_inf_vec[i]) real_part_plot = np.hstack(real_part_plot) imag_part_plot = np.hstack(imag_part_plot) uinf_part_plot = np.hstack(uinf_part_plot) velocity_file_name = self.folder + '/velocity_analysis_min{:04g}_max{:04g}_nvel{:04g}.dat'.format( ulb * 10, uub * 10, num_u) np.savetxt(velocity_file_name, np.concatenate((uinf_part_plot, real_part_plot, imag_part_plot)).reshape((-1, 3), order='F')) if self.print_info: cout.cout_wrap('\t\tSuccessfully saved velocity analysis to {:s}'.format(velocity_file_name), 2) @staticmethod def display_root_locus(eigenvalues): """ Displays root locus diagrams. Returns the ``fig`` and ``ax`` handles for further editing. Returns: fig: ax: """ try: import matplotlib.pyplot as plt except ModuleNotFoundError: cout.cout_wrap('Could not plot in asymptoticstability beacuse there is no Matplotlib', 4) return fig, ax = plt.subplots() ax.scatter(np.real(eigenvalues), np.imag(eigenvalues), s=6, color='k', marker='s') ax.set_xlabel(r'Real, $\mathbb{R}(\lambda_i)$ [rad/s]') ax.set_ylabel(r'Imag, $\mathbb{I}(\lambda_i)$ [rad/s]') ax.grid(True) fig.show() return fig, ax def plot_modes(self, eigenvectors): r""" Plot the aeroelastic mode shapes for the first ``n_modes_to_plot`` Plots the 0, 45, 90 and 135 degrees phase of the mode. Also plots the reference at the linearisation state. .. math:: x_{out} = Re(\Phi_i e^{i\theta}) for :math:`\theta \in \{0, \pi/4, \pi/2, 3\pi/4}`. """ try: import matplotlib.pyplot as plt except ModuleNotFoundError: cout.cout_wrap('Could not plot in asymptoticstability because there is no Matplotlib', 4) return mode_shape_list = self.settings['modes_to_plot'] route = self.folder + '/modes/' if not os.path.isdir(route): os.makedirs(route, exist_ok=True) for mode in mode_shape_list: # Scale mode beam = self.data.linear.linear_system.beam displacement_states = beam.ss.states // 2 structural_states = beam.ss.states structural_modal_coords = beam.sys.modal for phase in [0, 1, 2, 3]: v = eigenvectors[-structural_states:-structural_states//2, mode] v_dot = eigenvectors[-structural_states//2:, mode] if beam.sys.clamped: num_dof_rig = 1 # to get the full vector (if 0 line356 returns empty array) else: warnings.warn('The rigid body motion contribution to the mode will not be shown, ' 'just the flexible contributions.') num_dof_rig = beam.sys.num_dof_rig if structural_modal_coords: # project aeroelastic mode back to modal coordinates phi = beam.sys.U eta = phi.dot(v) eta_dot = phi.dot(v_dot)[:-num_dof_rig] else: eta = v[:-num_dof_rig] eta_dot = v_dot[:-num_dof_rig] eta_phase = np.real(np.exp(1j * phase * np.pi / 4) * eta) amplitude_factor = modalutils.scale_mode(self.data, eta_phase, rot_max_deg=10, perc_max=0.15) eta_phase *= amplitude_factor zeta_mode = modalutils.get_mode_zeta(self.data, eta_phase) modalutils.write_zeta_vtk(zeta_mode, self.data.linear.tsaero0.zeta, filename_root=route + f'mode_{mode:06g}_phase{phase:04g}') # Reference - linearisation state eta *= 0 zeta_mode = modalutils.get_mode_zeta(self.data, eta.real) modalutils.write_zeta_vtk(zeta_mode, self.data.linear.tsaero0.zeta, filename_root=route + 'mode_ref') @staticmethod def sort_eigenvalues(eigenvalues, eigenvectors, frequency_cutoff=0, number_of_eigenvalues=None): """ Sort continuous-time eigenvalues by order of magnitude. The conjugate of complex eigenvalues is removed, then if specified, high frequency modes are truncated. Finally, the eigenvalues are sorted by largest to smallest real part. Args: eigenvalues (np.ndarray): Continuous-time eigenvalues eigenvectors (np.ndarray): Corresponding right eigenvectors frequency_cutoff (float): Cutoff frequency for truncation ``[rad/s]`` number_of_eigenvalues (int (optional)): Number of eigenvalues to retain Returns: tuple(np.array, np.array): eigenvalues and eigenvectors """ if frequency_cutoff == 0: frequency_cutoff = np.inf # Remove poles in the negative imaginary plane (Im(\lambda)<0) criteria_a = np.abs(np.imag(eigenvalues)) <= frequency_cutoff # criteria_b = np.imag(eigenvalues) > -1e-2 eigenvalues_truncated = eigenvalues[criteria_a].copy() eigenvectors_truncated = eigenvectors[:, criteria_a].copy() order = np.argsort(eigenvalues_truncated.real)[::-1] if number_of_eigenvalues is not None: if number_of_eigenvalues > len(order): cout.cout_wrap(f'Desired number of eigenvalues ({number_of_eigenvalues}) exceeds system size ' f'({len(order)}) after frequency truncation.', 3) else: order = order[:number_of_eigenvalues] return eigenvalues_truncated[order], eigenvectors_truncated[:, order] ================================================ FILE: sharpy/postproc/beamloads.py ================================================ import os import numpy as np import os from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils import sharpy.structure.utils.xbeamlib as xbeamlib @solver class BeamLoads(BaseSolver): """ Writes to file the total loads acting on the beam elements """ solver_id = 'BeamLoads' solver_classification = 'post-processor' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['csv_output'] = 'bool' settings_default['csv_output'] = False settings_description['csv_output'] = 'Write ``csv`` file with results' settings_types['output_file_name'] = 'str' settings_default['output_file_name'] = 'beam_loads' settings_description['output_file_name'] = 'Output file name' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.settings = None self.data = None self.folder = None self.caller = None def initialise(self, data, custom_settings=None, caller=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) self.caller = caller self.folder = data.output_folder + '/beam/' if not os.path.isdir(self.folder): os.makedirs(self.folder) def run(self, **kwargs): online = settings_utils.set_value_or_default(kwargs, 'online', False) self.calculate_loads(online) if self.settings['csv_output']: self.print_loads(online) return self.data def print_loads(self, online): if online: it = len(self.data.structure.timestep_info) - 1 n_elem = self.data.structure.timestep_info[it].psi.shape[0] data = np.zeros((n_elem, 10)) # coords data[:, 0:3] = self.data.structure.timestep_info[it].postproc_cell['coords_a'] header = 'x_a, y_a, z_a, ' # beam number data[:, 3] = self.data.structure.beam_number header += 'beam_number, ' # loads_0 data[:, 4:10] = self.data.structure.timestep_info[it].postproc_cell['loads'][:, :] header += 'Fx, Fy, Fz, Mx, My, Mz' filename = self.folder filename += self.settings['output_file_name'] + '_' + '{0}'.format(it) filename += '.csv' np.savetxt(filename, data, delimiter=',', header=header) else: for it in range(len(self.data.structure.timestep_info)): it = len(self.data.structure.timestep_info) - 1 n_elem = self.data.structure.timestep_info[it].num_elem data = np.zeros((n_elem, 10)) # coords data[:, 0:3] = self.data.structure.timestep_info[it].postproc_cell['coords_a'] header = 'x_a, y_a, z_a, ' # beam number data[:, 3] = self.data.structure.beam_number header += 'beam_number, ' # loads_0 data[:, 4:10] = self.data.structure.timestep_info[it].postproc_cell['loads'][:, :] header += 'Fx, Fy, Fz, Mx, My, Mz' filename = self.folder filename += self.settings['output_file_name'] + '_' + '{0}'.format(it) filename += '.csv' np.savetxt(filename, data, delimiter=',', header=header) def calculate_loads(self, online): if online: it = -1 timestep_add_loads(self.data.structure, self.data.structure.timestep_info[it]) self.calculate_coords_a(self.data.structure.timestep_info[it]) else: for it in range(len(self.data.structure.timestep_info)): timestep_add_loads(self.data.structure, self.data.structure.timestep_info[it]) self.calculate_coords_a(self.data.structure.timestep_info[it]) def calculate_coords_a(self, timestep_info): timestep_info.postproc_cell['coords_a'] = np.zeros((timestep_info.num_elem, 3)) for ielem in range(timestep_info.num_elem): iglobal_node = self.data.structure.connectivities[ielem, 2] timestep_info.postproc_cell['coords_a'][ielem, :] = timestep_info.pos[iglobal_node, :] def timestep_add_loads(structure, timestep): timestep.postproc_cell['strain'], timestep.postproc_cell['loads'] = \ xbeamlib.cbeam3_loads(structure, timestep) # def calculate_loads(self): # # initial (ini) loads # tstep = self.data.structure.ini_info # pos = tstep.pos # psi = tstep.psi # # tstep.postproc_cell['gamma'] = np.zeros((self.data.structure.num_elem, 3)) # tstep.postproc_cell['kappa'] = np.zeros((self.data.structure.num_elem, 3)) # # for ielem in range(self.data.structure.num_elem): # crv = 0.5*(psi[ielem, 1, :] + psi[ielem, 0, :]) # cba = algebra.crv2rotation(crv).T # tan = algebra.crv2tan(crv) # # inode0 = self.data.structure.elements[ielem].global_connectivities[0] # inode1 = self.data.structure.elements[ielem].global_connectivities[1] # tstep.postproc_cell['gamma'][ielem, :] = ( # np.dot(cba, # pos[inode1, :] - pos[inode0, :]) / # np.linalg.norm(pos[inode1, :] - pos[inode0, :])) # tstep.postproc_cell['kappa'][ielem, :] = ( # np.dot(tan, # psi[ielem, 1, :] - psi[ielem, 0, :]) / # np.linalg.norm(pos[inode1, :] - pos[inode0, :])) # # # time-dependant loads # for it in range(len(self.data.structure.timestep_info)): # tstep = self.data.structure.timestep_info[it] # pos = tstep.pos # psi = tstep.psi # # tstep.postproc_cell['gamma'] = np.zeros((self.data.structure.num_elem, 3)) # tstep.postproc_cell['kappa'] = np.zeros((self.data.structure.num_elem, 3)) # tstep.postproc_cell['strain'] = np.zeros((self.data.structure.num_elem, 6)) # tstep.postproc_cell['loads'] = np.zeros((self.data.structure.num_elem, 6)) # # for ielem in range(self.data.structure.num_elem): # crv = 0.5*(psi[ielem, 2, :] + psi[ielem, 0, :]) # cba = algebra.crv2rotation(crv).T # tan = algebra.crv2tan(crv) # # inode0 = self.data.structure.elements[ielem].global_connectivities[0] # inode1 = self.data.structure.elements[ielem].global_connectivities[1] # tstep.postproc_cell['gamma'][ielem, :] = ( # np.dot(cba, # pos[inode1, :] - pos[inode0, :]) / # np.linalg.norm(pos[inode1, :] - pos[inode0, :])) # tstep.postproc_cell['kappa'][ielem, :] = ( # np.dot(tan, # psi[ielem, 1, :] - psi[ielem, 0, :]) / # np.linalg.norm(pos[inode1, :] - pos[inode0, :])) # # tstep.postproc_cell['strain'][ielem, 0:3] = ( # tstep.postproc_cell['gamma'][ielem, :] # - # self.data.structure.ini_info.postproc_cell['gamma'][ielem, :]) # tstep.postproc_cell['strain'][ielem, 3:6] = ( # tstep.postproc_cell['kappa'][ielem, :] # - # self.data.structure.ini_info.postproc_cell['kappa'][ielem, :]) # tstep.postproc_cell['loads'][ielem, :] = np.dot( # self.data.structure.stiffness_db[self.data.structure.elements[ielem].stiff_index, :, :], # tstep.postproc_cell['strain'][ielem, :]) # def calculate_loads(self): # order = [0, 2, 1] # # initial (ini) loads # tstep = self.data.structure.ini_info # pos = tstep.pos # psi = tstep.psi # # gamma0 = np.zeros((self.data.structure.num_elem, 2, 3)) # kappa0 = np.zeros((self.data.structure.num_elem, 2, 3)) # counter = np.zeros((self.data.structure.num_node,), dtype=int) # # for ielem in range(self.data.structure.num_elem): # for isegment in range(self.data.structure.elements[ielem].n_nodes - 1): # i_local_node0 = order[isegment] # i_local_node1 = order[isegment + 1] # crv = 0.5*(psi[ielem, i_local_node1, :] + psi[ielem, i_local_node0, :]) # # cba = algebra.crv2rotation(crv).T # tan = algebra.crv2tan(crv) # # inode0 = self.data.structure.elements[ielem].global_connectivities[i_local_node0] # inode1 = self.data.structure.elements[ielem].global_connectivities[i_local_node1] # # counter[inode0] += 1 # counter[inode1] += 1 # # print('----') # print(ielem) # print(isegment) # print(inode0, inode1) # print(counter[inode0], counter[inode1]) # print('----') # # gamma0[ielem, isegment, :] = ( # np.dot(cba, # pos[inode1, :] - pos[inode0, :]) / # np.linalg.norm(pos[inode1, :] - pos[inode0, :])) # kappa0[ielem, isegment, :] = ( # np.dot(tan, # psi[ielem, i_local_node1, :] - psi[ielem, i_local_node0, :]) / # np.linalg.norm(pos[inode1, :] - pos[inode0, :])) # # # time-dependant loads # for it in range(len(self.data.structure.timestep_info)): # tstep = self.data.structure.timestep_info[it] # pos = tstep.pos # psi = tstep.psi # # gamma = np.zeros((self.data.structure.num_elem, 2, 3)) # kappa = np.zeros((self.data.structure.num_elem, 2, 3)) # # tstep.postproc_cell['strain'] = np.zeros((self.data.structure.num_elem, 6)) # tstep.postproc_node['loads'] = np.zeros((self.data.structure.num_node, 6)) # strain = np.zeros((self.data.structure.num_elem, 2, 6)) # for ielem in range(self.data.structure.num_elem): # for isegment in range(self.data.structure.elements[ielem].n_nodes - 1): # i_local_node0 = order[isegment] # i_local_node1 = order[isegment + 1] # crv = 0.5*(psi[ielem, i_local_node1, :] + psi[ielem, i_local_node0, :]) # # cba = algebra.crv2rotation(crv).T # tan = algebra.crv2tan(crv) # # inode0 = self.data.structure.elements[ielem].global_connectivities[i_local_node0] # inode1 = self.data.structure.elements[ielem].global_connectivities[i_local_node1] # # gamma[ielem, isegment, :] = ( # np.dot(cba, # pos[inode1, :] - pos[inode0, :]) / # np.linalg.norm(pos[inode1, :] - pos[inode0, :])) # kappa[ielem, isegment, :] = ( # np.dot(tan, # psi[ielem, i_local_node1, :] - psi[ielem, i_local_node0, :]) / # np.linalg.norm(pos[inode1, :] - pos[inode0, :])) # strain[ielem, isegment, 0:3] += ( # gamma[ielem, isegment, :] # - # gamma0[ielem, isegment, :]) # strain[ielem, isegment, 3:6] += ( # kappa[ielem, isegment, :] # - # kappa0[ielem, isegment, :]) # # it might be necessary to rotate the results so that the B frame is the # # Master FoR (so that all the loads -- intrinsically in material FoR -- can be # # added together at the nodes). # prerotate = np.eye(6) # posrotate = np.eye(6) # if not self.data.structure.node_master_elem[inode0, 0] == ielem: # cab2 = algebra.crv2rotation(psi[ielem, self.data.structure.node_master_elem[inode0, 1], :]) # prerotate[0:3, 0:3] = np.dot(cab2.T, cba.T) # prerotate[3:6, 3:6] = np.dot(cab2.T, cba.T) # # tstep.postproc_node['loads'][inode0, :] = np.dot(prerotate, # np.dot(np.dot( # self.data.structure.stiffness_db[self.data.structure.elements[ielem].stiff_index, :, :], # strain[ielem, isegment, :])/counter[inode0], posrotate)) # prerotate = np.eye(6) # posrotate = np.eye(6) # if not self.data.structure.node_master_elem[inode1, 0] == ielem: # cab2 = algebra.crv2rotation(psi[ielem, self.data.structure.node_master_elem[inode1, 1], :]) # prerotate[0:3, 0:3] = np.dot(cab2.T, cba.T) # prerotate[3:6, 3:6] = np.dot(cab2.T, cba.T) # # tstep.postproc_node['loads'][inode1, :] = np.dot(prerotate, # np.dot(np.dot( # self.data.structure.stiffness_db[self.data.structure.elements[ielem].stiff_index, :, :], # strain[ielem, isegment, :])/counter[inode1], posrotate)) # # tstep.postproc_node['loads'][inode1, :] = np.dot( # # self.data.structure.stiffness_db[self.data.structure.elements[ielem].stiff_index, :, :], # # strain[ielem, isegment, :])/counter[inode1] ================================================ FILE: sharpy/postproc/beamplot.py ================================================ import os import numpy as np import sharpy.utils.cout_utils as cout from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils import sharpy.utils.algebra as algebra import vtk from vtk.numpy_interface import algorithms as algs from vtk.numpy_interface import dataset_adapter as dsa @solver class BeamPlot(BaseSolver): """ Plots beam to Paraview format """ solver_id = 'BeamPlot' solver_classification = 'post-processor' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['include_rbm'] = 'bool' settings_default['include_rbm'] = True settings_description['include_rbm'] = 'Include frame of reference rigid body motion' settings_types['include_FoR'] = 'bool' settings_default['include_FoR'] = False settings_description['include_FoR'] = 'Include frame of reference variables' settings_types['include_applied_forces'] = 'bool' settings_default['include_applied_forces'] = True settings_description['include_applied_forces'] = 'Write beam applied forces' settings_types['include_applied_moments'] = 'bool' settings_default['include_applied_moments'] = True settings_description['include_applied_moments'] = 'Write beam applied moments' settings_types['name_prefix'] = 'str' settings_default['name_prefix'] = '' settings_description['name_prefix'] = 'Name prefix for files' settings_types['output_rbm'] = 'bool' settings_default['output_rbm'] = True settings_description['output_rbm'] = 'Write ``csv`` file with rigid body motion data' settings_types['stride'] = 'int' settings_default['stride'] = 1 settings_description['stride'] = 'Number of steps between the execution calls when run online' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.settings = None self.data = None self.folder = '' self.filename = '' self.filename_for = '' self.caller = None def initialise(self, data, custom_settings=None, caller=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) # create folder for containing files if necessary self.folder = data.output_folder + '/beam/' if not os.path.exists(self.folder): os.makedirs(self.folder) self.filename = (self.folder + self.settings['name_prefix'] + 'beam_' + self.data.settings['SHARPy']['case']) self.filename_for = (self.folder + self.settings['name_prefix'] + 'for_' + self.data.settings['SHARPy']['case']) self.caller = caller def run(self, **kwargs): online = settings_utils.set_value_or_default(kwargs, 'online', False) self.plot(online) if not online: self.write() cout.cout_wrap('...Finished', 1) return self.data def write(self): if self.settings['output_rbm']: filename = self.filename + '_rbm_acc.csv' timesteps = len(self.data.structure.timestep_info) temp_matrix = np.zeros((timesteps, 6)) for it in range(timesteps): if self.data.structure.timestep_info[it] is not None: temp_matrix[it, :] = self.data.structure.timestep_info[it].for_acc np.savetxt(filename, temp_matrix, delimiter=',') def plot(self, online): if not online: for it in range(len(self.data.structure.timestep_info)): if self.data.structure.timestep_info[it] is not None: self.write_beam(it) if self.settings['include_FoR']: self.write_for(it) elif ((len(self.data.structure.timestep_info) - 1) % self.settings['stride'] == 0): it = len(self.data.structure.timestep_info) - 1 self.write_beam(it) if self.settings['include_FoR']: self.write_for(it) def write_beam(self, it): it_filename = f"{self.filename}{it:06d}.vtp" num_nodes = self.data.structure.num_node num_elem = self.data.structure.num_elem conn = np.zeros((num_elem, 3), dtype=int) node_id = np.zeros((num_nodes,), dtype=int) elem_id = np.zeros((num_elem,), dtype=int) coords_a_cell = np.zeros((num_elem, 3), dtype=int) local_x = np.zeros((num_nodes, 3)) local_y = np.zeros((num_nodes, 3)) local_z = np.zeros((num_nodes, 3)) coords_a = np.zeros((num_nodes, 3)) app_forces = np.zeros((num_nodes, 3)) app_moment = np.zeros((num_nodes, 3)) forces_constraints_nodes = np.zeros((num_nodes, 3)) moments_constraints_nodes = np.zeros((num_nodes, 3)) tstep = self.data.structure.timestep_info[it] # aero2inertial rotation aero2inertial = tstep.cga() # coordinates of corners coords = tstep.glob_pos(include_rbm=self.settings['include_rbm']) if tstep.mb_dict is None: pass else: for i_node in range(tstep.num_node): c = self.data.structure.timestep_info[0].cga() coords[i_node, :] += c @ tstep.for_pos[:3] # check if I can output gravity forces with_gravity = False try: gravity_forces = tstep.gravity_forces[:] gravity_forces_g = np.zeros_like(gravity_forces) with_gravity = True except AttributeError: pass # check if postproc dicts are present and count/prepare with_postproc_cell = False try: tstep.postproc_cell with_postproc_cell = True except AttributeError: pass with_postproc_node = False try: tstep.postproc_node with_postproc_node = True except AttributeError: pass # count number of arguments postproc_cell_vector = [] postproc_cell_6vector = [] for k, v in tstep.postproc_cell.items(): _, cols = v.shape if cols == 1: raise NotImplementedError('scalar cell types not supported in beamplot (Easy to implement)') # postproc_cell_scalar.append(k) elif cols == 3: postproc_cell_vector.append(k) elif cols == 6: postproc_cell_6vector.append(k) else: raise AttributeError('Only scalar and 3-vector types supported in beamplot') # count number of arguments postproc_node_scalar = [] postproc_node_vector = [] postproc_node_6vector = [] for k, v in tstep.postproc_node.items(): try: _, cols = v.shape except ValueError: # for np.arrays with shape (x,) cols = 1 if cols == 1: postproc_node_scalar.append(k) elif cols == 3: postproc_node_vector.append(k) elif cols == 6: postproc_node_6vector.append(k) else: raise AttributeError('Only scalar and 3-vector types supported in beamplot') for i_node in range(num_nodes): i_elem = self.data.structure.node_master_elem[i_node, 0] i_local_node = self.data.structure.node_master_elem[i_node, 1] node_id[i_node] = i_node v1 = np.array([1., 0, 0]) v2 = np.array([0., 1, 0]) v3 = np.array([0., 0, 1]) cab = algebra.crv2rotation( tstep.psi[i_elem, i_local_node, :]) local_x[i_node, :] = np.dot(aero2inertial, np.dot(cab, v1)) local_y[i_node, :] = np.dot(aero2inertial, np.dot(cab, v2)) local_z[i_node, :] = np.dot(aero2inertial, np.dot(cab, v3)) if i_local_node == 2: coords_a_cell[i_elem, :] = tstep.pos[i_node, :] coords_a[i_node, :] = tstep.pos[i_node, :] # applied forces cab = algebra.crv2rotation(tstep.psi[i_elem, i_local_node, :]) app_forces[i_node, :] = np.dot(aero2inertial, np.dot(cab, tstep.steady_applied_forces[i_node, 0:3]+ tstep.unsteady_applied_forces[i_node, 0:3])) app_moment[i_node, :] = np.dot(aero2inertial, np.dot(cab, tstep.steady_applied_forces[i_node, 3:6]+ tstep.unsteady_applied_forces[i_node, 3:6])) forces_constraints_nodes[i_node, :] = np.dot(aero2inertial, np.dot(cab, tstep.forces_constraints_nodes[i_node, 0:3])) moments_constraints_nodes[i_node, :] = np.dot(aero2inertial, np.dot(cab, tstep.forces_constraints_nodes[i_node, 3:6])) if with_gravity: gravity_forces_g[i_node, 0:3] = np.dot(aero2inertial, gravity_forces[i_node, 0:3]) gravity_forces_g[i_node, 3:6] = np.dot(aero2inertial, gravity_forces[i_node, 3:6]) for i_elem in range(num_elem): conn[i_elem, :] = self.data.structure.elements[i_elem].reordered_global_connectivities elem_id[i_elem] = i_elem ug = vtk.vtkPolyData(points=coords) cells = vtk.vtkCellArray() for _conn in conn: line = vtk.vtkPolyLine() line.GetPointIds().SetNumberOfIds(3) line.GetPointIds().SetId(0, _conn[0]) line.GetPointIds().SetId(1, _conn[1]) line.GetPointIds().SetId(2, _conn[2]) cells.InsertNextCell(line) ug.SetLines(cells) ug.GetCellData().AddArray(dsa.numpyTovtkDataArray(np.array(elem_id), name='elem_id')) ug.GetCellData().AddArray(dsa.numpyTovtkDataArray(coords_a_cell, name="coords_a_elem")) ug.GetPointData().AddArray( dsa.numpyTovtkDataArray(node_id, name="node_id") ) ug.GetPointData().AddArray(dsa.numpyTovtkDataArray(local_x, name="local_x")) ug.GetPointData().AddArray(dsa.numpyTovtkDataArray(local_y, name="local_y")) ug.GetPointData().AddArray(dsa.numpyTovtkDataArray(local_z, name="local_z")) ug.GetPointData().AddArray(dsa.numpyTovtkDataArray(coords_a, name="coords_a")) if with_postproc_cell: for k in postproc_cell_vector: ug.GetCellData().AddArray( dsa.numpyTovtkDataArray(tstep.postproc_cell[k], name=f"{k}_cell") ) for k in postproc_cell_6vector: for i in range(2): ug.GetCellData().AddArray( dsa.numpyTovtkDataArray( tstep.postproc_cell[k][:, 3*i:3*(i+1)], name=f"{k}_{i}_cell" ) ) if self.settings['include_applied_forces']: ug.GetPointData().AddArray(dsa.numpyTovtkDataArray(app_forces, name="app_forces")) ug.GetPointData().AddArray( dsa.numpyTovtkDataArray(forces_constraints_nodes, name="forces_constraints_node") ) if with_gravity: ug.GetPointData().AddArray( dsa.numpyTovtkDataArray( gravity_forces_g[:, :3], name="gravity_forces" ) ) if self.settings['include_applied_moments']: ug.GetPointData().AddArray( dsa.numpyTovtkDataArray(app_moment, name="app_moments") ) ug.GetPointData().AddArray( dsa.numpyTovtkDataArray(moments_constraints_nodes, name="moments_constraints_nodes") ) if with_gravity: ug.GetPointData().AddArray(dsa.numpyTovtkDataArray(gravity_forces_g[:, 3:6], name="gravity_moments")) if with_postproc_node: for k in postproc_node_vector: ug.GetPointData().AddArray( dsa.numpyTovtkDataArray( tstep.postproc_node[k], name=f"{k}_point" ) ) for k in postproc_node_6vector: for i in range(2): ug.GetPointData().AddArray( dsa.numpyTovtkDataArray( tstep.postproc_node[k][:, 3*i:3*(i+1)], name=f"{k}_{i}_point" ) ) for k in postproc_node_scalar: ug.GetPointData().AddArray( dsa.numpyTovtkDataArray( tstep.postproc_node[k], name=str(k), ) ) writer = vtk.vtkXMLPolyDataWriter() writer.SetFileName(it_filename) writer.SetInputData(ug) writer.Write() def write_for(self, it): it_filename = f"{self.filename_for}{it:06d}.vtp" forces_constraints_for = np.zeros((self.data.structure.num_bodies, 3)) moments_constraints_for = np.zeros((self.data.structure.num_bodies, 3)) # aero2inertial rotation aero2inertial = self.data.structure.timestep_info[it].cga() # coordinates of corners for_coords = np.zeros((self.data.structure.num_bodies, 3)) if self.settings['include_rbm']: offset = np.zeros((3,)) else: offset = self.data.structure.timestep_info[it].mb_FoR_pos[0, 0:3] for ibody in range(self.data.structure.num_bodies): for_coords[ibody, :] = self.data.structure.timestep_info[it].mb_FoR_pos[ibody, 0:3] - offset forces_constraints_for[ibody, :] = aero2inertial @ self.data.structure.timestep_info[it].forces_constraints_FoR[ibody, :3] moments_constraints_for[ibody, :] = aero2inertial @ self.data.structure.timestep_info[it].forces_constraints_FoR[ibody, 3:6] for_mesh = vtk.vtkPolyData(points=for_coords) for_mesh.GetPointData().AddArray( dsa.numpyTovtkDataArray( forces_constraints_for, 'forces_constraints_FoR', ) ) for_mesh.GetPointData().AddArray( dsa.numpyTovtkDataArray( moments_constraints_for, "moments_constraints_FoR", ) ) writer = vtk.vtkXMLPolyDataWriter() writer.SetFileName(it_filename) writer.SetInputData(for_mesh) writer.Write() ================================================ FILE: sharpy/postproc/cleanup.py ================================================ from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils @solver class Cleanup(BaseSolver): """ Clean-up old timesteps to save RAM memory """ solver_id = 'Cleanup' solver_classification = 'post-processor' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['clean_structure'] = 'bool' settings_default['clean_structure'] = True settings_description['clean_structure'] = 'Clean-up :class:`~sharpy.utils.datastructures.StructTimeStepInfo`' settings_types['clean_aero'] = 'bool' settings_default['clean_aero'] = True settings_description['clean_aero'] = 'Clean-up :class:`~sharpy.utils.datastructures.AeroTimeStepInfo`' settings_types['remaining_steps'] = 'int' settings_default['remaining_steps'] = 10 settings_description['remaining_steps'] = 'The last `remaining_steps` are not cleaned up' table = settings_utils.SettingsTable() __doc__ += table.generate(settings_types, settings_default, settings_description) def __init__(self): self.settings = None self.data = None self.caller = None def initialise(self, data, custom_settings=None, caller=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) self.caller = caller def run(self, **kwargs): online = settings_utils.set_value_or_default(kwargs, 'online', False) if self.settings['clean_structure']: self.clean(self.data.structure.timestep_info, self.settings['remaining_steps']) if self.settings['clean_aero']: self.clean(self.data.aero.timestep_info, self.settings['remaining_steps']) return self.data def clean(self, series, n_steps): for i in range(len(series[:-n_steps])): series[i] = None ================================================ FILE: sharpy/postproc/frequencyresponse.py ================================================ import numpy as np import time import os import sharpy.utils.solver_interface as solver_interface import sharpy.utils.settings as settings_utils import sharpy.utils.cout_utils as cout import warnings import sharpy.linear.src.libss as libss import h5py as h5 import sharpy.utils.frequencyutils as frequencyutils from sharpy.utils.frequencyutils import find_target_system @solver_interface.solver class FrequencyResponse(solver_interface.BaseSolver): """ Frequency Response Calculator. Computes the frequency response of a built linear system. The frequency will be calculated for the systems specified in the ``target_system`` list. The desired ``frequency_unit`` will be either ``w`` for radians/s or ``k`` for reduced frequency (if the system is scaled). The ``frequency_bounds`` setting will set the lower and upper bounds of the response, while ``num_freqs`` will specify the number of evaluations. The option ``frequency_spacing`` allows you to space the evaluations point following a ``log`` or ``linear`` spacing. If ``compute_hinf`` is set, the H-infinity norm of the system is calculated. This will be saved to a binary ``.h5`` file as detailed in :func:`save_freq_resp`. Finally, the ``quick_plot`` option will plot some quick and dirty bode plots of the response. This requires access to ``matplotlib``. """ solver_id = 'FrequencyResponse' solver_classification = 'post-processor' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = False settings_description['print_info'] = 'Write output to screen.' settings_types['target_system'] = 'list(str)' settings_default['target_system'] = ['aeroelastic'] settings_description['target_system'] = 'System or systems for which to find frequency response.' settings_options['target_system'] = ['aeroelastic', 'aerodynamic', 'structural'] settings_types['frequency_unit'] = 'str' settings_default['frequency_unit'] = 'k' settings_description['frequency_unit'] = 'Units of frequency, ``w`` for rad/s, ``k`` reduced.' settings_options['frequency_unit'] = ['w', 'k'] settings_types['frequency_scaling'] = 'dict' settings_default['frequency_scaling'] = {} settings_description['frequency_scaling'] = 'Dictionary containing the frequency scaling factors, if the ' \ 'aerodynamic system has not been previously scaled. Applied also if ' \ 'the desired unit is reduced frequency.' scaling_types = dict() scaling_default = dict() scaling_description = dict() scaling_types['length'] = 'float' scaling_default['length'] = 1. scaling_description['length'] = 'Length scaling factor.' scaling_types['speed'] = 'float' scaling_default['speed'] = 1. scaling_description['speed'] = 'Speed scaling factor.' settings_types['frequency_bounds'] = 'list(float)' settings_default['frequency_bounds'] = [1e-3, 1] settings_description['frequency_bounds'] = 'Lower and upper frequency bounds in the corresponding unit.' settings_types['frequency_spacing'] = 'str' settings_default['frequency_spacing'] = 'linear' settings_description['frequency_spacing'] = 'Compute the frequency response in a ``linear`` or ``log`` grid.' settings_options['frequency_spacing'] = ['linear', 'log'] settings_types['num_freqs'] = 'int' settings_default['num_freqs'] = 50 settings_description['num_freqs'] = 'Number of frequencies to evaluate.' settings_types['compute_hinf'] = 'bool' settings_default['compute_hinf'] = False settings_description['compute_hinf'] = 'Compute Hinfinity norm of the system.' settings_types['quick_plot'] = 'bool' settings_default['quick_plot'] = False settings_description['quick_plot'] = 'Produce array of ``.png`` plots showing response. Requires matplotlib.' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) scaling_table = settings_utils.SettingsTable() __doc__ += scaling_table.generate(scaling_types, scaling_default, scaling_description, header_line='The scaling dictionary takes the following entries:') def __init__(self): self.settings = None self.data = None self.folder = None self.print_info = False self.scaled = False self.w_to_k = 1 self.wv = None self.caller = None def initialise(self, data, custom_settings=None, caller=None, restart=False): self.data = data if not custom_settings: self.settings = self.data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, self.settings_options, no_ctype=True) self.print_info = self.settings['print_info'] if self.settings['frequency_unit'] == 'k': self.scaled = True if self.data.linear.linear_system.uvlm.scaled: scaling = self.data.linear.linear_system.uvlm.sys.ScalingFacts else: scaling = self.settings['frequency_scaling'] settings_utils.to_custom_types(scaling, self.scaling_types, self.scaling_default, no_ctype=True) self.w_to_k = scaling['length'] / scaling['speed'] else: self.w_to_k = 1. # Frequency bounds in radians lb = self.settings['frequency_bounds'][0] / self.w_to_k ub = self.settings['frequency_bounds'][1] / self.w_to_k nfreqs = self.settings['num_freqs'] if self.settings['frequency_spacing'] == 'linear': self.wv = np.linspace(lb, ub, nfreqs) elif self.settings['frequency_spacing'] == 'log': self.wv = np.logspace(np.log10(lb), np.log10(ub), nfreqs) else: raise NotImplementedError('Unrecognised frequency spacing setting %s' % self.settings['frequency_spacing']) self.folder = data.output_folder + '/frequencyresponse/' if not os.path.exists(self.folder): os.makedirs(self.folder) self.caller = caller def run(self, **kwargs): """ Computes the frequency response of the linear state-space. Args: ss (sharpy.linear.src.libss.StateSpace (Optional)): State-space object for which to compute the frequency response. If not given, the response for the previously assembled systems and specified in ``target_system`` will be performed. """ online = settings_utils.set_value_or_default(kwargs, 'online', False) ss = settings_utils.set_value_or_default(kwargs, 'ss', None) if ss is None: ss_list = [find_target_system(self.data, system_name) for system_name in self.settings['target_system']] elif type(ss) is libss.StateSpace: ss_list = [ss] elif type(ss) is list: ss_list = ss else: raise TypeError('StateSpace input must be either a libss.StateSpace instance or a list of libss.StateSpace') for ith, system in enumerate(ss_list): if self.print_info: cout.cout_wrap('Computing frequency response...') if ss is None: try: system_name = self.settings['target_system'][ith] if self.print_info: cout.cout_wrap('\tComputing frequency response for %s system' % system_name, 1) except IndexError: system_name = None else: system_name = None # For the case where the state-space is parsed in run(). t0fom = time.time() y_freq_fom = system.freqresp(self.wv) tfom = time.time() - t0fom if self.settings['compute_hinf']: if self.print_info: cout.cout_wrap('Computing H-infinity norm...') try: hinf = frequencyutils.h_infinity_norm(system, iter_max=50, print_info=self.settings['print_info']) except np.linalg.LinAlgError: hinf = None cout.cout_wrap('H-infinity calculation did not converge', 4) else: hinf = None self.save_freq_resp(self.wv * self.w_to_k, y_freq_fom, system_name=system_name, hinf=hinf) cout.cout_wrap('\tComputed the frequency response in %f s' % tfom, 2) if self.settings['quick_plot']: self.quick_plot(y_freq_fom, subfolder=system_name) return self.data def save_freq_resp(self, wv, Yfreq, system_name=None, hinf=None): """ Saves the frequency response to a binary ``.h5`` file. If the system has not been scaled, the units of frequency are ``rad/s`` and the response is given in complex form. The response is saved in a ``[p, m, n_freq_eval]`` format, where ``p`` corresponds to the system's outputs, ``n`` to the number of inputs and ``n_freq_eval`` to the number of frequency evaluations. Args: wv (np.ndarray): Frequency array. Y_freq (np.ndarray): Frequency response data ``[p, m, n_freq_eval]`` matrix. system_name (str (optional)): State-space system name. hinf (float (optional)): H-infinity norm of the system. """ with open(self.folder + '/freqdata_readme.txt', 'w') as outfile: outfile.write('Frequency Response Data Output\n\n') outfile.write('Frequency data found in the relevant .h5 file\n') outfile.write('The units of frequency are rad/s\nThe frequency' \ 'response is given in complex form.') case_name = '' if system_name is not None: case_name += system_name + '.' p, m, _ = Yfreq.shape h5filename = self.folder + '/' + case_name + 'freqresp.h5' with h5.File(h5filename, 'w') as f: f.create_dataset('frequency', data=wv) f.create_dataset('response', data=Yfreq, dtype=complex) f.create_dataset('inputs', data=m) f.create_dataset('outputs', data=p) if hinf is not None: f.create_dataset('hinf_norm', data=hinf) if self.print_info: cout.cout_wrap('Saved .h5 file to %s with frequency response data' % h5filename) def quick_plot(self, y_freq_fom=None, subfolder=None): p, m, _ = y_freq_fom.shape try: cout.cout_wrap('\tCreating Quick plots of the frequency response', 1) out_folder = self.folder if subfolder: out_folder += '/' + subfolder if not os.path.isdir(out_folder): os.makedirs(out_folder, exist_ok=True) import matplotlib.pyplot as plt for mj in range(m): for pj in range(p): fig1, ax1 = plt.subplots(nrows=2) fig_title = 'in%02g_out%02g' % (mj, pj) ax1[0].set_title(fig_title) if y_freq_fom is not None: ax1[0].plot(self.wv * self.w_to_k, 20 * np.log10(np.abs(y_freq_fom[pj, mj, :])), color='C0') ax1[1].plot(self.wv * self.w_to_k, np.angle(y_freq_fom[pj, mj, :]), '-', color='C0') if self.scaled: ax1[1].set_xlabel('Reduced Frequency, k [-]') else: ax1[1].set_xlabel(r'Frequency, $\omega$ [rad/s]') ax1[0].set_ylabel('Amplitude [dB]') ax1[1].set_ylabel('Phase [rad]') fig1.savefig(out_folder + '/' + fig_title + '.png') plt.close() cout.cout_wrap('\tPlots saved to %s' % out_folder, 1) except ModuleNotFoundError: warnings.warn('Matplotlib not found - skipping plot') ================================================ FILE: sharpy/postproc/liftdistribution.py ================================================ import os import numpy as np from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils import sharpy.aero.utils.mapping as mapping import sharpy.utils.algebra as algebra import sharpy.aero.utils.utils as aeroutils @solver class LiftDistribution(BaseSolver): """LiftDistribution Calculates and exports the lift distribution on lifting surfaces """ solver_id = 'LiftDistribution' solver_classification = 'post-processor' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['text_file_name'] = 'str' settings_default['text_file_name'] = 'liftdistribution' settings_description['text_file_name'] = 'Text file name' settings_default['coefficients'] = True settings_types['coefficients'] = 'bool' settings_description['coefficients'] = 'Calculate aerodynamic lift coefficients' settings_types['rho'] = 'float' settings_default['rho'] = 1.225 settings_description['rho'] = 'Reference freestream density [kg/m³]' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.settings = None self.data = None self.folder = None self.caller = None def initialise(self, data, custom_settings=None, restart=False, caller=None): self.data = data self.settings = data.settings[self.solver_id] settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) self.caller = caller self.folder = data.output_folder + '/liftdistribution/' if not os.path.exists(self.folder): os.makedirs(self.folder) def run(self, **kwargs): self.lift_distribution(self.data.structure.timestep_info[self.data.ts], self.data.aero.timestep_info[self.data.ts]) return self.data def lift_distribution(self, struct_tstep, aero_tstep): # Force mapping forces = mapping.aero2struct_force_mapping( aero_tstep.forces + aero_tstep.dynamic_forces, self.data.aero.struct2aero_mapping, aero_tstep.zeta, struct_tstep.pos, struct_tstep.psi, self.data.structure.node_master_elem, self.data.structure.connectivities, struct_tstep.cag(), self.data.aero.data_dict) # Prepare output matrix and file N_nodes = self.data.structure.num_node numb_col = 6 header = "x,y,z,fx,fy,fz" # get aero forces # get rotation matrix cga = algebra.quat2rotation(struct_tstep.quat) if self.settings["coefficients"]: # TODO: add nondimensional spanwise column y/s header += ", cfx, cfy, cfz" numb_col += 3 lift_distribution = np.zeros((N_nodes, numb_col)) for inode in range(N_nodes): if self.data.aero.data_dict['aero_node'][inode]: local_node = self.data.aero.struct2aero_mapping[inode][0]["i_n"] ielem, inode_in_elem = self.data.structure.node_master_elem[inode] i_surf = int(self.data.aero.surface_distribution[ielem]) # get c_gb cab = algebra.crv2rotation(struct_tstep.psi[ielem, inode_in_elem, :]) cgb = np.dot(cga, cab) # Get c_bs urel, dir_urel = aeroutils.magnitude_and_direction_of_relative_velocity(struct_tstep.pos[inode, :], struct_tstep.pos_dot[inode, :], struct_tstep.for_vel[:], cga, aero_tstep.u_ext[i_surf][:, :, local_node]) dir_span, span, dir_chord, chord = aeroutils.span_chord(local_node, aero_tstep.zeta[i_surf]) # Stability axes - projects forces in B onto S c_bs = aeroutils.local_stability_axes(cgb.T.dot(dir_urel), cgb.T.dot(dir_chord)) aero_forces = c_bs.T.dot(forces[inode, :3]) # Store data in export matrix lift_distribution[inode, 3:6] = aero_forces lift_distribution[inode, 2] = struct_tstep.pos[inode, 2] # z lift_distribution[inode, 1] = struct_tstep.pos[inode, 1] # y lift_distribution[inode, 0] = struct_tstep.pos[inode, 0] # x if self.settings["coefficients"]: # Get lift coefficient for idim in range(3): lift_distribution[inode, 6+idim] = np.sign(aero_forces[idim]) * np.linalg.norm(aero_forces[idim]) \ / (0.5 * self.settings['rho'] \ * np.linalg.norm(urel) ** 2 * span * chord) # Check if shared nodes from different surfaces exist (e.g. two wings joining at symmetry plane) # Leads to error since panel area just donates for half the panel size while lift forces is summed up lift_distribution[inode, 6+idim] /= len(self.data.aero.struct2aero_mapping[inode]) # Export lift distribution data np.savetxt(os.path.join(self.folder, self.settings['text_file_name'] + '_ts{}'.format(str(self.data.ts)) + '.txt'), lift_distribution, fmt='%10e,' * (numb_col - 1) + '%10e', delimiter=", ", header=header) ================================================ FILE: sharpy/postproc/pickledata.py ================================================ import os import pickle import h5py import sharpy from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils # Define basic numerical types # BasicNumTypes=(float,float32,float64,int,int32,int64,complex) @solver class PickleData(BaseSolver): """ This postprocessor writes the SHARPy ``data`` structure in a pickle file, such that classes and methods from SHARPy are retained for restarted solutions or further post-processing. A pickle is saved to the SHARPy output folder, specified in the settings for SHARPy as ``log_folder``. This solver does not have settings, yet it still needs to be included in the `.sharpy` file as an empty dictionary. """ solver_id = 'PickleData' solver_classification = 'post-processor' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['stride'] = 'int' settings_default['stride'] = 1 settings_description['stride'] = 'Number of steps between the execution calls when run online' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): import sharpy self.data = None self.filename = None self.folder = None self.caller = None def initialise(self, data, custom_settings=None, caller=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) self.folder = data.output_folder self.filename = self.folder + self.data.settings['SHARPy']['case']+'.pkl' self.caller = caller def run(self, **kwargs): online = settings_utils.set_value_or_default(kwargs, 'online', False) solvers = settings_utils.set_value_or_default(kwargs, 'solvers', None) if ((online and (self.data.ts % self.settings['stride'] == 0)) or (not online)): with open(self.filename, 'wb') as f: pickle.dump(self.data, f, protocol=pickle.HIGHEST_PROTOCOL) pickle.dump(solvers, f, protocol=pickle.HIGHEST_PROTOCOL) return self.data ================================================ FILE: sharpy/postproc/plotflowfield.py ================================================ import os import numpy as np from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.generator_interface as gen_interface import sharpy.utils.settings as settings_utils import sharpy.aero.utils.uvlmlib as uvlmlib import ctypes as ct from sharpy.utils.constants import vortex_radius_def @solver class PlotFlowField(BaseSolver): """ Plots the flow field in Paraview and computes the velocity at a set of points in a grid. """ solver_id = 'PlotFlowField' solver_classification = 'post-processor' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['postproc_grid_generator'] = 'str' settings_default['postproc_grid_generator'] = 'GridBox' settings_description['postproc_grid_generator'] = 'Generator used to create grid and plot flow field' settings_options['postproc_grid_generator'] = ['GridBox'] settings_types['postproc_grid_input'] = 'dict' settings_default['postproc_grid_input'] = dict() settings_description['postproc_grid_input'] = 'Dictionary containing settings for ``postproc_grid_generator``.' settings_types['velocity_field_generator'] = 'str' settings_default['velocity_field_generator'] = 'SteadyVelocityField' settings_description['velocity_field_generator'] = 'Chosen velocity field generator' settings_types['velocity_field_input'] = 'dict' settings_default['velocity_field_input'] = dict() settings_description['velocity_field_input'] = 'Dictionary containing settings for the selected ``velocity_field_generator``.' settings_types['dt'] = 'float' settings_default['dt'] = 0.1 settings_description['dt'] = 'Time step.' settings_types['include_external'] = 'bool' settings_default['include_external'] = True settings_description['include_external'] = 'Include external velocities.' settings_types['include_induced'] = 'bool' settings_default['include_induced'] = True settings_description['include_induced'] = 'Include induced velocities.' settings_types['stride'] = 'int' settings_default['stride'] = 1 settings_description['stride'] = 'Number of time steps between plots.' settings_types['num_cores'] = 'int' settings_default['num_cores'] = 1 settings_description['num_cores'] = 'Number of cores to use.' settings_types['vortex_radius'] = 'float' settings_default['vortex_radius'] = vortex_radius_def settings_description['vortex_radius'] = 'Distance below which inductions are not computed.' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) def __init__(self): self.settings = None self.data = None self.folder = None self.caller = None def initialise(self, data, custom_settings=None, caller=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, self.settings_options) self.folder = data.output_folder + '/' + 'GenerateFlowField/' if not os.path.isdir(self.folder): os.makedirs(self.folder) # init velocity generator velocity_generator_type = gen_interface.generator_from_string( self.settings['velocity_field_generator']) self.velocity_generator = velocity_generator_type() self.velocity_generator.initialise(self.settings['velocity_field_input'], restart=restart) # init postproc grid generator postproc_grid_generator_type = gen_interface.generator_from_string( self.settings['postproc_grid_generator']) self.postproc_grid_generator = postproc_grid_generator_type() self.postproc_grid_generator.initialise(self.settings['postproc_grid_input'], restart=restart) self.caller = caller def output_velocity_field(self, ts): # Notice that SHARPy utilities deal with several two-dimensional surfaces # To be able to build 3D volumes, I will make use of the surface index as # the third index in space # It does not apply to the 'u' array because this way it is easier to # write it in paraview # Generate the grid vtk_info, grid = self.postproc_grid_generator.generate({ 'for_pos': self.data.structure.timestep_info[ts].for_pos[0:3]}) # Compute the induced velocities nx = grid[0].shape[1] ny = grid[0].shape[2] nz = len(grid) array_counter = 0 u_ind = np.zeros((nx, ny, nz, 3), dtype=float) if self.settings['include_induced']: target_triads = np.zeros((nx*ny*nz, 3)) ipoint = -1 for iz in range(nz): for ix in range(nx): for iy in range(ny): ipoint += 1 target_triads[ipoint, :] = grid[iz][:, ix, iy].astype(dtype=ct.c_double, order='F', copy=True) u_ind_points = uvlmlib.uvlm_calculate_total_induced_velocity_at_points(self.data.aero.timestep_info[ts], target_triads, self.settings['vortex_radius'], self.data.structure.timestep_info[ts].for_pos[0:3], self.settings['num_cores']) ipoint = -1 for iz in range(nz): for ix in range(nx): for iy in range(ny): ipoint += 1 u_ind[ix, iy, iz, :] = u_ind_points[ipoint, :] # Write the data vtk_info.point_data.add_array(u_ind.reshape((-1, u_ind.shape[-1]), order='F')) # Reshape the array except from the last dimension vtk_info.point_data.get_array(array_counter).name = 'induced_velocity' vtk_info.point_data.update() array_counter += 1 # Add the external velocities u_ext_out = np.zeros((nx, ny, nz, 3), dtype=float) if self.settings['include_external']: u_ext = [] for iz in range(nz): u_ext.append(np.zeros((3, nx, ny), dtype=ct.c_double)) self.velocity_generator.generate({'zeta': grid, 'override': True, 't': ts*self.settings['dt'], 'ts': ts, 'dt': self.settings['dt'], 'for_pos': 0*self.data.structure.timestep_info[ts].for_pos}, u_ext) for iz in range(nz): for ix in range(nx): for iy in range(ny): u_ext_out[ix, iy, iz, :] += u_ext[iz][:, ix, iy] # Write the data vtk_info.point_data.add_array(u_ext_out.reshape((-1, u_ext_out.shape[-1]), order='F')) # Reshape the array except from the last dimension vtk_info.point_data.get_array(array_counter).name = 'external_velocity' vtk_info.point_data.update() array_counter += 1 # add the data u = u_ind + u_ext_out # Write the data vtk_info.point_data.add_array(u.reshape((-1, u.shape[-1]), order='F')) # Reshape the array except from the last dimension vtk_info.point_data.get_array(array_counter).name = 'velocity' vtk_info.point_data.update() array_counter += 1 filename = self.folder + "VelocityField_" + '%06u' % ts + ".vtk" from tvtk.api import write_data write_data(vtk_info, filename) def run(self, **kwargs): online = settings_utils.set_value_or_default(kwargs, 'online', False) if online: if divmod(self.data.ts, self.settings['stride'])[1] == 0: self.output_velocity_field(len(self.data.structure.timestep_info) - 1) else: for ts in range(0, len(self.data.structure.timestep_info)): if not self.data.structure.timestep_info[ts] is None: self.output_velocity_field(ts) return self.data ================================================ FILE: sharpy/postproc/savedata.py ================================================ import os import h5py import copy import sharpy import sharpy.utils.cout_utils as cout from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils import sharpy.utils.h5utils as h5utils from sharpy.presharpy.presharpy import PreSharpy @solver class SaveData(BaseSolver): """ The ``SaveData`` postprocessor writes the SHARPy variables into ``hdf5`` files. The linear state space files may be saved to ``.mat`` if desired instead. It has options to save the following classes: * :class:`~sharpy.sharpy.aero.models.Aerogrid` including :class:`sharpy.sharpy.utils.datastructures.AeroTimeStepInfo` * :class:`~sharpy.sharpy.structure.beam.Beam` including :class:`sharpy.sharpy.utils.datastructures.StructTimeStepInfo` * :class:`sharpy.solvers.linearassembler.Linear` including classes in :exc:`sharpy.linear.assembler` Notes: This method saves simply the data. If you would like to preserve the SHARPy methods of the relevant classes see also :class:`sharpy.solvers.pickledata.PickleData`. """ solver_id = 'SaveData' solver_classification = 'post-processor' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['save_aero'] = 'bool' settings_default['save_aero'] = True settings_description['save_aero'] = 'Save aerodynamic classes.' settings_types['save_nonlifting'] = 'bool' settings_default['save_nonlifting'] = False settings_description['save_nonlifting'] = 'Save aerodynamic classes.' settings_types['save_struct'] = 'bool' settings_default['save_struct'] = True settings_description['save_struct'] = 'Save structural classes.' settings_types['save_linear'] = 'bool' settings_default['save_linear'] = False settings_description['save_linear'] = 'Save linear state space system.' settings_types['save_linear_uvlm'] = 'bool' settings_default['save_linear_uvlm'] = False settings_description['save_linear_uvlm'] = 'Save linear UVLM state space system. Use with caution when dealing with ' \ 'large systems.' settings_types['save_wake'] = 'bool' settings_default['save_wake'] = True settings_description['save_wake'] = 'Save aero wake classes.' settings_types['save_rom'] = 'bool' settings_default['save_rom'] = False settings_description['save_rom'] = 'Saves the ROM matrices and the reduced order model' settings_types['skip_attr'] = 'list(str)' settings_default['skip_attr'] = ['fortran', 'airfoils', 'airfoil_db', 'settings_types', # 'beam', 'ct_dynamic_forces_list', # 'ct_forces_list', 'ct_gamma_dot_list', 'ct_gamma_list', 'ct_gamma_star_list', 'ct_normals_list', 'ct_u_ext_list', 'ct_u_ext_star_list', 'ct_zeta_dot_list', 'ct_zeta_list', 'ct_zeta_star_list', 'dynamic_input'] settings_description['skip_attr'] = 'List of attributes to skip when writing file' settings_types['compress_float'] = 'bool' settings_default['compress_float'] = False settings_description['compress_float'] = 'Compress float' settings_types['format'] = 'str' settings_default['format'] = 'h5' settings_description['format'] = 'Save linear state space to hdf5 ``.h5`` or Matlab ``.mat`` format.' settings_options['format'] = ['h5', 'mat'] settings_types['stride'] = 'int' settings_default['stride'] = 1 settings_description['stride'] = 'Number of steps between the execution calls when run online' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options=settings_options) def __init__(self): self.settings = None self.data = None self.folder = '' self.filename = '' self.filename_linear = '' self.caller = None ### specify which classes are saved as hdf5 group # see initialise and add_as_grp self.ClassesToSave = (PreSharpy,) def initialise(self, data, custom_settings=None, caller=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, options=self.settings_options) # Add these anyway - therefore if you add your own skip_attr you don't have to retype all of these self.settings['skip_attr'].extend(['fortran', 'airfoils', 'airfoil_db', 'settings_types', 'ct_dynamic_forces_list', 'ct_forces_list', 'ct_gamma_dot_list', 'ct_gamma_list', 'ct_gamma_star_list', 'ct_normals_list', 'ct_u_ext_list', 'ct_u_ext_star_list', 'ct_zeta_dot_list', 'ct_zeta_list', 'ct_zeta_star_list', 'dynamic_input']) # create folder for containing files if necessary self.folder = data.output_folder + '/savedata/' if not os.path.exists(self.folder): os.makedirs(self.folder) self.filename = self.folder + self.data.settings['SHARPy']['case'] + '.data.h5' self.filename_linear = self.folder + self.data.settings['SHARPy']['case'] + '.linss.h5' # remove old file if it exists self.remove_file_if_exist(self.filename) # check that there is a linear system - else return setting to false if self.settings['save_linear'] or self.settings['save_linear_uvlm']: try: self.data.linear except AttributeError: cout.cout_wrap('SaveData variables ``save_linear`` and/or ``save_linear`` are True but no linear system' 'been found', 3) self.settings['save_linear'] = False self.settings['save_linear_uvlm'] = False if self.settings['format'] == 'h5': # allocate list of classes to be saved if self.settings['save_aero']: self.ClassesToSave += (sharpy.aero.models.aerogrid.Aerogrid, sharpy.utils.datastructures.AeroTimeStepInfo,) if not self.settings['save_wake']: self.settings['skip_attr'].append('zeta_star') self.settings['skip_attr'].append('u_ext_star') self.settings['skip_attr'].append('gamma_star') self.settings['skip_attr'].append('dist_to_orig') self.settings['skip_attr'].append('wake_conv_vel') if self.settings['save_nonlifting']: self.ClassesToSave += (sharpy.aero.models.nonliftingbodygrid.NonliftingBodyGrid, sharpy.utils.datastructures.NonliftingBodyTimeStepInfo,) if self.settings['save_struct']: self.ClassesToSave += ( sharpy.structure.models.beam.Beam, sharpy.utils.datastructures.StructTimeStepInfo,) if self.settings['save_linear']: self.ClassesToSave += (sharpy.solvers.linearassembler.Linear, sharpy.linear.assembler.linearaeroelastic.LinearAeroelastic, sharpy.linear.assembler.linearbeam.LinearBeam, sharpy.linear.assembler.linearuvlm.LinearUVLM, sharpy.linear.src.libss.StateSpace, sharpy.linear.src.lingebm.FlexDynamic,) if self.settings['save_linear_uvlm']: self.ClassesToSave += (sharpy.solvers.linearassembler.Linear, sharpy.linear.src.libss.ss_block) self.caller = caller def remove_file_if_exist(self, filepath): if os.path.isfile(filepath): os.remove(filepath) def run(self, **kwargs): online = settings_utils.set_value_or_default(kwargs, 'online', False) # Use the following statement in case the ct types are not defined and # you need them on uvlm3d # self.data.aero.timestep_info[-1].generate_ctypes_pointers() if ((online and (self.data.ts % self.settings['stride'] == 0)) or (not online)): if self.settings['format'] == 'h5': file_exists = os.path.isfile(self.filename) hdfile = h5py.File(self.filename, 'a') if (online and file_exists): self.save_timestep(self.data, self.settings, self.data.ts, hdfile) else: skip_attr_init = copy.deepcopy(self.settings['skip_attr']) skip_attr_init.append('timestep_info') h5utils.add_as_grp(self.data, hdfile, grpname='data', ClassesToSave=self.ClassesToSave, SkipAttr=skip_attr_init, compress_float=self.settings['compress_float']) if self.settings['save_struct']: h5utils.add_as_grp(list(), hdfile['data']['structure'], grpname='timestep_info') if self.settings['save_aero']: h5utils.add_as_grp(list(), hdfile['data']['aero'], grpname='timestep_info') if self.settings['save_nonlifting']: h5utils.add_as_grp(list(), hdfile['data']['nonlifting_body'], grpname='timestep_info') for it in range(len(self.data.structure.timestep_info)): tstep_p = self.data.structure.timestep_info[it] if tstep_p is not None: self.save_timestep(self.data, self.settings, it, hdfile) hdfile.close() if self.settings['save_linear_uvlm']: linhdffile = h5py.File(self.filename.replace('.data.h5', '.uvlmss.h5'), 'a') self.remove_file_if_exist(linhdffile) h5utils.add_as_grp(self.data.linear.linear_system.uvlm.ss, linhdffile, grpname='ss', ClassesToSave=self.ClassesToSave, SkipAttr=self.settings['skip_attr'], compress_float=self.settings['compress_float']) h5utils.add_as_grp(self.data.linear.linear_system.linearisation_vectors, linhdffile, grpname='linearisation_vectors', ClassesToSave=self.ClassesToSave, SkipAttr=self.settings['skip_attr'], compress_float=self.settings['compress_float']) linhdffile.close() if self.settings['save_linear']: self.remove_file_if_exist(self.filename_linear) with h5py.File(self.filename_linear, 'a') as linfile: h5utils.add_as_grp(self.data.linear.linear_system.linearisation_vectors, linfile, grpname='linearisation_vectors', ClassesToSave=self.ClassesToSave, SkipAttr=self.settings['skip_attr'], compress_float=self.settings['compress_float']) h5utils.add_as_grp(self.data.linear.ss, linfile, grpname='ss', ClassesToSave=self.ClassesToSave, SkipAttr=self.settings['skip_attr'], compress_float=self.settings['compress_float']) if self.settings['save_rom']: try: for k, rom in self.data.linear.linear_system.uvlm.rom.items(): romhdffile = self.filename.replace('.data.h5', '_{:s}.rom.h5'.format(k.lower())) self.remove_file_if_exist(romhdffile) rom.save(romhdffile) except AttributeError: cout.cout_wrap('Could not locate a reduced order model to save') elif self.settings['format'] == 'mat': from scipy.io import savemat if self.settings['save_linear']: # reference-forces linearisation_vectors = self.data.linear.linear_system.linearisation_vectors matfilename = self.filename.replace('.data.h5', '.linss.mat') A, B, C, D = self.data.linear.ss.get_mats() savedict = {'A': A, 'B': B, 'C': C, 'D': D} for k, v in linearisation_vectors.items(): savedict[k] = v dt = self.data.linear.ss.dt if dt is not None: savedict['dt'] = dt savemat(matfilename, savedict) if self.settings['save_linear_uvlm']: matfilename = self.filename.replace('.data.h5', '.uvlmss.mat') linearisation_vectors = self.data.linear.linear_system.uvlm.linearisation_vectors A, B, C, D = self.data.linear.linear_system.uvlm.ss.get_mats() savedict = {'A': A, 'B': B, 'C': C, 'D': D} for k, v in linearisation_vectors.items(): savedict[k] = v dt = self.data.linear.linear_system.uvlm.ss.dt if dt is not None: savedict['dt'] = dt savemat(matfilename, savedict) return self.data @staticmethod def save_timestep(data, settings, ts, hdfile): if settings['save_aero']: h5utils.add_as_grp(data.aero.timestep_info[ts], hdfile['data']['aero']['timestep_info'], grpname=("%05d" % ts), ClassesToSave=(sharpy.utils.datastructures.AeroTimeStepInfo,), SkipAttr=settings['skip_attr'], compress_float=settings['compress_float']) if settings['save_nonlifting']: h5utils.add_as_grp(data.nonlifting_body.timestep_info[ts], hdfile['data']['nonlifting_body']['timestep_info'], grpname=("%05d" % ts), ClassesToSave=(sharpy.utils.datastructures.NonliftingBodyTimeStepInfo,), SkipAttr=settings['skip_attr'], compress_float=settings['compress_float']) if settings['save_struct']: tstep = data.structure.timestep_info[ts] h5utils.add_as_grp(tstep, hdfile['data']['structure']['timestep_info'], grpname=("%05d" % ts), ClassesToSave=(sharpy.utils.datastructures.StructTimeStepInfo,), SkipAttr=settings['skip_attr'], compress_float=settings['compress_float']) ================================================ FILE: sharpy/postproc/saveparametriccase.py ================================================ from sharpy.utils.solver_interface import solver, BaseSolver, initialise_solver import sharpy.utils.settings as settings_utils import configobj import os import sharpy.utils.cout_utils as cout import warnings @solver class SaveParametricCase(BaseSolver): """ SaveParametricCase is a post-processor that creates a ConfigParser text file called ``.pmor.sharpy`` that contains information on certain simulation parameters. It is useful as a record keeper if you are doing a parametric study and for parametric model interpolation. If the setting ``save_case`` is selected and the post processor :class:`~sharpy.solvers.pickledata.PickleData` is not present in the SHARPy flow, this solver will pickle the data to the path given in the ``folder`` setting. The setting ``save_pmor_items`` saves to h5 the following state-spaces and gains, if present: * Aeroelastic state-space saved to: / save_pmor_data / _statespace.h5 * Aerodynamic ROM reduced order bases saved to: / save_pmor_data / _aerorob.h5 * Structural ROM reduced order bases saved to: / save_pmor_data / _modal_structrob.h5 The setting ``save_pmor_subsystem saves the additional state-spaces to h5 files: * Structural matrices saved to: / save_pmor_data / _struct_matrices.h5 * Structural state-space saved to: / save_pmor_data / _beamstatespace.h5 * Aerodynamic state-space saved to: / save_pmor_data / _aerostatespace.h5 Examples: In the case you are running several SHARPy cases, varying for instance the velocity, the settings would be something like: >>> parameter_value = 10 # parameter of study >>> input_settings = {'': value # the name of the parameter is at the user's discretion >>> } # add more parameters as required The result would be the ``.pmor.sharpy`` file with the following content: .. code-block:: none [parameters] = value """ solver_id = 'SaveParametricCase' solver_classification = 'post-processor' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['save_case'] = 'bool' settings_default['save_case'] = False settings_description['save_case'] = 'DeprecationWarning - Save a .pkl of the SHARPy case. Required for PMOR.' settings_types['parameters'] = 'dict' settings_default['parameters'] = None settings_description['parameters'] = 'Dictionary containing the chosen simulation parameters and their values.' settings_types['save_pmor_items'] = 'bool' settings_default['save_pmor_items'] = False settings_description['save_pmor_items'] = 'Saves to h5 the items required for PMOR interpolation: the aerodynamic ' \ 'reduced order bases, the structural modal matrix and the ' \ 'reduced state-space' settings_types['save_pmor_subsystems'] = 'bool' settings_default['save_pmor_subsystems'] = False settings_description['save_pmor_subsystems'] = 'Saves to h5 the statespaces and matrices of the UVLM and beam and ' \ 'the M, C, K matrices. The setting ``save_pmor_items`` ' \ 'should be set to `on`' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None self.folder = None self.case_name = None def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) self.folder = data.output_folder self.case_name = self.data.settings['SHARPy']['case'] def run(self, **kwargs): online = settings_utils.set_value_or_default(kwargs, 'online', False) restart = settings_utils.set_value_or_default(kwargs, 'restart', False) config = configobj.ConfigObj() file_name = self.folder + '/' + self.data.settings['SHARPy']['case'] + '.pmor.sharpy' config.filename = file_name config['parameters'] = dict() for k, v in self.settings['parameters'].items(): cout.cout_wrap('\tWriting parameter %s: %s' % (k, str(v)), 1) config['parameters'][k] = v sim_info = dict() sim_info['case'] = self.data.settings['SHARPy']['case'] if 'PickleData' not in self.data.settings['SHARPy']['flow'] and self.settings['save_case']: warnings.warn('Post-proc: SaveParametricCase: Saving a pickle is not recommended - try saving required ' 'attributes individually', DeprecationWarning) pickle_solver = initialise_solver('PickleData') pickle_solver.initialise(self.data, restart=restart) self.data = pickle_solver.run() sim_info['path_to_data'] = os.path.abspath(self.folder) sim_info['path_to_data'] = os.path.abspath(self.folder) config['sim_info'] = sim_info config.write() if self.settings['save_pmor_items']: try: self.data.linear except AttributeError: pass else: if not os.path.exists(self.folder + '/save_pmor_data/'): os.makedirs(self.folder + '/save_pmor_data/') self.save_state_space() self.save_aero_rom_bases() self.save_structural_modal_matrix() if self.settings['save_pmor_subsystems']: self.save_structural_matrices() self.save_aero_state_space() return self.data def save_aero_rom_bases(self): """Save the aerodynamic reduced order bases to h5 files in the output directory""" # check rom's exist rom_dict = self.data.linear.linear_system.uvlm.rom if rom_dict is None: return None for rom_name, rom_class in rom_dict.items(): rom_class.save_reduced_order_bases(self.base_name + f'_{rom_name.lower()}_aerorob.h5') def save_structural_modal_matrix(self): if not self.data.linear.linear_system.beam.sys.modal: return None self.data.linear.linear_system.beam.save_reduced_order_bases(self.base_name + f'_modal_structrob.h5') def save_state_space(self): self.data.linear.linear_system.ss.save(self.base_name + '_statespace.h5') def save_structural_matrices(self): self.data.linear.linear_system.beam.ss.save(self.base_name + '_beamstatespace.h5') self.data.linear.linear_system.beam.save_structural_matrices(self.base_name + '_struct_matrices.h5') def save_aero_state_space(self): self.data.linear.linear_system.uvlm.ss.save(self.base_name + '_aerostatespace.h5') @property def base_name(self): return self.folder + '/save_pmor_data/' + self.case_name ================================================ FILE: sharpy/postproc/stabilityderivatives.py ================================================ import sharpy.utils.solver_interface as solver_interface import os import numpy as np import sharpy.utils.cout_utils as cout import sharpy.utils.settings as settings_utils from sharpy.linear.utils.derivatives import Derivatives @solver_interface.solver class StabilityDerivatives(solver_interface.BaseSolver): """ Outputs the stability derivatives of a free-flying aircraft """ solver_id = 'StabilityDerivatives' solver_classification = 'post-processor' settings_default = dict() settings_description = dict() settings_types = dict() settings_options = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Display info to screen' settings_types['u_inf'] = 'float' settings_default['u_inf'] = 1. settings_description['u_inf'] = 'Free stream reference velocity' settings_types['S_ref'] = 'float' settings_default['S_ref'] = 1. settings_description['S_ref'] = 'Reference planform area' settings_types['b_ref'] = 'float' settings_default['b_ref'] = 1. settings_description['b_ref'] = 'Reference span' settings_types['c_ref'] = 'float' settings_default['c_ref'] = 1. settings_description['c_ref'] = 'Reference chord' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = dict() self.u_inf = 1 self.inputs = 0 self.caller = None self.folder = None self.ppal_axes = None self.n_control_surfaces = None self.coefficients = dict # type: dict # name: scaling coefficient def initialise(self, data, custom_settings=None, caller=None, restart=False): self.data = data if custom_settings: self.settings = custom_settings else: self.settings = self.data.settings[self.solver_id] settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, options=self.settings_options, no_ctype=True) self.caller = caller self.folder = data.output_folder + '/derivatives/' if not os.path.isdir(self.folder): os.makedirs(self.folder, exist_ok=True) u_inf = self.settings['u_inf'] s_ref = self.settings['S_ref'] b_ref = self.settings['b_ref'] c_ref = self.settings['c_ref'] rho = self.data.linear.tsaero0.rho self.ppal_axes = self.data.settings['Modal']['rigid_modes_ppal_axes'] # need to decide whether coefficients stay here or goes just in Derivatives class self.coefficients = {'force': 0.5 * rho * u_inf ** 2 * s_ref, 'moment_lon': 0.5 * rho * u_inf ** 2 * s_ref * c_ref, 'moment_lat': 0.5 * rho * u_inf ** 2 * s_ref * b_ref, 'force_angular_vel': 0.5 * rho * u_inf ** 2 * s_ref * c_ref / u_inf, 'moment_lon_angular_vel': 0.5 * rho * u_inf ** 2 * s_ref * c_ref * c_ref / u_inf} # missing rates reference_dimensions = {} for k in ['S_ref', 'b_ref', 'c_ref', 'u_inf']: reference_dimensions[k] = self.settings[k] reference_dimensions['rho'] = rho reference_dimensions['quat'] = self.data.linear.tsstruct0.quat self.data.linear.derivatives = dict() # {str:Derivatives()} (sharpy.linear.utils.derivatives.Derivatives) self.data.linear.derivatives['aerodynamic'] = Derivatives(reference_dimensions, static_state=self.steady_aero_forces(), target_system='aerodynamic') self.data.linear.derivatives['aeroelastic'] = Derivatives(reference_dimensions, static_state=self.steady_aero_forces(), target_system='aeroelastic') def run(self, **kwargs): online = settings_utils.set_value_or_default(kwargs, 'online', False) # TODO: consider running all required solvers inside this one to keep the correct settings # i.e: run Modal, Linear Assembly derivatives = self.data.linear.derivatives # {str:Derivatives()} (sharpy.linear.utils.derivatives.Derivatives) if self.data.linear.linear_system.beam.sys.modal: phi = self.data.linear.linear_system.linearisation_vectors['mode_shapes'].real else: phi = None steady_forces = self.data.linear.linear_system.linearisation_vectors['forces_aero_beam_dof'] v0 = self.get_freestream_velocity() quat = self.data.linear.tsstruct0.quat try: tpa = self.data.linear.tsstruct0.modal['t_pa'] except KeyError: tpa = None if self.data.linear.linear_system.uvlm.scaled: raise NotImplementedError('Stability Derivatives not yet implented for scaled system') self.data.linear.linear_system.update(self.settings['u_inf']) for target_system in ['aerodynamic', 'aeroelastic']: state_space = self.get_state_space(target_system) target_system_derivatives = derivatives[target_system] target_system_derivatives.initialise_derivatives(state_space, steady_forces, quat, v0, phi, self.data.linear.tsstruct0.modal['cg'], tpa=tpa) target_system_derivatives.dict_of_derivatives[ 'force_angle_velocity'] = target_system_derivatives.new_derivative( 'stability', 'angle_derivatives', 'Force/Angle via velocity') # useful to double check the effect of the ``track_body`` == 'on' setting # current_derivative.dict_of_derivatives['force_angle_angle'] = current_derivative.new_derivative( # 'stability', # 'angle_derivatives_tb', # 'Force/Angle via Track Body') target_system_derivatives.dict_of_derivatives['force_velocity'] = target_system_derivatives.new_derivative( 'body', 'body_derivatives') target_system_derivatives.dict_of_derivatives['force_cs'] = target_system_derivatives.new_derivative( 'body', 'control_surface_derivatives') target_system_derivatives.save(self.folder) target_system_derivatives.savetxt(self.folder) return self.data def get_freestream_velocity(self): try: u_inf = self.data.settings['StaticUvlm']['aero_solver_settings']['u_inf'] u_inf_direction = self.data.settings['StaticCoupled']['aero_solver_settings']['u_inf_direction'] except KeyError: try: u_inf = self.data.settings['StaticCoupled']['aero_solver_settings']['velocity_field_input']['u_inf'] u_inf_direction = self.data.settings['StaticCoupled']['aero_solver_settings']['velocity_field_input']['u_inf_direction'] except KeyError: cout.cout_wrap('Unable to find free stream velocity settings in StaticUvlm or StaticCoupled,' 'please ensure these settings are provided in the config .sharpy file. If' 'you are running a restart simulation make sure they are included too, regardless' 'of these solvers being present in the SHARPy flow', 4) raise KeyError try: v0 = u_inf * u_inf_direction * -1 except TypeError: # For restart solutions, where the settings may have not been processed and thus may # exist but in string format try: u_inf_direction = np.array(u_inf_direction, dtype=float) except ValueError: if u_inf_direction.find(',') < 0: u_inf_direction = np.fromstring(u_inf_direction.strip('[]'), sep=' ', dtype=float) else: u_inf_direction = np.fromstring(u_inf_direction.strip('[]'), sep=',', dtype=float) finally: v0 = np.array(u_inf_direction, dtype=float) * float(u_inf) * -1 return v0 def get_state_space(self, target_system): if target_system == 'aerodynamic': ss = self.data.linear.linear_system.uvlm.ss elif target_system == 'aeroelastic': ss = self.data.linear.ss else: raise NameError('Unknown target system {:s}'.format(target_system)) return ss def steady_aero_forces(self): fx = np.sum(self.data.aero.timestep_info[0].inertial_steady_forces[:, 0], 0) + \ np.sum(self.data.aero.timestep_info[0].inertial_unsteady_forces[:, 0], 0) fy = np.sum(self.data.aero.timestep_info[0].inertial_steady_forces[:, 1], 0) + \ np.sum(self.data.aero.timestep_info[0].inertial_unsteady_forces[:, 1], 0) fz = np.sum(self.data.aero.timestep_info[0].inertial_steady_forces[:, 2], 0) + \ np.sum(self.data.aero.timestep_info[0].inertial_unsteady_forces[:, 2], 0) return fx, fy, fz ================================================ FILE: sharpy/postproc/stallcheck.py ================================================ import numpy as np import sharpy.utils.cout_utils as cout from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils from sharpy.utils.datastructures import init_matrix_structure, standalone_ctypes_pointer import sharpy.aero.utils.uvlmlib as uvlmlib @solver class StallCheck(BaseSolver): """ Outputs the incidence angle of every panel of the surface as cell variables for visualisation in Paraview. Note: This postprocessor appends the information to the current SHARPy timestep being run, therefore, in order to visualise the result in Paraview, it must be run prior to `AerogridPlot`. Otherwise, the panel stall check will be performed but the actual angle not produced in the Paraview visualisation. It also checks that the angles do not exceed the specified limit, with a warning in the log if the angle of attack exceeds such limits (both positive and negative). The limits are set through the setting ``airfoil_stall_angles``, which takes a dictionary where the key is the ID to the airfoil (in string format) and the value is a 2-tuple containing the negative and positive limits in radians. """ solver_id = 'StallCheck' solver_classification = 'post-processor' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Print info to screen ' settings_types['airfoil_stall_angles'] = 'dict' settings_default['airfoil_stall_angles'] = dict() settings_description['airfoil_stall_angles'] = 'Dictionary of stall angles for each airfoil as per the details ' \ 'above' settings_types['output_degrees'] = 'bool' settings_default['output_degrees'] = False settings_description['output_degrees'] = 'Output incidence angles in degrees vs radians' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.settings = None self.data = None self.ts_max = None self.ts = None self.caller = None def initialise(self, data, custom_settings=None, caller=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) self.ts_max = len(self.data.structure.timestep_info) self.caller = caller def run(self, **kwargs): online = settings_utils.set_value_or_default(kwargs, 'online', False) if not online: for self.ts in range(self.ts_max): self.check_stall() cout.cout_wrap('...Finished', 1) else: self.ts = len(self.data.structure.timestep_info) - 1 self.check_stall() return self.data def check_stall(self): # add entry to dictionary for postproc tstep = self.data.aero.timestep_info[self.ts] tstep.postproc_cell['incidence_angle'] = init_matrix_structure(dimensions=tstep.dimensions, with_dim_dimension=False) # create ctypes pointers tstep.postproc_cell['incidence_angle_ct_list'] = None tstep.postproc_cell['incidence_angle_ct_pointer'] = None tstep.postproc_cell['incidence_angle_ct_list'], tstep.postproc_cell['incidence_angle_ct_pointer'] = \ standalone_ctypes_pointer(tstep.postproc_cell['incidence_angle']) # call calculate uvlmlib.uvlm_calculate_incidence_angle(self.data.aero.timestep_info[self.ts], self.data.structure.timestep_info[self.ts]) # calculate ratio of stalled panels and print stalled_panels = False stalled_surfs = np.zeros((tstep.n_surf, ), dtype=int) added_panels = [] for i_surf in range(tstep.n_surf): added_panels.append([]) for i_elem in range(self.data.structure.num_elem): for i_local_node in range(self.data.structure.num_node_elem): airfoil_id = self.data.aero.data_dict['airfoil_distribution'][i_elem, i_local_node] if self.settings['airfoil_stall_angles']: i_global_node = self.data.structure.connectivities[i_elem, i_local_node] for i_dict in self.data.aero.struct2aero_mapping[i_global_node]: i_surf = i_dict['i_surf'] i_n = i_dict['i_n'] if i_n in added_panels[i_surf]: continue if i_n == tstep.dimensions[i_surf][1]: continue limits = self.settings['airfoil_stall_angles'][str(airfoil_id)] if tstep.postproc_cell['incidence_angle'][i_surf][0, i_n] < float(limits[0]): stalled_panels = True stalled_surfs[i_surf] += tstep.postproc_cell['incidence_angle'][i_surf].shape[1] elif tstep.postproc_cell['incidence_angle'][i_surf][0, i_n] > float(limits[1]): stalled_panels = True stalled_surfs[i_surf] += tstep.postproc_cell['incidence_angle'][i_surf].shape[1] if stalled_panels: if self.settings['print_info']: cout.cout_wrap('Some panel has an incidence angle out of the linear region', 1) cout.cout_wrap('The number of stalled panels per surface id are:', 1) for i_surf in range(tstep.n_surf): cout.cout_wrap('\ti_surf = ' + str(i_surf) + ': ' + str(stalled_surfs[i_surf]) + ' panels.', 1) # cout.cout_wrap('In total, the ratio of stalled panels is: ', str(stalled_surfs.sum()/)) if self.settings['output_degrees']: for i_surf in range(tstep.n_surf): tstep.postproc_cell['incidence_angle'][i_surf] *= 180/np.pi ================================================ FILE: sharpy/postproc/udpout.py ================================================ from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils import sharpy.io.network_interface as network_interface import sharpy.utils.cout_utils as cout @solver class UDPout(BaseSolver): """ Send output data via UDP This post-processor is in essence a wrapper of the :class:`~sharpy.io.network_interface.NetworkLoader` where only an output network adapter is created. """ solver_id = 'UDPout' solver_classification = 'post-processor' settings_types = network_interface.NetworkLoader.settings_types.copy() settings_default = network_interface.NetworkLoader.settings_default.copy() settings_description = network_interface.NetworkLoader.settings_description.copy() # Remove unnecessary settings from NetworkLoader (all related to the inputs) del settings_default['input_network_settings'] del settings_types['input_network_settings'] del settings_description['input_network_settings'] del settings_default['received_data_filename'] del settings_types['received_data_filename'] del settings_description['received_data_filename'] del settings_types['send_output_to_all_clients'] del settings_default['send_output_to_all_clients'] del settings_description['send_output_to_all_clients'] table = settings_utils.SettingsTable() __doc__ += table.generate(settings_types, settings_default, settings_description, header_line='This post-processor takes in the following settings, for a more ' 'detailed description see ' ':class:`~sharpy.io.network_interface.NetworkLoader`') def __init__(self): self.settings = None self.data = None self.network_loader = None self.out_network = None self.set_of_variables = None self.ts_max = 0 self.caller = None def initialise(self, data, custom_settings=None, caller=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings self.network_loader = network_interface.NetworkLoader() self.network_loader.initialise(in_settings=self.settings) self.set_of_variables = self.network_loader.get_inout_variables() self.out_network = self.network_loader.get_networks(networks='out') self.ts_max = self.data.ts + 1 self.caller = caller def run(self, **kwargs): online = settings_utils.set_value_or_default(kwargs, 'online', False) if online: self.set_of_variables.get_value(self.data) msg = self.set_of_variables.encode() self.out_network.send(msg, self.out_network.clients) else: for ts_index in range(self.ts_max): self.set_of_variables.get_value(self.data, timestep_index=ts_index) msg = self.set_of_variables.encode() self.out_network.send(msg, self.out_network.clients) cout.cout_wrap('...Finished', 1) self.shutdown() return self.data def shutdown(self): self.out_network.close() ================================================ FILE: sharpy/postproc/writevariablestime.py ================================================ import os import numpy as np from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils @solver class WriteVariablesTime(BaseSolver): r""" Write variables with time ``WriteVariablesTime`` is a class inherited from ``BaseSolver`` It is a postprocessor that outputs the value of variables with time onto a text file. Attributes: settings_types (dict): Acceptable data types of the input data settings_default (dict): Default values for input data should the user not provide them See the list of arguments dir (str): directory to output the information """ solver_id = 'WriteVariablesTime' solver_classification = 'post-processor' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['delimiter'] = 'str' settings_default['delimiter'] = ' ' settings_description['delimiter'] = 'Delimiter to be used in the output file' settings_types['FoR_variables'] = 'list(str)' settings_default['FoR_variables'] = [''] settings_description['FoR_variables'] = 'Variables of :class:`~sharpy.utils.datastructures.StructTimeStepInfo` associated to the frame of reference to be writen' settings_types['FoR_number'] = 'list(int)' settings_default['FoR_number'] = np.array([0], dtype=int) settings_description['FoR_number'] = 'Number of the A frame of reference to output (for multibody configurations)' settings_types['structure_variables'] = 'list(str)' settings_default['structure_variables'] = [''] settings_description['structure_variables'] = 'Variables of :class:`~sharpy.utils.datastructures.StructTimeStepInfo` associated to the frame of reference to be writen' settings_types['structure_nodes'] = 'list(int)' settings_default['structure_nodes'] = np.array([-1]) settings_description['structure_nodes'] = 'Number of the nodes to be writen' settings_types['nonlifting_nodes_variables'] = 'list(str)' settings_default['nonlifting_nodes_variables'] = [''] settings_description['nonlifting_nodes_variables'] = 'Variables of :class:`~sharpy.utils.datastructures.NonliftingBodyTimeStepInfo` associated to panels to be writen' settings_types['nonlifting_nodes_im'] = 'list(int)' settings_default['nonlifting_nodes_im'] = np.array([0]) settings_description['nonlifting_nodes_im'] = 'Chordwise index of the nonlifting panels to be output' settings_types['nonlifting_nodes_in'] = 'list(int)' settings_default['nonlifting_nodes_in'] = np.array([0]) settings_description['nonlifting_nodes_in'] = 'Spanwise index of the nonlifting panels to be output' settings_types['nonlifting_nodes_isurf'] = 'list(int)' settings_default['nonlifting_nodes_isurf'] = np.array([0]) settings_description['nonlifting_nodes_isurf'] = "Number of the panels' surface to be output" settings_types['aero_panels_variables'] = 'list(str)' settings_default['aero_panels_variables'] = [''] settings_description['aero_panels_variables'] = 'Variables of :class:`~sharpy.utils.datastructures.AeroTimeStepInfo` associated to panels to be writen' settings_types['aero_panels_isurf'] = 'list(int)' settings_default['aero_panels_isurf'] = np.array([0]) settings_description['aero_panels_isurf'] = "Number of the panels' surface to be output" settings_types['aero_panels_im'] = 'list(int)' settings_default['aero_panels_im'] = np.array([0]) settings_description['aero_panels_im'] = 'Chordwise index of the panels to be output' settings_types['aero_panels_in'] = 'list(int)' settings_default['aero_panels_in'] = np.array([0]) settings_description['aero_panels_in'] = 'Spanwise index of the panels to be output' settings_types['aero_nodes_variables'] = 'list(str)' settings_default['aero_nodes_variables'] = [''] settings_description['aero_nodes_variables'] = 'Variables of :class:`~sharpy.utils.datastructures.AeroTimeStepInfo` associated to nodes to be writen' settings_types['aero_nodes_isurf'] = 'list(int)' settings_default['aero_nodes_isurf'] = np.array([0]) settings_description['aero_nodes_isurf'] = "Number of the nodes' surface to be output" settings_types['aero_nodes_im'] = 'list(int)' settings_default['aero_nodes_im'] = np.array([0]) settings_description['aero_nodes_im'] = 'Chordwise index of the nodes to be output' settings_types['aero_nodes_in'] = 'list(int)' settings_default['aero_nodes_in'] = np.array([0]) settings_description['aero_nodes_in'] = 'Spanwise index of the nodes to be output' settings_types['cleanup_old_solution'] = 'bool' settings_default['cleanup_old_solution'] = False settings_description['cleanup_old_solution'] = 'Remove the existing files' settings_types['vel_field_variables'] = 'list(str)' settings_default['vel_field_variables'] = list() settings_description['vel_field_variables'] = 'Variables associated to the velocity field. Only ``uext`` implemented so far' settings_types['vel_field_points'] = 'list(float)' settings_default['vel_field_points'] = np.array([0., 0., 0.]) settings_description['vel_field_points'] = 'List of coordinates of the control points as x1, y1, z1, x2, y2, z2 ...' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.settings = None self.data = None self.folder = None self.n_velocity_field_points = None self.velocity_field_points = None self.caller = None self.velocity_generator = None def initialise(self, data, custom_settings=None, caller=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) self.folder = data.output_folder + '/WriteVariablesTime/' if not os.path.isdir(self.folder): os.makedirs(self.folder) # Check inputs if not ((len(self.settings['aero_panels_isurf']) == len(self.settings['aero_panels_im'])) and (len(self.settings['aero_panels_isurf']) == len(self.settings['aero_panels_in']))): raise RuntimeError("aero_panels should be defined as [i_surf,i_m,i_n]") if not ((len(self.settings['aero_nodes_isurf']) == len(self.settings['aero_nodes_im'])) and (len(self.settings['aero_nodes_isurf']) == len(self.settings['aero_nodes_in']))): raise RuntimeError("aero_nodes should be defined as [i_surf,i_m,i_n]") if len(self.settings['vel_field_variables']) > 0: if not (len(self.settings['vel_field_points']) % 3 == 0): raise RuntimeError('Number of entries in ``vel_field_points`` has to be a multiple of 3') else: self.n_vel_field_points = len(self.settings['vel_field_points']) // 3 self.vel_field_points = [np.zeros((3, self.n_vel_field_points, 1))] for ipoint in range(self.n_vel_field_points): self.vel_field_points[0][:, ipoint, 0] = self.settings['vel_field_points'][ipoint*3:(ipoint + 1)*3] # Initialise files with headers and clean them if required for ivariable in range(len(self.settings['FoR_variables'])): for ifor in range(len(self.settings['FoR_number'])): filename = self.folder + "FoR_" + '%02d' % self.settings['FoR_number'][ifor] + "_" + self.settings['FoR_variables'][ivariable] + ".dat" if self.settings['cleanup_old_solution']: if os.path.isfile(filename): os.remove(filename) # Structure variables at nodes for ivariable in range(len(self.settings['structure_variables'])): for inode in range(len(self.settings['structure_nodes'])): node = self.settings['structure_nodes'][inode] filename = self.folder + "struct_" + self.settings['structure_variables'][ivariable] + "_node" + str(node) + ".dat" if self.settings['cleanup_old_solution']: if os.path.isfile(filename): os.remove(filename) # Nonlifting variables at panels for ivariable in range(len(self.settings['nonlifting_nodes_variables'])): for ipanel in range(len(self.settings['nonlifting_nodes_isurf'])): i_surf = self.settings['nonlifting_nodes_isurf'][ipanel] i_m = self.settings['nonlifting_nodes_im'][ipanel] i_n = self.settings['nonlifting_nodes_in'][ipanel] filename = self.folder + "nonlifting_" + self.settings['nonlifting_nodes_variables'][ivariable] + "_panel" + "_isurf" + str(i_surf) + "_im"+ str(i_m) + "_in"+ str(i_n) + ".dat" if self.settings['cleanup_old_solution']: if os.path.isfile(filename): os.remove(filename) # Aerodynamic variables at panels for ivariable in range(len(self.settings['aero_panels_variables'])): for ipanel in range(len(self.settings['aero_panels_isurf'])): i_surf = self.settings['aero_panels_isurf'][ipanel] i_m = self.settings['aero_panels_im'][ipanel] i_n = self.settings['aero_panels_in'][ipanel] filename = self.folder + "aero_" + self.settings['aero_panels_variables'][ivariable] + "_panel" + "_isurf" + str(i_surf) + "_im"+ str(i_m) + "_in"+ str(i_n) + ".dat" if self.settings['cleanup_old_solution']: if os.path.isfile(filename): os.remove(filename) # Aerodynamic variables at nodes for ivariable in range(len(self.settings['aero_nodes_variables'])): for inode in range(len(self.settings['aero_nodes_isurf'])): i_surf = self.settings['aero_nodes_isurf'][inode] i_m = self.settings['aero_nodes_im'][inode] i_n = self.settings['aero_nodes_in'][inode] filename = self.folder + "aero_" + self.settings['aero_nodes_variables'][ivariable] + "_node" + "_isurf" + str(i_surf) + "_im"+ str(i_m) + "_in"+ str(i_n) + ".dat" if self.settings['cleanup_old_solution']: if os.path.isfile(filename): os.remove(filename) # Velocity field variables at points for ivariable in range(len(self.settings['vel_field_variables'])): for ipoint in range(self.n_vel_field_points): filename = self.folder + "vel_field_" + self.settings['vel_field_variables'][ivariable] + "_point" + str(ipoint) + ".dat" if self.settings['cleanup_old_solution']: if os.path.isfile(filename): os.remove(filename) if not os.path.isfile(filename): fid = open(filename, 'w') fid.write(("#t[s]%suext_x[m/s]%suext_y[m/s]%suext_z[m/s]\n" % ((self.settings['delimiter'],)*3))) fid.close() # Initialise velocity generator self.caller = caller if ((not self.caller is None) and (not len(self.settings['vel_field_variables']) == 0)): if self.caller.solver_classification.lower() == 'aero': # For aerodynamic solvers self.velocity_generator = self.caller.velocity_generator elif self.caller.solver_classification.lower() == 'coupled': # For coupled solvers self.velocity_generator = self.caller.aero_solver.velocity_generator def run(self, **kwargs): online = settings_utils.set_value_or_default(kwargs, 'online', False) if online: self.data = self.write(-1) else: for it in range(len(self.data.structure.timestep_info)): if self.data.structure.timestep_info[it] is not None: self.data = self.write(it) return self.data def write(self, it): # FoR variables if 'FoR_number' in self.settings: pass else: self.settings['FoR_number'] = np.array([0], dtype=int) tstep = self.data.structure.timestep_info[it] for ivariable in range(len(self.settings['FoR_variables'])): if self.settings['FoR_variables'][ivariable] == '': continue for ifor in range(len(self.settings['FoR_number'])): filename = self.folder + "FoR_" + '%02d' % self.settings['FoR_number'][ifor] + "_" + self.settings['FoR_variables'][ivariable] + ".dat" with open(filename, 'a') as fid: var = np.atleast_2d(getattr(tstep, self.settings['FoR_variables'][ivariable])) rows, cols = var.shape if ((cols == 1) and (rows == 1)): self.write_value_to_file(fid, self.data.ts, var, self.settings['delimiter']) elif ((cols > 1) and (rows == 1)): self.write_nparray_to_file(fid, self.data.ts, var, self.settings['delimiter']) elif ((cols == 1) and (rows >= 1)): self.write_value_to_file(fid, self.data.ts, var[ifor], self.settings['delimiter']) else: self.write_nparray_to_file(fid, self.data.ts, var[ifor,:], self.settings['delimiter']) # Structure variables at nodes for ivariable in range(len(self.settings['structure_variables'])): if self.settings['structure_variables'][ivariable] == '': continue #TODO: fix for lack of g frame description in nonlineardynamicmultibody.py if tstep.mb_dict is None: var = getattr(tstep, self.settings['structure_variables'][ivariable]) else: if self.settings['structure_variables'][ivariable] == 'for_pos': import sharpy.utils.algebra as ag #TODO: uncomment for dynamic trim # try: # # import pdb # # pdb.set_trace() # cga = ag.euler2rot([0, self.data.trimmed_values[0], 0]) # cag = cga.T # tstep.for_pos[0:3] = np.dot(cga,tstep.for_pos[0:3]) # var = getattr(tstep, self.settings['structure_variables'][ivariable]).copy() # tstep.for_pos[0:3] = np.dot(cag,tstep.for_pos[0:3]) # except AttributeError: t0step = self.data.structure.timestep_info[0] tstep.for_pos[0:3] = np.dot(t0step.cga(), tstep.for_pos[0:3]) var = getattr(tstep, self.settings['structure_variables'][ivariable]).copy() tstep.for_pos[0:3] = np.dot(t0step.cag(), tstep.for_pos[0:3]) else: var = getattr(tstep, self.settings['structure_variables'][ivariable]) var = getattr(tstep, self.settings['structure_variables'][ivariable]) num_indices = len(var.shape) if num_indices == 1: # Beam global variables (i.e. not node dependant) filename = self.folder + "struct_" + self.settings['structure_variables'][ivariable] + ".dat" with open(filename, 'a') as fid: self.write_nparray_to_file(fid, self.data.ts, var, self.settings['delimiter']) else: # These variables have nodal values (i.e the number of indices is either 2 or 3) for inode in range(len(self.settings['structure_nodes'])): node = self.settings['structure_nodes'][inode] filename = self.folder + "struct_" + self.settings['structure_variables'][ivariable] + "_node" + str(node) + ".dat" with open(filename, 'a') as fid: if num_indices == 2: self.write_nparray_to_file(fid, self.data.ts, var[node,:], self.settings['delimiter']) elif num_indices == 3: ielem, inode_in_elem = self.data.structure.node_master_elem[node] self.write_nparray_to_file(fid, self.data.ts, var[ielem,inode_in_elem,:], self.settings['delimiter']) # Aerodynamic variables at nonlifting panels for ivariable in range(len(self.settings['nonlifting_nodes_variables'])): if self.settings['nonlifting_nodes_variables'][ivariable] == '': continue for ipanel in range(len(self.settings['nonlifting_nodes_isurf'])): i_surf = self.settings['nonlifting_nodes_isurf'][ipanel] i_m = self.settings['nonlifting_nodes_im'][ipanel] i_n = self.settings['nonlifting_nodes_in'][ipanel] filename = self.folder + "nonlifting_" + self.settings['nonlifting_nodes_variables'][ivariable] + "_panel" + "_isurf" + str(i_surf) + "_im"+ str(i_m) + "_in"+ str(i_n) + ".dat" with open(filename, 'a') as fid: var = getattr(self.data.nonlifting_body.timestep_info[it], self.settings['nonlifting_nodes_variables'][ivariable]) self.write_value_to_file(fid, self.data.ts, var[i_surf][i_m,i_n], self.settings['delimiter']) # Aerodynamic variables at panels for ivariable in range(len(self.settings['aero_panels_variables'])): if self.settings['aero_panels_variables'][ivariable] == '': continue for ipanel in range(len(self.settings['aero_panels_isurf'])): i_surf = self.settings['aero_panels_isurf'][ipanel] i_m = self.settings['aero_panels_im'][ipanel] i_n = self.settings['aero_panels_in'][ipanel] filename = self.folder + "aero_" + self.settings['aero_panels_variables'][ivariable] + "_panel" + "_isurf" + str(i_surf) + "_im"+ str(i_m) + "_in"+ str(i_n) + ".dat" with open(filename, 'a') as fid: var = getattr(self.data.aero.timestep_info[it], self.settings['aero_panels_variables'][ivariable]) self.write_value_to_file(fid, self.data.ts, var[i_surf][i_m,i_n], self.settings['delimiter']) # Aerodynamic variables at nodes for ivariable in range(len(self.settings['aero_nodes_variables'])): if self.settings['aero_nodes_variables'][ivariable] == '': continue for inode in range(len(self.settings['aero_nodes_isurf'])): i_surf = self.settings['aero_nodes_isurf'][inode] i_m = self.settings['aero_nodes_im'][inode] i_n = self.settings['aero_nodes_in'][inode] filename = self.folder + "aero_" + self.settings['aero_nodes_variables'][ivariable] + "_node" + "_isurf" + str(i_surf) + "_im"+ str(i_m) + "_in"+ str(i_n) + ".dat" with open(filename, 'a') as fid: var = getattr(self.data.aero.timestep_info[it], self.settings['aero_nodes_variables'][ivariable]) self.write_nparray_to_file(fid, self.data.ts, var[i_surf][:,i_m,i_n], self.settings['delimiter']) # Velocity field variables at points for ivariable in range(len(self.settings['vel_field_variables'])): if self.settings['vel_field_variables'][ivariable] == 'uext': uext = [np.zeros((3, self.n_vel_field_points, 1))] self.velocity_generator.generate({'zeta': self.vel_field_points, 'for_pos': tstep.for_pos[0:3], 't': self.data.ts*self.caller.settings['dt'], 'is_wake': False, 'override': True}, uext) for ipoint in range(self.n_vel_field_points): filename = self.folder + "vel_field_" + self.settings['vel_field_variables'][ivariable] + "_point" + str(ipoint) + ".dat" with open(filename, 'a') as fid: self.write_nparray_to_file(fid, self.data.ts, uext[0][:,ipoint,0], self.settings['delimiter']) return self.data def write_nparray_to_file(self, fid, ts, nparray, delimiter): fid.write("%d%s" % (ts,delimiter)) for idim in range(nparray.shape[0]): try: for jdim in range(nparray.shape[1] - 1): fid.write("%e%s" % (nparray[idim, jdim],delimiter)) fid.write("%e" % (nparray[idim, -1])) except IndexError: fid.write("%e%s" % (nparray[idim],delimiter)) fid.write("\n") def write_value_to_file(self, fid, ts, value, delimiter): fid.write("%d%s%e\n" % (ts,delimiter,value)) ================================================ FILE: sharpy/presharpy/__init__.py ================================================ ================================================ FILE: sharpy/presharpy/presharpy.py ================================================ import configparser import configobj import os import sharpy.utils.cout_utils as cout from sharpy.utils.solver_interface import solver, dict_of_solvers import sharpy.utils.settings as settings import sharpy.utils.exceptions as exceptions @solver class PreSharpy(object): """ The PreSharpy solver is the main loader solver of SHARPy. It takes the admin-like settings for the simulation, including the case name, case route and the list of solvers to run and in which order to run them. This order of solvers is referred to, throughout SHARPy, as the ``flow`` setting. This is a mandatory solver for all simulations at the start so it is never included in the ``flow`` setting. The settings for this solver are parsed through in the configuration file under the header ``SHARPy``. I.e, when you are defining the config file for a simulation, the settings for PreSharpy are included as: .. code-block:: python import configobj filename = '/.sharpy' config = configobj.ConfigObj() config.filename = filename config['SHARPy'] = {'case': '', # an example setting # Rest of your settings for the PreSHARPy class } """ solver_id = 'PreSharpy' solver_classification = 'loader' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['flow'] = 'list(str)' settings_default['flow'] = None settings_description['flow'] = "List of the desired solvers' ``solver_id`` to run in sequential order." settings_types['case'] = 'str' settings_default['case'] = 'default_case_name' settings_description['case'] = 'Case name' settings_types['route'] = 'str' settings_default['route'] = None settings_description['route'] = 'Route to case files' settings_types['write_screen'] = 'bool' settings_default['write_screen'] = True settings_description['write_screen'] = 'Display output on terminal screen' settings_types['write_log'] = 'bool' settings_default['write_log'] = False settings_description['write_log'] = 'Write log file' settings_types['log_folder'] = 'str' settings_default['log_folder'] = './output/' settings_description['log_folder'] = 'A folder with the case name will be created at this directory ' \ 'containing the SHARPy log and output folders' settings_types['log_file'] = 'str' settings_default['log_file'] = 'log' settings_description['log_file'] = 'Name of the log file' settings_types['save_settings'] = 'bool' settings_default['save_settings'] = False settings_description['save_settings'] = 'Save a copy of the settings to a ``.sharpy`` file in the output ' \ 'directory specified in ``log_folder``' settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, header_line='The following are the settings that the PreSharpy class takes:') def __init__(self, in_settings=None): self._settings = True if in_settings is None: # call for documentation only self._settings = False self.ts = 0 if self._settings: self.settings = in_settings self.settings['SHARPy']['flow'] = self.settings['SHARPy']['flow'] settings.to_custom_types(self.settings['SHARPy'], self.settings_types, self.settings_default) self.output_folder = self.settings['SHARPy']['log_folder'] + '/' + self.settings['SHARPy']['case'] + '/' if not os.path.isdir(self.output_folder): os.makedirs(self.output_folder) cout.cout_wrap.initialise(self.settings['SHARPy']['write_screen'], self.settings['SHARPy']['write_log'], self.output_folder, self.settings['SHARPy']['log_file']) self.case_route = in_settings['SHARPy']['route'] + '/' self.case_name = in_settings['SHARPy']['case'] for solver_name in in_settings['SHARPy']['flow']: try: dict_of_solvers[solver_name] except KeyError: exceptions.NotImplementedSolver(solver_name) cout.cout_wrap('SHARPy output folder set') cout.cout_wrap('\t' + self.output_folder, 1) if self.settings['SHARPy']['save_settings']: self.save_settings() def initialise(self): pass def update_settings(self, new_settings): self.settings = new_settings self.settings['SHARPy']['flow'] = self.settings['SHARPy']['flow'] settings.to_custom_types(self.settings['SHARPy'], self.settings_types, self.settings_default) self.output_folder = self.settings['SHARPy']['log_folder'] + '/' + self.settings['SHARPy']['case'] + '/' if not os.path.isdir(self.output_folder): os.makedirs(self.output_folder) cout.cout_wrap.initialise(self.settings['SHARPy']['write_screen'], self.settings['SHARPy']['write_log'], self.output_folder, self.settings['SHARPy']['log_file']) self.case_route = self.settings['SHARPy']['route'] + '/' self.case_name = self.settings['SHARPy']['case'] def save_settings(self): """ Saves the settings to a ``.sharpy`` config obj file in the output directory. """ out_settings = configobj.ConfigObj() for k, v in self.settings.items(): out_settings[k] = v out_settings.filename = self.output_folder + self.settings['SHARPy']['case'] + '.sharpy' out_settings.write() @staticmethod def load_config_file(file_name): config = configparser.ConfigParser() config.read(file_name) return config ================================================ FILE: sharpy/rom/__init__.py ================================================ """Model Order Reduction""" import importlib import os import sharpy.linear.utils.ss_interface as ss_interface import sharpy.utils.sharpydir as sharpydir files = ss_interface.sys_list_from_path(os.path.dirname(__file__)) import_path = os.path.realpath(os.path.dirname(__file__)) import_path = import_path.replace(sharpydir.SharpyDir, "") if import_path[0] == "/": import_path = import_path[1:] import_path = import_path.replace("/", ".") for file in files: ss_interface.systems_dict_import[file] = importlib.import_module(import_path + "." + file) ================================================ FILE: sharpy/rom/balanced.py ================================================ """Balancing Methods The following classes are available to reduce a linear system employing balancing methods. The main class is :class:`.Balanced` and the other available classes: * :class:`.Direct` * :class:`.Iterative` * :class:`.FrequencyLimited` correspond to the reduction algorithm. """ import sharpy.utils.settings as settings import numpy as np from abc import ABCMeta import sharpy.utils.cout_utils as cout import sharpy.utils.rom_interface as rom_interface import sharpy.rom.utils.librom as librom import sharpy.linear.src.libss as libss import time from sharpy.linear.utils.ss_interface import LinearVector, StateVariable dict_of_balancing_roms = dict() def bal_rom(arg): global dict_of_balancing_roms try: arg._bal_rom_id except AttributeError: raise AttributeError('Class defined as balanced rom has no _bal_rom_id attribute') dict_of_balancing_roms[arg._bal_rom_id] = arg return arg class BaseBalancedRom(metaclass=ABCMeta): print_info = False def initialise(self, in_settings=None): pass def run(self, ss): pass @bal_rom class Direct(BaseBalancedRom): __doc__ = librom.balreal_direct_py.__doc__ _bal_rom_id = 'Direct' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['tune'] = 'bool' settings_default['tune'] = True settings_description['tune'] = 'Tune ROM to specified tolerance' settings_types['use_schur'] = 'bool' settings_default['use_schur'] = False settings_description['use_schur'] = 'Use Schur decomposition during build' settings_types['rom_tolerance'] = 'float' settings_default['rom_tolerance'] = 1e-2 settings_description['rom_tolerance'] = 'Absolute accuracy with respect to full order frequency response' settings_types['rom_tune_freq_range'] = 'list(float)' settings_default['rom_tune_freq_range'] = [0, 1] settings_description['rom_tune_freq_range'] = 'Beginning and end of frequency range where to tune ROM' settings_types['convergence'] = 'str' settings_default['convergence'] = 'min' settings_description['convergence'] = 'ROM tuning convergence. If ``min`` attempts to find minimal number of states.' \ 'If ``all`` it starts from larger size ROM until convergence to ' \ 'specified tolerance is found.' settings_types['reduction_method'] = 'str' settings_default['reduction_method'] = 'realisation' settings_description['reduction_method'] = 'Desired reduction method' settings_options['reduction_method'] = ['realisation', 'truncation'] settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) def __init__(self): self.settings = dict() def initialise(self, in_settings=None): if in_settings is not None: self.settings = in_settings settings.to_custom_types(self.settings, self.settings_types, self.settings_default, self.settings_options, no_ctype=True) def run(self, ss): if self.print_info: cout.cout_wrap('Reducing system using a Direct balancing method...') t0 = time.time() A, B, C, D = ss.get_mats() try: if ss.dt is not None: dtsystem = True else: dtsystem = False except AttributeError: dtsystem = False S, T, Tinv = librom.balreal_direct_py(A, B, C, DLTI=dtsystem, Schur=self.settings['use_schur']) Ar = T.dot(A.dot(Tinv)) Br = T.dot(B) Cr = C.dot(Tinv) if dtsystem: ss_bal = libss.StateSpace(Ar, Br, Cr, D, dt=ss.dt) else: ss_bal = libss.StateSpace(Ar, Br, Cr, D) t1 = time.time() if self.print_info: cout.cout_wrap('\t...completed balancing in %.2fs' % (t1-t0), 1) if self.settings['tune']: cout.cout_wrap('Tuning ROM to specified tolerance...', 1) kv = np.linspace(self.settings['rom_tune_freq_range'][0], self.settings['rom_tune_freq_range'][1]) ssrom = librom.tune_rom(ss_bal, kv=kv, tol=self.settings['rom_tolerance'], gv=S, convergence=self.settings['convergence'], method=self.settings['reduction_method']) if librom.check_stability(ssrom.A, dt=True): if self.print_info: cout.cout_wrap('ROM by direct balancing is stable') t2 = time.time() cout.cout_wrap('\t...completed reduction in %.2fs' % (t2-t0), 1) return ssrom else: return ss_bal @bal_rom class FrequencyLimited(BaseBalancedRom): __doc__ = librom.balfreq.__doc__ _bal_rom_id = 'FrequencyLimited' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['frequency'] = 'float' settings_default['frequency'] = 1. settings_description['frequency'] = 'defines limit frequencies for balancing. The balanced model will be accurate ' \ 'in the range ``[0,F]``, where ``F`` is the value of this key. Note that ``F`` ' \ 'units must be consistent with the units specified in the in ' \ 'the ``self.ScalingFacts`` dictionary.' settings_types['method_low'] = 'str' settings_default['method_low'] = 'trapz' settings_description['method_low'] = 'Specifies whether to use gauss quadrature or ' \ 'trapezoidal rule in the low-frequency range ``[0,F]``' settings_options['method_low'] = ['gauss', 'trapz'] settings_types['options_low'] = 'dict' settings_default['options_low'] = dict() settings_description['options_low'] = 'Settings for the low frequency integration. See Notes.' settings_types['method_high'] = 'str' settings_default['method_high'] = 'trapz' settings_description['method_high'] = 'Specifies whether to use gauss quadrature or ' \ 'trapezoidal rule in the high-frequency range ``[F,FN]``' settings_options['method_high'] = ['gauss', 'trapz'] settings_types['options_high'] = 'dict' settings_default['options_high'] = dict() settings_description['options_high'] = 'Settings for the high frequency integration. See Notes.' settings_types['check_stability'] = 'bool' settings_default['check_stability'] = True settings_description['check_stability'] = 'if True, the balanced model is truncated to eliminate ' \ 'unstable modes - if any is found. Note that very accurate ' \ 'balanced model can still be obtained, even if high order ' \ 'modes are unstable.' settings_types['get_frequency_response'] = 'bool' settings_default['get_frequency_response'] = False settings_description['get_frequency_response'] = 'if True, the function also returns the frequency ' \ 'response evaluated at the low-frequency range integration' \ ' points. If True, this option also allows to automatically' \ ' tune the balanced model.' # Integrator options settings_options_types = dict() settings_options_default = dict() settings_options_description = dict() settings_options_types['points'] = 'int' settings_options_default['points'] = 12 settings_options_description['points'] = 'Trapezoidal points of integration' settings_options_types['partitions'] = 'int' settings_options_default['partitions'] = 2 settings_options_description['partitions'] = 'Number of Gauss-Lobotto quadratures' settings_options_types['order'] = 'int' settings_options_default['order'] = 2 settings_options_description['order'] = 'Order of Gauss-Lobotto quadratures' settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) options_table = settings.SettingsTable() __doc__ += options_table.generate(settings_options_types, settings_options_default, settings_options_description, header_line='The parameters of integration take the following options:\n') def __init__(self): self.settings = dict() def initialise(self, in_settings=None): if in_settings is not None: self.settings = in_settings settings.to_custom_types(self.settings, self.settings_types, self.settings_default, self.settings_options, no_ctype=True) settings.to_custom_types(self.settings['options_low'], self.settings_options_types, self.settings_options_default, no_ctype=True) settings.to_custom_types(self.settings['options_high'], self.settings_options_types, self.settings_options_default, no_ctype=True) def run(self, ss): output_results = librom.balfreq(ss, self.settings) return output_results[0] @bal_rom class Iterative(BaseBalancedRom): __doc__ = librom.balreal_iter.__doc__ _bal_rom_id = 'Iterative' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['lowrank'] = 'bool' settings_default['lowrank'] = True settings_description['lowrank'] = 'Use low rank methods' settings_types['smith_tol'] = 'float' settings_default['smith_tol'] = 1e-10 settings_description['smith_tol'] = 'Smith tolerance' settings_types['tolSVD'] = 'float' settings_default['tolSVD'] = 1e-6 settings_description['tolSVD'] = 'SVD threshold' settings_types['tolSVD'] = 'float' settings_default['tolSVD'] = 1e-6 settings_description['tolSVD'] = 'SVD threshold' settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.settings = dict() def initialise(self, in_settings=None): if in_settings is not None: self.settings = in_settings settings.to_custom_types(self.settings, self.settings_types, self.settings_default, no_ctype=True) def run(self, ss): A, B, C, D = ss.get_mats() s, T, Tinv, rcmax, romax = librom.balreal_iter(A, B, C, lowrank=self.settings['lowrank'], tolSmith=self.settings['smith_tol'], tolSVD=self.settings['tolSVD']) Ar = Tinv.dot(A.dot(T)) Br = Tinv.dot(B) Cr = C.dot(T) ssrom = libss.StateSpace(Ar, Br, Cr, D, dt=ss.dt) return ssrom @rom_interface.rom class Balanced(rom_interface.BaseRom): """Balancing ROM methods Main class to load a balancing ROM. See below for the appropriate settings to be parsed in the ``algorithm_settings`` based on your selection. Supported algorithms: * Direct balancing :class:`.Direct` * Iterative balancing :class:`.Iterative` * Frequency limited balancing :class:`.FrequencyLimited` """ rom_id = 'Balanced' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Write output to screen' settings_types['algorithm'] = 'str' settings_default['algorithm'] = '' settings_description['algorithm'] = 'Balanced realisation method' settings_options['algorithm'] = ['Direct', 'Iterative', 'FrequencyLimited'] settings_types['algorithm_settings'] = 'dict' settings_default['algorithm_settings'] = dict() settings_description['algorithm_settings'] = 'Settings for the desired algorithm' settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) def __init__(self): self.settings = dict() self.algorithm = None self.ssrom = None self.ss = None self.dtsystem = None def initialise(self, in_settings=None): if in_settings is not None: self.settings = in_settings settings.to_custom_types(self.settings, self.settings_types, self.settings_default, self.settings_options) if not (self.settings['algorithm'] in dict_of_balancing_roms): raise AttributeError('Balancing algorithm %s is not yet implemented' % self.settings['algorithm']) self.algorithm = dict_of_balancing_roms[self.settings['algorithm']]() self.algorithm.initialise(self.settings['algorithm_settings']) self.algorithm.print_info = self.settings['print_info'] def run(self, ss): self.ss = ss A, B, C, D = self.ss.get_mats() if self.ss.dt: self.dtsystem = True else: self.dtsystem = False out = self.algorithm.run(ss) if type(out) == libss.StateSpace: self.ssrom = out else: Ar, Br, Cr = out if self.dtsystem: self.ssrom = libss.StateSpace(Ar, Br, Cr, D, dt=self.ss.dt) else: self.ssrom = libss.StateSpace(Ar, Br, Cr, D) try: self.ssrom.input_variables = self.ss.input_variables.copy() self.ssrom.output_variables = self.ss.output_variables.copy() self.ssrom.state_variables = LinearVector( [StateVariable('balanced_{:s}'.format(self.settings['algorithm'].lower()), size=self.ssrom.states, index=0)]) except AttributeError: pass return self.ssrom ================================================ FILE: sharpy/rom/krylov.py ================================================ """Krylov-subspaces model order reduction techniques """ import numpy as np import scipy.linalg as sclalg import sharpy.linear.src.libss as libss import time import sharpy.utils.settings as settings import sharpy.utils.cout_utils as cout import sharpy.utils.rom_interface as rom_interface import sharpy.utils.h5utils as h5 import sharpy.rom.utils.krylovutils as krylovutils import warnings as warn import h5py from sharpy.linear.utils.ss_interface import LinearVector, StateVariable, InputVariable, OutputVariable @rom_interface.rom class Krylov(rom_interface.BaseRom): """ Model Order Reduction Methods for Single Input Single Output (SISO) and MIMO Linear Time-Invariant (LTI) Systems using moment matching (Krylov Methods). Examples: General calling sequences for different systems SISO single point interpolation: >>> algorithm = 'one_sided_arnoldi' >>> interpolation_point = np.array([0.0]) >>> krylov_r = 4 >>> >>> rom = Krylov() >>> rom.initialise(sharpy_data, FullOrderModelSS) >>> rom.run(algorithm, krylov_r, interpolation_point) 2 by 2 MIMO with tangential, multipoint interpolation: >>> algorithm = 'dual_rational_arnoldi' >>> interpolation_point = np.array([0.0, 1.0j]) >>> krylov_r = 4 >>> right_vector = np.block([[1, 0], [0, 1]]) >>> left_vector = right_vector >>> >>> rom = Krylov() >>> rom.initialise(sharpy_data, FullOrderModelSS) >>> rom.run(algorithm, krylov_r, interpolation_point, right_vector, left_vector) 2 by 2 MIMO multipoint interpolation: >>> algorithm = 'mimo_rational_arnoldi' >>> interpolation_point = np.array([0.0]) >>> krylov_r = 4 >>> >>> rom = Krylov() >>> rom.initialise(sharpy_data, FullOrderModelSS) >>> rom.run(algorithm, krylov_r, interpolation_point) """ rom_id = 'Krylov' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Write ROM information to screen and log' settings_types['frequency'] = 'list(complex)' settings_default['frequency'] = [0] settings_description['frequency'] = 'Interpolation points in the continuous time complex plane [rad/s]' settings_types['algorithm'] = 'str' settings_default['algorithm'] = '' settings_description['algorithm'] = 'Krylov reduction method algorithm' settings_types['r'] = 'int' settings_default['r'] = 1 settings_description['r'] = 'Moments to match at the interpolation points' settings_types['single_side'] = 'str' settings_default['single_side'] = '' settings_description['single_side'] = 'Construct the rom using a single side. Leave blank (or empty string) for both.' settings_options['single_side'] = ['controllability', 'observability'] settings_types['tangent_input_file'] = 'str' settings_default['tangent_input_file'] = '' settings_description['tangent_input_file'] = 'Filepath to .h5 file containing tangent interpolation vectors' settings_types['restart_arnoldi'] = 'bool' settings_default['restart_arnoldi'] = False settings_description['restart_arnoldi'] = 'Restart Arnoldi iteration with r-=1 if ROM is unstable' settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) supported_methods = ('one_sided_arnoldi', 'two_sided_arnoldi', 'dual_rational_arnoldi', 'mimo_rational_arnoldi', 'mimo_block_arnoldi') def __init__(self): self.settings = dict() self.frequency = None self.algorithm = None self.ss = None self.r = 1 self.V = None self.H = None self.W = None self.ssrom = None self.sstype = None self.nfreq = None self.restart_arnoldi = None self.stable = None self.cpu_summary = dict() self.eigenvalue_table = None def initialise(self, in_settings=None): if in_settings is not None: self.settings = in_settings settings.to_custom_types(self.settings, self.settings_types, self.settings_default, self.settings_options) try: if self.settings['print_info']: cout.cout_wrap('Initialising Krylov Model Order Reduction') except ValueError: pass self.algorithm = self.settings['algorithm'] if self.algorithm not in self.supported_methods: raise NotImplementedError('Algorithm %s not recognised, check for spelling or it' 'could be that is not yet implemented' % self.algorithm) self.frequency = np.array(self.settings['frequency']) self.r = self.settings['r'] self.restart_arnoldi = self.settings['restart_arnoldi'] try: self.nfreq = self.frequency.shape[0] except AttributeError: self.nfreq = 1 def save(self, filename): """ Saves to an ``.h5`` file of name ``filename`` the left and right projectors and the reduced order model Args: filename (str): path and filename to which to save the data """ rom_projectors = {'right_projector': self.V, 'left_projector': self.W, } if '.h5' not in filename[-3:]: filename += '.h5' with h5py.File(filename, 'a') as outfile: h5.add_as_grp(rom_projectors, outfile, grpname='projectors', compress_float=True) h5.add_as_grp(self.ssrom, outfile, grpname='ssrom', ClassesToSave=(libss.StateSpace, ), compress_float=True) def save_reduced_order_bases(self, file_name): """ Save reduced order bases to an h5 file Args: file_name (str): path to h5 file """ if not isinstance(self.V, libss.Gain): V = libss.Gain(self.V) W = libss.Gain(self.W) else: V = self.V W = self.W if self.settings['single_side'] == 'observability' or self.settings['single_side'] == 'controllability': # if single sided, the projector is V and W = V therefore no need to duplicate libss.Gain.save_multiple_gains(file_name, ('V', V)) cout.cout_wrap(f'Saved Krylov reduced order bases, V to file: {file_name}', 1) else: libss.Gain.save_multiple_gains(file_name, ('V', V), ('W', W)) cout.cout_wrap(f'Saved Krylov reduced order bases, V and W to file: {file_name}', 1) def run(self, ss): """ Performs Model Order Reduction employing Krylov space projection methods. Supported methods include: ========================= ==================== ========================================================== Algorithm Interpolation Points Systems ========================= ==================== ========================================================== ``one_sided_arnoldi`` 1 SISO Systems ``two_sided_arnoldi`` 1 SISO Systems ``dual_rational_arnoldi`` K SISO systems and Tangential interpolation for MIMO systems ``mimo_rational_arnoldi`` K MIMO systems. Uses vector-wise construction (more robust) ``mimo_block_arnoldi`` K MIMO systems. Uses block Arnoldi methods (more efficient) ========================= ==================== ========================================================== Args: ss (sharpy.linear.src.libss.StateSpace): State space to reduce Returns: (libss.StateSpace): Reduced state space system """ self.ss = ss if self.settings['print_info']: cout.cout_wrap('Model Order Reduction in progress...') self.print_header() if self.ss.dt is None: self.sstype = 'ct' else: self.sstype = 'dt' self.frequency = np.exp(self.frequency * ss.dt) t0 = time.time() Ar, Br, Cr = self.__getattribute__(self.algorithm)(self.frequency, self.r) self.ssrom = libss.StateSpace(Ar, Br, Cr, self.ss.D, self.ss.dt) try: self.ssrom.input_variables = self.ss.input_variables.copy() self.ssrom.output_variables = self.ss.output_variables.copy() self.ssrom.state_variables = LinearVector([StateVariable('krylov', size=self.ssrom.states, index=0)]) except AttributeError: pass self.stable = self.check_stability(restart_arnoldi=self.restart_arnoldi) if not self.stable: pass warn.warn('Reduced Order Model Unstable') # Under development # TL, TR = self.restart() # Wtr, Vr = self.restart() # TL, TR = self.stable_realisation() # self.ssrom = libss.ss(TL.T.dot(Ar.dot(TR)), TL.T.dot(Br), Cr.dot(TR), self.ss.D, self.ss.dt) # self.ssrom = libss.ss(Wtr.dot(self.ssrom.A.dot(Vr)), Wtr.dot(self.ssrom.B), self.ssrom.C.dot(Vr), self.ss.D, self.ss.dt) # self.stable = self.check_stability(restart_arnoldi=self.restart_arnoldi) t_rom = time.time() - t0 self.cpu_summary['run'] = t_rom if self.settings['print_info']: cout.cout_wrap('System reduced from order %d to ' % self.ss.states) cout.cout_wrap('\tn = %d states' % self.ssrom.states, 1) cout.cout_wrap('...Completed Model Order Reduction in %.2f s' % t_rom) return self.ssrom def print_header(self): cout.cout_wrap('Moment Matching Krylov Model Reduction') cout.cout_wrap('\tConstruction Algorithm:') cout.cout_wrap('\t\t%s' % self.algorithm, 1) cout.cout_wrap('\tInterpolation points:') if self.frequency.dtype == complex: cout.cout_wrap(self.nfreq * '\t\tsigma = %4f + %4fj [rad/s]\n' %tuple(self.frequency.view(float)), 1) else: cout.cout_wrap(self.nfreq * '\t\tsigma = %4f [rad/s]\n' % self.frequency, 1) cout.cout_wrap('\tKrylov order:') cout.cout_wrap('\t\tr = %d' % self.r, 1) def one_sided_arnoldi(self, frequency, r): r""" One-sided Arnoldi method expansion about a single interpolation point, :math:`\sigma`. The projection matrix :math:`\mathbf{V}` is constructed using an order :math:`r` Krylov space. The space for a single finite interpolation point known as a Pade approximation is described by: .. math:: \text{range}(\textbf{V}) = \mathcal{K}_r((\sigma\mathbf{I}_n - \mathbf{A})^{-1}, (\sigma\mathbf{I}_n - \mathbf{A})^{-1}\mathbf{b}) In the case of an interpolation about infinity, the problem is known as partial realisation and the Krylov space is .. math:: \text{range}(\textbf{V}) = \mathcal{K}_r(\mathbf{A}, \mathbf{b}) The resulting orthogonal projection leads to the following reduced order system: .. math:: \hat{\Sigma} : \left(\begin{array}{c|c} \hat{A} & \hat{B} \\ \hline \hat{C} & {D}\end{array}\right) \text{with } \begin{cases}\hat{A}=V^TAV\in\mathbb{R}^{k\times k},\,\\ \hat{B}=V^TB\in\mathbb{R}^{k\times m},\,\\ \hat{C}=CV\in\mathbb{R}^{p\times k},\,\\ \hat{D}=D\in\mathbb{R}^{p\times m}\end{cases} Args: frequency (complex): Interpolation point :math:`\sigma \in \mathbb{C}` r (int): Number of moments to match. Equivalent to Krylov space order and order of the ROM. Returns: tuple: The reduced order model matrices: :math:`\mathbf{A}_r`, :math:`\mathbf{B}_r` and :math:`\mathbf{C}_r` """ A = self.ss.A B = self.ss.B C = self.ss.C nx = A.shape[0] if frequency != np.inf and frequency is not None: lu_A = krylovutils.lu_factor(frequency, A) V = krylovutils.construct_krylov(r, lu_A, B, 'Pade', 'b') else: V = krylovutils.construct_krylov(r, A, B, 'partial_realisation', 'b') # Reduced state space model Ar = V.T.dot(A.dot(V)) Br = V.T.dot(B) Cr = C.dot(V) self.V = V self.W = V return Ar, Br, Cr def two_sided_arnoldi(self, frequency, r): r""" Two-sided projection with a single interpolation point following the Arnoldi procedure. Very similar to the one-sided method available, but it adds the projection :math:`\mathbf{W}` built using the Krylov space for the :math:`\mathbf{c}` vector: .. math:: \mathcal{K}_r((\sigma\mathbf{I}_n - \mathbf{A})^{-T}, (\sigma\mathbf{I}_n - \mathbf{A})^{-T}\mathbf{c}^T)\subseteq\mathcal{W}=\text{range}(\mathbf{W}) The oblique projection :math:`\mathbf{VW}^T` matches twice as many moments as the single sided projection. The resulting system takes the form: .. math:: \hat{\Sigma} : \left(\begin{array}{c|c} \hat{A} & \hat{B} \\ \hline \hat{C} & {D}\end{array}\right) \text{with } \begin{cases}\hat{A}=W^TAV\in\mathbb{R}^{k\times k},\,\\ \hat{B}=W^TB\in\mathbb{R}^{k\times m},\,\\ \hat{C}=CV\in\mathbb{R}^{p\times k},\,\\ \hat{D}=D\in\mathbb{R}^{p\times m}\end{cases} Args: frequency (complex): Interpolation point :math:`\sigma \in \mathbb{C}` r (int): Number of moments to match on each side. The resulting ROM will be of order :math:`2r`. Returns: tuple: The reduced order model matrices: :math:`\mathbf{A}_r`, :math:`\mathbf{B}_r` and :math:`\mathbf{C}_r`. """ A = self.ss.A B = self.ss.B C = self.ss.C nx = A.shape[0] if frequency != np.inf and frequency is not None: lu_A = krylovutils.lu_factor(frequency, A) V = krylovutils.construct_krylov(r, lu_A, B, 'Pade', 'b') W = krylovutils.construct_krylov(r, lu_A, C.T, 'Pade', 'c') else: V = krylovutils.construct_krylov(r, A, B, 'partial_realisation', 'b') W = krylovutils.construct_krylov(r, A, C.T, 'partial_realisation', 'c') T = W.T.dot(V) Tinv = sclalg.inv(T) reduction_checks(T, Tinv) self.W = W self.V = V # Reduced state space model Ar = W.T.dot(self.ss.A.dot(V.dot(Tinv))) Br = W.T.dot(self.ss.B) Cr = self.ss.C.dot(V.dot(Tinv)) return Ar, Br, Cr def real_rational_arnoldi(self, frequency, r): """ When employing complex frequencies, the projection matrix can be normalised to be real Following Algorithm 1b in Lee(2006) Args: frequency: r: Returns: """ raise NotImplementedError('Real valued rational Arnoldi Method Work in progress - use mimo_rational_arnoldi') ### Not working, having trouble with the last column of H. need to investigate the background behind the creation of H and see hwat can be done A = self.ss.A B = self.ss.B C = self.ss.C nx = A.shape[0] nfreq = frequency.shape[0] # Columns of matrix v v_ncols = 2 * np.sum(r) # Output projection matrices V = np.zeros((nx, v_ncols), dtype=float) H = np.zeros((v_ncols, v_ncols), dtype=float) res = np.zeros((nx,v_ncols+2), dtype=float) # lu_A = krylovutils.lu_factor(frequency[0] * np.eye(nx) - A) v_res = sclalg.lu_solve(lu_A, B) H[0, 0] = np.linalg.norm(v_res) V[:, 0] = v_res.real / H[0, 0] k = 0 for i in range(nfreq): for j in range(r[i]): # k = 2*(i*r[i] + j) print("i = %g\t j = %g\t k = %g" % (i, j, k)) # res[:, k] = np.imag(v_res) # if k > 0: # res[:, k-1] = np.real(v_res) # # # Working on the last finished column i.e. k-1 only when k>0 # if k > 0: # for t in range(k): # H[t, k-1] = V[:, t].T.dot(res[:, k-1]) # res[:, k-1] -= res[:, k-1] - H[t, k-1] * V[:, t] # # H[k, k-1] = np.linalg.norm(res[:, k-1]) # V[:, k] = res[:, k-1] / H[k, k-1] # # # Normalise working column k # for t in range(k+1): # H[t, k] = V[:, t].T.dot(res[:, k]) # res[:, k] -= H[t, k] * V[:, t] # # # Subdiagonal term # H[k+1, k] = np.linalg.norm(res[:, k]) # V[:, k + 1] = res[:, k] / np.linalg.norm(res[:, k]) # # if j == r[i] - 1 and i < nfreq - 1: # lu_A = krylovutils.lu_factor(frequency[i+1] * np.eye(nx) - A) # v_res = sclalg.lu_solve(lu_A, B) # else: # v_res = - sclalg.lu_solve(lu_A, V[:, k+1]) if k == 0: V[:, 0] = v_res.real / np.linalg.norm(v_res.real) else: res[:, k] = np.imag(v_res) res[:, k-1] = np.real(v_res) for t in range(k): H[t, k-1] = np.linalg.norm(res[:, k-1]) res[:, k-1] -= H[t, k-1]*V[:, t] H[k, k-1] = np.linalg.norm(res[:, k-1]) V[:, k] = res[:, k-1] / H[k, k-1] if k == 0: H[0, 0] = V[:, 0].T.dot(v_res.imag) res[:, 0] -= H[0, 0] * V[:, 0] else: for t in range(k+1): H[t, k] = V[:, t].T.dot(res[:, k]) res[:, k] -= H[t, k] * V[:, t] H[k+1, k] = np.linalg.norm(res[:, k]) V[:, k+1] = res[:, k] / H[k+1, k] if j == r[i] - 1 and i < nfreq - 1: lu_A = krylovutils.lu_factor(frequency[i+1], A) v_res = sclalg.lu_solve(lu_A, B) else: v_res = - sclalg.lu_solve(lu_A, V[:, k+1]) k += 2 # Add last column of H print(k) res[:, k-1] = - sclalg.lu_solve(lu_A, V[:, k-1]) for t in range(k-1): H[t, k-1] = V[:, t].T.dot(res[:, k-1]) res[:, k-1] -= H[t, k-1]*V[:, t] self.V = V self.H = H Ar = V.T.dot(A.dot(V)) Br = V.T.dot(B) Cr = C.dot(V) return Ar, Br, Cr def dual_rational_arnoldi(self, frequency, r): r""" Dual Rational Arnoli Interpolation for SISO sytems [1] and MIMO systems through tangential interpolation [2]. Effectively the same as the two_sided_arnoldi and the resulting V matrices for each interpolation point are concatenated .. math:: \bigcup\limits_{k = 1}^K\mathcal{K}_{b_k}((\sigma_i\mathbf{I}_n - \mathbf{A})^{-1}, (\sigma_i\mathbf{I}_n - \mathbf{A})^{-1}\mathbf{b})\subseteq\mathcal{V}&=\text{range}(\mathbf{V}) \\ \bigcup\limits_{k = 1}^K\mathcal{K}_{c_k}((\sigma_i\mathbf{I}_n - \mathbf{A})^{-T}, (\sigma_i\mathbf{I}_n - \mathbf{A})^{-T}\mathbf{c}^T)\subseteq\mathcal{Z}&=\text{range}(\mathbf{Z}) For MIMO systems, tangential interpolation is used through the right and left tangential direction vectors :math:`\mathbf{r}_i` and :math:`\mathbf{l}_i`. .. math:: \bigcup\limits_{k = 1}^K\mathcal{K}_{b_k}((\sigma_i\mathbf{I}_n - \mathbf{A})^{-1}, (\sigma_i\mathbf{I}_n - \mathbf{A})^{-1}\mathbf{Br}_i)\subseteq\mathcal{V}&=\text{range}(\mathbf{V}) \\ \bigcup\limits_{k = 1}^K\mathcal{K}_{c_k}((\sigma_i\mathbf{I}_n - \mathbf{A})^{-T}, (\sigma_i\mathbf{I}_n - \mathbf{A})^{-T}\mathbf{C}^T\mathbf{l}_i)\subseteq\mathcal{Z}&=\text{range}(\mathbf{Z}) Args: frequency (np.ndarray): Array containing the interpolation points :math:`\sigma = \{\sigma_1, \dots, \sigma_K\}\in\mathbb{C}` r (int): Krylov space order :math:`b_k` and :math:`c_k`. At the moment, different orders for the controllability and observability constructions are not supported. right_tangent (np.ndarray): Matrix containing the right tangential direction interpolation vector for each interpolation point in column form, i.e. :math:`\mathbf{r}\in\mathbb{R}^{m \times K}`. left_tangent (np.ndarray): Matrix containing the left tangential direction interpolation vector for each interpolation point in column form, i.e. :math:`\mathbf{l}\in\mathbb{R}^{p \times K}`. Returns: tuple: The reduced order model matrices: :math:`\mathbf{A}_r`, :math:`\mathbf{B}_r` and :math:`\mathbf{C}_r`. References: [1] Grimme [2] Gallivan """ A = self.ss.A B = self.ss.B C = self.ss.C nx = self.ss.states nu = self.ss.inputs ny = self.ss.outputs B.shape = (nx, nu) if nu != 1: left_tangent, right_tangent, rc, ro, fc, fo = self.load_tangent_vectors() assert right_tangent is not None and left_tangent is not None, 'Missing interpolation vectors for MIMO case' else: fc = np.array(frequency) fo = np.array(frequency) left_tangent = np.zeros((1, len(fo))) right_tangent = np.zeros((1, len(fc))) rc = np.array([r]*len(fc)) ro = np.array([r]*len(fc)) right_tangent[0, :] = 1 left_tangent[0, :] = 1 try: nfreq = frequency.shape[0] except AttributeError: nfreq = 1 t0 = time.time() # # Tangential interpolation for MIMO systems # if right_tangent is None: # right_tangent = np.eye((nu, nfreq)) # # else: # # assert right_tangent.shape == (nu, nfreq), 'Right Tangential Direction vector not the correct shape' # # if left_tangent is None: # left_tangent = np.eye((ny, nfreq)) # # else: # # assert left_tangent.shape == (ny, nfreq), 'Left Tangential Direction vector not the correct shape' rom_dim = max(np.sum(rc), np.sum(ro)) V = np.zeros((nx, rom_dim), dtype=complex) W = np.zeros((nx, rom_dim), dtype=complex) we = 0 dict_of_luas = dict() for i in range(len(fc)): sigma = fc[i] if sigma == np.inf: approx_type = 'partial_realisation' lu_A = A else: approx_type = 'Pade' try: lu_A = dict_of_luas[sigma] except KeyError: lu_A = krylovutils.lu_factor(sigma, A) dict_of_luas[sigma] = lu_A V[:, we:we+rc[i]] = krylovutils.construct_krylov(rc[i], lu_A, B.dot(right_tangent[:, i:i+1]), approx_type, 'b') we += rc[i] we = 0 for i in range(len(fo)): sigma = fo[i] if sigma == np.inf: approx_type = 'partial_realisation' lu_A = A else: approx_type = 'Pade' try: lu_A = dict_of_luas[sigma] except KeyError: lu_A = krylovutils.lu_factor(sigma, A) dict_of_luas[sigma] = lu_A W[:, we:we+ro[i]] = krylovutils.construct_krylov(ro[i], lu_A, C.T.dot(left_tangent[:, i:i+1]), approx_type, 'c') we += ro[i] T = W.T.dot(V) Tinv = sclalg.inv(T) reduction_checks(T, Tinv) self.W = W self.V = V # Reduced state space model Ar = W.T.dot(self.ss.A.dot(V.dot(Tinv))) Br = W.T.dot(self.ss.B) Cr = self.ss.C.dot(V.dot(Tinv)) del dict_of_luas self.cpu_summary['algorithm'] = time.time() - t0 return Ar, Br, Cr def mimo_rational_arnoldi(self, frequency, r): r""" Construct full rank orthonormal projection basis :math:`\mathbf{V}` and :math:`\mathbf{W}`. The main issue that one normally encounters with MIMO systems is that the minimality assumption of the system does not guarantee the resulting Krylov space to be full rank, unlike in the SISO case. Therefore, the construction is performed vector by vector, where linearly dependent vectors are eliminated or deflated from the Krylov subspace. If the number of inputs differs the number of outputs, both Krylov spaces will be built such that both are the same size, therefore one Krylov space may be of higher order than the other one. Following the method for vector-wise construction in Gugercin [1]. Args: frequency (np.ndarray): Array containing interpolation frequencies r (int): Krylov space order Returns: tuple: Tuple of reduced system matrices ``A``, ``B`` and ``C``. References: [1] Gugercin, S. Projection Methods for Model Reduction of Large-Scale Dynamical Systems PhD Thesis. Rice University 2003. """ m = self.ss.inputs # Full system number of inputs n = self.ss.states # Full system number of states p = self.ss.outputs # Full system number of outputs # If the number of inputs is not the same as the number of outputs, a larger than necessary projection matrix # is built for the one with fewer inputs/outputs. Thence, the larger matrix is truncated to have the same number # of columns as the smaller matrix if m != p: if m < p: r_o = r r_c = r_o * int(np.ceil(p / m)) else: r_c = r r_o = r_c * int(np.ceil(m / p)) else: r_c = r r_o = r if self.settings['single_side']: # if only a single side is built, use the original setting without modification r_c = r r_o = r V = None W = None for i in range(self.nfreq): if self.settings['single_side'] == 'controllability' or self.settings['single_side'] == '': cout.cout_wrap('\tConstructing controllability space', 1) if i == 0: V = krylovutils.build_krylov_space(frequency[i], r_c, side='b', a=self.ss.A, b=self.ss.B) else: Vi = krylovutils.build_krylov_space(frequency[i], r_c, side='b', a=self.ss.A, b=self.ss.B) V = np.hstack((V, Vi)) V = krylovutils.mgs_ortho(V) if self.settings['single_side'] == 'observability' or self.settings['single_side'] == '': cout.cout_wrap('\tConstructing observability space', 1) if i == 0: W = krylovutils.build_krylov_space(frequency[i], r_o, side='c', a=self.ss.A, b=self.ss.C.T) else: Wi = krylovutils.build_krylov_space(frequency[i], r_o, side='c', a=self.ss.A, b=self.ss.C.T) W = np.hstack((W, Wi)) W = krylovutils.mgs_ortho(W) if self.settings['single_side'] == 'controllability' or self.settings['single_side'] == 'observability': if self.settings['single_side'] == 'observability': V = W if self.settings['single_side'] == 'controllability': W = V # needed to properly save gains Ar = V.T.dot(self.ss.A.dot(V)) Br = V.T.dot(self.ss.B) Cr = self.ss.C.dot(V) else: # Match number of columns in each matrix min_cols = min(V.shape[1], W.shape[1]) V = V[:, :min_cols] W = W[:, :min_cols] V, W = check_rank(V, W) # checks the product for rank deficiencies # V = krylovutils.mgs_ortho(V) # need to verify whether this is necessary # W = krylovutils.mgs_ortho(W) T = W.T.dot(V) Tinv = sclalg.inv(T) krylovutils.check_eye(T, Tinv) # Reduced state space model Ar = W.T.dot(self.ss.A.dot(V.dot(Tinv))) Br = W.T.dot(self.ss.B) Cr = self.ss.C.dot(V.dot(Tinv)) self.W = libss.Gain(W, input_vars=LinearVector([InputVariable('krylov', size=W.shape[1], index=0)]), output_vars=LinearVector.transform(self.ss.state_variables, OutputVariable)) self.V = libss.Gain(V, input_vars=LinearVector([InputVariable('krylov', size=V.shape[1], index=0)]), output_vars=LinearVector.transform(self.ss.state_variables, OutputVariable)) # for state recovery purposes self.projection_gain = libss.Gain(V, input_vars=LinearVector([InputVariable('krylov', size=V.shape[1], index=0)]), output_vars=LinearVector.transform(self.ss.state_variables, OutputVariable)) return Ar, Br, Cr def mimo_block_arnoldi(self, frequency, r): n = self.ss.states A = self.ss.A B = self.ss.B C = self.ss.C for i in range(self.nfreq): if self.frequency[i] == np.inf: F = A G = B else: lu_a = krylovutils.lu_factor(frequency[i], A) F = krylovutils.lu_solve(lu_a, np.eye(n)) G = krylovutils.lu_solve(lu_a, B) if i == 0: V = krylovutils.block_arnoldi_krylov(r, F, G) else: Vi = krylovutils.block_arnoldi_krylov(r, F, G) V = np.block([V, Vi]) self.V = V Ar = V.T.dot(A.dot(V)) Br = V.T.dot(B) Cr = C.dot(V) return Ar, Br, Cr def check_stability(self, restart_arnoldi=False): r""" Checks the stability of the ROM by computing its eigenvalues. If the resulting system is unstable, the Arnoldi procedure can be restarted to eliminate the eigenvalues outside the stability boundary. However, if this is the case, the ROM no longer matches the moments of the original system at the specific frequencies since now the approximation is done with respect to a system of the form: .. math:: \Sigma = \left(\begin{array}{c|c} \mathbf{A} & \mathbf{\bar{B}} \\ \hline \mathbf{C} & \ \end{array}\right) where :math:`\mathbf{\bar{B}} = (\mu \mathbf{I}_n - \mathbf{A})\mathbf{B}` Args: restart_arnoldi (bool): Restart the relevant Arnoldi algorithm with the unstable eigenvalues removed. """ assert self.ssrom is not None, 'ROM not calculated yet' eigs = sclalg.eigvals(self.ssrom.A) eigs_abs = np.abs(eigs) order = np.argsort(eigs_abs)[::-1] eigs = eigs[order] eigs_abs = eigs_abs[order] unstable = False if self.sstype == 'dt': if any(eigs_abs > 1.): unstable = True unstable_eigenvalues = eigs[eigs_abs > 1.] try: cout.cout_wrap('Unstable ROM - %d Eigenvalues with |r| > 1' % len(unstable_eigenvalues)) except ValueError: pass for mu in unstable_eigenvalues: try: cout.cout_wrap('\tmu = %f + %fj' % (mu.real, mu.imag)) except ValueError: pass else: try: cout.cout_wrap('ROM is stable') cout.cout_wrap('\tDT Eigenvalues:') cout.cout_wrap(len(eigs_abs) * '\t\tmu = %4f + %4fj\n' % tuple(eigs.view(float))) except ValueError: pass else: if any(eigs.real > 0): unstable = True cout.cout_wrap('Unstable ROM', 3) unstable_eigenvalues = eigs[eigs.real > 0] else: cout.cout_wrap('ROM is stable') # Restarted Arnoldi # Modify the B matrix in the full state system -> maybe better to have a copy if unstable and restart_arnoldi: print('Restarting the Arnoldi method - Reducing ROM order from r = %d to r = %d' % (self.r, self.r-1)) self.ss_original = self.ss remove_unstable = np.eye(self.ss.states) for mu in unstable_eigenvalues: remove_unstable = np.matmul(remove_unstable, mu * np.eye(self.ss.states) - self.ss.A) self.ss.B = remove_unstable.dot(self.ss.B) # self.ss.C = self.ss.C.dot(remove_unstable.T) if self.r > 1: self.r -= 1 self.run(self.algorithm, self.r, self.frequency) else: print('Unable to reduce ROM any further - ROM still unstable...') return not unstable def load_tangent_vectors(self): tangent_file = self.settings['tangent_input_file'] if tangent_file: tangents = h5.readh5(tangent_file) right_tangent = tangents.right_tangent left_tangent = tangents.left_tangent rc = tangents.rc ro = tangents.ro fc = tangents.fc fo = tangents.fo else: left_tangent = None right_tangent = None return left_tangent, right_tangent, rc, ro, fc, fo def stable_realisation(self, *args, **kwargs): r"""Remove unstable poles left after reduction Using a Schur decomposition of the reduced plant matrix :math:`\mathbf{A}_m\in\mathbb{C}^{m\times m}`, the method removes the unstable eigenvalues that could have appeared after the moment-matching reduction. The oblique projection matrices :math:`\mathbf{T}_L\in\mathbb{C}^{m \times p}` and :math:`\mathbf{T}_R\in\mathbb{C}^{m \times p}`` result in a stable realisation .. math:: \mathbf{A}_s = \mathbf{T}_L^\top\mathbf{AT}_R \in \mathbb{C}^{p\times p}. Args: A (np.ndarray): plant matrix (if not provided ``self.ssrom.A`` will be used). Returns: tuple: Left and right projection matrices :math:`\mathbf{T}_L\in\mathbb{C}^{m \times p}` and :math:`\mathbf{T}_R\in\mathbb{C}^{m \times p}` References: Jaimoukha, I. M., Kasenally, E. D.. Implicitly Restarted Krylov Subspace Methods for Stable Partial Realizations. SIAM Journal of Matrix Analysis and Applications, 1997. See Also: The method employs :func:`sharpy.rom.utils.krylovutils.schur_ordered()` and :func:`sharpy.rom.utils.krylovutils.remove_a12`. """ cout.cout_wrap('Stabilising system by removing unstable eigenvalues using a Schur decomposition', 1) if self.ssrom is None: A = args[0] ct = kwargs['ct'] assert type(ct) == bool, 'CT system flag should be a bool' else: A = self.ssrom.A if self.sstype == 'ct': ct = True else: ct = False m = A.shape[0] As, T1, n_stable = krylovutils.schur_ordered(A, ct=ct) # Remove the (1,2) block of the Schur ordered matrix T2, X = krylovutils.remove_a12(As, n_stable) T3 = np.eye(m, n_stable) TL = T3.T.dot(T2.dot(np.conj(T1))) TR = T1.T.dot(np.linalg.inv(T2).dot(T3)) cout.cout_wrap('System reduced to %g states' %n_stable, 1) # for debugging # import matplotlib.pyplot as plt Ar = TL.dot(A.dot(TR)) eigsA = np.linalg.eigvals(A) eigsAr = np.linalg.eigvals(Ar) # # plt.scatter(eigsA.real, eigsA.imag) # plt.scatter(eigsAr.real, eigsAr.imag) # plt.show() if ct: assert np.sum(np.real(eigsAr) <= 0.) == n_stable, 'Number of stable eigvals computed not equal to those in Ar' else: assert np.sum(np.abs(eigsAr) <= 1.) == n_stable, 'Number of stable eigvals computed not equal to those in Ar' return TL.T, TR def restart(self): """ Implicitly Restarted Krylov Algorithm """ # Run krylov here W = self.W V = self.V check_eye(V, V.T) check_eye(W, W.T) Tm = W.T.dot(V) Tminv = sclalg.inv(Tm) check_eye(Tm, Tminv, 'Tm = W^T.dot(V)') # d = Tminv.dot(W.T) # # Tmcond = np.linalg.cond(Tm) # # if Tmcond > 1e10: # cout.cout_wrap('Matrix Tm = W^T.dot(V) is poorly conditioned. Condition = %e' % Tmcond, 3) TL, TR = self.stable_realisation() m, r = TR.shape Ar = TL.T.dot(self.ssrom.A.dot(TR)) eigsAr = np.linalg.eigvals(Ar) n_stable_r = np.sum(np.abs(eigsAr)<=1.) assert n_stable_r == r, 'lost evals' # QR decomposition QRfull, RRfull = sclalg.qr(TR) QLfull, RLfull = sclalg.qr(sclalg.inv(Tm.T).dot(TL)) QR = QRfull[:, :r] QRcomp = QRfull[:, r:] RR = RRfull[:r, :] RRcomp = RRfull[r:, :] QL = QLfull[:, :r] RL = RLfull[:r, :] Vr = V.dot(QR) Wr = W.dot(QL) Tr = Wr.T.dot(Vr) print('Tr cond = %e' % np.linalg.cond(Tr)) Trinv = sclalg.inv(QL.T.dot(Tm.dot(QR))) Trinv2 = RR.dot(RL.T) # print('Trinv diff = %f' % (np.max(np.abs(Trinv - Trinv2)))) Wtr = Trinv.dot(Wr.T) # Wtr = Wr.T # return QL.dot(Trinv), QR Wtr2 = TL.T.dot(Tminv.dot(W.T)) # return Wtr2, V.dot(TR) Ar = RR.dot(TL.T).dot(self.ssrom.A.dot(QR)) eigsAr = np.linalg.eigvals(Ar) n_stable_r = np.sum(np.abs(eigsAr)<=1.) assert n_stable_r == r, 'lost evals' return RR.dot(TL.T), QR # pass def reduction_checks(T, Tinv): cout.cout_wrap('Tm condition = %e' % np.linalg.cond(T)) check_eye(T, Tinv) def check_eye(T, Tinv, msg=''): eye_approx = Tinv.dot(T) max_diff = np.max(np.abs(np.eye(eye_approx.shape[0]) - eye_approx)) if max_diff != 0.0: log_error = np.log10(max_diff) assert log_error < -6, 'T.dot(Tinv) not equal to identity, %s \nlog(error) = %.e' \ % (msg, np.log10(max_diff)) def check_rank(V, W): n_cols = V.shape[1] for col in range(1, n_cols): T = W[:, :col].T.dot(V[:, :col]) Tinv = np.linalg.inv(T) try: check_eye(T, Tinv) except AssertionError: cout.cout_wrap('\tDeflating column %g' % col, 1) W = np.hstack((W[:, :col-1], W[:, col:])) V = np.hstack((V[:, :col-1], V[:, col:])) try: check_eye(T, Tinv) except AssertionError: V, W = check_rank(V, W) # need to check if necessary # V = krylovutils.mgs_ortho(V) # W = krylovutils.mgs_ortho(W) # V, W = check_rank(V, W) # breakpoint() return V, W if __name__=="__main__": import numpy as np A = np.random.rand(20, 20) eigsA = np.sort(np.abs(np.linalg.eigvals(A))) print(eigsA) print("Number of stable eigvals = %g" %np.sum(np.abs(eigsA)<=1) ) rom = Krylov() TL, TR = rom.stable_realisation(A) Ap = TL.T.dot(A.dot(TR)) eigsA = np.sort(np.abs(np.linalg.eigvals(Ap))) print("\nNew matrix size %g" % Ap.shape[0]) print('Stable eigvals = %g' % np.sum(np.abs(eigsA)<=1)) print(eigsA) ================================================ FILE: sharpy/rom/utils/__init__.py ================================================ ================================================ FILE: sharpy/rom/utils/krylovutils.py ================================================ """Krylov Model Reduction Methods Utilities""" import scipy.sparse as scsp import numpy as np import scipy.linalg as sclalg import sharpy.linear.src.libsparse as libsp import sharpy.utils.cout_utils as cout def block_arnoldi_krylov(r, F, G, approx_type='Pade', side='controllability'): n = G.shape[0] m = G.shape[1] Q0, R0, P0 = sclalg.qr(G, pivoting=True) Q0 = Q0[:, :m] Q = np.zeros((n,m*r), dtype=complex) V = np.zeros((n,m*r), dtype=complex) for k in range(r): if k == 0: Q[:, 0:m] = F.dot(Q0) else: Q[:, k*m: k*m + m] = F.dot(Q[:, (k-1)*m:(k-1)*m + m]) Q[:, :k*m + m] = mgs_ortho(Q[:, :k*m+m]) Qf, R, P = sclalg.qr(Q[:, k*m: k*m + m], pivoting=True) Q[:, k*m: k*m + m ] = Qf[:, :m] if R[0,0] >= 1e-6: V[:, k*m:k*m + m] = Q[:, k*m: k*m + m ] else: print('Deflating') k -= 1 V = mgs_ortho(V) return V def mgs_ortho(X): r""" Modified Gram-Schmidt Orthogonalisation Orthogonalises input matrix :math:`\mathbf{X}` column by column. Args: X (np.ndarray): Input matrix of dimensions :math:`n` by :math:`m`. Returns: np.ndarray: Orthogonalised matrix of dimensions :math:`n` by :math:`m`. Notes: This method is faster than scipy's :func:`scipy.linalg.qr` method that returns an orthogonal matrix as part of the QR decomposition, albeit at a higher number of function calls. """ # Q, R = sclalg.qr(X) n = X.shape[1] m = X.shape[0] Q = np.zeros((m, n), dtype=complex) for i in range(n): w = X[:, i] for j in range(i): h = Q[:, j].T.dot(w) w = w - h * Q[:, j] Q[:, i] = w / sclalg.norm(w) return Q def construct_krylov(r, lu_A, B, approx_type='Pade', side='b'): r""" Contructs a Krylov subspace in an iterative manner following the methods of Gugercin [1]. The construction of the Krylov space is focused on Pade and partial realisation cases for the purposes of model reduction. I.e. the partial realisation form of the Krylov space is used if ``approx_type = 'partial_realisation'`` .. math:: \text{range}(\textbf{V}) = \mathcal{K}_r(\mathbf{A}, \mathbf{b}) Else, it is replaced by the Pade approximation form: .. math:: \text{range}(\textbf{V}) = \mathcal{K}_r((\sigma\mathbf{I}_n - \mathbf{A})^{-1}, (\sigma\mathbf{I}_n - \mathbf{A})^{-1}\mathbf{b}) Note that no inverses are actually computed but rather a single LU decomposition is performed at the beginning of the algorithm. Forward and backward substitution is used thereinafter to calculate the required vectors. The algorithm also builds the Krylov space for the :math:`\mathbf{C}^T` matrix. It should simply replace ``B`` and ``side`` should be ``side = 'c'``. Examples: Partial Realisation: >>> V = construct_krylov(r, A, B, 'partial_realisation', 'b') >>> W = construct_krylov(r, A, C.T, 'partial_realisation', 'c') Pade Approximation: >>> V = construct_krylov(r, (sigma * np.eye(nx) - A), B, 'Pade', 'b') >>> W = construct_krylov(r, (sigma * np.eye(nx) - A), C.T, 'Pade', 'c') References: [1]. Gugercin, S. - Projection Methods for Model Reduction of Large-Scale Dynamical Systems. PhD Thesis. Rice University. 2003. Args: r (int): Krylov space order lu_A (np.ndarray): For Pade approximations it should be the LU decomposition of :math:`(\sigma I - \mathbf{A})` in tuple form, as output from the :func:`scipy.linalg.lu_factor`. For partial realisations it is simply :math:`\mathbf{A}`. B (np.ndarray): If doing the B side it should be :math:`\mathbf{B}`, else :math:`\mathbf{C}^T`. approx_type (str): Type of approximation: ``partial_realisation`` or ``Pade``. side: Side of the projection ``b`` or ``c``. Returns: np.ndarray: Projection matrix """ nx = B.shape[0] # Side indicates projection side. if using C then it needs to be transposed if side == 'c': transpose_mode = 1 B.shape = (nx, 1) else: transpose_mode = 0 B.shape = (nx, 1) # Output projection matrices V = np.zeros((nx, r), dtype=complex) H = np.zeros((r, r), dtype=complex) # Declare iterative variables f = np.zeros((nx, r), dtype=complex) if approx_type == 'partial_realisation': A = lu_A v_arb = B v = v_arb / np.linalg.norm(v_arb) w = A.dot(v) else: # LU decomposition v = lu_solve(lu_A, B, trans=transpose_mode) v = v / np.linalg.norm(v) w = lu_solve(lu_A, v) alpha = v.T.dot(w) # Initial assembly f[:, :1] = w - v.dot(alpha) V[:, :1] = v H[0, 0] = alpha for j in range(0, r-1): beta = np.linalg.norm(f[:, j]) v = 1 / beta * f[:, j] V[:, j+1] = v H_hat = np.block([[H[:j+1, :j+1]], [beta * evec(j)]]) if approx_type == 'partial_realisation': w = A.dot(v) else: w = lu_solve(lu_A, v, trans=transpose_mode) h = V[:, :j+2].T.dot(w) f[:, j+1] = w - V[:, :j+2].dot(h) # Finite precision s = V[:, :j+2].T.dot(f[:, j+1]) f[:, j+1] = f[:, j+1] - V[:, :j+2].dot(s) h += s h.shape = (j+2, 1) # Enforce shape for concatenation H[:j+2, :j+2] = np.block([H_hat, h]) return V def lu_factor(sigma, A): r""" LU Factorisation wrapper of: .. math:: LU = (\sigma \mathbf{I} - \mathbf{A}) In the case of ``A`` being a sparse matrix, the sparse methods in scipy are employed Args: sigma (float): Expansion frequency A (csc_matrix or np.ndarray): Dynamics matrix Returns: tuple or SuperLU: tuple (dense) or SuperLU (sparse) objects containing the LU factorisation """ n = A.shape[0] if type(A) == libsp.csc_matrix: return scsp.linalg.splu(sigma * scsp.identity(n, dtype=complex, format='csc') - A) else: return sclalg.lu_factor(sigma * np.eye(n) - A) def lu_solve(lu_A, b, trans=0): r""" LU solve wrapper. Computes the solution to .. math:: \mathbf{Ax} = \mathbf{b} or .. math:: \mathbf{A}^T\mathbf{x} = \mathbf{b} if ``trans=1``. It uses the ``SuperLU.solve()`` method if the input is a ``SuperLU`` or else will revert to the dense methods in scipy. Args: lu_A (SuperLU or tuple): object or tuple containing the information of the LU factorisation b (np.ndarray): Right hand side vector to solve trans (int): ``0`` or ``1`` for either solution option. Returns: np.ndarray: Solution to the system. """ transpose_mode_dict = {0: 'N', 1: 'T'} if type(lu_A) == scsp.linalg.SuperLU: return lu_A.solve(b, trans=transpose_mode_dict[trans]) else: return sclalg.lu_solve(lu_A, b, trans=trans) def construct_mimo_krylov(r, lu_A_input, B, approx_type='Pade', side='controllability'): if side == 'controllability' or side == 'b': transpose_mode = 0 elif side == 'observability' or side == 'c': transpose_mode = 1 else: raise NameError('Unknown option for side: %s', side) m = B.shape[1] # Full system number of inputs/outputs n = B.shape[0] # Full system number of states deflation_tolerance = 1e-4 # Inexact deflation tolerance to approximate norm(V)=0 in machine precision # Preallocated size may be too large in case columns are deflated last_column = 0 # Pre-allocate w, V V = np.zeros((n, m * r), dtype=complex) w = np.zeros((n, m * r), dtype=complex) # Initialise w, may be smaller than this due to deflation # V = np.zeros((n, m * r)) # w = np.zeros((n, m * r)) # Initialise w, may be smaller than this due to deflation if approx_type == 'partial_realisation': G = B F = lu_A_input else: G = lu_solve(lu_A_input, B, transpose_mode) for k in range(m): w[:, k] = G[:, k] ## Orthogonalise w_k to preceding w_j for j < k if k >= 1: w[:, :k+1] = mgs_ortho(w[:, :k+1])[:, :k+1] # from sharpy.rom.krylov import check_eye # try: # check_eye(w[:, :k+1], w[:, :k+1].T) # except AssertionError: # print('failing here - k = %g' % k) V[:, :m+1] = w[:, :m+1] last_column += m mu = m # Initialise controllability index mu_c = m # Guess at controllability index with no deflation t = m # worked column index # from sharpy.rom.krylov import check_eye # try: # check_eye(V[:, :m+1], V[:, :m+1].T) # except AssertionError: # print('failing here') for k in range(1, r): for j in range(mu_c): if approx_type == 'partial_realisation': w[:, t] = F.dot(w[:, t-mu]) else: w[:, t] = lu_solve(lu_A_input, w[:, t-mu], transpose_mode) # Orthogonalise w[:,t] against V_i - w[:, :t+1] = mgs_ortho(w[:, :t+1])[:, :t+1] # if np.linalg.norm(w[:, t]) < deflation_tolerance: # # Deflate w_k # cout.cout_wrap('\tVector deflated', 3) # w = [w[:, 0:t], w[:, t+1:]] # last_column -= 1 # mu -= 1 # else: # pass # # V[:, t] = w[:, t] # # last_column += 1 # # t += 1 try: check_eye(w[:, :t+1], w[:, :t+1].T) V[:, t] = w[:, t] last_column += 1 t += 1 except AssertionError: cout.cout_wrap('\tMatrix lost orthogonality creating Krylov subspace:' ' \n\t\tKrylov order = %g\n\t\tInput vector = %g' % (k, j), 2) w = np.hstack((w[:, 0:t], w[:, t+1:])) cout.cout_wrap('\t\tVector deflated', 2) last_column -= 1 mu -= 1 mu_c = mu try: check_eye(V[:, :t], V[:, :t].T) except AssertionError: raise ValueError('Krylov space construction failed to create an orthogonal space') return V[:, :t] def build_krylov_space(frequency, r, side, a, b): if frequency == np.inf or frequency.real == np.inf: approx_type = 'partial_realisation' lu_a = a else: approx_type = 'Pade' lu_a = lu_factor(frequency, a) try: nu = b.shape[1] except IndexError: nu = 1 if nu == 1: krylov_function = construct_krylov else: krylov_function = construct_mimo_krylov v = krylov_function(r, lu_a, b, approx_type, side) return v def evec(j): """j-th unit vector (in row format) Args: j: Unit vector dimension Returns: np.ndarray: j-th unit vector Examples: >>> evec(2) np.array([0, 1]) >>> evec(3) np.array([0, 0, 1]) """ e = np.zeros(j+1) e[j] = 1 return e def schur_ordered(A, ct=False): r"""Returns block ordered complex Schur form of matrix :math:`\mathbf{A}` .. math:: \mathbf{TAT}^H = \mathbf{A}_s = \begin{bmatrix} A_{11} & A_{12} \\ 0 & A_{22} \end{bmatrix} where :math:`A_{11}\in\mathbb{C}^{s\times s}` contains the :math:`s` stable eigenvalues of :math:`\mathbf{A}\in\mathbb{R}^{m\times m}`. Args: A (np.ndarray): Matrix to decompose. ct (bool): Continuous time system. Returns: tuple: Tuple containing the Schur decomposition of :math:`\mathbf{A}`, :math:`\mathbf{A}_s`; the transformation :math:`\mathbf{T}\in\mathbb{C}^{m\times m}`; and the number of stable eigenvalues of :math:`\mathbf{A}`. Notes: This function is a wrapper of ``scipy.linalg.schur`` imposing the settings required for this application. """ if ct: sort_eigvals = 'lhp' else: sort_eigvals = 'iuc' # if A.dtype == complex: # output_form = 'complex' # else: # output_form = 'real' # issues when not using the complex form of the Schur decomposition output_form = 'complex' As, Tt, n_stable1 = sclalg.schur(A, output=output_form, sort=sort_eigvals) if sort_eigvals == 'lhp': n_stable = np.sum(np.linalg.eigvals(A).real <= 0) elif sort_eigvals == 'iuc': n_stable = np.sum(np.abs(np.linalg.eigvals(A)) <= 1.) else: raise NameError('Unknown sorting of eigenvalues. Either iuc or lhp') assert n_stable == n_stable1, 'Number of stable eigenvalues not equal in Schur output and manual calculation' assert (np.abs(As-np.conj(Tt.T).dot(A.dot(Tt))) < 1e-4).all(), 'Schur breakdown - A_schur != T^H A T' return As, Tt.T, n_stable def remove_a12(As, n_stable): r"""Basis change to remove the (1, 2) block of the block-ordered real Schur matrix :math:`\mathbf{A}` Being :math:`\mathbf{A}_s\in\mathbb{R}^{m\times m}` a matrix of the form .. math:: \mathbf{A}_s = \begin{bmatrix} A_{11} & A_{12} \\ 0 & A_{22} \end{bmatrix} the (1,2) block is removed by solving the Sylvester equation .. math:: \mathbf{A}_{11}\mathbf{X} - \mathbf{X}\mathbf{A}_{22} + \mathbf{A}_{12} = 0 used to build the change of basis .. math:: \mathbf{T} = \begin{bmatrix} \mathbf{I}_{s,s} & -\mathbf{X}_{s,u} \\ \mathbf{0}_{u, s} & \mathbf{I}_{u,u} \end{bmatrix} where :math:`s` and :math:`u` are the respective number of stable and unstable eigenvalues, such that .. math:: \mathbf{TA}_s\mathbf{T}^\top = \begin{bmatrix} A_{11} & \mathbf{0} \\ 0 & A_{22} \end{bmatrix}. Args: As (np.ndarray): Block-ordered real Schur matrix (can be built using :func:`sharpy.rom.utils.krylovutils.schur_ordered`). n_stable (int): Number of stable eigenvalues in ``As``. Returns: np.ndarray: Basis transformation :math:`\mathbf{T}\in\mathbb{R}^{m\times m}`. References: Jaimoukha, I. M., Kasenally, E. D.. Implicitly Restarted Krylov Subspace Methods for Stable Partial Realizations SIAM Journal of Matrix Analysis and Applications, 1997. """ A11 = As[:n_stable, :n_stable] A12 = As[:n_stable, n_stable:] A22 = As[n_stable:, n_stable:] n = As.shape[0] X = sclalg.solve_sylvester(A11, -A22, -A12) T = np.block([[np.eye(n_stable), -X], [np.zeros((n-n_stable, n_stable)), np.eye(n-n_stable)]]) T2 = np.eye(n, n_stable) # App = T2.T.dot(T.dot(As.dot(np.linalg.inv(T).dot(T2)))) return T, X def check_eye(T, Tinv, msg='', eps=-6): r"""Simple utility to verify matrix inverses Asserts that .. math:: \mathbf{T}^{-1}\mathbf{T} = \mathbf{I} Args: T (np.ndarray): Matrix to test Tinv (np.ndarray): Supposed matrix inverse msg (str): Output error message if inverse check not satisfied eps (float): Error threshold (:math:`10^\varepsilon`) Raises: AssertionError: if matrix inverse check is not satisfied """ eye_approx = Tinv.dot(T) max_diff = np.max(np.abs(np.eye(eye_approx.shape[0]) - eye_approx)) try: log_error = np.log10(max_diff) assert log_error < eps, 'Tinv.dot(T) not equal to identity, %s \nlog(error) = %.e' \ % (msg, log_error) except RuntimeWarning: # unlikely event that both matrices are identical and max_diff == 0 pass ================================================ FILE: sharpy/rom/utils/librom.py ================================================ """General ROM utilities S. Maraniello, 14 Feb 2018 """ import warnings import numpy as np import scipy.linalg as scalg import sharpy.linear.src.libsparse as libsp import sharpy.linear.src.libss as libss def balreal_direct_py(A, B, C, DLTI=True, Schur=False, full_outputs=False): r""" Find balanced realisation of continuous (``DLTI = False``) and discrete (``DLTI = True``) time of LTI systems using scipy libraries. The function proceeds to achieve balanced realisation of the state-space system by first solving the Lyapunov equations. They are solved using Barlets-Stewart algorithm for Sylvester equation, which is based on A matrix Schur decomposition. .. math:: \mathbf{A\,W_c + W_c\,A^T + B\,B^T} &= 0 \\ \mathbf{A^T\,W_o + W_o\,A + C^T\,C} &= 0 to obtain the reachability and observability gramians, which are positive definite matrices. Then, the gramians are decomposed into their Cholesky factors such that: .. math:: \mathbf{W_c} &= \mathbf{Q_c\,Q_c^T} \\ \mathbf{W_o} &= \mathbf{Q_o\,Q_o^T} A singular value decomposition (SVD) of the product of the Cholesky factors is performed .. math:: (\mathbf{Q_o^T\,Q_c}) = \mathbf{U\,\Sigma\,V^*} The singular values are then used to build the transformation matrix :math:`\mathbf{T}` .. math:: \mathbf{T} &= \mathbf{Q_c\,V\,\Sigma}^{-1/2} \\ \mathbf{T}^{-1} &= \mathbf{\Sigma}^{-1/2}\,\mathbf{U^T\,Q_o^T} The balanced system is therefore of the form: .. math:: \mathbf{A_b} &= \mathbf{T^{-1}\,A\,T} \\ \mathbf{B_b} &= \mathbf{T^{-1}\,B} \\ \mathbf{C_b} &= \mathbf{C\,T} \\ \mathbf{D_b} &= \mathbf{D} Warnings: This function may be less computationally efficient than the ``balreal`` Matlab implementation and does not offer the option to bound the realisation in frequency and time. Notes: - Lyapunov equations are solved using Barlets-Stewart algorithm for Sylvester equation, which is based on A matrix Schur decomposition. - Notation above is consistent with Gawronski [2]. Args: A (np.ndarray): Plant Matrix B (np.ndarray): Input Matrix C (np.ndarray): Output Matrix DLTI (bool): Discrete time state-space flag Schur (bool): Use Schur decomposition to solve the Lyapunov equations Returns: tuple of np.ndarrays: Tuple of the form ``(S, T, Tinv)`` containing: - Singular values in diagonal matrix (``S``) - Transformation matrix (``T``). - Inverse transformation matrix(``Tinv``). References: [1] Anthoulas, A.C.. Approximation of Large Scale Dynamical Systems. Chapter 7. Advances in Design and Control. SIAM. 2005. [2] Gawronski, W.. Dynamics and control of structures. New York: Springer. 1998 """ ### select solver for Lyapunov equation # Notation reminder: # scipy: A X A.T - X = -Q # contr: A W A.T - W = - B B.T # obser: A.T W A - W = - C.T C if DLTI: sollyap = scalg.solve_discrete_lyapunov else: sollyap = scalg.solve_lyapunov # A is a sparse matrix in csr_matrix(sparse) format, can not be directly passed into functions used in scipy _solver.py # Sparse matrices do not work well with Scipy (Version 1.7.3) in the following code, so A is transformed into a dense matrix here first. if type(A) is not np.ndarray: try: A = A.todense() except AttributeError: raise TypeError(f'Matrix needs to be in dense form. Unable to convert A matrix of type {type(A)} to ' f'dense using method .todense()') # solve Lyapunov if Schur: # decompose A Atri, U = scalg.schur(A) # solve Lyapunov BBtri = np.dot(U.T, np.dot(B, np.dot(B.T, U))) CCtri = np.dot(U.T, np.dot(C.T, np.dot(C, U))) Wctri = sollyap(Atri, BBtri) Wotri = sollyap(Atri.T, CCtri) # reconstruct Wo,Wc Wc = np.dot(U, np.dot(Wctri, U.T)) Wo = np.dot(U, np.dot(Wotri, U.T)) else: Wc = sollyap(A, np.dot(B, B.T)) Wo = sollyap(A.T, np.dot(C.T, C)) # Choleski factorisation: W=Q Q.T # Qc = scalg.cholesky(Wc).T # Qo = scalg.cholesky(Wo).T # build M matrix and SVD # M = np.dot(Qo.T, Qc) # U, s, Vh = scalg.svd(M) # S = np.diag(s) # Sinv = np.diag(1. / s) # V = Vh.T # Build transformation matrices # T = np.dot(Qc, np.dot(V, np.sqrt(Sinv))) # Tinv = np.dot(np.sqrt(Sinv), np.dot(U.T, Qo.T)) # return S, T, Tinv ### Find transformation matrices # avoid Cholevski - unstable # Building T and Tinv using SVD: Uc, Sc, Vc = scalg.svd(Wc) Uo, So, Vo = scalg.svd(Wo) # Perform decomposition: Sc = np.sqrt(np.diag(Sc)) So = np.sqrt(np.diag(So)) Qc = Uc @ Sc Qot = So @ Vo # Build Hankel matrix: H = Qot @ Qc # Find SVD of Hankel matrix: U, hsv, Vt = scalg.svd(H) # hsv = np.diag(hsv) # Find T and Tinv: S = np.sqrt(np.diag(hsv)) # Please note, the notation below is swapped as compared to regular notation in the literature. # This is a known feature of SHARPy, hence it is maintained throughout (including documentation). Tinv = scalg.inv(S) @ U.T @ Qot T = Qc @ Vt.T @ scalg.inv(S) if full_outputs is False: return hsv, T, Tinv else: # get square-root factors # UT, QoT = scalg.qr(np.dot(np.diag(np.sqrt(hsv)), Tinv), pivoting=False) # Vh, QcT = scalg.qr(np.dot(T, np.diag(np.sqrt(hsv))).T, pivoting=False) # return hsv, UT.T, Vh, QcT.T, QoT.T return hsv, U, Vt, Qc, Qot.T def balreal_iter(A, B, C, lowrank=True, tolSmith=1e-10, tolSVD=1e-6, kmin=None, tolAbs=False, Print=False, outFacts=False): """ Find balanced realisation of DLTI system. Notes: Lyapunov equations are solved using iterative squared Smith algorithm, in its low or full rank version. These implementations are as per the low_rank_smith and smith_iter functions respectively but, for computational efficiency, the iterations are rewritten here so as to solve for the observability and controllability Gramians contemporary. - Exploiting sparsity: This algorithm is not ideal to exploit sparsity. However, the following strategies are implemented: - if the A matrix is provided in sparse format, the powers of A will be calculated exploiting sparsity UNTIL the number of non-zero elements is below 15% the size of A. Upon this threshold, the cost of the matrix multiplication rises dramatically, and A is hence converted to a dense numpy array. """ ### Solve Lyapunov equations # Notation reminder: # scipy: A X A.T - X = -Q # contr: A W A.T - W = - B B.T # obser: A.T W A - W = - C.T C # low-rank smith: A.T X A - X = -Q Q.T # matrices size N = A.shape[0] rC = B.shape[1] rO = C.shape[0] if lowrank: # low-rank square-Smith iteration (with SVD) # initialise smith iteration DeltaNorm = 1e6 # error DeltaNormNext = DeltaNorm ** 2 # error expected at next iter kk = 0 Qck = B Qok = C.T if Print: print('Iter\tMaxZ\t|\trank_c\trank_o\tA size') while DeltaNorm > tolSmith and DeltaNormNext > 1e-3 * tolSmith: ###### controllability ### compute Ak^2 * Qck # (future: use block Arnoldi) Qcright = libsp.dot(A, Qck) MaxZhere = np.max(np.abs(Qcright)) ### enlarge Z matrices Qck = np.concatenate((Qck, Qcright), axis=1) Qcright = None rC = Qck.shape[1] if kmin == None or kmin < rC: ### "cheap" SVD truncation Uc, svc = scalg.svd(Qck, full_matrices=False, overwrite_a=True, lapack_driver='gesdd')[:2] # import scipy.linalg.interpolative as sli # Ucnew,svcnew,temp=sli.svd(Qck,tolSVD) if tolAbs: rcmax = np.sum(svc > tolSVD) else: rcmax = np.sum(svc > tolSVD * svc[0]) if kmin != None: rC = max(rcmax, kmin) else: rC = rcmax Qck = Uc[:, :rC] * svc[:rC] # free memory Uc = None Qcright = None ###### observability ### compute Ak^2 * Qok # (future: use block Arnoldi) Qoright = np.transpose(libsp.dot(Qok.T, A)) DeltaNorm = max(MaxZhere, np.max(np.abs(Qoright))) ### enlarge Z matrices Qok = np.concatenate((Qok, Qoright), axis=1) Qoright = None rO = Qok.shape[1] if kmin == None or kmin < rO: ### "cheap" SVD truncation Uo, svo = scalg.svd(Qok, full_matrices=False)[:2] if tolAbs: romax = np.sum(svo > tolSVD) else: romax = np.sum(svo > tolSVD * svo[0]) if kmin != None: rO = max(romax, kmin) else: rO = romax Qok = Uo[:, :rO] * svo[:rO] Uo = None ##### Prepare next time step if Print: print('%.3d\t%.2e\t%.5d\t%.5d\t%.5d' % (kk, DeltaNorm, rC, rO, N)) DeltaNormNext = DeltaNorm ** 2 if DeltaNorm > tolSmith and DeltaNormNext > 1e-3 * tolSmith: # compute power if type(A) is libsp.csc_matrix: A = A.dot(A) # check sparsity if A.size > 0.15 * N ** 2: A = A.toarray() elif type(A) is np.ndarray: A = np.linalg.matrix_power(A, 2) else: raise NameError('Type of A not supported') ### update kk = kk + 1 A = None else: # full-rank squared smith iteration (with Cholevsky) raise NameError('Use balreal_iter_old instead!') # find min size (only if iter used) cc, co = Qck.shape[1], Qok.shape[1] if Print: print('cc=%.2d, co=%.2d' % (cc, co)) print('rank(Zc)=%.4d\trank(Zo)=%.4d' % (rcmax, romax)) # build M matrix and SVD M = libsp.dot(Qok.T, Qck) U, s, Vh = scalg.svd(M, full_matrices=False) if outFacts: return s, Qck, Qok else: sinv = s ** (-0.5) T = libsp.dot(Qck, Vh.T * sinv) Tinv = np.dot((U * sinv).T, Qok.T) if Print: print('rank(Zc)=%.4d\trank(Zo)=%.4d' % (rcmax, romax)) return s, T, Tinv, rcmax, romax def balreal_iter_old(A, B, C, lowrank=True, tolSmith=1e-10, tolSVD=1e-6, kmax=None, tolAbs=False): """ Find balanced realisation of DLTI system. Notes: Lyapunov equations are solved using iterative squared Smith algorithm, in its low or full rank version. These implementations are as per the low_rank_smith and smith_iter functions respectively but, for computational efficiency,, the iterations are rewritten here so as to solve for the observability and controllability Gramians contemporary. """ ### Solve Lyapunov equations # Notation reminder: # scipy: A X A.T - X = -Q # contr: A W A.T - W = - B B.T # obser: A.T W A - W = - C.T C # low-rank smith: A.T X A - X = -Q Q.T if lowrank: # low-rank square-Smith iteration (with SVD) # matrices size N = A.shape[0] rB = B.shape[1] rC = C.shape[0] # initialise smith iteration DeltaNorm = 1e6 print('Iter\tMaxZhere') kk = 0 Apow = A Qck = B Qok = C.T while DeltaNorm > tolSmith: ### compute products Ak^2 * Zk ### (use block Arnoldi) Qcright = np.dot(Apow, Qck) Qoright = np.dot(Apow.T, Qok) Apow = np.dot(Apow, Apow) ### enlarge Z matrices Qck = np.concatenate((Qck, Qcright), axis=1) Qok = np.concatenate((Qok, Qoright), axis=1) ### check convergence without reconstructing the added term MaxZhere = max(np.max(np.abs(Qoright)), np.max(np.abs(Qcright))) print('%.4d\t%.3e' % (kk, MaxZhere)) DeltaNorm = MaxZhere # fixed columns chopping if kmax is None: # cheap SVD truncation if Qck.shape[1] > .4 * N or Qok.shape[1] > .4 * N: Uc, svc = scalg.svd(Qck, full_matrices=False)[:2] Uo, svo = scalg.svd(Qok, full_matrices=False)[:2] if tolAbs: rcmax = np.sum(svc > tolSVD) romax = np.sum(svo > tolSVD) else: rcmax = np.sum(svc > tolSVD * svc[0]) romax = np.sum(svo > tolSVD * svo[0]) pmax = max(rcmax, romax) Qck = Uc[:, :pmax] * svc[:pmax] Qok = Uo[:, :pmax] * svo[:pmax] # Qck_old=np.dot(Uc[:,:pmax],np.diag(svc[:pmax])) # Qok_old=np.dot(Uo[:,:pmax],np.diag(svo[:pmax])) # Qck=np.dot(Uc[:,:rcmax],np.diag(svc[:rcmax])) # Qok=np.dot(Uo[:,:romax],np.diag(svo[:romax])) else: if Qck.shape[1] > kmax: Uc, svc = scalg.svd(Qck, full_matrices=False)[:2] Qck = Uc[:, :kmax] * svc[:kmax] if Qok.shape[1] > kmax: Uo, svo = scalg.svd(Qok, full_matrices=False)[:2] Qok = Uo[:, :kmax] * svo[:kmax] ### update kk = kk + 1 del Apow Qc, Qo = Qck, Qok else: # full-rank squared smith iteration (with Cholevsky) # first iteration Wc = np.dot(B, B.T) Wo = np.dot(C.T, C) Apow = A AXAobs = np.dot(np.dot(A.T, Wo), A) AXActrl = np.dot(np.dot(A, Wc), A.T) DeltaNorm = max(np.max(np.abs(AXAobs)), np.max(np.abs(AXActrl))) kk = 1 print('Iter\tRes') while DeltaNorm > tolSmith: kk = kk + 1 # update Wo = Wo + AXAobs Wc = Wc + AXActrl # incremental Apow = np.dot(Apow, Apow) AXAobs = np.dot(np.dot(Apow.T, Wo), Apow) AXActrl = np.dot(np.dot(Apow, Wc), Apow.T) DeltaNorm = max(np.max(np.abs(AXAobs)), np.max(np.abs(AXActrl))) print('%.4d\t%.3e' % (kk, DeltaNorm)) # final update (useless in very low tolerance) Wo = Wo + AXAobs Wc = Wc + AXActrl # Choleski factorisation: W=Q Q.T. If unsuccessful, directly solve # eigenvalue problem Qc = scalg.cholesky(Wc).T Qo = scalg.cholesky(Wo).T # # eigenvalues are normalised by one, hence Tinv and T matrices # # here are not scaled # ssq,Tinv,T=scalg.eig(np.dot(Wc,Wo),left=True,right=True) # Tinv=Tinv.T # #Tinv02=Tinv02.T # S=np.diag(np.sqrt(ssq)) # return S,T,Tinv # find min size (only if iter used) cc, co = Qc.shape[1], Qo.shape[1] cmin = min(cc, co) print('cc=%.2d, co=%.2d' % (cc, co)) # build M matrix and SVD M = np.dot(Qo.T, Qc) # ### not optimised # U,s,Vh=scalg.svd(M,full_matrices=True) # U,Vh,s=U[:,:cmin],Vh[:cmin,:],s[:cmin] # S=np.diag(s) # Sinv=np.diag(1./s) # V=Vh.T # # Build transformation matrices # T=np.dot(Qc,np.dot(V,np.sqrt(Sinv))) # Tinv=np.dot(np.sqrt(Sinv),np.dot(U.T,Qo.T)) ### optimised U, s, Vh = scalg.svd(M, full_matrices=True) # as M is square, full_matrices has no effect sinv = s ** (-0.5) T = np.dot(Qc, Vh.T * sinv) Tinv = np.dot((U * sinv).T, Qo.T) return s, T, Tinv def smith_iter(S, T, tol=1e-8, Square=True): """ Solves the Stein equation S.T X S - X = -T by mean of Smith or squared-Smith algorithm. Note that a solution X exists only if the eigenvalues of S are stricktly smaller than one, and the algorithm will not converge otherwise. The algorithm can not exploit sparsity, hence, while convergence can be improved for very large matrices, it can not be employed if matrices are too large to be stored in memory. Ref. Penzt, "A cyclic low-rank Smith method for large sparse Lyapunov equations", 2000. """ N = S.shape[0] if Square: # first iteration X = T Spow = S STXS = np.dot(np.dot(S.T, X), S) DeltaNorm = np.max(np.abs(STXS)) # # second iteration: # # can be removed using Spow=np.dot(Spow,Spow) # X=X+STXS # S=np.dot(S,S) # Spow=S # STXS=np.dot(np.dot(Spow.T,X),Spow) # DeltaNorm=np.max(np.abs(STXS)) counter = 1 print('Iter\tRes') while DeltaNorm > tol: counter = counter + 1 # update X = X + STXS # incremental # Spow=np.dot(Spow,S) # use this if uncomment second iter Spow = np.dot(Spow, Spow) STXS = np.dot(np.dot(Spow.T, X), Spow) DeltaNorm = np.max(np.abs(STXS)) print('%.4d\t%.3e' % (counter, DeltaNorm)) else: # first iteration X = T Spow = S STTS = np.dot(np.dot(Spow.T, T), Spow) DeltaNorm = np.max(np.abs(STTS)) counter = 1 print('Iter\tRes') while DeltaNorm > tol: counter = counter + 1 # update X = X + STTS # incremental Spow = np.dot(Spow, S) STTS = np.dot(np.dot(Spow.T, T), Spow) DeltaNorm = np.max(np.abs(STTS)) print('%.4d\t%.3e' % (counter, DeltaNorm)) print('Error %.2e achieved after %.4d iteration!' % (DeltaNorm, counter)) return X def res_discrete_lyap(A, Q, Z, Factorised=True): """ Provides residual of discrete Lyapunov equation: A.T X A - X = -Q Q.T If Factorised option is true, X=Z*Z.T otherwise X=Z is chosen. Reminder: contr: A W A.T - W = - B B.T obser: A.T W A - W = - C.T C """ if Factorised: X = np.dot(Z, Z.T) else: X = Z R = np.dot(A.T, np.dot(X, A)) - X + np.dot(Q, Q.T) resinf = np.max(np.abs(R)) return resinf def low_rank_smith(A, Q, tol=1e-10, Square=True, tolSVD=1e-12, tolAbs=False, kmax=None, fullOut=True, Convergence='Zk'): """ Low-rank smith algorithm for Stein equation A.T X A - X = -Q Q.T The algorithm can only be used if T is symmetric positive-definite, but this is not checked in this routine for computational performance. The solution X is provided in its factorised form: X=Z Z.T As in the most general case, a solution X exists only if the eigenvalues of S are stricktly smaller than one, and the algorithm will not converge otherwise. The algorithm can not exploits parsity, hence, while convergence can be improved for very large matrices, it can not be employed if matrices are too large to be stored in memory. Parameters: - tol: tolerance for stopping convergence of Smith algorithm - Square: if true the squared-Smith algorithm is used - tolSVD: tolerance for reduce Z matrix based on singular values - kmax: if given, the Z matrix is forced to have size kmax - tolAbs: if True, the tolerance - fullOut: not implemented - Convergence: 'Zk','res'. - If 'Zk' the iteration is stopped when the inf norm of the incremental matrix goes below tol. - If 'res' the residual of the Lyapunov equation is computed. This strategy may fail to converge if kmax is too low or tolSVD too large! Ref. P. Benner, G.E. Khoury and M. Sadkane, "On the squared Smith method for large-scale Stein equations", 2014. """ N = A.shape[0] ncol = Q.shape[1] AT = A.T DeltaNorm = 1e6 print('Iter\tMaxZhere') kk = 0 SvList = [] ZcColList = [] if Square: # ------------------------------------------------- squared iter Zk = Q while DeltaNorm > tol: ### compute product Ak^2 * Zk ### use block Arnoldi ## too expensive!! # Zright=Zk # for ii in range(2**kk): # Zright=np.dot(AT,Zright) Zright = np.dot(AT, Zk) AT = np.dot(AT, AT) ### enlarge Z matrix Zk = np.concatenate((Zk, Zright), axis=1) ### check convergence if Convergence == 'Zk': ### check convergence without reconstructing the added term MaxZhere = np.max(np.abs(Zright)) print('%.4d\t%.3e' % (kk, MaxZhere)) DeltaNorm = MaxZhere elif Convergence == 'res': ### check convergence through residual resinf = res_discrete_lyap(A, Q, Zk, Factorised=True) print('%.4d\t%.3e\t%.3e' % (kk, MaxZhere, resinf)) DeltaNorm = resinf # cheap SVD truncation U, sv, Vh = scalg.svd(Zk, full_matrices=False) # embed() if kmax == None: if tolAbs: pmax = np.sum(sv > tolSVD) else: pmax = np.sum(sv > tolSVD * sv[0]) else: pmax = kmax Ut = U[:, :pmax] svt = sv[:pmax] # Vht=Vh[:pmax,:] # Zkrec=np.dot(Ut,np.dot(np.diag(svt),Vht)) Zk = np.dot(Ut, np.diag(svt)) ### update kk = kk + 1 else: # -------------------------------------------------------- smith iter raise NameError( 'Smith method without SVD will lead to extremely large matrices') Zk = [] Zk.append(Q) while DeltaNorm > tol: Zk.append(np.dot(AT, Zk[-1])) kk = kk + 1 # check convergence without reconstructing Z*Z.T MaxZhere = np.max(np.abs(Zk[-1])) print('%.4d\t%.3e' % (kk, MaxZhere)) DeltaNorm = MaxZhere Zk = np.concatenate(tuple(Zk), axis=1) return Zk ### utilities for balfreq def get_trapz_weights(k0, kend, Nk, knyq=False): """ Returns uniform frequency grid (kv of length Nk) and weights (wv) for Gramians integration using trapezoidal rule. If knyq is True, it is assumed that kend is also the Nyquist frequency. """ assert k0 >= 0. and kend >= 0., 'Frequencies must be positive!' dk = (kend - k0) / (Nk - 1.) kv = np.linspace(k0, kend, Nk) wv = np.ones((Nk,)) * dk * np.sqrt(2) if k0 / (kend - k0) < 1e-10: wv[0] = .5 * dk else: wv[0] = dk / np.sqrt(2) if knyq: wv[-1] = .5 * dk else: wv[-1] = dk / np.sqrt(2) return kv, wv def get_gauss_weights(k0, kend, Npart, order): """ Returns gauss-legendre frequency grid (kv of length Npart*order) and weights (wv) for Gramians integration. The integration grid is divided into Npart partitions, and in each of them integration is performed using a Gauss-Legendre quadrature of order order. Note: integration points are never located at k0 or kend, hence there is no need for special treatment as in (for e.g.) a uniform grid case (see get_unif_weights) """ if Npart == 1: # get gauss normalised coords and weights xad, wad = np.polynomial.legendre.leggauss(order) krange = kend - k0 kv = .5 * (k0 + kend) + .5 * krange * xad wv = wad * (.5 * krange) * np.sqrt(2) print('partitioning: %.3f to %.3f' % (k0, kend)) else: kv = np.zeros((Npart * order,)) wv = np.zeros((Npart * order,)) dk_part = (kend - k0) / Npart for ii in range(Npart): k0_part = k0 + ii * dk_part kend_part = k0_part + dk_part iivec = range(order * ii, order * (ii + 1)) kv[iivec], wv[iivec] = get_gauss_weights(k0_part, kend_part, Npart=1, order=order) return kv, wv def balfreq(SS, DictBalFreq): """ Method for frequency limited balancing. The Observability and controllability Gramians over the frequencies kv are solved in factorised form. Balanced modes are then obtained with a square-root method. Details: * Observability and controllability Gramians are solved in factorised form through explicit integration. The number of integration points determines both the accuracy and the maximum size of the balanced model. * Stability over all (Nb) balanced states is achieved if: a. one of the Gramian is integrated through the full Nyquist range b. the integration points are enough. Input: - DictBalFreq: dictionary specifying integration method with keys: - ``frequency``: defines limit frequencies for balancing. The balanced model will be accurate in the range ``[0,F]``, where ``F`` is the value of this key. Note that ``F`` units must be consistent with the units specified in the ``self.ScalingFacts`` dictionary. - ``method_low``: ``['gauss','trapz']`` specifies whether to use gauss quadrature or trapezoidal rule in the low-frequency range ``[0,F]``. - ``options_low``: options to use for integration in the low-frequencies. These depend on the integration scheme (See below). - ``method_high``: method to use for integration in the range [F,F_N], where F_N is the Nyquist frequency. See 'method_low'. - ``options_high``: options to use for integration in the high-frequencies. - ``check_stability``: if True, the balanced model is truncated to eliminate unstable modes - if any is found. Note that very accurate balanced model can still be obtained, even if high order modes are unstable. Note that this option is overridden if "" - ``get_frequency_response``: if True, the function also returns the frequency response evaluated at the low-frequency range integration points. If True, this option also allows to automatically tune the balanced model. Future options: - Ncpu: for parallel run The following integration schemes are available: - ``trapz``: performs integration over equally spaced points using trapezoidal rule. It accepts options dictionaries with keys: - ``points``: number of integration points to use (including domain boundary) - ``gauss`` performs gauss-lobotto quadrature. The domain can be partitioned in Npart sub-domain in which the gauss-lobotto quadrature of order Ord can be applied. A total number of Npart*Ord points is required. It accepts options dictionaries of the form: - ``partitions``: number of partitions - ``order``: quadrature order. Examples: The following dictionary >>> DictBalFreq={'frequency': 1.2, >>> 'method_low': 'trapz', >>> 'options_low': {'points': 12}, >>> 'method_high': 'gauss', >>> 'options_high': {'partitions': 2, 'order': 8}, >>> 'check_stability': True } balances the state-space model in the frequency range [0, 1.2] using: a. 12 equally-spaced points integration of the Gramians in the low-frequency range [0,1.2] and b. A 2 Gauss-Lobotto 8-th order quadratures of the controllability Gramian in the high-frequency range. A total number of 28 integration points will be required, which will result into a balanced model with number of states >>> min{ 2*28* number_inputs, 2*28* number_outputs } The model is finally truncated so as to retain only the first Ns stable modes. """ ### check input dictionary if 'frequency' not in DictBalFreq: raise NameError('Solution dictionary must include the "frequency" key') if 'method_low' not in DictBalFreq: warnings.warn('Setting default options for low-frequency integration') DictBalFreq['method_low'] = 'trapz' DictBalFreq['options_low'] = {'points': 12} if 'method_high' not in DictBalFreq: warnings.warn('Setting default options for high-frequency integration') DictBalFreq['method_high'] = 'gauss' DictBalFreq['options_high'] = {'partitions': 2, 'order': 8} if 'check_stability' not in DictBalFreq: DictBalFreq['check_stability'] = True if 'output_modes' not in DictBalFreq: DictBalFreq['output_modes'] = True if 'get_frequency_response' not in DictBalFreq: DictBalFreq['get_frequency_response'] = False ### get integration points and weights # Nyquist frequency kn = np.pi / SS.dt Opt = DictBalFreq['options_low'] if DictBalFreq['method_low'] == 'trapz': kv_low, wv_low = get_trapz_weights(0., DictBalFreq['frequency'], Opt['points'], False) elif DictBalFreq['method_low'] == 'gauss': kv_low, wv_low = get_gauss_weights(0., DictBalFreq['frequency'], Opt['partitions'], Opt['order']) else: raise NameError( 'Invalid value %s for key "method_low"' % DictBalFreq['method_low']) Opt = DictBalFreq['options_high'] if DictBalFreq['method_high'] == 'trapz': if Opt['points'] == 0: warnings.warn('You have chosen no points in high frequency range!') kv_high, wv_high = [], [] else: kv_high, wv_high = get_trapz_weights(DictBalFreq['frequency'], kn, Opt['points'], True) elif DictBalFreq['method_high'] == 'gauss': if Opt['order'] * Opt['partitions'] == 0: warnings.warn('You have chosen no points in high frequency range!') kv_high, wv_high = [], [] else: kv_high, wv_high = get_gauss_weights(DictBalFreq['frequency'], kn, Opt['partitions'], Opt['order']) else: raise NameError( 'Invalid value %s for key "method_high"' % DictBalFreq['method_high']) ### -------------------------------------------------- loop frequencies ### merge vectors Nk_low = len(kv_low) kvdt = np.concatenate((kv_low, kv_high)) * SS.dt wv = np.concatenate((wv_low, wv_high)) * SS.dt zv = np.cos(kvdt) + 1.j * np.sin(kvdt) Eye = libsp.eye_as(SS.A) Zc = np.zeros((SS.states, 2 * SS.inputs * len(kvdt)), ) Zo = np.zeros((SS.states, 2 * SS.outputs * Nk_low), ) if DictBalFreq['get_frequency_response']: Yfreq = np.empty((SS.outputs, SS.inputs, Nk_low,), dtype=np.complex_) kv = kv_low for kk in range(len(kvdt)): zval = zv[kk] Intfact = wv[kk] # integration factor Qctrl = Intfact * libsp.solve(zval * Eye - SS.A, SS.B) kkvec = range(2 * kk * SS.inputs, 2 * (kk + 1) * SS.inputs) Zc[:, kkvec[:SS.inputs]] = Qctrl.real Zc[:, kkvec[SS.inputs:]] = Qctrl.imag ### ----- frequency response if DictBalFreq['get_frequency_response'] and kk < Nk_low: Yfreq[:, :, kk] = (1. / Intfact) * \ libsp.dot(SS.C, Qctrl, type_out=np.ndarray) + SS.D ### ----- observability if kk >= Nk_low: continue Qobs = Intfact * libsp.solve(np.conj(zval) * Eye - SS.A.T, SS.C.T) kkvec = range(2 * kk * SS.outputs, 2 * (kk + 1) * SS.outputs) Zo[:, kkvec[:SS.outputs]] = Intfact * Qobs.real Zo[:, kkvec[SS.outputs:]] = Intfact * Qobs.imag # delete full matrices Kernel = None Qctrl = None Qobs = None # LRSQM (optimised) U, hsv, Vh = scalg.svd(np.dot(Zo.T, Zc), full_matrices=False) sinv = hsv ** (-0.5) T = np.dot(Zc, Vh.T * sinv) Ti = np.dot((U * sinv).T, Zo.T) # Zc,Zo=None,None ### build frequency balanced model Ab = libsp.dot(Ti, libsp.dot(SS.A, T)) Bb = libsp.dot(Ti, SS.B) Cb = libsp.dot(SS.C, T) SSb = libss.StateSpace(Ab, Bb, Cb, SS.D, dt=SS.dt) ### Eliminate unstable modes - if any: if DictBalFreq['check_stability']: for nn in range(1, len(hsv) + 1): eigs_trunc = scalg.eigvals(SSb.A[:nn, :nn]) eigs_trunc_max = np.max(np.abs(eigs_trunc)) if eigs_trunc_max > 1. - 1e-16: SSb.truncate(nn - 1) hsv = hsv[:nn - 1] T = T[:, :nn - 1] Ti = Ti[:nn - 1, :] break outs = (SSb, hsv) if DictBalFreq['output_modes']: outs += (T, Ti, Zc, Zo, U, Vh) return outs def modred(SSb, N, method='residualisation'): """ Produces a reduced order model with N states from balanced or modal system SSb. Both "truncation" and "residualisation" methods are employed. Note: - this method is designed for small size systems, i.e. a deep copy of SSb is produced by default. """ assert method in ['residualisation', 'realisation', 'truncation'], \ "method must be equal to 'residualisation' or 'truncation'!" assert SSb.dt is not None, 'SSb is not a DLTI!' Nb = SSb.A.shape[0] if Nb == N: SSrom = libss.StateSpace(SSb.A, SSb.B, SSb.C, SSb.D, dt=SSb.dt) return SSrom A11 = SSb.A[:N, :N] B11 = SSb.B[:N, :] C11 = SSb.C[:, :N] D = SSb.D if method == 'truncation': SSrom = libss.StateSpace(A11, B11, C11, D, dt=SSb.dt) else: Nb = SSb.A.shape[0] IA22inv = -SSb.A[N:, N:].copy() eevec = range(Nb - N) IA22inv[eevec, eevec] += 1. IA22inv = scalg.inv(IA22inv, overwrite_a=True) SSrom = libss.StateSpace( A11 + np.dot(SSb.A[:N, N:], np.dot(IA22inv, SSb.A[N:, :N])), B11 + np.dot(SSb.A[:N, N:], np.dot(IA22inv, SSb.B[N:, :])), C11 + np.dot(SSb.C[:, N:], np.dot(IA22inv, SSb.A[N:, :N])), D + np.dot(SSb.C[:, N:], np.dot(IA22inv, SSb.B[N:, :])), dt=SSb.dt) return SSrom def tune_rom(SSb, kv, tol, gv, method='realisation', convergence='all', Print=False): """ Starting from a balanced DLTI, this function determines the number of states N required in a ROM (obtained either through 'residualisation' or 'truncation' as specified in method - see also librom.modred) to match the frequency response of SSb over the frequency array, kv, with absolute accuracy tol. gv contains the balanced system Hankel singular value, and is used to determine the upper bound for the ROM order N. Unless kv does not conver the full Nyquist frequency range, the ROM accuracy is not guaranteed to increase monothonically with the number of states. To account for this, two criteria can be used to determine the ROM convergence: - convergence='all': in this case, the number of ROM states N is chosen such that any ROM of order greater than N produces an error smaller than tol. To guarantee this the ROM frequency response is computed for all N<=Nb, where Nb is the number of balanced states. This method is numerically inefficient. - convergence='min': atempts to find the minimal number of states to achieve the accuracy tol. Note: - the input state-space model, SSb, must be balanced. - the routine in not implemented for numerical efficiency and assumes that SSb is small. """ # reference frequency response Nb = SSb.A.shape[0] Yb = libss.freqresp(SSb, kv, dlti=True) if gv is None: Nmax = Nb else: Nmax = min(np.sum(gv > tol) + 1, Nb) if convergence == 'all': # start from larger size and decrease untill the ROm accuracy is over tol Found = False N = Nmax while not Found: SSrom = modred(SSb, N, method) Yrom = libss.freqresp(SSrom, kv, dlti=True) er = np.max(np.abs(Yrom - Yb)) if Print: print('N=%.3d, er:%.2e (tol=%.2e)' % (N, er, tol)) if N == Nmax and er > tol: warnings.warn( 'librom.tune_rom: error %.2e above tolerance %.2e and HSV bound %.2e' \ % (er, tol, gv[N - 1])) # raise NameError('Hankel singluar values do not '\ # 'provide a bound for error! '\ # 'The balanced system may not be accurate') if er < tol: N -= 1 else: N += 1 Found = True SSrom = modred(SSb, N, method) elif convergence == 'min': Found = False N = 1 while not Found: SSrom = modred(SSb, N, method) Yrom = libss.freqresp(SSrom, kv, dlti=True) er = np.max(np.abs(Yrom - Yb)) if Print: print('N=%.3d, er:%.2e (tol=%.2e)' % (N, er, tol)) if er < tol: Found = True else: N += 1 else: raise NameError("'convergence' method not implemented") return SSrom def eigen_dec(A, B, C, dlti=True, N=None, eigs=None, UR=None, URinv=None, order_by='damp', tol=1e-10, complex=False): """ Eigen decomposition of state-space model (either discrete or continuous time) defined by the A,B,C matrices. Eigen-states are organised in decreasing damping order or increased frequency order such that the truncation ``A[:N,:N], B[:N,:], C[:,:N]`` will retain the least N damped (or lower frequency) modes. If the eigenvalues of A, eigs, are complex, the state-space is automatically convert into real by separating its real and imaginary part. This procedure retains the minimal number of states as only 2 equations are added for each pair of complex conj eigenvalues. Extra care is however required when truncating the system, so as to ensure that the chosen value of N does not retain the real part, but not the imaginary part, of a complex pair. For this reason, the function also returns an optional output, ``Nlist``, such that, for each N in Nlist, the truncation A[:N,:N], B[:N,:], C[:,:N] does guarantee that both the real and imaginary part of a complex conj pair is included in the truncated model. Note that if ```order_by == None``, the eigs and UR must be given in input and must be such that complex pairs are stored consecutively. Args: A: state-space matrix B: state-space matrix C: matrices of state-space model dlti: specifies whether discrete (True) or continuous-time. This information is only required to order the eigenvalues in decreasing dmaping order N: number of states to retain. If None, all states are retained eigs,Ur: eigenvalues and right eigenvector of A matrix as given by: eigs,Ur=scipy.linalg.eig(A,b=None,left=False,right=True) Urinv: inverse of Ur order_by={'damp','freq','stab'}: order according to increasing damping (damp) or decreasing frequency (freq) or decreasing damping (stab). If None, the same order as eigs/UR is followed. tol: absolute tolerance used to identify complex conj pair of eigenvalues complex: if true, the system is left in complex form Returns: (Aproj,Bproj,Cproj): state-space matrices projected over the first N (or N+1 if N removes the imaginary part equations of a complex conj pair of eigenvalues) related to the least damped modes Nlist: list of acceptable truncation values """ if N == None: N = A.shape[0] if order_by is None: assert ((eigs is not None) and (UR is not None)), \ 'Specify criterion to order eigenvalues or provide both eigs and UR' ### compute eigevalues/eigenvectors if eigs is None: eigs, UR = scalg.eig(A, b=None, left=False, right=True) if URinv is None: try: URinv = np.linalg.inv(UR) except LinAlgError: print('The A matrix can not be diagonalised as does not admit ' \ 'linearly independent eigenvectors') ### order eigenvalues/eigenvectors (or verify format) if order_by is None: # verify format nn = 0 while nn < N: if np.abs(eigs[nn].imag) > tol: if nn < N - 1: assert np.abs(eigs[nn].imag + eigs[nn + 1].imag) < tol, \ 'When order_by is None, eigs and UR much be organised such ' \ 'that complex conj pairs are consecutives' else: assert np.abs(eigs[nn].imag + eigs[nn - 1].imag) < tol, \ 'When order_by is None, eigs and UR much be organised such ' \ 'that complex conj pairs are consecutives' nn += 2 else: nn += 1 else: if order_by == 'damp': if dlti: order = np.argsort(np.abs(eigs))[::-1] else: order = np.argsort(eigs.real)[::-1] elif order_by == 'freq': if dlti: order = np.argsort(np.abs(np.angle(eigs))) else: order = np.argsort(np.abs(eigs.imag)) elif order_by == 'stab': if dlti: order = np.argsort(np.abs(eigs)) else: order = np.argsort(eigs.real) else: raise NameError("order_by must be equal to 'damp' or 'freq'") eigs = eigs[order] UR = UR[:, order] URinv = URinv[order, :] ### compute list of available truncation size, Nlist Nlist = [] nn = 0 while nn < N: # check if eig are complex conj if nn < N - 1 and np.abs(eigs[nn] - eigs[nn + 1].conjugate()) < tol: nn += 2 else: nn += 1 Nlist.append(nn) assert Nlist[-1] >= N, \ 'Something failed when identifying the admissible truncation sizes' if Nlist[-1] > N: warnings.warn( 'Resizing the eigendecomposition from %.3d to %.3d states' \ % (N, Nlist[-1])) N = Nlist[-1] ### build complex form if complex: Aproj = np.diag(eigs[:N]) Bproj = np.dot(URinv[:N, :], B) Cproj = np.dot(C, UR[:, :N]) return Aproj, Bproj, Cproj, Nlist ### build real values form Aproj = np.zeros((N, N)) Bproj = np.zeros((N, B.shape[1])) Cproj = np.zeros((C.shape[0], N)) nn = 0 while nn < N: # redundant check if (nn + 1 in Nlist) and np.abs(eigs[nn].imag) < tol: Aproj[nn, nn] = eigs[nn].real Bproj[nn, :] = np.dot(URinv[nn, :].real, B) Cproj[:, nn] = np.dot(C, UR[:, nn].real) nn += 1 else: Aproj[nn, nn] = eigs[nn].real Aproj[nn, nn + 1] = -eigs[nn].imag Aproj[nn + 1, nn] = eigs[nn].imag Aproj[nn + 1, nn + 1] = eigs[nn].real # Bproj[nn, :] = np.dot(URinv[nn, :].real, B) Bproj[nn + 1, :] = np.dot(URinv[nn, :].imag, B) # Cproj[:, nn] = 2. * np.dot(C, UR[:, nn].real) Cproj[:, nn + 1] = -2. * np.dot(C, UR[:, nn].imag) nn += 2 return Aproj, Bproj, Cproj, Nlist def check_stability(A, dt=True): """ Checks the stability of the system. Args: A (np.ndarray): System plant matrix dt (bool): Discrete time system Returns: bool: True if the system is stable """ eigvals = scalg.eigvals(A) if dt: criteria = np.abs(eigvals) > 1. else: criteria = np.real(eigvals) > 0.0 if np.sum(criteria) >= 1.0: return True else: return False ================================================ FILE: sharpy/rom/utils/librom_interp.py ================================================ """Methods for the interpolation of DLTI ROMs This is library for state-space models interpolation. These routines are intended for small size state-space models (ROMs), hence some methods may not be optimised to exploit sparsity structures. For generality purposes, all methods require in input interpolatory weights. The module includes the methods: - :func:`~sharpy.rom.utils.librom_interp.transfer_function`: returns an interpolatory state-space model based on the transfer function method [1]. This method is general and is, effectively, a wrapper of the :func:`sharpy.linear.src.libss.join` method. - :func:`~sharpy.rom.utils.librom_interp.BT_transfer_function`: evolution of transfer function methods. The growth of the interpolated system size is avoided through balancing. References: [1] Benner, P., Gugercin, S. & Willcox, K., 2015. A Survey of Projection-Based Model Reduction Methods for Parametric Dynamical Systems. SIAM Review, 57(4), pp.483–531. Author: S. Maraniello Date: Mar-Apr 2019 """ import warnings import numpy as np import scipy.linalg as scalg # dependency import sharpy.linear.src.libss as libss def transfer_function(SS_list, wv): """ Returns an interpolatory state-space model based on the transfer function method [1]. This method is general and is, effectively, a wrapper of the :func:`sharpy.linear.src.libss.join` method. Features: - stability preserved - system size increases with interpolatory order, but can be optimised for fast on-line evaluation Args: SS_list (list): List of state-space models instances of :class:`sharpy.linear.src.libss.StateSpace` class. wv (list): list of interpolatory weights. Notes: For fast online evaluation, this routine can be optimised to return a class that handles each state-space model independently. See ref. [1] for more details. References: [1] Benner, P., Gugercin, S. & Willcox, K., 2015. A Survey of Projection-Based Model Reduction Methods for Parametric Dynamical Systems. SIAM Review, 57(4), pp.483–531. """ return libss.join(SS_list, wv) def FLB_transfer_function(SS_list, wv, U_list, VT_list, hsv_list=None, M_list=None): r""" Returns an interpolatory state-space model based on the transfer function method [1]. This method is applicable to frequency limited balanced state-space models only. Features: - stability preserved - the interpolated state-space model has the same size than the tabulated ones - all state-space models, need to have the same size and the same numbers of hankel singular values. - suitable for any ROM Args: SS_list (list): List of state-space models instances of :class:`sharpy.linear.src.libss.StateSpace` class. wv (list): list of interpolatory weights. U_list (list): small size, thin SVD factors of Gramians square roots of each state space model (:math:`\mathbf{U}`). VT_list (list): small size, thin SVD factors of Gramians square roots of each state space model (:math:`\mathbf{V}^\top`). hsv_list (list): small size, thin SVD factors of Gramians square roots of each state space model. If ``None``, it is assumed that ``U_list = [ U_i sqrt(hsv_i) ]`` ``VT_list = [ sqrt(hsv_i) V_i.T ]`` where ``U_i`` and ``V_i.T`` are square matrices and hsv is an array. M_list (list): for fast on-line evaluation. Small size product of Gramians factors of each state-space model. Each element of this list is equal to: ``M_i = U_i hsv_i V_i.T`` Notes: Message for future generations: - the implementation is divided into an offline and online part. References: Maraniello S. and Palacios R., Frequency-limited balanced truncation for parametric reduced-order modelling of the UVLM. Only in the best theaters. See Also: Frequency-Limited Balanced ROMs may be obtained from SHARPy using :class:`sharpy.rom.balanced.FrequencyLimited`. """ # ----------------------------------------------------------------- offline ### checks sizes N_interp = len(SS_list) states = SS_list[0].states inputs = SS_list[0].inputs outputs = SS_list[0].outputs for ss_here in SS_list: assert ss_here.states == states, \ 'State-space models must have the same number of states!' assert ss_here.inputs == inputs, \ 'State-space models must have the same number of states!' assert ss_here.outputs == outputs, \ 'State-space models must have the same number of states!' ### case of unbalanced state-space models # in this case, U_list and VT_list contain the full-rank Gramians factors # of each ROM if U_list is None and VT_list is None: raise NameError('apply FLB before calling this routine') # hsv_list = None # M_list, U_list, VT_list = [], [], [] # for ii in range(N_interp): # # # avoid direct # # hsv,U,Vh,Zc,Zo = librom.balreal_direct_py( # # SS_list[ii].A, SS_list[ii].B, SS_list[ii].C, # # DLTI=True,full_outputs=True) # # iterative also fails # hsv,Zc,Zo = librom.balreal_iter(SS_list[ii].A, SS_list[ii].B, SS_list[ii].C, # lowrank=True,tolSmith=1e-10,tolSVD=1e-10, # kmin=None, tolAbs=False, Print=True, outFacts=True) # # M_list.append( np.dot( np.dot(U,np.diag(hsv)), Vh) ) # M_list.append( np.dot( Zo.T,Zc ) ) # U_list.append(Zo.T) # VT_list.append(Zc) # calculate small size product of Gramians factors elif M_list is None: if hsv_list is None: M_list = [np.dot(U, VT) for U, VT in zip(U_list, VT_list)] else: M_list = [np.dot(U * hsv, VT) for U, hsv, VT in zip(U_list, hsv_list, VT_list)] # ------------------------------------------------------------------ online ### balance interpolated model M_int = np.zeros_like(M_list[0]) for ii in range(N_interp): M_int += wv[ii] * M_list[ii] U_int, hsv_int, Vh_int = scalg.svd(M_int, full_matrices=False) sinv_int = hsv_int ** (-0.5) ### build projection matrices sinvUT_int = (U_int * sinv_int).T Vsinv_int = Vh_int.T * sinv_int if hsv_list is None: Ti_int_list = [np.dot(sinvUT_int, U) for U in U_list] T_int_list = [np.dot(VT, Vsinv_int) for VT in VT_list] else: Ti_int_list = [np.dot(sinvUT_int, U * np.sqrt(hsv)) \ for U, hsv in zip(U_list, hsv_list)] T_int_list = [np.dot(np.dot(np.diag(np.sqrt(hsv)), VT), Vsinv_int) \ for hsv, VT in zip(hsv_list, VT_list)] ### assemble interp state-space model A_int = np.zeros((states, states)) B_int = np.zeros((states, inputs)) C_int = np.zeros((outputs, states)) D_int = np.zeros((outputs, inputs)) for ii in range(N_interp): # in A and B the weigths come from Ti A_int += wv[ii] * np.dot(Ti_int_list[ii], np.dot(SS_list[ii].A, T_int_list[ii])) B_int += wv[ii] * np.dot(Ti_int_list[ii], SS_list[ii].B) # in C and D the weights come from the interp system expression C_int += wv[ii] * np.dot(SS_list[ii].C, T_int_list[ii]) D_int += wv[ii] * SS_list[ii].D return libss.StateSpace(A_int, B_int, C_int, D_int, dt=SS_list[0].dt), hsv_int class InterpROM: r""" State-space 1D interpolation class. This class allows interpolating from a list of state-space models, SS. State-space models are required to have the same number of inputs and outputs and need to have the same number of states. For state-space interpolation, state-space models also need to be defined over the same set of generalised coordinates. If this is not the case, the projection matrices W and V used to produce the ROMs, ie .. math:: \mathbf{A}_{proj} = \mathbf{W}^\top \mathbf{A V} where A is the full-states matrix, also need to be provided. This will allow projecting the state-space models onto a common set of generalised coordinates before interpoling. For development purposes, the method currently creates a hard copy of the projected matrices into the self.AA, self.BB, self.CC lists Inputs: - SS: list of state-space models (instances of libss.StateSpace class) - VV: list of V matrices used to produce SS. If None, it is assumed that ROMs are defined over the same basis - WWT: list of W^T matrices used to derive the ROMs. - Vref, WTref: reference subspaces for projection. Some methods neglect this input (e.g. panzer) - method_proj: method for projection of state-space models over common coordinates. Available options are: - leastsq: find left/right projectors using least squares approx. Suitable for all basis. - strongMAC: strong Modal Assurance Criterion [4] enforcement for general basis. See Ref. [3], Eq. (7) - strongMAC_BT: strong Modal Assurance Criterion [4] enforcement for basis obtained by Balanced Truncation. Equivalent to strongMAC - maraniello_BT: this is equivalent to strongMAC and strongMAC_BT but avoids inversions. However, performance are the same as other strongMAC approaches - it works only when basis map the same subspaces - weakMAC_right_orth: weak MAC enforcement [1,3] for state-space models with right orthonoraml basis, i.e. V.T V = I. This is like Ref. [1], but implemented only on one side. - weakMAC: implementation of weak MAC enforcement for a general system. The method orthonormalises the right basis (V) and then solves the orthogonal Procrustes problem. - for orthonormal basis (V.T V = I): !!! These methods are not tested !!! - panzer: produces a new reference point based on svd [2] - amsallem: project over Vref,WTref [1] References: [1] D. Amsallem and C. Farhat, An online method for interpolating linear parametric reduced-order models, SIAM J. Sci. Comput., 33 (2011), pp. 2169–2198. [2] Panzer, J. Mohring, R. Eid, and B. Lohmann, Parametric model order reduction by matrix interpolation, at–Automatisierungstechnik, 58 (2010), pp. 475–484. [3] Mahony, R., Sepulchre, R. & Absil, P. -a., 2004. Riemannian Geometry of Grassmann Manifolds with a View on Algorithmic Computation. Acta Applicandae Mathematicae, 80(2), pp.199–220. [4] Geuss, M., Panzer, H. & Lohmann, B., 2013. On parametric model order reduction by matrix interpolation. 2013 European Control Conference (ECC), pp.3433–3438. """ def __init__(self, SS, VV=None, WWT=None, Vref=None, WTref=None, method_proj=None): self.SS = SS self.VV = VV self.WWT = WWT self.Vref = Vref self.WTref = WTref self.method_proj = method_proj self.Projected = False if VV is None or WWT is None: self.Projected = True self.AA = [ss_here.A for ss_here in SS] self.BB = [ss_here.B for ss_here in SS] self.CC = [ss_here.C for ss_here in SS] # projection required for D self.DD = [ss_here.D for ss_here in SS] ### check state-space models Nx, Nu, Ny = SS[0].states, SS[0].inputs, SS[0].outputs dt = SS[0].dt for ss_here in SS: assert ss_here.states == Nx, \ 'State-space models do not have the same number of states' assert ss_here.inputs == Nu, \ 'State-space models do not have the same number of inputs' assert ss_here.outputs == Ny, \ 'State-space models do not have the same number of outputs' assert ss_here.dt == dt, \ 'State-space models do not have same timestep' def __call__(self, wv): """ Evaluate interpolated model using weights wv. """ assert self.Projected, ('You must project the state-space models over' + ' a common basis before interpolating') Aint = np.zeros_like(self.AA[0]) Bint = np.zeros_like(self.BB[0]) Cint = np.zeros_like(self.CC[0]) Dint = np.zeros_like(self.DD[0]) for ii in range(len(self.AA)): Aint += wv[ii] * self.AA[ii] Bint += wv[ii] * self.BB[ii] Cint += wv[ii] * self.CC[ii] Dint += wv[ii] * self.DD[ii] return libss.StateSpace(Aint, Bint, Cint, Dint, self.SS[0].dt) def project(self): """ Project the state-space models onto the generalised coordinates of state-space model IImap """ self.AA = [] self.BB = [] self.CC = [] self.QQ = [] self.QQinv = [] if self.method_proj == 'amsallem': warnings.warn('Method untested!') for ii in range(len(self.SS)): U, sv, Z = scalg.svd(np.dot(self.VV[ii].T, self.Vref), full_matrices=False, overwrite_a=False, lapack_driver='gesdd') Q = np.dot(U, Z.T) U, sv, Z = scalg.svd(np.dot(self.WWT[ii], self.WTref), full_matrices=False, overwrite_a=False, lapack_driver='gesdd') Qinv = np.dot(U, Z.T).T self.QQ.append(Q) self.QQinv.append(Qinv) elif self.method_proj == 'panzer': warnings.warn('Method untested!') # generate basis U, sv = scalg.svd(np.concatenate(self.VV, axis=1), full_matrices=False, overwrite_a=False, lapack_driver='gesdd')[:2] # chop U U = U[:, :self.SS[0].states] for ii in range(len(self.SS)): Qinv = np.linalg.inv(np.dot(self.WWT[ii], U)) Q = np.linalg.inv(np.dot(self.VV[ii].T, U)) self.QQ.append(Q) self.QQinv.append(Qinv) elif self.method_proj == 'leastsq': for ii in range(len(self.SS)): Q, _, _, _ = scalg.lstsq(self.VV[ii], self.Vref) print('det(Q): %.3e\tcond(Q): %.3e' \ % (np.linalg.det(Q), np.linalg.cond(Q))) # if cond(Q) is small... # Qinv = np.linalg.inv(Q) P, _, _, _ = scalg.lstsq(self.WWT[ii].T, self.WTref.T) self.QQ.append(Q) self.QQinv.append(P.T) elif self.method_proj == 'strongMAC': """ Strong MAC enforcements as per Ref.[4] """ VTVref = np.dot(self.Vref.T, self.Vref) for ii in range(len(self.SS)): Q = np.linalg.solve(np.dot(self.Vref.T, self.VV[ii]), VTVref) Qinv = np.linalg.inv(Q) print('det(Q): %.3e\tcond(Q): %.3e' \ % (np.linalg.det(Q), np.linalg.cond(Q))) self.QQ.append(Q) self.QQinv.append(Qinv) elif self.method_proj == 'strongMAC_BT': """ This is equivalent to Mahony 2004, Eq. 7, for the case of basis obtained by balancing. In general, it will fail if VV[ii] and Vref do not describe the same subspace """ for ii in range(len(self.SS)): Q = np.linalg.inv(np.dot(self.WTref, self.VV[ii])) Qinv = np.dot(self.WTref, self.VV[ii]) print('det(Q): %.3e\tcond(Q): %.3e' \ % (np.linalg.det(Q), np.linalg.cond(Q))) self.QQ.append(Q) self.QQinv.append(Qinv) elif self.method_proj == 'maraniello_BT': """ Projection over ii. This is a sort of weak enforcement """ for ii in range(len(self.SS)): Q = np.dot(self.WWT[ii], self.Vref) Qinv = np.dot(self.WTref, self.VV[ii]) print('det(Q): %.3e\tcond(Q): %.3e' \ % (np.linalg.det(Q), np.linalg.cond(Q))) self.QQ.append(Q) self.QQinv.append(Qinv) elif self.method_proj == 'weakMAC_right_orth': """ This is like Amsallem, but only for state-space models with right orthogonal basis """ for ii in range(len(self.SS)): Q, sc = scalg.orthogonal_procrustes(self.VV[ii], self.Vref) Qinv = Q.T print('det(Q): %.3e\tcond(Q): %.3e' \ % (np.linalg.det(Q), np.linalg.cond(Q))) self.QQ.append(Q) self.QQinv.append(Qinv) elif self.method_proj == 'weakMAC': """ WeakMAC enforcement on the right hand side basis, V """ # svd of reference Uref, svref, Zhref = scalg.svd(self.Vref, full_matrices=False) for ii in range(len(self.SS)): # svd of basis Uhere, svhere, Zhhere = scalg.svd(self.VV[ii], full_matrices=False) R, sc = scalg.orthogonal_procrustes(Uhere, Uref) Q = np.dot(np.dot(Zhhere.T, np.diag(svhere ** (-1))), R) Qinv = np.dot(R.T, np.dot(np.diag(svhere), Zhhere)) print('det(Q): %.3e\tcond(Q): %.3e' \ % (np.linalg.det(Q), np.linalg.cond(Q))) self.QQ.append(Q) self.QQinv.append(Qinv) else: raise NameError('Projection method %s not implemented!' % self.method_proj) ### Project for ii in range(len(self.SS)): self.AA.append(np.dot(self.QQinv[ii], np.dot(self.SS[ii].A, self.QQ[ii]))) self.BB.append(np.dot(self.QQinv[ii], self.SS[ii].B)) self.CC.append(np.dot(self.SS[ii].C, self.QQ[ii])) self.Projected = True # ------------------------------------------------------------------------------ if __name__ == '__main__': import unittest class Test_librom_inter(unittest.TestCase): """ Test methods for DLTI ROM interpolation """ def setUp(self): # allocate some state-space model (dense and sparse) dt = 0.3 Ny, Nx, Nu = 4, 3, 2 A = np.random.rand(Nx, Nx) B = np.random.rand(Nx, Nu) C = np.random.rand(Ny, Nx) D = np.random.rand(Ny, Nu) self.SS = libss.StateSpace(A, B, C, D, dt=dt) # class Interp1d(): # """ # State-space 1D interpolation class. # This class allows interpolating from a list of state-space models, SS, # defined over the 1D parameter space zv. # State-space models are required to have the same number of inputs and outputs # and need to have the same number of states. # For state-space interpolation, state-space models also need to be defined # over the same set of generalised coordinates. If this is not the case, the # projection matrices W and T, such that # A_proj = W^T A V # also need to be provided. This will allow projecting the state-space models # onto a common set of generalised coordinates before interpoling. # Inputs: # - method_interp: interpolation method as per scipy.interpolate.interp1d class # - method_proj: method for projection of state-space models over common # coordinates. Available: # - panzer: Panzer, J. Mohring, R. Eid, and B. Lohmann, Parametric model # order reduction by matrix interpolation, at–Automatisierungstechnik, 58 # (2010), pp. 475–484. # - amsallem: D. Amsallem and C. Farhat, An online method for interpolating # linear parametric reduced-order models, SIAM J. Sci. Comput., 33 (2011), # pp. 2169–2198. # Note that 'panzel' and 'amsallem' only apply to orthogonal basis WT,V. # - Map: map A matrices over Riemannian manifold # - IImap=if given, maps A matrices over manifold derived around A matrix # of ii-th state-space in SSlist. # """ # def __init__(self, zv, SS, VV=None, WW=None, method_interp='cubic', # method_proj='panzer', Map=True, IImap=None): # assert IImap is not None, 'Option IImap=None not developed yet' # self.SS=SS # self.zv=zv # self.VV=VV # self.WW=WW # self.method_interp=method_interp # self.method_proj=method_proj # self.Map=Map # self.IImap=IImap # ### check state-space models # Nx,Nu,Ny = SS[0].states, SS[0].inputs, SS[0].outputs # for ss_here in SS: # assert ss_here.states == Nx,\ # 'State-space models do not have the same number of states' # assert ss_here.inputs == Nu,\ # 'State-space models do not have the same number of inputs' # assert ss_here.outputs == Ny,\ # 'State-space models do not have the same number of outputs' # self.debug=False # debug mode flag # def __call__(self, zint): # """ # Evaluate at interpolation point zint. Returns a list of classes StateSpace # """ # Nint=len(zint) # # interpolate A matrices, # if self.Map is True: # IImap=self.IImap # if IImap is not None: # # get inverse, # AIIinv=np.linalg.inv(self.SS[IImap].A) # # map, # TT=[] # for ii in range( len(self.zv) ): # TT.append(scalg.logm( np.dot(self.SS[ii].A,AIIinv) )) # # interpolate # TTint=self._interp_mats(TT, zint) # # and map back # Aint=np.zeros( (Nint,)+AIIinv.shape ) # for ii in range(Nint): # Aint[ii,:,:]= np.dot( scalg.expm(TTint[ii,:,:]), self.SS[IImap].A) # else: # # get index of closest A for each element in zint and define mapping # pass # else: # Aint=self._interp_mats( # [getattr(ss_here,'A') for ss_here in self.SS], zint) # # and B, C, D... # Bint=self._interp_mats( # [getattr(ss_here,'B') for ss_here in self.SS], zint) # Cint=self._interp_mats( # [getattr(ss_here,'C') for ss_here in self.SS], zint) # Dint=self._interp_mats( # [getattr(ss_here,'D') for ss_here in self.SS], zint) # # and pack everything # SSint=[] # for ii in range(Nint): # SSint.append( StateSpace( Aint[ii,:,:], Bint[ii,:,:], # Cint[ii,:,:], Dint[ii,:,:], dt=self.SS[0].dt)) # return SSint # def _interp_mats(self,Mats,zint): # """ # Interpolate a list of equal-size arrays, Mats, defined over zv at the # points zint. The Mats are assumed to be defined onto the same set of # generalised coordinates. # """ # # define interpolator class # # try: # IntA=scint.interp1d(self.zv,Mats,kind=self.method_interp, # copy=False,assume_sorted=True,axis=0) # return IntA(zint) # def project(self): # """ # Project the state-space models onto the generalised coordinates of # state-space model IImap # """ # if self.method_proj=='amsallem': # # get reference basis # Vref=self.VV[self.IImap] # Wref=self.WW[self.IImap] # for ii in range(len(self.SS)): # if ii == self.IImap: # continue # # get rotations # U,sv,Z = scalg.svd( np.dot(self.VV[ii].T, Vref) , # full_matrices=False,overwrite_a=False, # lapack_driver='gesdd') # RotV = np.dot(U,Z.T) # U,sv,Z = scalg.svd( np.dot(self.WW[ii].T, Wref) , # full_matrices=False,overwrite_a=False, # lapack_driver='gesdd') # RotW = np.dot(U,Z.T) # # project state-space # self.SS[ii].project(RotW.T,RotV) # elif self.method_proj=='panzer': # # generate basis # U,sv = scalg.svd( np.concatenate(self.VV,axis=1), # full_matrices=False,overwrite_a=False, # lapack_driver='gesdd')[:2] # # chop U # U=U[:,:self.SS[0].states]#*sv[:self.SS[0].states] # print('Panzer projection: neglecting singular values below %.2e (max: %.2e)'\ # %(sv[self.SS[0].states],sv[0]) ) # for ii in range(len(self.SS)): # # get projection matrices # M = np.linalg.inv( np.dot( self.WW[ii].T, U) ) # N = np.linalg.inv( np.dot( self.VV[ii].T, U) ) # # project # self.SS[ii].project(M,N) # else: # raise NameError('Projection method %s not implemented!' %self.method_proj) ================================================ FILE: sharpy/sharpy_main.py ================================================ """sharpy_main: Where it all starts """ import warnings import sys import dill as pickle from sharpy.utils import cout_utils as cout from .version import __version__ def main(args=None, sharpy_input_dict=None): """ Main ``SHARPy`` routine This is the main ``SHARPy`` routine. It starts the solution process by reading the settings that are included in the ``.sharpy`` file that is parsed as an argument, or an equivalent dictionary given as ``sharpy_input_dict``. It reads the solvers specific settings and runs them in order Args: args (str): ``.sharpy`` file with the problem information and settings sharpy_input_dict (dict): ``dict`` with the same contents as the ``solver.txt`` file would have. Returns: sharpy.presharpy.presharpy.PreSharpy: object containing the simulation results. """ import time import argparse import sharpy.utils.input_arg as input_arg import sharpy.utils.solver_interface as solver_interface from sharpy.presharpy.presharpy import PreSharpy from sharpy.utils.cout_utils import start_writer, finish_writer import logging import os import h5py import sharpy.utils.h5utils as h5utils # Loading solvers and postprocessors import sharpy.solvers import sharpy.postproc import sharpy.generators import sharpy.controllers # ------------ try: # output writer start_writer() # timing t = time.process_time() t0_wall = time.perf_counter() if sharpy_input_dict is None: parser = argparse.ArgumentParser(prog='SHARPy', description= """This is the executable for Simulation of High Aspect Ratio Planes.\n Imperial College London 2024""") parser.add_argument('input_filename', help='path to the *.sharpy input file', type=str, default='') parser.add_argument('-r', '--restart', help='restart the solution with a given snapshot', type=str, default=None) parser.add_argument('-d', '--docs', help='generates the solver documentation in the specified location. ' 'Code does not execute if running this flag', action='store_true') parser.add_argument('-v', '--version', action='version', version='Running %(prog)s version {version}'.format(version=__version__)) if args is not None: args = parser.parse_args(args[1:]) else: args = parser.parse_args() if args.docs: import subprocess import sharpy.utils.docutils as docutils import sharpy.utils.sharpydir as sharpydir docutils.generate_documentation() # run make cout.cout_wrap('Running make html in sharpy/docs') subprocess.Popen(['make', 'html'], stdout=None, cwd=sharpydir.SharpyDir + '/docs') return 0 if args.input_filename == '': parser.error('input_filename is a required argument of SHARPy.') settings = input_arg.read_settings(args) missing_solvers = False if args.restart is None: # run preSHARPy data = PreSharpy(settings) solvers = dict() restart = False else: try: with open(args.restart, 'rb') as restart_file: data = pickle.load(restart_file) try: solvers = pickle.load(restart_file) except EOFError: # For backwards compatibility missing_solvers = True solvers = dict() cout.cout_wrap('Solvers not found in Pickle file. Using the settings in *.sharpy file.') if "UpdatePickle" in solvers.keys(): # For backwards compatibility missing_solvers = True solvers = dict() except FileNotFoundError: raise FileNotFoundError('The file specified for the snapshot \ restart (-r) does not exist. Please check.') restart = True # update the settings data.update_settings(settings) # Read again the dyn.h5 file data.structure.dynamic_input = [] dyn_file_name = data.case_route + '/' + data.case_name + '.dyn.h5' if os.path.isfile(dyn_file_name): fid = h5py.File(dyn_file_name, 'r') data.structure.dyn_dict = h5utils.load_h5_in_dict(fid) # for it in range(self.num_steps): # data.structure.dynamic_input.append(dict()) # Restart the solvers old_solvers_list = list(solvers.keys()) for old_solver_name in old_solvers_list: if old_solver_name not in settings['SHARPy']['flow']: del solvers[old_solver_name] # Loop for the solvers specified in *.sharpy['SHARPy']['flow'] for solver_name in settings['SHARPy']['flow']: if (args.restart is None) or (solver_name not in solvers.keys()) or (missing_solvers): solvers[solver_name] = solver_interface.initialise_solver(solver_name) if missing_solvers: solvers[solver_name].initialise(data, restart=False) else: solvers[solver_name].initialise(data, restart=restart) data = solvers[solver_name].run(solvers=solvers) solvers[solver_name].teardown() cpu_time = time.process_time() - t wall_time = time.perf_counter() - t0_wall cout.cout_wrap('FINISHED - Elapsed time = %f6 seconds' % wall_time, 2) cout.cout_wrap('FINISHED - CPU process time = %f6 seconds' % cpu_time, 2) finish_writer() except Exception as e: try: logdir = settings['SHARPy']['log_folder'] + '/' + settings['SHARPy']['case'] except KeyError: logdir = './' except NameError: logdir = './' logdir = os.path.abspath(logdir) cout.cout_wrap(('Exception raised, writing error log in %s/error.log' % logdir), 4) logging.basicConfig(filename='%s/error.log' % logdir, filemode='w', format='%(asctime)s-%(levelname)s-%(message)s', datefmt='%d-%b-%y %H:%M:%S', level=logging.INFO) logging.info('SHARPy Error Log') logging.error("Exception occurred", exc_info=True) raise e return data def sharpy_run(): """ This is a wrapper function for the console command "sharpy" """ data = None with warnings.catch_warnings(): warnings.simplefilter('ignore') data = main(sys.argv) ================================================ FILE: sharpy/solvers/__init__.py ================================================ import importlib import os import sharpy.utils.solver_interface as solver_interface import sharpy.utils.sharpydir as sharpydir files = solver_interface.solver_list_from_path(os.path.dirname(__file__)) import_path = os.path.realpath(os.path.dirname(__file__)) import_path = import_path.replace(sharpydir.SharpyDir, "") if import_path[0] == "/": import_path = import_path[1:] import_path = import_path.replace("/", ".") for file in sorted(files): solver_interface.solvers[file] = importlib.import_module(import_path + "." + file) ================================================ FILE: sharpy/solvers/_basestructural.py ================================================ from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.cout_utils as cout @solver class _BaseStructural(BaseSolver): """ Structural solver used for the dynamic simulation of free-flying structures. This solver provides an interface to the structural library (``xbeam``) and updates the structural parameters for every time step of the simulation. This solver is called as part of a standalone structural simulation. """ solver_id = '_BaseStructural' solver_classification = 'structural' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Print output to screen' settings_types['max_iterations'] = 'int' settings_default['max_iterations'] = 100 settings_description['max_iterations'] = 'Sets maximum number of iterations' settings_types['num_load_steps'] = 'int' settings_default['num_load_steps'] = 1 settings_types['delta_curved'] = 'float' settings_default['delta_curved'] = 1e-2 settings_types['min_delta'] = 'float' settings_default['min_delta'] = 1e-5 settings_description['min_delta'] = 'Structural solver relative tolerance' settings_types['abs_threshold'] = 'float' settings_default['abs_threshold'] = 1e-13 settings_description['abs_threshold'] = 'Structural solver absolute tolerance' settings_types['newmark_damp'] = 'float' settings_default['newmark_damp'] = 1e-4 settings_description['newmark_damp'] = 'Sets the Newmark damping coefficient' settings_types['gravity_on'] = 'bool' settings_default['gravity_on'] = False settings_description['gravity_on'] = 'Flag to include gravitational forces' settings_types['gravity'] = 'float' settings_default['gravity'] = 9.81 settings_description['gravity'] = 'Gravitational acceleration' settings_types['gravity_dir'] = 'list(float)' settings_default['gravity_dir'] = [0., 0., 1.] settings_description['gravity_dir'] = 'Direction in G where gravity applies' settings_types['relaxation_factor'] = 'float' settings_default['relaxation_factor'] = 0.3 settings_types['dt'] = 'float' settings_default['dt'] = 0.01 settings_description['dt'] = 'Time step increment' settings_types['num_steps'] = 'int' settings_default['num_steps'] = 500 def initialise(self, data, restart=False): pass def run(self, **kwargs): pass ================================================ FILE: sharpy/solvers/aerogridloader.py ================================================ import h5py as h5 import numpy as np from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.aero.models.aerogrid as aerogrid import sharpy.utils.settings as settings_utils import sharpy.utils.h5utils as h5utils import sharpy.utils.generator_interface as gen_interface from sharpy.solvers.gridloader import GridLoader @solver class AerogridLoader(GridLoader): """ ``AerogridLoader`` class, inherited from ``GridLoader`` Generates aerodynamic grid based on the input data The initial wake shape is now defined in SHARPy (instead of UVLM) through a wake shape generator ``wake_shape_generator`` and the required inputs ``wake_shape_generator_input``. The supported wake generators are :class:`sharpy.generators.straightwake.StraighWake` and :class:`sharpy.generators.helicoidalwake.HelicoidalWake`. The ``control_surface_deflection`` setting allows the user to use a time specific control surface deflection, should the problem include them. This setting takes a list of strings, each for the required control surface generator. The ``control_surface_deflection_generator_settings`` setting is a list of dictionaries, one for each control surface. The dictionaries specify the settings for the generator ``DynamicControlSurface``. If the relevant control surface is simply static, an empty string should be parsed. See the documentation for ``DynamicControlSurface`` generators for accepted key-value pairs as settings. The ``initial_align`` setting aligns the wing panel discretization with the freestream for the undeformed structure, and applies this Z rotation at every timestep (panels become misaligned when the wing deforms). The ``aligned_grid`` setting aligns the wing panel discretization with the flow at every time step and takes precedence. Args: data (PreSharpy): ``ProblemData`` class structure Attributes: settings (dict): Name-value pair of the settings employed by the aerodynamic solver settings_types (dict): Acceptable types for the values in ``settings`` settings_default (dict): Name-value pair of default values for the aerodynamic settings data (ProblemData): class structure file_name (str): name of the ``.aero.h5`` HDF5 file aero: empty attribute data_dict (dict): key-value pairs of aerodynamic data wake_shape_generator (class): Wake shape generator """ solver_id = 'AerogridLoader' solver_classification = 'loader' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['unsteady'] = 'bool' settings_default['unsteady'] = False settings_description['unsteady'] = 'Unsteady effects' settings_types['aligned_grid'] = 'bool' settings_default['aligned_grid'] = True settings_description['aligned_grid'] = 'Align grid' settings_types['initial_align'] = 'bool' settings_default['initial_align'] = True settings_description['initial_align'] = "Initially align grid" settings_types['freestream_dir'] = 'list(float)' settings_default['freestream_dir'] = [1.0, 0.0, 0.0] settings_description['freestream_dir'] = 'Free stream flow direction' settings_types['mstar'] = ['int', 'list(int)'] settings_default['mstar'] = 10 settings_description['mstar'] = 'Number of chordwise wake panels' settings_types['control_surface_deflection'] = 'list(str)' settings_default['control_surface_deflection'] = [] settings_description['control_surface_deflection'] = 'List of control surface generators for each control surface' settings_types['control_surface_deflection_generator_settings'] = 'dict' settings_default['control_surface_deflection_generator_settings'] = dict() settings_description['control_surface_deflection_generator_settings'] = 'List of dictionaries with the settings ' \ 'for each generator' settings_types['wake_shape_generator'] = 'str' settings_default['wake_shape_generator'] = 'StraightWake' settings_description['wake_shape_generator'] = 'ID of the generator to define the initial wake shape' settings_options['wake_shape_generator'] = ['StraightWake', 'HelicoidalWake'] settings_types['wake_shape_generator_input'] = 'dict' settings_default['wake_shape_generator_input'] = dict() settings_description['wake_shape_generator_input'] = 'Dictionary of inputs needed by the wake shape generator' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options=settings_options) def __init__(self): super().__init__ self.file_name = '.aero.h5' self.aero = None self.wake_shape_generator = None def initialise(self, data, restart=False): super().initialise(data) wake_shape_generator_type = gen_interface.generator_from_string( self.settings['wake_shape_generator']) self.wake_shape_generator = wake_shape_generator_type() self.wake_shape_generator.initialise(data, self.settings['wake_shape_generator_input'], restart=restart) def run(self, **kwargs): self.data.aero = aerogrid.Aerogrid() self.data.aero.generate(self.data_dict, self.data.structure, self.settings, self.data.ts) aero_tstep = self.data.aero.timestep_info[self.data.ts] self.wake_shape_generator.generate({'zeta': aero_tstep.zeta, 'zeta_star': aero_tstep.zeta_star, 'gamma': aero_tstep.gamma, 'gamma_star': aero_tstep.gamma_star, 'dist_to_orig': aero_tstep.dist_to_orig}) # keep the call to the wake generator # because it might be needed by other solvers self.data.aero.wake_shape_generator = self.wake_shape_generator return self.data ================================================ FILE: sharpy/solvers/beamloader.py ================================================ import h5py as h5 from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.structure.models.beam as beam import sharpy.utils.settings as settings_utils import sharpy.utils.h5utils as h5utils import os @solver class BeamLoader(BaseSolver): """ ``BeamLoader`` class solver inherited from ``BaseSolver`` Loads the structural beam solver with the specified user settings. Args: data (ProblemData): class containing the problem information Attributes: settings (dict): contains the specific settings for the solver settings_types (dict): Key value pairs of the accepted types for the settings values settings_default (dict): Dictionary containing the default solver settings, should none be provided. data (ProblemData): class containing the data for the problem fem_file_name (str): name of the ``.fem.h5`` HDF5 file dyn_file_name (str): name of the ``.dyn.h5`` HDF5 file fem_data_dict (dict): key-value pairs of FEM data dyn_data_dict (dict): key-value pairs of data for dynamic problems structure (None): Empty attribute Notes: For further reference on Quaternions see: `https://en.wikipedia.org/wiki/Quaternion `_ See Also: .. py:class:: sharpy.utils.solver_interface.BaseSolver .. py:class:: sharpy.structure.models.beam.Beam """ solver_id = 'BeamLoader' solver_classification = 'loader' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['unsteady'] = 'bool' settings_default['unsteady'] = True settings_description['unsteady'] = 'If ``True`` it will be a dynamic problem and the solver will look for the' \ ' ``.dyn.h5`` file that contains the time varying input to the problem.' settings_types['orientation'] = 'list(float)' settings_default['orientation'] = [1., 0, 0, 0] settings_description['orientation'] = 'Initial attitude of the structure given as the quaternion that parametrises the rotation from G to A frames of reference.' settings_types['for_pos'] = 'list(float)' settings_default['for_pos'] = [0., 0, 0] settings_description['for_pos'] = 'Initial position of the A FoR.' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None self.fem_file_name = '' self.dyn_file_name = '' # storage of file contents self.fem_data_dict = dict() self.dyn_data_dict = dict() self.mb_data_dict = dict() # structure storage self.structure = None def initialise(self, data, restart=False): self.data = data self.settings = data.settings[self.solver_id] # init settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) # read input files (fem and dyn) self.read_files() def read_files(self): # open fem file # first, file names self.fem_file_name = self.data.case_route + '/' + self.data.case_name + '.fem.h5' if self.settings['unsteady']: self.dyn_file_name = self.data.case_route + '/' + self.data.case_name + '.dyn.h5' # then check that the files exists h5utils.check_file_exists(self.fem_file_name) if self.settings['unsteady']: try: h5utils.check_file_exists(self.dyn_file_name) except FileNotFoundError: self.settings['unsteady'] = False # read and store the hdf5 files with h5.File(self.fem_file_name, 'r') as fem_file_handle: # store files in dictionary self.fem_data_dict = h5utils.load_h5_in_dict(fem_file_handle) # TODO implement fem file validation # self.validate_fem_file() if self.settings['unsteady']: with h5.File(self.dyn_file_name, 'r') as dyn_file_handle: # store files in dictionary self.dyn_data_dict = h5utils.load_h5_in_dict(dyn_file_handle) # TODO implement dyn file validation # self.validate_dyn_file() # Multibody information self.mb_file_name = self.data.case_route + '/' + self.data.case_name + '.mb.h5' if os.path.isfile(self.mb_file_name): # h5utils.check_file_exists(self.mb_file_name) with h5.File(self.mb_file_name, 'r') as mb_file_handle: self.mb_data_dict = h5utils.load_h5_in_dict(mb_file_handle) # Need to redefine strings to remove the "b" at the beginning for iconstraint in range(self.mb_data_dict['num_constraints']): self.mb_data_dict["constraint_%02d" % iconstraint]['behaviour'] = self.mb_data_dict["constraint_%02d" % iconstraint]['behaviour'].decode() for ibody in range(self.mb_data_dict['num_bodies']): self.mb_data_dict["body_%02d" % ibody]['FoR_movement'] = self.mb_data_dict["body_%02d" % ibody]['FoR_movement'].decode() def validate_fem_file(self): raise NotImplementedError('validation of the fem file in beamloader is not yet implemented!') def validate_dyn_file(self): raise NotImplementedError('validation of the dyn file in beamloader is not yet implemented!') def run(self, **kwargs): self.data.structure = beam.Beam() self.data.structure.ini_mb_dict = self.mb_data_dict self.data.structure.generate(self.fem_data_dict, self.settings) self.data.structure.dyn_dict = self.dyn_data_dict return self.data ================================================ FILE: sharpy/solvers/dynamiccoupled.py ================================================ import ctypes as ct import time import copy import threading import logging import concurrent.futures import queue import numpy as np import sharpy.aero.utils.mapping as mapping import sharpy.utils.cout_utils as cout import sharpy.utils.solver_interface as solver_interface import sharpy.utils.controller_interface as controller_interface from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils import sharpy.utils.algebra as algebra import sharpy.utils.exceptions as exc import sharpy.io.network_interface as network_interface import sharpy.utils.generator_interface as gen_interface @solver class DynamicCoupled(BaseSolver): """ The :class:`~sharpy.solvers.dynamiccoupled.DynamicCoupled` solver couples the aerodynamic and structural solvers of choice to march forward in time the aeroelastic system's solution. Using the :class:`~sharpy.solvers.dynamiccoupled.DynamicCoupled` solver requires that an instance of the ``StaticCoupled`` solver is called in the SHARPy solution ``flow`` when defining the problem case. Input data (from external controllers) can be received and data sent using the SHARPy network interface, specified through the setting ``network_settings`` of this solver. For more detail on how to send and receive data see the :class:`~sharpy.io.network_interface.NetworkLoader` documentation. Changes to the structural properties or external forces that depend on the instantaneous situation of the system can be applied through ``runtime_generators``. These runtime generators are parsed through dictionaries, with the key being the name of the generator and the value the settings for such generator. The currently available ``runtime_generators`` are :class:`~sharpy.generators.externalforces.ExternalForces` and :class:`~sharpy.generators.modifystructure.ModifyStructure`. """ solver_id = 'DynamicCoupled' solver_classification = 'Coupled' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Write status to screen' settings_types['structural_solver'] = 'str' settings_default['structural_solver'] = None settings_description['structural_solver'] = 'Structural solver to use in the coupled simulation' settings_types['structural_solver_settings'] = 'dict' settings_default['structural_solver_settings'] = None settings_description['structural_solver_settings'] = 'Dictionary of settings for the structural solver' settings_types['aero_solver'] = 'str' settings_default['aero_solver'] = None settings_description['aero_solver'] = 'Aerodynamic solver to use in the coupled simulation' settings_types['aero_solver_settings'] = 'dict' settings_default['aero_solver_settings'] = None settings_description['aero_solver_settings'] = 'Dictionary of settings for the aerodynamic solver' settings_types['n_time_steps'] = 'int' settings_default['n_time_steps'] = None settings_description['n_time_steps'] = 'Number of time steps for the simulation' settings_types['dt'] = 'float' settings_default['dt'] = None settings_description['dt'] = 'Time step' settings_types['fsi_substeps'] = 'int' settings_default['fsi_substeps'] = 70 settings_description['fsi_substeps'] = 'Max iterations in the FSI loop' settings_types['fsi_tolerance'] = 'float' settings_default['fsi_tolerance'] = 1e-5 settings_description['fsi_tolerance'] = 'Convergence threshold for the FSI loop' settings_types['structural_substeps'] = 'int' settings_default['structural_substeps'] = 0 # 0 is normal coupled sim. settings_description['structural_substeps'] = 'Number of extra structural time steps per aero time step. ``0`` ' \ 'is a fully coupled simulation.' settings_types['relaxation_factor'] = 'float' settings_default['relaxation_factor'] = 0.2 settings_description['relaxation_factor'] = 'Relaxation parameter in the FSI iteration. ``0`` is no relaxation ' \ 'and -> ``1`` is very relaxed' settings_types['final_relaxation_factor'] = 'float' settings_default['final_relaxation_factor'] = 0.0 settings_description['final_relaxation_factor'] = 'Relaxation factor reached in ``relaxation_steps`` with ' \ '``dynamic_relaxation`` on' settings_types['minimum_steps'] = 'int' settings_default['minimum_steps'] = 3 settings_description['minimum_steps'] = 'Number of minimum FSI iterations before convergence' settings_types['relaxation_steps'] = 'int' settings_default['relaxation_steps'] = 100 settings_description['relaxation_steps'] = 'Length of the relaxation factor ramp between ``relaxation_factor`` ' \ 'and ``final_relaxation_factor`` with ``dynamic_relaxation`` on' settings_types['dynamic_relaxation'] = 'bool' settings_default['dynamic_relaxation'] = False settings_description['dynamic_relaxation'] = 'Controls if relaxation factor is modified during the FSI iteration ' \ 'process' settings_types['postprocessors'] = 'list(str)' settings_default['postprocessors'] = list() settings_description['postprocessors'] = 'List of the postprocessors to run at the end of every time step' settings_types['postprocessors_settings'] = 'dict' settings_default['postprocessors_settings'] = dict() settings_description['postprocessors_settings'] = 'Dictionary with the applicable settings for every ' \ '' \ '``postprocessor``. Every ``postprocessor`` needs its entry, ' \ 'even if empty' settings_types['controller_id'] = 'dict' settings_default['controller_id'] = dict() settings_description['controller_id'] = 'Dictionary of id of every controller (key) and its type (value)' settings_types['controller_settings'] = 'dict' settings_default['controller_settings'] = dict() settings_description['controller_settings'] = 'Dictionary with settings (value) of every controller id (key)' settings_types['cleanup_previous_solution'] = 'bool' settings_default['cleanup_previous_solution'] = False settings_description['cleanup_previous_solution'] = 'Controls if previous ``timestep_info`` arrays are ' \ 'reset before running the solver' settings_types['include_unsteady_force_contribution'] = 'bool' settings_default['include_unsteady_force_contribution'] = False settings_description['include_unsteady_force_contribution'] = 'If on, added mass contribution is added to the ' \ 'forces. This depends on the time derivative of ' \ 'the bound circulation. Check ``filter_gamma_dot`` ' \ 'in the aero solver' settings_types['steps_without_unsteady_force'] = 'int' settings_default['steps_without_unsteady_force'] = 0 settings_description['steps_without_unsteady_force'] = 'Number of initial timesteps that don\'t include unsteady ' \ 'forces contributions. This avoids oscillations due to ' \ 'no perfectly trimmed initial conditions' settings_types['pseudosteps_ramp_unsteady_force'] = 'int' settings_default['pseudosteps_ramp_unsteady_force'] = 0 settings_description['pseudosteps_ramp_unsteady_force'] = 'Length of the ramp with which unsteady force ' \ 'contribution is introduced every time step during ' \ 'the FSI iteration process' settings_types['correct_forces_method'] = 'str' settings_default['correct_forces_method'] = '' settings_description['correct_forces_method'] = 'Function used to correct aerodynamic forces. ' \ 'See :py:mod:`sharpy.generators.polaraeroforces`' settings_options['correct_forces_method'] = ['EfficiencyCorrection', 'PolarCorrection'] settings_types['correct_forces_settings'] = 'dict' settings_default['correct_forces_settings'] = {} settings_description['correct_forces_settings'] = 'Settings for corrected forces evaluation' settings_types['network_settings'] = 'dict' settings_default['network_settings'] = dict() settings_description['network_settings'] = 'Network settings. See ' \ ':class:`~sharpy.io.network_interface.NetworkLoader` for supported ' \ 'entries' settings_types['runtime_generators'] = 'dict' settings_default['runtime_generators'] = dict() settings_description['runtime_generators'] = 'The dictionary keys are the runtime generators to be used. ' \ 'The dictionary values are dictionaries with the settings ' \ 'needed by each generator.' settings_types['nonlifting_body_interactions'] = 'bool' settings_default['nonlifting_body_interactions'] = False settings_description['nonlifting_body_interactions'] = 'Effect of Nonlifting Bodies on Lifting bodies are considered' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) def __init__(self): self.data = None self.settings = None self.structural_solver = None self.aero_solver = None self.print_info = False self.res = 0.0 self.res_dqdt = 0.0 self.res_dqddt = 0.0 self.previous_force = None self.dt = 0. self.substep_dt = 0. self.initial_n_substeps = None self.predictor = False self.residual_table = None self.postprocessors = dict() self.with_postprocessors = False self.controllers = None self.time_aero = 0. self.time_struc = 0. self.correct_forces = False self.correct_forces_generator = None self.logger = logging.getLogger(__name__) # used with the network interface # variables to send and receive self.network_loader = None self.set_of_variables = None self.runtime_generators = dict() self.with_runtime_generators = False def get_g(self): """ Getter for ``g``, the gravity value """ return self.structural_solver.settings['gravity'] def set_g(self, new_g): """ Setter for ``g``, the gravity value """ self.structural_solver.settings['gravity'] = ct.c_double(new_g) def get_rho(self): """ Getter for ``rho``, the density value """ return self.aero_solver.settings['rho'] def set_rho(self, new_rho): """ Setter for ``rho``, the density value """ self.aero_solver.settings['rho'] = ct.c_double(new_rho) def initialise(self, data, custom_settings=None, restart=False): """ Controls the initialisation process of the solver, including processing the settings and initialising the aero and structural solvers, postprocessors and controllers. """ self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, options=self.settings_options) self.original_settings = copy.deepcopy(self.settings) self.dt = self.settings['dt'] self.substep_dt = ( self.dt/(self.settings['structural_substeps'] + 1)) self.initial_n_substeps = self.settings['structural_substeps'] self.print_info = self.settings['print_info'] if self.settings['cleanup_previous_solution']: # if there's data in timestep_info[>0], copy the last one to # timestep_info[0] and remove the rest self.cleanup_timestep_info() if not restart: self.structural_solver = solver_interface.initialise_solver( self.settings['structural_solver']) self.aero_solver = solver_interface.initialise_solver( self.settings['aero_solver']) self.structural_solver.initialise( self.data, self.settings['structural_solver_settings'], restart=restart) self.aero_solver.initialise(self.structural_solver.data, self.settings['aero_solver_settings'], restart=restart) self.data = self.aero_solver.data # initialise postprocessors if self.settings['postprocessors']: self.with_postprocessors = True # Remove previous postprocessors not required on restart old_list = list(self.postprocessors.keys()) for old_list_name in old_list: if old_list_name not in self.settings['postprocessors']: del self.postprocessors[old_list_name] for postproc in self.settings['postprocessors']: if not postproc in self.postprocessors.keys(): self.postprocessors[postproc] = solver_interface.initialise_solver( postproc) self.postprocessors[postproc].initialise( self.data, self.settings['postprocessors_settings'][postproc], caller=self, restart=restart) # initialise controllers self.with_controllers = False if self.settings['controller_id']: self.with_controllers = True # Remove previous controllers not required on restart if self.controllers is not None: old_list = list(self.controllers.keys()) for old_list_name in old_list: if old_list_name not in self.settings['controller_id']: del self.controllers[old_list_name] for controller_id, controller_type in self.settings['controller_id'].items(): if self.controllers is not None: if not controller_id in self.controllers.keys(): self.controllers[controller_id] = ( controller_interface.initialise_controller(controller_type)) else: self.controllers = dict() self.controllers[controller_id] = ( controller_interface.initialise_controller(controller_type)) self.controllers[controller_id].initialise(self.data, self.settings['controller_settings'][controller_id], controller_id, restart=restart) # print information header if self.print_info: self.residual_table = cout.TablePrinter(8, 12, ['g', 'f', 'g', 'f', 'f', 'f', 'e', 'e']) self.residual_table.field_length[0] = 5 self.residual_table.field_length[1] = 6 self.residual_table.field_length[2] = 4 self.residual_table.print_header(['ts', 't', 'iter', 'struc ratio', 'iter time', 'residual vel', 'FoR_vel(x)', 'FoR_vel(z)']) # Define the function to correct aerodynamic forces if self.settings['correct_forces_method'] != '': self.correct_forces = True self.correct_forces_generator = gen_interface.generator_from_string(self.settings['correct_forces_method'])() self.correct_forces_generator.initialise(in_dict=self.settings['correct_forces_settings'], aero=self.data.aero, structure=self.data.structure, rho=self.settings['aero_solver_settings']['rho'], vortex_radius=self.settings['aero_solver_settings']['vortex_radius'], output_folder = self.data.output_folder) # check for empty dictionary if self.settings['network_settings']: self.network_loader = network_interface.NetworkLoader() self.network_loader.initialise(in_settings=self.settings['network_settings']) # initialise runtime generators if self.settings['runtime_generators']: self.with_runtime_generators = True # Remove previous runtime generators not required on restart old_list = list(self.runtime_generators.keys()) for old_list_name in old_list: if old_list_name not in self.settings['runtime_generators']: del self.runtime_generators[old_list_name] for rg_id, param in self.settings['runtime_generators'].items(): if not rg_id in self.runtime_generators.keys(): gen = gen_interface.generator_from_string(rg_id) self.runtime_generators[rg_id] = gen() self.runtime_generators[rg_id].initialise(param, data=self.data, restart=restart) def cleanup_timestep_info(self): if max(len(self.data.aero.timestep_info), len(self.data.structure.timestep_info)) > 1: self.remove_old_timestep_info(self.data.structure.timestep_info) self.remove_old_timestep_info(self.data.aero.timestep_info) if self.settings['nonlifting_body_interactions']: self.remove_old_timestep_info(self.data.nonlifting_body.timestep_info) self.data.ts = 0 def remove_old_timestep_info(self, tstep_info): # copy last info to first tstep_info[0] = tstep_info[-1].copy() # delete all the rest while len(tstep_info) - 1: del tstep_info[-1] def process_controller_output(self, controlled_state): """ This function modified the solver properties and parameters as requested from the controller. This keeps the main loop much cleaner, while allowing for flexibility Please, if you add options in here, always code the possibility of that specific option not being there without the code complaining to the user. If it possible, use the same Key for the new setting as for the setting in the solver. For example, if you want to modify the `structural_substeps` variable in settings, use that Key in the `info` dictionary. As a convention: a value of None returns the value to the initial one specified in settings, while the key not being in the dict is ignored, so if any change was made before, it will stay there. """ try: info = controlled_state['info'] except KeyError: return controlled_state['structural'], controlled_state['aero'] # general copy-if-exists, restore if == None for info_k, info_v in info.items(): if info_k in self.settings: if info_v is not None: self.settings[info_k] = info_v else: self.settings[info_k] = self.original_settings[info_k] # specifics of every option for info_k, info_v in info.items(): if info_k in self.settings: if info_k == 'structural_substeps': if info_v is not None: self.substep_dt = ( self.settings['dt']/( self.settings['structural_substeps'] + 1)) elif info_k == 'structural_solver': if info_v is not None: self.structural_solver = solver_interface.initialise_solver( info['structural_solver']) self.structural_solver.initialise( self.data, self.settings['structural_solver_settings']) elif info_k == 'rotor_vel': for lc in self.structural_solver.lc_list: if lc._lc_id == 'hinge_node_FoR_pitch': lc.set_rotor_vel(info_v) elif info_k == 'pitch_vel': for lc in self.structural_solver.lc_list: if lc._lc_id == 'hinge_node_FoR_pitch': lc.set_pitch_vel(info_v) return controlled_state['structural'], controlled_state['aero'] def run(self, **kwargs): """ Run the time stepping procedure with controllers and postprocessors included. """ solvers = settings_utils.set_value_or_default(kwargs, 'solvers', None) if self.network_loader is not None: self.set_of_variables = self.network_loader.get_inout_variables() incoming_queue = queue.Queue(maxsize=1) outgoing_queue = queue.Queue(maxsize=1) finish_event = threading.Event() with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: netloop = executor.submit(self.network_loop, incoming_queue, outgoing_queue, finish_event) timeloop = executor.submit(self.time_loop, incoming_queue, outgoing_queue, finish_event, solvers) # TODO: improve exception handling to get exceptions when they happen from each thread for t1 in [netloop, timeloop]: try: t1.result() except Exception as e: print(e) raise Exception else: self.time_loop(solvers=solvers) if self.print_info: cout.cout_wrap('...Finished', 1) for postproc in self.postprocessors: try: self.postprocessors[postproc].shutdown() except AttributeError: pass return self.data def network_loop(self, in_queue, out_queue, finish_event): # runs in a separate thread from time_loop() out_network, in_network = self.network_loader.get_networks() out_network.set_queue(out_queue) in_network.set_message_length(self.set_of_variables.input_msg_len) in_network.set_queue(in_queue) previous_queue_empty = True while not finish_event.is_set(): # selector version events = network_interface.sel.select(timeout=1) if out_network.queue.empty() and not previous_queue_empty: out_network.set_selector_events_mask('r') previous_queue_empty = True elif not out_network.queue.empty() and previous_queue_empty: out_network.set_selector_events_mask('w') previous_queue_empty = False try: for key, mask in events: key.data.process_events(mask) except KeyboardInterrupt: break # close sockets in_network.close() out_network.close() def time_loop(self, in_queue=None, out_queue=None, finish_event=None, solvers=None): self.logger.debug('Inside time loop') # dynamic simulations start at tstep == 1, 0 is reserved for the initial state for self.data.ts in range( len(self.data.structure.timestep_info), self.settings['n_time_steps'] + 1): initial_time = time.perf_counter() # network only # get input from the other thread if in_queue: self.logger.info('Time Loop - Waiting for input') values = in_queue.get() # should be list of tuples self.logger.debug('Time loop - received {}'.format(values)) self.set_of_variables.update_timestep(self.data, values) structural_kstep = self.data.structure.timestep_info[-1].copy() aero_kstep = self.data.aero.timestep_info[-1].copy() if self.settings['nonlifting_body_interactions']: nl_body_kstep = self.data.nonlifting_body.timestep_info[-1].copy() else: nl_body_kstep = None self.logger.debug('Time step {}'.format(self.data.ts)) # Add the controller here if self.with_controllers: state = {'structural': structural_kstep, 'aero': aero_kstep} for k, v in self.controllers.items(): state, control = v.control(self.data, state) # this takes care of the changes in options for the solver structural_kstep, aero_kstep = self.process_controller_output( state) # Add external forces if self.with_runtime_generators: structural_kstep.runtime_steady_forces.fill(0.) structural_kstep.runtime_unsteady_forces.fill(0.) params = dict() params['data'] = self.data params['struct_tstep'] = structural_kstep params['aero_tstep'] = aero_kstep params['fsi_substep'] = -1 for id, runtime_generator in self.runtime_generators.items(): runtime_generator.generate(params) self.time_aero = 0.0 self.time_struc = 0.0 # Copy the controlled states so that the interpolation does not # destroy the previous information controlled_structural_kstep = structural_kstep.copy() controlled_aero_kstep = aero_kstep.copy() for k in range(self.settings['fsi_substeps'] + 1): if (k == self.settings['fsi_substeps'] and self.settings['fsi_substeps']): print_res = 0 if self.res == 0. else np.log10(self.res) print_res_dqdt = 0 if self.res_dqdt == 0. else np.log10(self.res_dqdt) cout.cout_wrap(("The FSI solver did not converge!!! residuals: %f %f" % (print_res, print_res_dqdt))) self.aero_solver.update_custom_grid( structural_kstep, aero_kstep, nl_body_kstep) break # generate new grid (already rotated) aero_kstep = controlled_aero_kstep.copy() self.aero_solver.update_custom_grid( structural_kstep, aero_kstep, nl_body_kstep) # compute unsteady contribution force_coeff = 0.0 unsteady_contribution = False if self.settings['include_unsteady_force_contribution']: if self.data.ts > self.settings['steps_without_unsteady_force']: unsteady_contribution = True if k < self.settings['pseudosteps_ramp_unsteady_force']: force_coeff = k/self.settings['pseudosteps_ramp_unsteady_force'] else: force_coeff = 1. previous_runtime_steady_forces = structural_kstep.runtime_steady_forces.astype(dtype=ct.c_double, order='F', copy=True) previous_runtime_unsteady_forces = structural_kstep.runtime_unsteady_forces.astype(dtype=ct.c_double, order='F', copy=True) # Add external forces if self.with_runtime_generators: structural_kstep.runtime_steady_forces.fill(0.) structural_kstep.runtime_unsteady_forces.fill(0.) params = dict() params['data'] = self.data params['struct_tstep'] = structural_kstep params['aero_tstep'] = aero_kstep params['fsi_substep'] = k for id, runtime_generator in self.runtime_generators.items(): runtime_generator.generate(params) # run the solver ini_time_aero = time.perf_counter() self.data = self.aero_solver.run(aero_step=aero_kstep, structural_step=structural_kstep, convect_wake=True, unsteady_contribution=unsteady_contribution, nl_body_tstep = nl_body_kstep) self.time_aero += time.perf_counter() - ini_time_aero previous_kstep = structural_kstep.copy() structural_kstep = controlled_structural_kstep.copy() structural_kstep.runtime_steady_forces = previous_kstep.runtime_steady_forces.astype(dtype=ct.c_double, order='F', copy=True) structural_kstep.runtime_unsteady_forces = previous_kstep.runtime_unsteady_forces.astype(dtype=ct.c_double, order='F', copy=True) previous_kstep.runtime_steady_forces = previous_runtime_steady_forces.astype(dtype=ct.c_double, order='F', copy=True) previous_kstep.runtime_unsteady_forces = previous_runtime_unsteady_forces.astype(dtype=ct.c_double, order='F', copy=True) # move the aerodynamic surface according the the structural one self.aero_solver.update_custom_grid( structural_kstep, aero_kstep, nl_body_kstep) self.map_forces(aero_kstep, structural_kstep, nl_body_kstep = nl_body_kstep, unsteady_forces_coeff = force_coeff) # relaxation relax_factor = self.relaxation_factor(k) relax(self.data.structure, structural_kstep, previous_kstep, relax_factor) # check if nan anywhere. # if yes, raise exception if np.isnan(structural_kstep.steady_applied_forces).any(): raise exc.NotConvergedSolver('NaN found in steady_applied_forces!') if np.isnan(structural_kstep.unsteady_applied_forces).any(): raise exc.NotConvergedSolver('NaN found in unsteady_applied_forces!') copy_structural_kstep = structural_kstep.copy() ini_time_struc = time.perf_counter() for i_substep in range( self.settings['structural_substeps'] + 1): # run structural solver coeff = ((i_substep + 1)/ (self.settings['structural_substeps'] + 1)) structural_kstep = self.interpolate_timesteps( step0=self.data.structure.timestep_info[-1], step1=copy_structural_kstep, out_step=structural_kstep, coeff=coeff) self.data = self.structural_solver.run( structural_step=structural_kstep, dt=self.substep_dt) self.time_struc += time.perf_counter() - ini_time_struc # check convergence if self.convergence(k, structural_kstep, previous_kstep, self.structural_solver, self.aero_solver, self.with_runtime_generators): # move the aerodynamic surface according to the structural one self.aero_solver.update_custom_grid(structural_kstep, aero_kstep, nl_body_tstep = nl_body_kstep) break # move the aerodynamic surface according the the structural one self.aero_solver.update_custom_grid(structural_kstep, aero_kstep, nl_body_tstep = nl_body_kstep) self.aero_solver.add_step() self.data.aero.timestep_info[-1] = aero_kstep.copy() if self.settings['nonlifting_body_interactions']: self.data.nonlifting_body.timestep_info[-1] = nl_body_kstep.copy() self.structural_solver.add_step() self.data.structure.timestep_info[-1] = structural_kstep.copy() final_time = time.perf_counter() if self.print_info: print_res = 0 if self.res_dqdt == 0. else np.log10(self.res_dqdt) self.residual_table.print_line([self.data.ts, self.data.ts*self.dt, k, self.time_struc/(self.time_aero + self.time_struc), final_time - initial_time, print_res, structural_kstep.for_vel[0], structural_kstep.for_vel[2], np.sum(structural_kstep.steady_applied_forces[:, 0]), np.sum(structural_kstep.steady_applied_forces[:, 2])]) (self.data.structure.timestep_info[self.data.ts].total_forces[0:3], self.data.structure.timestep_info[self.data.ts].total_forces[3:6]) = ( self.structural_solver.extract_resultants(self.data.structure.timestep_info[self.data.ts])) # run postprocessors if self.with_postprocessors: for postproc in self.postprocessors: self.data = self.postprocessors[postproc].run(online=True, solvers=solvers) # network only # put result back in queue if out_queue: self.logger.debug('Time loop - about to get out variables from data') self.set_of_variables.get_value(self.data) if out_queue.full(): # clear the queue such that it always contains the latest time step out_queue.get() # clear item from queue self.logger.debug('Data output Queue is full - clearing output') out_queue.put(self.set_of_variables) if finish_event: finish_event.set() self.logger.info('Time loop - Complete') def convergence(self, k, tstep, previous_tstep, struct_solver, aero_solver, with_runtime_generators): r""" Check convergence in the FSI loop. Convergence is determined as: .. math:: \epsilon_q^k = \frac{|| q^k - q^{k - 1} ||}{q^0} .. math:: \epsilon_\dot{q}^k = \frac{|| \dot{q}^k - \dot{q}^{k - 1} ||}{\dot{q}^0} FSI converged if :math:`\epsilon_q^k < \mathrm{FSI\ tolerance}` and :math:`\epsilon_\dot{q}^k < \mathrm{FSI\ tolerance}` """ # check for non-convergence if not all(np.isfinite(tstep.q)): raise Exception( '***Not converged! There is a NaN value in the forces!') if not k: # save the value of the vectors for normalising later self.base_q = np.linalg.norm(tstep.q.copy()) self.base_dqdt = np.linalg.norm(tstep.dqdt.copy()) if self.base_dqdt == 0: self.base_dqdt = 1. if with_runtime_generators: self.base_res_forces = np.linalg.norm(tstep.runtime_steady_forces + tstep.runtime_unsteady_forces) if self.base_res_forces == 0: self.base_res_forces = 1. return False # Check the special case of no aero and no runtime generators if (aero_solver.solver_id.lower() == "noaero"\ or struct_solver.solver_id.lower() == "nostructural")\ and not with_runtime_generators: return True # relative residuals self.res = (np.linalg.norm(tstep.q- previous_tstep.q)/ self.base_q) self.res_dqdt = (np.linalg.norm(tstep.dqdt- previous_tstep.dqdt)/ self.base_dqdt) if with_runtime_generators: res_forces = (np.linalg.norm(tstep.runtime_steady_forces - previous_tstep.runtime_steady_forces + tstep.runtime_unsteady_forces - previous_tstep.runtime_unsteady_forces)/ self.base_res_forces) else: res_forces = 0. # we don't want this to converge before introducing the gamma_dot forces! if self.settings['include_unsteady_force_contribution']: if k < self.settings['pseudosteps_ramp_unsteady_force'] \ and self.data.ts > self.settings['steps_without_unsteady_force']: return False # convergence rigid_solver = False if "rigid" in struct_solver.solver_id.lower(): rigid_solver = True elif "NonLinearDynamicMultibody" == struct_solver.solver_id.lower() and struct_solver.settings['rigid_bodies']: rigid_solver = True if k > self.settings['minimum_steps'] - 1: if self.res < self.settings['fsi_tolerance'] or rigid_solver: if self.res_dqdt < self.settings['fsi_tolerance']: if res_forces < self.settings['fsi_tolerance']: return True def map_forces(self, aero_kstep, structural_kstep, nl_body_kstep = None, unsteady_forces_coeff=1.0): # set all forces to 0 structural_kstep.steady_applied_forces.fill(0.0) structural_kstep.unsteady_applied_forces.fill(0.0) # aero forces to structural forces struct_forces = mapping.aero2struct_force_mapping( aero_kstep.forces, self.data.aero.struct2aero_mapping, aero_kstep.zeta, structural_kstep.pos, structural_kstep.psi, self.data.structure.node_master_elem, self.data.structure.connectivities, structural_kstep.cag(), self.data.aero.data_dict) dynamic_struct_forces = unsteady_forces_coeff*mapping.aero2struct_force_mapping( aero_kstep.dynamic_forces, self.data.aero.struct2aero_mapping, aero_kstep.zeta, structural_kstep.pos, structural_kstep.psi, self.data.structure.node_master_elem, self.data.structure.connectivities, structural_kstep.cag(), self.data.aero.data_dict) if self.correct_forces: struct_forces = \ self.correct_forces_generator.generate(aero_kstep=aero_kstep, structural_kstep=structural_kstep, struct_forces=struct_forces, ts=self.data.ts) aero_kstep.aero_steady_forces_beam_dof = struct_forces structural_kstep.postproc_node['aero_steady_forces'] = struct_forces structural_kstep.postproc_node['aero_unsteady_forces'] = dynamic_struct_forces # if self.settings['nonlifting_body_interactions']: # struct_forces += mapping.aero2struct_force_mapping( # nl_body_kstep.forces, # self.data.nonlifting_body.struct2aero_mapping, # nl_body_kstep.zeta, # structural_kstep.pos, # structural_kstep.psi, # self.data.structure.node_master_elem, # self.data.structure.connectivities, # structural_kstep.cag(), # self.data.nonlifting_body.data_dict) # prescribed forces + aero forces # prescribed forces + aero forces + runtime generated structural_kstep.steady_applied_forces += struct_forces structural_kstep.steady_applied_forces += self.data.structure.ini_info.steady_applied_forces structural_kstep.steady_applied_forces += structural_kstep.runtime_steady_forces structural_kstep.unsteady_applied_forces += dynamic_struct_forces if len(self.data.structure.dynamic_input) > 0: structural_kstep.unsteady_applied_forces += self.data.structure.dynamic_input[max(self.data.ts - 1, 0)]['dynamic_forces'] structural_kstep.unsteady_applied_forces += structural_kstep.runtime_unsteady_forces # Apply unsteady force coefficient structural_kstep.unsteady_applied_forces *= unsteady_forces_coeff def relaxation_factor(self, k): initial = self.settings['relaxation_factor'] if not self.settings['dynamic_relaxation']: return initial final = self.settings['final_relaxation_factor'] if k >= self.settings['relaxation_steps']: return final value = initial + (final - initial)/self.settings['relaxation_steps']*k return value @staticmethod def interpolate_timesteps(step0, step1, out_step, coeff): """ Performs a linear interpolation between step0 and step1 based on coeff in [0, 1]. 0 means info in out_step == step0 and 1 out_step == step1. Quantities interpolated: * `steady_applied_forces` * `unsteady_applied_forces` * `velocity` input in Lagrange constraints """ if not 0.0 <= coeff <= 1.0: return out_step # forces out_step.steady_applied_forces[:] = ( (1.0 - coeff)*step0.steady_applied_forces + (coeff)*(step1.steady_applied_forces)) out_step.unsteady_applied_forces[:] = ( (1.0 - coeff)*step0.unsteady_applied_forces + (coeff)*(step1.unsteady_applied_forces)) # multibody if necessary if out_step.mb_dict is not None: for key in step1.mb_dict.keys(): if 'constraint_' in key: try: out_step.mb_dict[key]['velocity'][:] = ( (1.0 - coeff)*step0.mb_dict[key]['velocity'] + (coeff)*step1.mb_dict[key]['velocity']) except KeyError: pass return out_step def teardown(self): self.structural_solver.teardown() self.aero_solver.teardown() if self.with_postprocessors: for pp in self.postprocessors.values(): pp.teardown() if self.with_controllers: for cont in self.controllers.values(): cont.teardown() if self.with_runtime_generators: for rg in self.runtime_generators.values(): rg.teardown() def relax(beam, timestep, previous_timestep, coeff): timestep.steady_applied_forces = ((1.0 - coeff)*timestep.steady_applied_forces + coeff*previous_timestep.steady_applied_forces) timestep.unsteady_applied_forces = ((1.0 - coeff)*timestep.unsteady_applied_forces + coeff*previous_timestep.unsteady_applied_forces) timestep.runtime_steady_forces = ((1.0 - coeff)*timestep.runtime_steady_forces + coeff*previous_timestep.runtime_steady_forces) timestep.runtime_unsteady_forces = ((1.0 - coeff)*timestep.runtime_unsteady_forces + coeff*previous_timestep.runtime_unsteady_forces) def normalise_quaternion(tstep): tstep.dqdt[-4:] = algebra.unit_vector(tstep.dqdt[-4:]) tstep.quat = tstep.dqdt[-4:].astype(dtype=ct.c_double, order='F', copy=True) ================================================ FILE: sharpy/solvers/dynamicuvlm.py ================================================ """ Time Domain Aerodynamic Solver N Goizueta Jan 19 """ import sharpy.utils.solver_interface as solver_interface from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils import sharpy.utils.cout_utils as cout @solver class DynamicUVLM(BaseSolver): """ Dynamic Aerodynamic Time Domain Simulation Provides an aerodynamic only simulation in time by time stepping the solution. The type of aerodynamic solver is parsed as a setting. To Do: Clean timestep information for memory efficiency Warnings: Under development. Issues encountered when using the linear UVLM as the aerodynamic solver with integration order = 1. """ solver_id = 'DynamicUVLM' solver_classification = 'Aero' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Write status to screen' settings_types['structural_solver'] = 'str' settings_default['structural_solver'] = None settings_description['structural_solver'] = 'Structural solver to use in the coupled simulation' settings_types['structural_solver_settings'] = 'dict' settings_default['structural_solver_settings'] = None settings_description['structural_solver_settings'] = 'Dictionary of settings for the structural solver' settings_types['aero_solver'] = 'str' settings_default['aero_solver'] = None settings_description['aero_solver'] = 'Aerodynamic solver to use in the coupled simulation' settings_types['aero_solver_settings'] = 'dict' settings_default['aero_solver_settings'] = None settings_description['aero_solver_settings'] = 'Dictionary of settings for the aerodynamic solver' settings_types['n_time_steps'] = 'int' settings_default['n_time_steps'] = None settings_description['n_time_steps'] = 'Number of time steps for the simulation' settings_types['dt'] = 'float' settings_default['dt'] = None settings_description['dt'] = 'Time step' settings_types['include_unsteady_force_contribution'] = 'bool' settings_default['include_unsteady_force_contribution'] = False settings_description['include_unsteady_force_contribution'] = 'If on, added mass contribution is added to the forces. This depends on the time derivative of the bound circulation. Check ``filter_gamma_dot`` in the aero solver' settings_types['postprocessors'] = 'list(str)' settings_default['postprocessors'] = list() settings_description['postprocessors'] = 'List of the postprocessors to run at the end of every time step' settings_types['postprocessors_settings'] = 'dict' settings_default['postprocessors_settings'] = dict() settings_description['postprocessors_settings'] = 'Dictionary with the applicable settings for every ``psotprocessor``. Every ``postprocessor`` needs its entry, even if empty' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None self.aero_solver = None self.print_info = False self.dt = None self.residual_table = None self.postprocessors = dict() self.with_postprocessors = False def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) self.dt = self.settings['dt'] self.print_info = self.settings['print_info'] self.aero_solver = solver_interface.initialise_solver(self.settings['aero_solver']) self.aero_solver.initialise(self.data, self.settings['aero_solver_settings'], restart=False) self.data = self.aero_solver.data # initialise postprocessors self.postprocessors = dict() if len(self.settings['postprocessors']) > 0: self.with_postprocessors = True for postproc in self.settings['postprocessors']: self.postprocessors[postproc] = solver_interface.initialise_solver(postproc) self.postprocessors[postproc].initialise( self.data, self.settings['postprocessors_settings'][postproc], caller=self, restart=False) if self.print_info: self.residual_table = cout.TablePrinter(2, 14, ['g', 'f']) self.residual_table.print_header(['ts', 't']) def run(self, **kwargs): # struct info - only for orientation, no structural solution is performed struct_ini_step = self.data.structure.timestep_info[-1] for self.data.ts in range(len(self.data.aero.timestep_info), len(self.data.aero.timestep_info) + self.settings['n_time_steps']): aero_tstep = self.data.aero.timestep_info[-1] self.aero_solver.update_custom_grid(struct_ini_step, aero_tstep) force_coeff = 0.0 if self.settings['include_unsteady_force_contribution']: force_coeff = 1.0 if self.data.ts < 5: force_coeff = 0.0 # run the solver if force_coeff == 0.: unsteady_contribution = False else: unsteady_contribution = True self.data = self.aero_solver.run(aero_tstep=aero_tstep, structure_tstep=struct_ini_step, convect_wake=True, unsteady_contribution=unsteady_contribution) self.aero_solver.add_step() self.data.aero.timestep_info[-1] = aero_tstep.copy() self.data.structure.timestep_info.append(struct_ini_step.copy()) if self.print_info: self.residual_table.print_line([self.data.ts, self.data.ts * self.dt]) if self.with_postprocessors: for postproc in self.postprocessors: self.data = self.postprocessors[postproc].run(online=True) if self.print_info: cout.cout_wrap('...Finished', 1) return self.data ================================================ FILE: sharpy/solvers/gridloader.py ================================================ import h5py as h5 import numpy as np from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils import sharpy.utils.h5utils as h5utils @solver class GridLoader(BaseSolver): """ ``GridLoader`` class, inherited from ``BaseSolver`` Parent class for Aerogridloader and Nonliftingbodygridloader. Both classes generate aerodynamic grids based on the input data Args: data (PreSharpy): ``ProblemData`` class structure Attributes: settings (dict): Name-value pair of the settings employed by the aerodynamic solver settings_types (dict): Acceptable types for the values in ``settings`` settings_default (dict): Name-value pair of default values for the aerodynamic settings data (ProblemData): class structure afile_name (str): name of the HDF5 file, e.g. ``.aero.h5`` aero: empty attribute data_dict (dict): key-value pairs of aerodynamic data """ solver_id = 'GridLoader' solver_classification = 'other' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() def __init__(self): self.data = None self.settings = None self.file_name = '' self.data_dict = dict() def initialise(self, data, restart=False): self.data = data self.read_input_files() self.settings = data.settings[self.solver_id] settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, options=self.settings_options) def read_input_files(self): self.file_name = (self.data.case_route + '/' + self.data.case_name + self.file_name) h5utils.check_file_exists(self.file_name) # read and store the hdf5 file in dictionary with h5.File(self.file_name, 'r') as file_handle: self.data_dict = h5utils.load_h5_in_dict(file_handle) ================================================ FILE: sharpy/solvers/initialaeroelasticloader.py ================================================ from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils import sharpy.utils.h5utils as h5utils import sharpy.utils.exceptions as exceptions @solver class InitialAeroelasticLoader(BaseSolver): r""" This solver prescribes pos, pos_dot, psi, psi_dot and for_vel at each time step from a .h5 file """ solver_id = 'InitialAeroelasticLoader' solver_classification = 'loader' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['input_file'] = 'str' settings_default['input_file'] = None settings_description['input_file'] = 'Input file containing the simulation data' settings_types['include_forces'] = 'bool' settings_default['include_forces'] = True settings_description['include_forces'] = 'Map the forces' settings_types['generate_aero'] = 'bool' settings_default['generate_aero'] = False settings_description['generate_aero'] = 'Generate the aerodynamics grids from scratch' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None self.file_info = None def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, no_ctype=True) # Load simulation data self.file_info = h5utils.readh5(self.settings['input_file']) def run(self, **kwargs): aero_step = settings_utils.set_value_or_default(kwargs, 'aero_step', self.data.aero.timestep_info[-1]) structural_step = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) # Copy structural information attributes = ['pos', 'pos_dot', 'pos_ddot', 'psi', 'psi_dot', 'psi_ddot', 'for_pos', 'for_vel', 'for_acc', 'quat', 'mb_FoR_pos', 'mb_FoR_vel', 'mb_FoR_acc', 'mb_quat'] if self.settings['include_forces']: attributes.extend(['runtime_steady_forces', 'runtime_unsteady_forces', 'steady_applied_forces', 'unsteady_applied_forces']) for att in attributes: new_attr = getattr(structural_step, att) db_attr = getattr(self.file_info.structure, att) if new_attr.shape == db_attr.shape: new_attr[...] = db_attr else: error_msg = "Non matching shapes in attribute %s" % att exceptions.NotValidInputFile(error_msg) # Copy aero information if self.settings['generate_aero']: # Generate aerodynamic surface self.data.aero.generate_zeta_timestep_info(structural_step, aero_step, self.data.structure, self.data.aero.aero_settings) # generate the wake because the solid shape might change self.data.aero.wake_shape_generator.generate({'zeta': aero_step.zeta, 'zeta_star': aero_step.zeta_star, 'gamma': aero_step.gamma, 'gamma_star': aero_step.gamma_star, 'dist_to_orig': aero_step.dist_to_orig}) else: attributes = ['zeta', 'zeta_star', 'normals', 'gamma', 'gamma_star', 'u_ext', 'u_ext_star', ] if self.settings['include_forces']: attributes.extend(['dynamic_forces', 'forces', ]) for att in attributes: for isurf in range(aero_step.n_surf): new_attr = getattr(aero_step, att)[isurf] db_attr = getattr(self.file_info.aero, att)[isurf] if new_attr.shape == db_attr.shape: new_attr[...] = db_attr else: error_msg = "Non matching shapes in attribute %s" % att exceptions.NotValidInputFile(error_msg) return self.data ================================================ FILE: sharpy/solvers/lindynamicsim.py ================================================ import numpy as np import os import h5py as h5 from sharpy.utils.solver_interface import solver, BaseSolver, initialise_solver import sharpy.utils.settings as settings_utils import sharpy.linear.src.libss as libss import scipy.linalg as sclalg import sharpy.utils.h5utils as h5utils from sharpy.utils.datastructures import LinearTimeStepInfo from sharpy.linear.utils.ss_interface import InputVariable, LinearVector import sharpy.utils.cout_utils as cout import time import warnings @solver class LinDynamicSim(BaseSolver): """Time-domain solution of Linear Time Invariant Systems Uses the derived linear time invariant systems and solves it in time domain. The inputs are provided by means of a list of dictionaries to the setting ``input_generators``. For each input you want, you need a dictionary entry containing ``name`` (str) which is the name of the variable, ``index`` (int) for the index of the variable in the case of multidimensional variables (if unspecified reverts to ``0``) and the ``file_path`` to a text file containing the time series of the input. If the input variable is multidimensional, ``index`` may be a list of indices for each column of time series in the array in the input file. Alternatively a ``case_name.lininput.h5`` file in the case root folder can be used with the following entries: * ``x0`` (optional): Initial state vector * ``input_vec``: Input vector ``(n_tsteps, n_inputs)``. Note: This solver is seldom used in SHARPy (its focus is on nonlinear time domain aeroelasticity) hence you may find this solver lacking in features. If you use it, you may need to make modifications. We would greatly appreciate that you contribute these modifications by means of a pull request! """ solver_id = 'LinDynamicSim' solver_classification = 'Coupled' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['write_dat'] = 'list(str)' settings_default['write_dat'] = [] settings_description['write_dat'] = 'List of vectors to write: ``x``, ``y``, ``u`` and/or ``t``' settings_types['reference_velocity'] = 'float' settings_default['reference_velocity'] = 1. settings_description['reference_velocity'] = 'Velocity to scale the structural equations when using a non-dimensional system' settings_default['n_tsteps'] = 10 settings_types['n_tsteps'] = 'int' settings_description['n_tsteps'] = 'Number of time steps to run' settings_types['physical_time'] = 'float' settings_default['physical_time'] = 2. settings_description['physical_time'] = 'Time to run' settings_types['input_generators'] = 'list(dict)' settings_default['input_generators'] = [] settings_description['input_generators'] = 'List of dictionaries for each input' settings_default['dt'] = 0.001 settings_types['dt'] = 'float' settings_description['dt'] = 'Time increment for the solution of systems without a specified dt' settings_types['postprocessors'] = 'list(str)' settings_default['postprocessors'] = list() settings_types['postprocessors_settings'] = 'dict' settings_default['postprocessors_settings'] = dict() settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = dict() self.postprocessors = dict() self.with_postprocessors = False self.input_data_dict = dict() self.input_file_name = "" self.folder = None def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings: self.settings = custom_settings else: self.settings = data.settings[self.solver_id] settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, no_ctype=True) # Read initial state and input data and store in dictionary self.read_files() # Output folder self.folder = data.output_folder + '/lindynamicsim/' if not os.path.exists(self.folder): os.makedirs(self.folder) # initialise postprocessors self.postprocessors = dict() if len(self.settings['postprocessors']) > 0: self.with_postprocessors = True for postproc in self.settings['postprocessors']: self.postprocessors[postproc] = initialise_solver(postproc) self.postprocessors[postproc].initialise( self.data, self.settings['postprocessors_settings'][postproc], caller=self, restart=False) def input_vector(self, ss): """ Generates an input vector ``u`` of size ``n_tsteps x inputs`` and populates the correct columns with the time series arrays provided as text files in the settings ``input_generators``. Args: ss (libss.StateSpace): State Space object for which to generate input Returns: np.array: Input vector. """ n_steps = self.settings['n_tsteps'] u_vect = np.zeros((n_steps, ss.inputs)) for in_settings in self.settings['input_generators']: var_name = in_settings['name'] index = in_settings.get('index', 0) file_path = in_settings['file_path'] variable = ss.input_variables.get_variable_from_name(var_name) input_data = np.loadtxt(file_path) cout.cout_wrap('Found input for {:s}'.format(str(variable))) if type(index) is list: for ith, i_ind in enumerate(index): in_channel = variable.cols_loc[i_ind] u_vect[:, in_channel] = input_data[:, ith] else: in_channel = variable.cols_loc[index] u_vect[:, in_channel] = input_data return u_vect def run(self, **kwargs): ss = self.data.linear.ss n_steps = self.settings['n_tsteps'] x0 = self.input_data_dict.get('x0', np.zeros(ss.states)) if len(self.settings['input_generators']) != 0: u = self.input_vector(ss) else: u = self.input_data_dict['u'] if len(x0) != ss.states: warnings.warn('Number of states in the initial state vector not equal to the number of states') x0 = np.zeros(ss.states) if u.shape[1] != ss.inputs: warnings.warn('Dimensions of the input vector not equal to the number of inputs') cout.cout_wrap('Number of inputs: %g' % ss.inputs, 3) cout.cout_wrap('Number of timesteps: %g' % n_steps, 3) cout.cout_wrap('Number of UVLM inputs: %g' % self.data.linear.linear_system.uvlm.ss.inputs, 3) cout.cout_wrap('Number of beam inputs: %g' % self.data.linear.linear_system.beam.ss.inputs, 3) breakpoint() try: dt = ss.dt except AttributeError: dt = self.settings['dt'] # Total time to run T = (n_steps - 1) * dt u_ref = self.settings['reference_velocity'] # If the system is scaled: if u_ref != 1.: scaling_factors = self.data.linear.linear_system.uvlm.sys.ScalingFacts dt_dimensional = scaling_factors['length'] / u_ref T_dimensional = (n_steps - 1) * dt_dimensional T = T_dimensional / scaling_factors['time'] ss = self.data.linear.linear_system.update(self.settings['reference_velocity']) t_dom = np.linspace(0, T, n_steps) # Use the scipy linear solver sys = libss.ss_to_scipy(ss) cout.cout_wrap('Solving linear system using scipy...') t0 = time.time() out = sys.output(u, t=t_dom, x0=x0) ts = time.time() - t0 cout.cout_wrap('\tSolved in %.2fs' % ts, 1) t_out = out[0] x_out = out[2] y_out = out[1] if self.settings['write_dat']: cout.cout_wrap('Writing linear simulation output .dat files to %s' % self.folder) if 'y' in self.settings['write_dat']: np.savetxt(self.folder + '/y_out.dat', y_out) cout.cout_wrap('Output vector written', 2) if 'x' in self.settings['write_dat']: np.savetxt(self.folder + '/x_out.dat', x_out) cout.cout_wrap('State vector written', 2) if 'u' in self.settings['write_dat']: np.savetxt(self.folder + '/u_out.dat', u) cout.cout_wrap('Input vector written', 2) if 't' in self.settings['write_dat']: np.savetxt(self.folder + '/t_out.dat', t_out) cout.cout_wrap('Time domain written', 2) cout.cout_wrap('Success', 1) # Pack state variables into linear timestep info cout.cout_wrap('Plotting results...') for n in range(len(t_out)-1): tstep = LinearTimeStepInfo() tstep.x = x_out[n, :] tstep.y = y_out[n, :] tstep.t = t_out[n] tstep.u = u[n, :] self.data.linear.timestep_info.append(tstep) # TODO: option to save to h5 # Pack variables into respective aero or structural time step infos (with the + f0 from lin) # Need to obtain information from the variables in a similar fashion as done with the database # for the beam case aero_tstep, struct_tstep = state_to_timestep(self.data, tstep.x, tstep.u, tstep.y) self.data.aero.timestep_info.append(aero_tstep) self.data.structure.timestep_info.append(struct_tstep) # run postprocessors if self.with_postprocessors: for postproc in self.postprocessors: self.data = self.postprocessors[postproc].run(online=True) return self.data def read_files(self): self.input_file_name = self.data.settings['SHARPy']['route'] + '/' + self.data.settings['SHARPy']['case'] + '.lininput.h5' # Check that the file exists try: h5utils.check_file_exists(self.input_file_name) # Read and store with h5.File(self.input_file_name, 'r') as input_file_handle: self.input_data_dict = h5utils.load_h5_in_dict(input_file_handle) except FileNotFoundError: pass def state_to_timestep(data, x, u=None, y=None): """ Warnings: Under development Writes a state-space vector to SHARPy timesteps Args: data: x: u: y: Returns: """ if data.settings['LinearAssembler']['linear_system_settings']['beam_settings']['modal_projection'] and \ data.settings['LinearAssembler']['linear_system_settings']['beam_settings']['inout_coords'] == 'modes': modal = True else: modal = False aero_state = x[:-data.linear.linear_system.beam.ss.states] # Beam output y_beam = y[-data.linear.linear_system.beam.ss.outputs:] u_q = np.zeros(data.linear.linear_system.uvlm.ss.inputs) if u is not None: u_q += u[:data.linear.linear_system.uvlm.ss.inputs] u_q[:y_beam.shape[0]] += y_beam else: u_q[:y_beam.shape[0]] += y_beam Kas = data.linear.linear_system.couplings['Kas'] if modal: # Transform to aerodynamic raw inputs uvlm_in_mode_gain = data.linear.linear_system.couplings['in_mode_gain'] aero_input = Kas.dot(uvlm_in_mode_gain.dot(u_q)) else: aero_input = Kas.dot(u_q) # Aero forces, gamma, gamma_dot, gamma_star, gust_state_vec = data.linear.linear_system.uvlm.unpack_ss_vector( data, x_n=aero_state, u_aero=aero_input, aero_tstep=data.linear.tsaero0, track_body=True, state_variables=data.linear.linear_system.uvlm.ss.state_variables, gust_in=True) if data.linear.linear_system.uvlm.gust_assembler: gust_in_loc = data.linear.ss.input_variables('u_gust').cols_loc u_in_gust = u[gust_in_loc] gust_assembler = data.linear.linear_system.uvlm.gust_assembler u_ext_gust = gust_assembler.state_to_uext.dot(gust_state_vec) + gust_assembler.uin_to_uext.dot(u_in_gust) else: u_ext_gust = np.array([]) uvlm_input_variables = LinearVector.transform(Kas.output_variables, InputVariable) # Unpack input zeta, zeta_dot, u_ext = data.linear.linear_system.uvlm.unpack_input_vector(aero_input, u_ext_gust, input_variables=uvlm_input_variables) current_aero_tstep = data.aero.timestep_info[-1].copy() current_aero_tstep.forces = [forces[i_surf] + data.linear.tsaero0.forces[i_surf] for i_surf in range(len(gamma))] current_aero_tstep.gamma = [gamma[i_surf] + data.linear.tsaero0.gamma[i_surf] for i_surf in range(len(gamma))] current_aero_tstep.gamma_dot = [gamma_dot[i_surf] + data.linear.tsaero0.gamma_dot[i_surf] for i_surf in range(len(gamma))] current_aero_tstep.gamma_star = [gamma_star[i_surf] + data.linear.tsaero0.gamma_star[i_surf] for i_surf in range(len(gamma))] current_aero_tstep.zeta = zeta current_aero_tstep.zeta_dot = zeta_dot current_aero_tstep.u_ext = u_ext aero_forces_vec = np.concatenate([forces[i_surf][:3, :, :].reshape(-1, order='C') for i_surf in range(len(forces))]) beam_forces = data.linear.linear_system.couplings['Ksa'].dot(aero_forces_vec) # Reconstruct the state if modal if modal: phi = data.linear.linear_system.beam.sys.U x_s = sclalg.block_diag(phi, phi).dot(y_beam) else: x_s = y_beam y_s = beam_forces #+ phi.dot(u_struct) current_struct_step = data.linear.linear_system.beam.unpack_ss_vector(x_s, y_s, data.linear.tsstruct0) return current_aero_tstep, current_struct_step ================================================ FILE: sharpy/solvers/linearassembler.py ================================================ """ Linear State Space Assembler """ from sharpy.utils.datastructures import Linear from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.linear.utils.ss_interface as ss_interface import sharpy.utils.settings as settings_utils import sharpy.utils.cout_utils as cout import sharpy.linear.assembler import sharpy.rom @solver class LinearAssembler(BaseSolver): r""" Warnings: Under development - please advise of new features and bugs! Creates a workspace containing the different linear elements of the state-space. The user specifies which elements to build sequentially via the ``linear_system`` setting. The most common uses will be: * Aerodynamic: :class:`sharpy.linear.assembler.LinearUVLM` solver * Structural: :class:`sharpy.linear.assembler.LinearBeam` solver * Aeroelastic: :class:`sharpy.linear.assembler.LinearAeroelastic` solver The solver enables to load a user specific assembly of a state-space by means of the ``LinearCustom`` block. See :class:`sharpy.sharpy.linear.assembler.LinearAssembler` for a detailed description of each of the state-space assemblies. Upon assembly of the linear system, the data structure ``data.linear`` will be created. The :class:`.Linear` contains the state-space as an attribute. This state space will be the one employed by postprocessors. Important: running the linear routines requires information on the tangent mass, stiffness and gyroscopic structural matrices therefore the solver :class:`solvers.modal.Modal` must have been run prior to linearisation. In addition, if the problem includes rigid body velocities, at least one timestep of :class:`solvers.DynamicCoupled` must have run such that the rigid body velocity is included. Example: The typical ``flow`` setting used prior to using this solver for an aeroelastic simulation with rigid body dynamics will be similar to: >>> flow = ['BeamLoader', >>> 'AerogridLoader', >>> 'StaticTrim', >>> 'DynamicCoupled', # a single time step will suffice >>> 'Modal', >>> 'LinearAssembler'] """ solver_id = 'LinearAssembler' solver_classification = 'Linear' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['linear_system'] = 'str' settings_default['linear_system'] = None settings_description['linear_system'] = 'Name of chosen state space assembly type' settings_types['linear_system_settings'] = 'dict' settings_default['linear_system_settings'] = dict() settings_description['linear_system_settings'] = 'Settings for the desired state space assembler' settings_types['linearisation_tstep'] = 'int' settings_default['linearisation_tstep'] = -1 settings_description['linearisation_tstep'] = 'Chosen linearisation time step number from available time steps' settings_types['modal_tstep'] = 'int' settings_default['modal_tstep'] = -1 settings_description['modal_tstep'] = 'Timestep in which modal information is stored. Useful if the ``Modal`` solver' \ ' is run at the start of the SHARPy flow.' settings_types['inout_coordinates'] = 'str' settings_default['inout_coordinates'] = '' settings_description['inout_coordinates'] = 'Input/output coordinates of the system. Nodal or modal space.' settings_options['inout_coordinates'] = ['', 'nodes', 'modes'] settings_types['retain_inputs'] = 'list(int)' settings_default['retain_inputs'] = [] settings_description['retain_inputs'] = 'List of input channels to retain in the chosen ``inout_coordinates``.' settings_types['retain_outputs'] = 'list(int)' settings_default['retain_outputs'] = [] settings_description['retain_outputs'] = 'List of output channels to retain in the chosen ``inout_coordinates``.' settings_types['retain_input_variables'] = 'list(str)' settings_default['retain_input_variables'] = [] settings_description['retain_input_variables'] = 'List of input channels to retain in the chosen ' \ '``inout_coordinates``.' settings_types['retain_output_variables'] = 'list(str)' settings_default['retain_output_variables'] = [] settings_description['retain_output_variables'] = 'List of output channels to retain in the chosen ' \ '``inout_coordinates``.' settings_types['recover_accelerations'] = 'bool' settings_default['recover_accelerations'] = False settings_description['recover_accelerations'] = 'Recover structural system accelerations as additional outputs.' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) def __init__(self): self.settings = dict() self.data = None def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings: self.data.settings[self.solver_id] = custom_settings self.settings = self.data.settings[self.solver_id] else: self.settings = data.settings[self.solver_id] settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, options=self.settings_options, no_ctype=True) # Get consistent linearisation timestep ii_step = self.settings['linearisation_tstep'] tsstruct0 = data.structure.timestep_info[ii_step] tsaero0 = data.aero.timestep_info[ii_step] try: tsstruct0.modal = data.structure.timestep_info[self.settings['modal_tstep']].modal except AttributeError: raise AttributeError('Unable to find modal information at desired ' 'timestep {:g}'.format(self.settings['modal_tstep'])) # Create data.linear self.data.linear = Linear(tsaero0, tsstruct0) lsys = ss_interface.initialise_system(self.settings['linear_system']) lsys.initialise(data) self.data.linear.linear_system = lsys def run(self, **kwargs): self.data.linear.ss = self.data.linear.linear_system.assemble() if self.settings['recover_accelerations']: gain = self.data.linear.linear_system.beam.recover_accelerations(self.data.linear.ss) self.data.linear.ss.addGain(gain, where='out') # modify inout coordinates if self.settings['inout_coordinates'] == 'nodes': try: self.data.linear.linear_system.to_nodal_coordinates() except AttributeError: pass # retain only selected inputs and outputs if len(self.settings['retain_inputs']) != 0: self.data.linear.ss.retain_inout_channels(self.settings['retain_inputs'], where='in') if len(self.settings['retain_outputs']) != 0: self.data.linear.ss.retain_inout_channels(self.settings['retain_outputs'], where='out') if len(self.settings['retain_input_variables']) != 0: ss = self.data.linear.ss input_vars = ss.input_variables removed_variables = [] for variable in input_vars: if variable.name not in self.settings['retain_input_variables']: removed_variables.append(variable.name) ss.remove_inputs(*removed_variables) if len(self.settings['retain_output_variables']) != 0: ss = self.data.linear.ss output_vars = ss.output_variables removed_variables = [] for variable in output_vars: if variable.name not in self.settings['retain_output_variables']: removed_variables.append(variable.name) ss.remove_outputs(*removed_variables) cout.cout_wrap('Final system is:', 1) cout.cout_wrap(str(self.data.linear.ss), 2) return self.data ================================================ FILE: sharpy/solvers/modal.py ================================================ import ctypes as ct import numpy as np import scipy as sc import os import itertools import warnings import sharpy.structure.utils.xbeamlib as xbeamlib from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils import sharpy.utils.algebra as algebra import sharpy.utils.cout_utils as cout import sharpy.structure.utils.modalutils as modalutils @solver class Modal(BaseSolver): """ ``Modal`` solver class, inherited from ``BaseSolver`` Extracts the ``M``, ``K`` and ``C`` matrices from the ``Fortran`` library for the beam. Depending on the choice of modal projection, these may or may not be transformed to a state-space form to compute the eigenvalues and mode shapes of the structure. """ solver_id = 'Modal' solver_classification = 'Linear' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Write status to screen' # solution options settings_types['rigid_body_modes'] = 'bool' settings_default['rigid_body_modes'] = False settings_description['rigid_body_modes'] = 'Write modes with rigid body mode shapes' settings_types['use_undamped_modes'] = 'bool' # basis for modal projection settings_default['use_undamped_modes'] = True settings_description['use_undamped_modes'] = 'Project the modes onto undamped mode shapes' settings_types['NumLambda'] = 'int' # no. of different modes to retain settings_default['NumLambda'] = 20 # doubles if use_undamped_modes is False settings_description['NumLambda'] = 'Number of modes to retain' # output options settings_types['write_modes_vtk'] = 'bool' # write displacements mode shapes in vtk file settings_default['write_modes_vtk'] = True settings_description['write_modes_vtk'] = 'Write Paraview files with mode shapes' settings_types['print_matrices'] = 'bool' # print M,C,K matrices to dat file settings_default['print_matrices'] = False settings_description['print_matrices'] = 'Write M, C and K matrices to file' settings_types['save_data'] = 'bool' # write modes shapes/freq./damp. to dat file settings_default['save_data'] = True settings_description['save_data'] = 'Write mode shapes, frequencies and damping to file' settings_types['continuous_eigenvalues'] = 'bool' settings_default['continuous_eigenvalues'] = False settings_description['continuous_eigenvalues'] = 'Use continuous time eigenvalues' settings_types['dt'] = 'float' settings_default['dt'] = 0 settings_description['dt'] = 'Time step to compute discrete time eigenvalues' settings_types['delta_curved'] = 'float' settings_default['delta_curved'] = 1e-2 settings_description['delta_curved'] = 'Threshold for linear expressions in rotation formulas' settings_types['plot_eigenvalues'] = 'bool' settings_default['plot_eigenvalues'] = False settings_description['plot_eigenvalues'] = 'Plot to screen root locus diagram' settings_types['max_rotation_deg'] = 'float' settings_default['max_rotation_deg'] = 15. settings_description['max_rotation_deg'] = 'Scale mode shape to have specified maximum rotation' settings_types['max_displacement'] = 'float' settings_default['max_displacement'] = 0.15 settings_description['max_displacement'] = 'Scale mode shape to have specified maximum displacement' settings_types['use_custom_timestep'] = 'int' settings_default['use_custom_timestep'] = -1 settings_description['use_custom_timestep'] = 'If > -1, it will use that time step geometry for calculating the modes' settings_types['rigid_modes_ppal_axes'] = 'bool' settings_default['rigid_modes_ppal_axes'] = False settings_description['rigid_modes_ppal_axes'] = 'Modify the ridid body modes such that they are defined wrt ' \ 'to the CG and aligned with the principal axes of inertia' settings_types['rigid_modes_cg'] = 'bool' settings_default['rigid_modes_cg'] = False settings_description['rigid_modes_cg'] = 'Not implemente yet' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None self.folder = None self.eigenvalue_table = None self.filename_freq = None self.filename_damp = None self.filename_shapes = None self.rigid_body_motion = None def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) self.rigid_body_motion = self.settings['rigid_body_modes'] self.data.ts = len(self.data.structure.timestep_info) - 1 if self.settings['use_custom_timestep'] > -1: self.data.ts = self.settings['use_custom_timestep'] # load info from dyn dictionary self.data.structure.add_unsteady_information( self.data.structure.dyn_dict, self.data.ts) # create folder for containing files if necessary self.folder = data.output_folder + '/beam_modal_analysis/' if not os.path.exists(self.folder): os.makedirs(self.folder) self.filename_freq = (self.folder + 'tstep' + ("%06d" % self.data.ts) + '_ModalFrequencies.dat') self.filename_damp = (self.folder + 'tstep' + ("%06d" % self.data.ts) + '_ModalDamping.dat') self.filename_shapes = (self.folder + 'tstep' + ("%06d" % self.data.ts) + '_ModalShape') if self.settings['print_info']: cout.cout_wrap('Structural eigenvalues') eigenvalue_filename = self.folder + '/eigenvaluetable.txt' self.eigenvalue_table = modalutils.EigenvalueTable(filename=eigenvalue_filename) self.eigenvalue_table.print_header(self.eigenvalue_table.headers) def run(self, **kwargs): r""" Extracts the eigenvalues and eigenvectors of the clamped structure. If ``use_undamped_modes == True`` then the free vibration modes of the clamped structure are found solving: .. math:: \mathbf{M\,\ddot{\eta}} + \mathbf{K\,\eta} = 0 that flows down to solving the non-trivial solutions to: .. math:: (-\omega_n^2\,\mathbf{M} + \mathbf{K})\mathbf{\Phi} = 0 On the other hand, if the damped modes are chosen because the system has damping, the free vibration modes are found solving the equation of motion of the form: .. math:: \mathbf{M\,\ddot{\eta}} + \mathbf{C\,\dot{\eta}} + \mathbf{K\,\eta} = 0 which can be written in state space form, with the state vector :math:`\mathbf{x} = [\eta^T,\,\dot{\eta}^T]^T` as .. math:: \mathbf{\dot{x}} = \begin{bmatrix} 0 & \mathbf{I} \\ -\mathbf{M^{-1}K} & -\mathbf{M^{-1}C} \end{bmatrix} \mathbf{x} and therefore the mode shapes and frequencies correspond to the solution of the eigenvalue problem .. math:: \mathbf{A\,\Phi} = \mathbf{\Lambda\,\Phi}. From the eigenvalues, the following system characteristics are provided: * Natural Frequency: :math:`\omega_n = |\lambda|` * Damped natural frequency: :math:`\omega_d = \text{Im}(\lambda) = \omega_n \sqrt{1-\zeta^2}` * Damping ratio: :math:`\zeta = -\frac{\text{Re}(\lambda)}{\omega_n}` In addition to the above, the modal output dictionary includes the following: * ``M``: Tangent mass matrix * ``C``: Tangent damping matrix * ``K``: Tangent stiffness matrix * ``Ccut``: Modal damping matrix :math:`\mathbf{C}_m = \mathbf{\Phi}^T\mathbf{C}\mathbf{\Phi}` * ``Kin_damp``: Forces gain matrix (when damped): :math:`K_{in} = \mathbf{\Phi}_L^T \mathbf{M}^{-1}` * ``eigenvectors``: Right eigenvectors * ``eigenvectors_left``: Left eigenvectors given when the system is damped Returns: PreSharpy: updated data object with modal analysis as part of the last structural time step. """ # Number of degrees of freedom num_str_dof = self.data.structure.num_dof.value num_rigid_dof = 10 if self.rigid_body_motion else 0 num_dof = num_str_dof + num_rigid_dof # Initialize matrices FullMglobal = np.zeros((num_dof, num_dof), dtype=ct.c_double, order='F') FullKglobal = np.zeros((num_dof, num_dof), dtype=ct.c_double, order='F') FullCglobal = np.zeros((num_dof, num_dof), dtype=ct.c_double, order='F') if self.rigid_body_motion: # Settings for the assembly of the matrices # try: # full_matrix_settings = self.data.settings['StaticCoupled']['structural_solver_settings'] # full_matrix_settings['dt'] = ct.c_double(0.01) # Dummy: required but not used # full_matrix_settings['newmark_damp'] = ct.c_double(1e-2) # Dummy: required but not used # except KeyError: # full_matrix_settings = self.data.settings['DynamicCoupled']['structural_solver_settings'] import sharpy.solvers._basestructural as basestructuralsolver full_matrix_settings = basestructuralsolver._BaseStructural().settings_default settings_utils.to_custom_types(full_matrix_settings, basestructuralsolver._BaseStructural().settings_types, full_matrix_settings) # Obtain the tangent mass, damping and stiffness matrices FullMglobal, FullCglobal, FullKglobal, FullQ = xbeamlib.xbeam3_asbly_dynamic(self.data.structure, self.data.structure.timestep_info[self.data.ts], full_matrix_settings) cg = modalutils.cg(FullMglobal) else: xbeamlib.cbeam3_solv_modal(self.data.structure, self.settings, self.data.ts, FullMglobal, FullCglobal, FullKglobal) cg = None # Print matrices if self.settings['print_matrices']: np.savetxt(self.folder + "Mglobal.dat", FullMglobal, fmt='%.12f', delimiter='\t', newline='\n') np.savetxt(self.folder + "Cglobal.dat", FullCglobal, fmt='%.12f', delimiter='\t', newline='\n') np.savetxt(self.folder + "Kglobal.dat", FullKglobal, fmt='%.12f', delimiter='\t', newline='\n') # Check if the damping matrix is zero (issue working) if self.settings['use_undamped_modes']: zero_FullCglobal = True for i,j in itertools.product(range(num_dof),range(num_dof)): if np.absolute(FullCglobal[i, j]) > np.finfo(float).eps: zero_FullCglobal = False warnings.warn('Projecting a system with damping on undamped modal shapes') break NumLambda = min(num_dof, self.settings['NumLambda']) if self.settings['use_undamped_modes']: # Solve for eigenvalues (with unit eigenvectors) eigenvalues,eigenvectors=np.linalg.eig( np.linalg.solve(FullMglobal,FullKglobal)) eigenvectors_left=None # Define vibration frequencies and damping freq_natural = np.sqrt(eigenvalues) order = np.argsort(freq_natural)[:NumLambda] freq_natural = freq_natural[order] eigenvalues = eigenvalues[order] eigenvectors = eigenvectors[:,order] damping = np.zeros((NumLambda,)) else: # State-space model Minv_neg = -np.linalg.inv(FullMglobal) A = np.zeros((2*num_dof, 2*num_dof), dtype=ct.c_double, order='F') A[:num_dof, num_dof:] = np.eye(num_dof) A[num_dof:, :num_dof] = np.dot(Minv_neg, FullKglobal) A[num_dof:, num_dof:] = np.dot(Minv_neg, FullCglobal) # Solve the eigenvalues problem eigenvalues, eigenvectors_left, eigenvectors = \ sc.linalg.eig(A,left=True,right=True) freq_natural = np.abs(eigenvalues) damping = np.zeros_like(freq_natural) iiflex = freq_natural > 1e-16*np.mean(freq_natural) # Pick only structural modes damping[iiflex] = -eigenvalues[iiflex].real/freq_natural[iiflex] freq_damped = freq_natural * np.sqrt(1-damping**2) # Order & downselect complex conj: # this algorithm assumes that complex conj eigenvalues appear consecutively # in eigenvalues. For symmetrical systems, this relies on the fact that: # - complex conj eigenvalues have the same absolute value (to machine # precision) # - couples of eigenvalues with multiplicity higher than 1, show larger # numerical difference order = np.argsort(freq_damped)[:2*NumLambda] freq_damped = freq_damped[order] freq_natural = freq_natural[order] eigenvalues = eigenvalues[order] include = np.ones((2*NumLambda,), dtype=np.bool) ii = 0 tol_rel = np.finfo(float).eps * freq_damped[ii] while ii < 2*NumLambda: # check complex if np.abs(eigenvalues[ii].imag) > 0.: if np.abs(eigenvalues[ii+1].real-eigenvalues[ii].real) > tol_rel or\ np.abs(eigenvalues[ii+1].imag+eigenvalues[ii].imag) > tol_rel: raise NameError('Complex conjugate expected but not found!') ii += 1 try: include[ii] = False except IndexError: pass ii += 1 freq_damped = freq_damped[include] eigenvalues = eigenvalues[include] if self.settings['continuous_eigenvalues']: if self.settings['dt'] == 0.: raise ValueError('Cannot compute the continuous eigenvalues without a dt value') eigenvalues = np.log(eigenvalues)/self.settings['dt'] order = order[include] damping = damping[order] eigenvectors = eigenvectors[:, order] eigenvectors_left = eigenvectors_left[:, order].conj() # Modify rigid body modes for them to be defined wrt the CG eigenvectors = modalutils.mode_sign_convention(self.data.structure.boundary_conditions, eigenvectors, self.rigid_body_motion) if not eigenvectors_left: if self.settings['rigid_modes_ppal_axes']: eigenvectors, t_pa, r_pa = modalutils.free_modes_principal_axes(eigenvectors, FullMglobal, return_transform=True) else: t_pa = None # Transformation matrix from the A frame to the P frame (principal axes of inertia) r_pa = None # Scaling eigenvectors, eigenvectors_left = self.scale_modes_unit_mass_matrix(eigenvectors, FullMglobal, eigenvectors_left) # Other terms required for state-space realisation # non-zero damping matrix # Modal damping matrix if self.settings['use_undamped_modes'] and not(zero_FullCglobal): Ccut = np.dot(eigenvectors.T, np.dot(FullCglobal, eigenvectors)) else: Ccut = None # forces gain matrix (nodal -> modal) if not self.settings['use_undamped_modes']: Kin_damp = np.dot(eigenvectors_left[num_dof:, :].T, -Minv_neg) else: Kin_damp = None # Plot eigenvalues using matplotlib if specified in settings if self.settings['plot_eigenvalues']: try: import matplotlib.pyplot as plt fig = plt.figure() plt.scatter(eigenvalues.real, eigenvalues.imag) plt.show() plt.savefig(self.folder + 'eigenvalues.png', transparent=True, bbox_inches='tight') except ModuleNotFoundError: warnings.warn('Unable to import matplotlib, skipping plot') # Write dat files if self.settings['save_data']: if type(eigenvalues) == complex: np.savetxt(self.folder + "eigenvalues.dat", eigenvalues.view(float).reshape(-1, 2), fmt='%.12f', delimiter='\t', newline='\n') else: np.savetxt(self.folder + "eigenvalues.dat", eigenvalues.view(float), fmt='%.12f', delimiter='\t', newline='\n') np.savetxt(self.folder + "eigenvectors.dat", eigenvectors[:num_dof].real, fmt='%.12f', delimiter='\t', newline='\n') if not self.settings['use_undamped_modes']: np.savetxt(self.folder + 'frequencies.dat', freq_damped[:NumLambda], fmt='%e', delimiter='\t', newline='\n') else: np.savetxt(self.folder + 'frequencies.dat', freq_natural[:NumLambda], fmt='%e', delimiter='\t', newline='\n') np.savetxt(self.filename_damp, damping[:NumLambda], fmt='%e', delimiter='\t', newline='\n') # Write vtk if self.settings['write_modes_vtk']: try: self.data.aero except AttributeError: warnings.warn('No aerodynamic model found - unable to project the mode onto aerodynamic grid') else: modalutils.write_modes_vtk( self.data, eigenvectors[:num_dof], NumLambda, self.filename_shapes, self.settings['max_rotation_deg'], self.settings['max_displacement'], ts=self.settings['use_custom_timestep']) outdict = dict() if self.settings['use_undamped_modes']: outdict['modes'] = 'undamped' outdict['freq_natural'] = freq_natural if not zero_FullCglobal: outdict['warning'] =\ 'system with damping: mode shapes and natural frequencies do not account for damping!' else: outdict['modes'] = 'damped' outdict['freq_damped'] = freq_damped outdict['freq_natural'] = freq_natural outdict['damping'] = damping outdict['eigenvalues'] = eigenvalues outdict['eigenvectors'] = eigenvectors if Ccut is not None: outdict['Ccut'] = Ccut if Kin_damp is not None: outdict['Kin_damp'] = Kin_damp if not self.settings['use_undamped_modes']: outdict['eigenvectors_left'] = eigenvectors_left if cg is not None: outdict['cg'] = cg outdict['M'] = FullMglobal outdict['C'] = FullCglobal outdict['K'] = FullKglobal if t_pa is not None: outdict['t_pa'] = t_pa outdict['r_pa'] = r_pa self.data.structure.timestep_info[self.data.ts].modal = outdict if self.settings['print_info']: if self.settings['use_undamped_modes']: self.eigenvalue_table.print_evals(np.sqrt(eigenvalues[:NumLambda])*1j) else: self.eigenvalue_table.print_evals(eigenvalues[:NumLambda]) self.eigenvalue_table.close_file() return self.data def scale_modes_unit_mass_matrix(self, eigenvectors, FullMglobal, eigenvectors_left=None): if self.settings['use_undamped_modes']: # mass normalise (diagonalises M and K) eigenvectors = modalutils.scale_mass_normalised_modes(eigenvectors, FullMglobal) else: # unit normalise (diagonalises A) if not self.rigid_body_motion: for ii in range(eigenvectors.shape[1]): # Issue - dot product = 0 when you have arbitrary damping fact = 1./np.sqrt(np.dot(eigenvectors_left[:, ii], eigenvectors[:, ii])) eigenvectors_left[:, ii] = fact*eigenvectors_left[:, ii] eigenvectors[:, ii] = fact*eigenvectors[:, ii] return eigenvectors, eigenvectors_left def free_free_modes(self, phi, M): r""" Warning: This function is deprecated. See :func:`~sharpy.structure.utils.modalutils.free_modes_principal_axes` for a transformation to the CG and with respect to the principal axes of inertia. Returns the rigid body modes defined with respect to the centre of gravity The transformation from the modes defined at the FoR A origin, :math:`\boldsymbol{\Phi}`, to the modes defined using the centre of gravity as a reference is .. math:: \boldsymbol{\Phi}_{rr,CG}|_{TRA} = \boldsymbol{\Phi}_{RR}|_{TRA} + \tilde{\mathbf{r}}_{CG} \boldsymbol{\Phi}_{RR}|_{ROT} .. math:: \boldsymbol{\Phi}_{rr,CG}|_{ROT} = \boldsymbol{\Phi}_{RR}|_{ROT} Returns: (np.array): Transformed eigenvectors """ # NG - 26/7/19 This is the transformation being performed by K_vec # Leaving this here for now in case it becomes necessary # .. math:: \boldsymbol{\Phi}_{ss,CG}|_{TRA} = \boldsymbol{\Phi}_{SS}|_{TRA} +\boldsymbol{\Phi}_{RS}|_{TRA} - # \tilde{\mathbf{r}}_{A}\boldsymbol{\Phi}_{RS}|_{ROT} # # .. math:: \boldsymbol{\Phi}_{ss,CG}|_{ROT} = \boldsymbol{\Phi}_{SS}|_{ROT} # + (\mathbf{T}(\boldsymbol{\Psi})^\top)^{-1}\boldsymbol{\Phi}_{RS}|_{ROT} warnings.warn('This function is deprecated. See sharpy.structure.utils.modalutils.free_modes_principal_axes', category=DeprecationWarning) if not self.rigid_body_motion: warnings.warn('No rigid body modes to transform because the structure is clamped') return phi else: pos = self.data.structure.timestep_info[self.data.ts].pos r_cg = modalutils.cg(M) jj = 0 K_vec = np.zeros((phi.shape[0], phi.shape[0])) jj_for_vel = range(self.data.structure.num_dof.value, self.data.structure.num_dof.value + 3) jj_for_rot = range(self.data.structure.num_dof.value + 3, self.data.structure.num_dof.value + 6) for node_glob in range(self.data.structure.num_node): ### detect bc at node (and no. of dofs) bc_here = self.data.structure.boundary_conditions[node_glob] if bc_here == 1: # clamp (only rigid-body) continue elif bc_here == -1 or bc_here == 0: # (rigid+flex body) dofs_here = 6 jj_tra = 6 * self.data.structure.vdof[node_glob] + np.array([0, 1, 2], dtype=int) jj_rot = 6 * self.data.structure.vdof[node_glob] + np.array([3, 4, 5], dtype=int) else: raise NameError('Invalid boundary condition (%d) at node %d!' \ % (bc_here, node_glob)) jj += dofs_here ee, node_loc = self.data.structure.node_master_elem[node_glob, :] psi = self.data.structure.timestep_info[self.data.ts].psi[ee, node_loc, :] Ra = pos[node_glob, :] # in A FoR with respect to G K_vec[np.ix_(jj_tra, jj_tra)] += np.eye(3) K_vec[np.ix_(jj_tra, jj_for_vel)] += np.eye(3) K_vec[np.ix_(jj_tra, jj_for_rot)] -= algebra.skew(Ra) K_vec[np.ix_(jj_rot, jj_rot)] += np.eye(3) K_vec[np.ix_(jj_rot, jj_for_rot)] += np.linalg.inv(algebra.crv2tan(psi).T) # Rigid-Rigid modes transform Krr = np.eye(10) Krr[np.ix_([0, 1, 2], [3, 4, 5])] += algebra.skew(r_cg) # Assemble transformed modes phirr = Krr.dot(phi[-10:, :10]) # Get rigid body modes to be positive in translation and rotation for i in range(10): ind = np.argmax(np.abs(phirr[:, i])) phirr[:, i] = np.sign(phirr[ind, i]) * phirr[:, i] # NG - 26/7/19 - Transformation of the rigid part of the elastic modes ended up not being necessary but leaving # here in case it becomes useful in the future phit = np.block([np.zeros((phi.shape[0], 10)), phi[:, 10:]]) phit[-10:, :10] = phirr return phit ================================================ FILE: sharpy/solvers/noaero.py ================================================ import sharpy.utils.settings as settings_utils from sharpy.utils.solver_interface import solver, BaseSolver @solver class NoAero(BaseSolver): """ Skip UVLM evaluation Solver to be used with either :class:`~sharpy.solvers.staticcoupled.StaticCoupled` or :class:`~sharpy.solvers.dynamiccoupled.DynamicCoupled` when aerodynamics are not of interest. An example would be running a structural-only simulation where you would like to keep an aerodynamic grid for visualisation purposes. """ solver_id = 'NoAero' solver_classification = 'Aero' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['dt'] = 'float' settings_default['dt'] = 0.1 settings_description['dt'] = 'Time step' settings_types['update_grid'] = 'bool' settings_default['update_grid'] = True settings_description['update_grid'] = 'Update aerodynamic grid as the structure deforms.' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, no_ctype=True) if len(self.data.aero.timestep_info) == 0: # initialise with zero timestep for static sims self.update_step() def run(self, **kwargs): aero_tstep = settings_utils.set_value_or_default(kwargs, 'aero_step', self.data.aero.timestep_info[-1]) dt = settings_utils.set_value_or_default(kwargs, 'dt', self.settings['dt']) # generate the wake because the solid shape might change self.data.aero.wake_shape_generator.generate({'zeta': aero_tstep.zeta, 'zeta_star': aero_tstep.zeta_star, 'gamma': aero_tstep.gamma, 'gamma_star': aero_tstep.gamma_star, 'dist_to_orig': aero_tstep.dist_to_orig}) return self.data def add_step(self): self.data.aero.add_timestep() def update_grid(self, beam): # called by DynamicCoupled if self.settings['update_grid']: self.data.aero.generate_zeta(beam, self.data.aero.aero_settings, -1, beam_ts=-1) def update_custom_grid(self, structure_tstep, aero_tstep, nl_body_tstep=None): # called by DynamicCoupled if self.settings['update_grid']: self.data.aero.generate_zeta_timestep_info(structure_tstep, aero_tstep, self.data.structure, self.data.aero.aero_settings, dt=None) def update_step(self): # called by StaticCoupled if self.settings['update_grid']: self.data.aero.generate_zeta(self.data.structure, self.data.aero.aero_settings, self.data.ts) else: self.add_step() def next_step(self): # called by StaticCoupled self.add_step() ================================================ FILE: sharpy/solvers/nonliftingbodygridloader.py ================================================ from sharpy.utils.solver_interface import solver import sharpy.aero.models.nonliftingbodygrid as nonliftingbodygrid import sharpy.utils.settings as settings_utils from sharpy.solvers.gridloader import GridLoader @solver class NonliftingbodygridLoader(GridLoader): """ ``NonliftingbodygridLoader`` class, inherited from ``GridLoader`` Generates aerodynamic grid for nonlifting bodies based on the input data Args: data (PreSharpy): ``ProblemData`` class structure Attributes: settings (dict): Name-value pair of the settings employed by the aerodynamic solver settings_types (dict): Acceptable types for the values in ``settings`` settings_default (dict): Name-value pair of default values for the aerodynamic settings data (ProblemData): class structure file_name (str): name of the ``.nonlifting_body.h5`` HDF5 file aero: empty attribute aero_data_dict (dict): key-value pairs of aerodynamic data """ solver_id = 'NonliftingbodygridLoader' solver_classification = 'loader' def __init__(self): super().__init__ self.file_name = '.nonlifting_body.h5' # nonlifting_body storage self.nonlifting_body = None def run(self, **kwargs): self.data.nonlifting_body = nonliftingbodygrid.NonliftingBodyGrid() self.data.nonlifting_body.generate(self.data_dict, self.data.structure, self.settings, self.data.ts) return self.data ================================================ FILE: sharpy/solvers/nonlineardynamic.py ================================================ """ @modified Alfonso del Carre """ import sharpy.structure.utils.xbeamlib as xbeamlib from sharpy.utils.settings import str2bool from sharpy.utils.solver_interface import solver, solver_from_string import sharpy.utils.settings as settings_utils import sharpy.utils.cout_utils as cout _BaseStructural = solver_from_string('_BaseStructural') @solver class NonLinearDynamic(_BaseStructural): """ Structural solver used for the dynamic simulation of free-flying structures. This solver provides an interface to the structural library (``xbeam``) and updates the structural parameters for every time step of the simulation. This solver is called as part of a standalone structural simulation. """ solver_id = 'NonLinearDynamic' solver_classification = 'structural' settings_types = _BaseStructural.settings_types.copy() settings_default = _BaseStructural.settings_default.copy() settings_description = _BaseStructural.settings_description.copy() settings_types['prescribed_motion'] = 'bool' settings_default['prescribed_motion'] = None settings_types['gravity_dir'] = 'list(float)' settings_default['gravity_dir'] = [0, 0, 1] settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None def initialise(self, data, restart=False): self.data = data self.settings = data.settings[self.solver_id] settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) # load info from dyn dictionary self.data.structure.add_unsteady_information(self.data.structure.dyn_dict, self.settings['num_steps']) # allocate timestep_info for i in range(self.settings['num_steps']): self.data.structure.add_timestep(self.data.structure.timestep_info) if i>0: self.data.structure.timestep_info[i].unsteady_applied_forces[:] = self.data.structure.dynamic_input[i - 1]['dynamic_forces'] self.data.structure.timestep_info[i].steady_applied_forces[:] = self.data.structure.ini_info.steady_applied_forces def run(self, **kwargs): prescribed_motion = False try: prescribed_motion = self.settings['prescribed_motion'] except KeyError: pass if prescribed_motion is True: cout.cout_wrap('Running non linear dynamic solver...', 2) xbeamlib.cbeam3_solv_nlndyn(self.data.structure, self.settings) else: cout.cout_wrap('Running non linear dynamic solver with RB...', 2) xbeamlib.xbeam_solv_couplednlndyn(self.data.structure, self.settings) self.data.ts = self.settings['num_steps'] cout.cout_wrap('...Finished', 2) return self.data ================================================ FILE: sharpy/solvers/nonlineardynamiccoupledstep.py ================================================ """ @modified Alfonso del Carre """ import numpy as np import sharpy.structure.utils.xbeamlib as xbeamlib from sharpy.utils.settings import str2bool from sharpy.utils.solver_interface import solver, BaseSolver, solver_from_string import sharpy.utils.settings as settings_utils _BaseStructural = solver_from_string('_BaseStructural') @solver class NonLinearDynamicCoupledStep(_BaseStructural): """ Structural solver used for the dynamic simulation of free-flying structures. This solver provides an interface to the structural library (``xbeam``) and updates the structural parameters for every k-th step in the FSI iteration. This solver can be called as part of a standalone structural simulation or as the structural solver of a coupled aeroelastic simulation. """ solver_id = 'NonLinearDynamicCoupledStep' solver_classification = 'structural' settings_types = _BaseStructural.settings_types.copy() settings_default = _BaseStructural.settings_default.copy() settings_description = _BaseStructural.settings_description.copy() settings_types['balancing'] = 'bool' settings_default['balancing'] = False # initial speed direction is given in inertial FOR!!! settings_types['initial_velocity_direction'] = 'list(float)' settings_default['initial_velocity_direction'] = [-1.0, 0.0, 0.0] settings_description['initial_velocity_direction'] = 'Initial velocity of the reference node given in the inertial FOR' settings_types['initial_velocity'] = 'float' settings_default['initial_velocity'] = 0 settings_description['initial_velocity'] = 'Initial velocity magnitude of the reference node' settings_types['relaxation_factor'] = 'float' settings_default['relaxation_factor'] = 0.3 settings_description['relaxation factor'] = 'Relaxation factor' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) # load info from dyn dictionary self.data.structure.add_unsteady_information(self.data.structure.dyn_dict, self.settings['num_steps']) # add initial speed to RBM if self.settings['initial_velocity']: new_direction = np.dot(self.data.structure.timestep_info[-1].cag(), self.settings['initial_velocity_direction']) self.data.structure.timestep_info[-1].for_vel[0:3] = new_direction*self.settings['initial_velocity'] # generate q, dqdt and dqddt xbeamlib.xbeam_solv_disp2state(self.data.structure, self.data.structure.timestep_info[-1]) def run(self, **kwargs): structural_step = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) dt= settings_utils.set_value_or_default(kwargs, 'dt', self.settings['dt']) xbeamlib.xbeam_step_couplednlndyn(self.data.structure, self.settings, self.data.ts, structural_step, dt=dt) self.extract_resultants(structural_step) self.data.structure.integrate_position(structural_step, dt) return self.data def add_step(self): self.data.structure.next_step() def next_step(self): pass def extract_resultants(self, tstep=None): if tstep is None: tstep = self.data.structure.timestep_info[self.data.ts] steady, unsteady, grav = tstep.extract_resultants(self.data.structure, force_type=['steady', 'unsteady', 'grav']) totals = steady + unsteady + grav return totals[0:3], totals[3:6] ================================================ FILE: sharpy/solvers/nonlineardynamicmultibody.py ================================================ import ctypes as ct import numpy as np import os from sharpy.utils.solver_interface import solver, BaseSolver, solver_from_string import sharpy.utils.settings as settings_utils import sharpy.utils.solver_interface as solver_interface import sharpy.utils.cout_utils as cout import sharpy.structure.utils.xbeamlib as xbeamlib import sharpy.utils.multibody as mb import sharpy.utils.algebra as algebra import sharpy.structure.utils.lagrangeconstraints as lagrangeconstraints import sharpy.utils.exceptions as exc _BaseStructural = solver_from_string('_BaseStructural') @solver class NonLinearDynamicMultibody(_BaseStructural): """ Nonlinear dynamic multibody Nonlinear dynamic step solver for multibody structures. """ solver_id = 'NonLinearDynamicMultibody' solver_classification = 'structural' settings_types = _BaseStructural.settings_types.copy() settings_default = _BaseStructural.settings_default.copy() settings_description = _BaseStructural.settings_description.copy() settings_options = dict() settings_types['time_integrator'] = 'str' settings_default['time_integrator'] = 'NewmarkBeta' settings_description['time_integrator'] = 'Method to perform time integration' settings_options['time_integrator'] = ['NewmarkBeta', 'GeneralisedAlpha'] settings_types['time_integrator_settings'] = 'dict' settings_default['time_integrator_settings'] = dict() settings_description['time_integrator_settings'] = 'Settings for the time integrator' settings_types['write_lm'] = 'bool' settings_default['write_lm'] = False settings_description['write_lm'] = 'Write lagrange multipliers to file' settings_types['relax_factor_lm'] = 'float' settings_default['relax_factor_lm'] = 0. settings_description['relax_factor_lm'] = 'Relaxation factor for Lagrange Multipliers. 0 no relaxation. 1 full relaxation' settings_types['allow_skip_step'] = 'bool' settings_default['allow_skip_step'] = False settings_description['allow_skip_step'] = 'Allow skip step when NaN is found while solving the system' settings_types['rigid_bodies'] = 'bool' settings_default['rigid_bodies'] = False settings_description['rigid_bodies'] = 'Set to zero the changes in flexible degrees of freedom (not very efficient)' settings_types['zero_ini_dot_ddot'] = 'bool' settings_default['zero_ini_dot_ddot'] = False settings_description['zero_ini_dot_ddot'] = 'Set to zero the position and crv derivatives at the first time step' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None # Total number of unknowns in the Multybody sistem self.sys_size = None # Total number of equations associated to the Lagrange multipliers self.lc_list = None self.num_LM_eq = None self.Lambda = None self.Lambda_dot = None self.Lambda_ddot = None self.gamma = None self.beta = None self.prev_Dq = None self.out_files = None # dict: containing output_variable:file_path if desired to write output def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, self.settings_options, no_ctype=True) # load info from dyn dictionary self.data.structure.add_unsteady_information( self.data.structure.dyn_dict, self.settings['num_steps']) # Define the number of equations self.lc_list = lagrangeconstraints.initialize_constraints(self.data.structure.ini_mb_dict) self.num_LM_eq = lagrangeconstraints.define_num_LM_eq(self.lc_list) self.Lambda = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') self.Lambda_dot = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') self.Lambda_ddot = np.zeros((self.num_LM_eq,), dtype=ct.c_double, order='F') if self.settings['write_lm']: dire = self.data.output_folder + '/NonLinearDynamicMultibody/' if not os.path.isdir(dire): os.makedirs(dire) self.out_files = {'lambda': dire + 'lambda.dat', 'lambda_dot': dire + 'lambda_dot.dat', 'lambda_ddot': dire + 'lambda_ddot.dat', 'cond_number': dire + 'cond_num.dat'} # clean up files for file in self.out_files.values(): if os.path.isfile(file): os.remove(file) # Define the number of dofs self.define_sys_size() self.prev_Dq = np.zeros((self.sys_size + self.num_LM_eq)) self.settings['time_integrator_settings']['sys_size'] = self.sys_size self.settings['time_integrator_settings']['num_LM_eq'] = self.num_LM_eq # Initialise time integrator if not restart: self.time_integrator = solver_interface.initialise_solver( self.settings['time_integrator']) self.time_integrator.initialise( self.data, self.settings['time_integrator_settings'], restart=restart) def add_step(self): self.data.structure.next_step() def next_step(self): pass def define_sys_size(self): """ This function defines the number of degrees of freedom in a multibody systems Each body contributes with ``num_dof`` degrees of freedom and 10 more if the associated local FoR can move or has Lagrange Constraints associated """ MBdict = self.data.structure.ini_mb_dict self.sys_size = self.data.structure.num_dof.value for ibody in range(self.data.structure.num_bodies): if (MBdict['body_%02d' % ibody]['FoR_movement'] == 'free'): self.sys_size += 10 def define_rigid_dofs(self, MB_beam): self.n_rigid_dofs = 0 self.rigid_dofs = [] first_dof = 0 for ibody in range(len(MB_beam)): last_dof = first_dof + MB_beam[ibody].num_dof.value if MB_beam[ibody].FoR_movement == 'free': self.n_rigid_dofs += 10 self.rigid_dofs += (np.arange(10, dtype=int) + last_dof).tolist() last_dof += 10 first_dof = last_dof + 0 def assembly_MB_eq_system(self, MB_beam, MB_tstep, ts, dt, Lambda, Lambda_dot, MBdict): r""" This function generates the matrix and vector associated to the linear system to solve a structural iteration It usses a Newmark-beta scheme for time integration. Being M, C and K the mass, damping and stiffness matrices of the system: .. math:: MB_Asys = MB_K + MB_C \frac{\gamma}{\beta dt} + \frac{1}{\beta dt^2} MB_M Args: MB_beam (list(:class:`~sharpy.structure.models.beam.Beam`)): each entry represents a body MB_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body ts (int): Time step number dt(int): time step Lambda (np.ndarray): Lagrange Multipliers array Lambda_dot (np.ndarray): Time derivarive of ``Lambda`` MBdict (dict): Dictionary including the multibody information Returns: MB_Asys (np.ndarray): Matrix of the systems of equations MB_Q (np.ndarray): Vector of the systems of equations """ MB_M = np.zeros((self.sys_size, self.sys_size), dtype=ct.c_double, order='F') MB_C = np.zeros((self.sys_size, self.sys_size), dtype=ct.c_double, order='F') MB_K = np.zeros((self.sys_size, self.sys_size), dtype=ct.c_double, order='F') MB_Q = np.zeros((self.sys_size,), dtype=ct.c_double, order='F') first_dof = 0 last_dof = 0 # Loop through the different bodies for ibody in range(len(MB_beam)): # Initialize matrices M = None C = None K = None Q = None # Generate the matrices for each body if MB_beam[ibody].FoR_movement == 'prescribed': last_dof = first_dof + MB_beam[ibody].num_dof.value M, C, K, Q = xbeamlib.cbeam3_asbly_dynamic(MB_beam[ibody], MB_tstep[ibody], self.settings) elif MB_beam[ibody].FoR_movement == 'free': last_dof = first_dof + MB_beam[ibody].num_dof.value + 10 M, C, K, Q = xbeamlib.xbeam3_asbly_dynamic(MB_beam[ibody], MB_tstep[ibody], self.settings) ############### Assembly into the global matrices # Flexible and RBM contribution to Asys MB_M[first_dof:last_dof, first_dof:last_dof] = M.astype(dtype=ct.c_double, copy=True, order='F') MB_C[first_dof:last_dof, first_dof:last_dof] = C.astype(dtype=ct.c_double, copy=True, order='F') MB_K[first_dof:last_dof, first_dof:last_dof] = K.astype(dtype=ct.c_double, copy=True, order='F') #Q MB_Q[first_dof:last_dof] = Q first_dof = last_dof # Define the number of equations # Generate matrices associated to Lagrange multipliers LM_C, LM_K, LM_Q = lagrangeconstraints.generate_lagrange_matrix( self.lc_list, MB_beam, MB_tstep, ts, self.num_LM_eq, self.sys_size, dt, Lambda, Lambda_dot, "dynamic") # Include the matrices associated to Lagrange Multipliers MB_C += LM_C[:self.sys_size, :self.sys_size] MB_K += LM_K[:self.sys_size, :self.sys_size] MB_Q += LM_Q[:self.sys_size] # Only working for non-holonomic constratints kBnh = LM_C[self.sys_size:, :self.sys_size] strict_LM_Q = LM_Q[self.sys_size:] return MB_M, MB_C, MB_K, MB_Q, kBnh, strict_LM_Q def integrate_position(self, MB_beam, MB_tstep, dt): """ This function integrates the position of each local A FoR after the structural iteration has been solved. It uses a Newmark-beta approximation. Args: MB_beam (list(:class:`~sharpy.structure.models.beam.Beam`)): each entry represents a body MB_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body dt(int): time step """ vel = np.zeros((6,),) acc = np.zeros((6,),) for ibody in range(0, len(MB_tstep)): # I think this is the right way to do it, but to make it match the rest I change it temporally if True: acc[0:3] = (0.5-self.beta)*np.dot(MB_beam[ibody].timestep_info.cga(),MB_beam[ibody].timestep_info.for_acc[0:3])+self.beta*np.dot(MB_tstep[ibody].cga(),MB_tstep[ibody].for_acc[0:3]) vel[0:3] = np.dot(MB_beam[ibody].timestep_info.cga(),MB_beam[ibody].timestep_info.for_vel[0:3]) MB_tstep[ibody].for_pos[0:3] += dt*(vel[0:3] + dt*acc[0:3]) else: MB_tstep[ibody].for_pos[0:3] += dt*np.dot(MB_tstep[ibody].cga(),MB_tstep[ibody].for_vel[0:3]) def extract_resultants(self, tstep): # TODO: code return np.zeros((3)), np.zeros((3)) def compute_forces_constraints(self, MB_beam, MB_tstep, ts, dt, Lambda, Lambda_dot): """ This function computes the forces generated at Lagrange Constraints Args: MB_beam (list(:class:`~sharpy.structure.models.beam.Beam`)): each entry represents a body MB_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body ts (int): Time step number dt(float): Time step increment Lambda (np.ndarray): Lagrange Multipliers array Lambda_dot (np.ndarray): Time derivarive of ``Lambda`` Warning: This function is underdevelopment and not fully functional """ try: self.lc_list[0] except IndexError: return # TODO the output of this routine is wrong. check at some point. LM_C, LM_K, LM_Q = lagrangeconstraints.generate_lagrange_matrix(self.lc_list, MB_beam, MB_tstep, ts, self.num_LM_eq, self.sys_size, dt, Lambda, Lambda_dot, "dynamic") F = -np.dot(LM_C[:, -self.num_LM_eq:], Lambda_dot) - np.dot(LM_K[:, -self.num_LM_eq:], Lambda) first_dof = 0 for ibody in range(len(MB_beam)): # Forces associated to nodes body_numdof = MB_beam[ibody].num_dof.value body_freenodes = np.sum(MB_beam[ibody].vdof > -1) last_dof = first_dof + body_numdof MB_tstep[ibody].forces_constraints_nodes[(MB_beam[ibody].vdof > -1), :] = F[first_dof:last_dof].reshape(body_freenodes, 6, order='C') # Forces associated to the frame of reference if MB_beam[ibody].FoR_movement == 'free': # TODO: How are the forces in the quaternion equation interpreted? MB_tstep[ibody].forces_constraints_FoR[ibody, :] = F[last_dof:last_dof+10] last_dof += 10 first_dof = last_dof # TODO: right now, these forces are only used as an output, they are not read when the multibody is splitted def write_lm_cond_num(self, iteration, Lambda, Lambda_dot, Lambda_ddot, cond_num, cond_num_lm): # Maybe not the most efficient way to output this, as files are opened and closed every time data is written # However, containing the writing in the with statement prevents from files remaining open in the previous # implementation out_data = {'lambda': Lambda, 'lambda_dot': Lambda_dot, 'lambda_ddot': Lambda_ddot} for out_var, data in out_data.items(): file_name = self.out_files[out_var] with open(file_name, 'a') as fid: fid.write(f'{self.data.ts:g} {iteration:g} ') for ilm in range(self.num_LM_eq): fid.write(f'{data[ilm]} ') fid.write(f'\n') with open(self.out_files['cond_number'], 'a') as fid: fid.write(f'{self.data.ts:g} {iteration:g} ') fid.write(f'{cond_num:e} {cond_num_lm:e} \n') def run(self, **kwargs): structural_step = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) dt= settings_utils.set_value_or_default(kwargs, 'dt', self.settings['dt']) if structural_step.mb_dict is not None: MBdict = structural_step.mb_dict else: MBdict = self.data.structure.ini_mb_dict MB_beam, MB_tstep = mb.split_multibody( self.data.structure, structural_step, MBdict, self.data.ts) self.define_rigid_dofs(MB_beam) num_LM_eq = self.num_LM_eq if self.data.ts == 1 and self.settings['zero_ini_dot_ddot']: for ibody in range(len(MB_tstep)): MB_beam[ibody].ini_info.pos_dot *= 0. MB_beam[ibody].ini_info.pos_ddot *= 0. MB_beam[ibody].ini_info.psi_dot *= 0. MB_beam[ibody].ini_info.psi_dot_local *= 0. MB_beam[ibody].ini_info.psi_ddot *= 0. MB_tstep[ibody].pos_dot *= 0. MB_tstep[ibody].pos_ddot *= 0. MB_tstep[ibody].psi_dot *= 0. MB_tstep[ibody].psi_dot_local *= 0. MB_tstep[ibody].psi_ddot *= 0. # Initialize # TODO: i belive this can move into disp_and_accel2 state as self.Lambda, self.Lambda_dot if not num_LM_eq == 0: Lambda = self.Lambda.astype(dtype=ct.c_double, copy=True, order='F') Lambda_dot = self.Lambda_dot.astype(dtype=ct.c_double, copy=True, order='F') Lambda_ddot = self.Lambda_ddot.astype(dtype=ct.c_double, copy=True, order='F') else: Lambda = np.zeros((1,)) Lambda_dot = np.zeros((1,)) Lambda_ddot = np.zeros((1,)) # Predictor step q, dqdt, dqddt = mb.disp_and_accel2state(MB_beam, MB_tstep, Lambda, Lambda_dot, self.sys_size, num_LM_eq) self.time_integrator.predictor(q, dqdt, dqddt) # Reference residuals old_Dq = 1.0 LM_old_Dq = 1.0 skip_step = False for iteration in range(self.settings['max_iterations']): # Check if the maximum of iterations has been reached if iteration == self.settings['max_iterations'] - 1: error = ('Solver did not converge in %d iterations.\n res = %e \n LM_res = %e' % (iteration, res, LM_res)) raise exc.NotConvergedSolver(error) # Update positions and velocities Lambda, Lambda_dot = mb.state2disp_and_accel(q, dqdt, dqddt, MB_beam, MB_tstep, num_LM_eq) if self.settings['write_lm'] and iteration: self.write_lm_cond_num(iteration, Lambda, Lambda_dot, Lambda_ddot, cond_num, cond_num_lm) MB_M, MB_C, MB_K, MB_Q, kBnh, LM_Q = self.assembly_MB_eq_system(MB_beam, MB_tstep, self.data.ts, dt, Lambda, Lambda_dot, MBdict) Asys, Q = self.time_integrator.build_matrix(MB_M, MB_C, MB_K, MB_Q, kBnh, LM_Q) if self.settings['write_lm']: cond_num = np.linalg.cond(Asys[:self.sys_size, :self.sys_size]) cond_num_lm = np.linalg.cond(Asys) if self.settings['rigid_bodies']: rigid_LM_dofs = self.rigid_dofs + (np.arange(self.num_LM_eq, dtype=int) + self.sys_size).tolist() rigid_Asys = Asys[np.ix_(rigid_LM_dofs, rigid_LM_dofs)].copy() rigid_Q = Q[rigid_LM_dofs].copy() rigid_Dq = np.linalg.solve(rigid_Asys, -rigid_Q) Dq = np.zeros((self.sys_size + self.num_LM_eq)) Dq[rigid_LM_dofs] = rigid_Dq.copy() else: Dq = np.linalg.solve(Asys, -Q) # Relaxation relax_Dq = np.zeros_like(Dq) relax_Dq[:self.sys_size] = Dq[:self.sys_size].copy() relax_Dq[self.sys_size:] = ((1. - self.settings['relax_factor_lm'])*Dq[self.sys_size:] + self.settings['relax_factor_lm']*self.prev_Dq[self.sys_size:]) self.prev_Dq = Dq.copy() # Corrector step self.time_integrator.corrector(q, dqdt, dqddt, relax_Dq) # Reference values for convergence if iteration == 0: old_Dq = np.max(np.abs(Dq[0:self.sys_size])) if num_LM_eq: LM_old_Dq = np.max(np.abs(Dq[self.sys_size:self.sys_size+num_LM_eq])) else: LM_old_Dq = 0. # Change the reference values if old_Dq == 0: old_Dq = 1. if LM_old_Dq == 0: LM_old_Dq = 1. # Evaluate convergence res = np.max(np.abs(Dq[0:self.sys_size]))/old_Dq if np.isnan(res): if self.settings['allow_skip_step']: skip_step = True cout.cout_wrap("Skipping step", 3) break else: raise exc.NotConvergedSolver('Multibody Dq = NaN') if num_LM_eq: LM_res = np.max(np.abs(Dq[self.sys_size:self.sys_size+num_LM_eq]))/LM_old_Dq else: LM_res = 0.0 if (res < self.settings['min_delta']) and (LM_res < self.settings['min_delta']): break Lambda, Lambda_dot = mb.state2disp_and_accel(q, dqdt, dqddt, MB_beam, MB_tstep, num_LM_eq) if self.settings['write_lm']: self.write_lm_cond_num(iteration, Lambda, Lambda_dot, Lambda_ddot, cond_num, cond_num_lm) # end: comment time stepping if skip_step: # Use the original time step MB_beam, MB_tstep = mb.split_multibody( self.data.structure, structural_step, MBdict, self.data.ts) # Perform rigid body motions self.integrate_position(MB_beam, MB_tstep, dt) for ibody in range(0, len(MB_tstep)): Temp = np.linalg.inv(np.eye(4) + 0.25*algebra.quadskew(MB_tstep[ibody].for_vel[3:6])*dt) MB_tstep[ibody].quat = np.dot(Temp, np.dot(np.eye(4) - 0.25*algebra.quadskew(MB_tstep[ibody].for_vel[3:6])*dt, MB_tstep[ibody].quat)) # End of Newmark-beta iterations # self.integrate_position(MB_beam, MB_tstep, dt) lagrangeconstraints.postprocess(self.lc_list, MB_beam, MB_tstep, "dynamic") self.compute_forces_constraints(MB_beam, MB_tstep, self.data.ts, dt, Lambda, Lambda_dot) if self.settings['gravity_on']: for ibody in range(len(MB_beam)): xbeamlib.cbeam3_correct_gravity_forces(MB_beam[ibody], MB_tstep[ibody], self.settings) mb.merge_multibody(MB_tstep, MB_beam, self.data.structure, structural_step, MBdict, dt) self.Lambda = Lambda.astype(dtype=ct.c_double, copy=True, order='F') self.Lambda_dot = Lambda_dot.astype(dtype=ct.c_double, copy=True, order='F') self.Lambda_ddot = Lambda_ddot.astype(dtype=ct.c_double, copy=True, order='F') return self.data ================================================ FILE: sharpy/solvers/nonlineardynamicmultibodyjax.py ================================================ import numpy as np import typing from sharpy.utils.solver_interface import solver, solver_from_string import sharpy.utils.settings as settings_utils import sharpy.utils.solver_interface as solver_interface import sharpy.structure.utils.xbeamlib as xbeamlib import sharpy.utils.multibodyjax as mb import sharpy.structure.utils.lagrangeconstraintsjax as lagrangeconstraints _BaseStructural = solver_from_string('_BaseStructural') @solver class NonLinearDynamicMultibodyJAX(_BaseStructural): """ Nonlinear dynamic multibody Nonlinear dynamic step solver for multibody structures. """ solver_id = 'NonLinearDynamicMultibodyJAX' solver_classification = 'structural' settings_types = _BaseStructural.settings_types.copy() settings_default = _BaseStructural.settings_default.copy() settings_description = _BaseStructural.settings_description.copy() settings_options = dict() settings_types['time_integrator'] = 'str' settings_default['time_integrator'] = 'NewmarkBetaJAX' settings_description['time_integrator'] = 'Method to perform time integration' settings_options['time_integrator'] = ['NewmarkBetaJAX', 'GeneralisedAlphaJAX'] settings_types['time_integrator_settings'] = 'dict' settings_default['time_integrator_settings'] = dict() settings_description['time_integrator_settings'] = 'Settings for the time integrator' settings_types['jacobian_method'] = 'str' settings_default['jacobian_method'] = 'forward' settings_description['jacobian_method'] = 'Autodifferentiation method used to calculate system jacobians' settings_options['jacobian_method'] = ['forward', 'reverse'] settings_types['write_lm'] = 'bool' settings_default['write_lm'] = False settings_description['write_lm'] = 'Write lagrange multipliers to file' settings_types['relax_factor_lm'] = 'float' settings_default['relax_factor_lm'] = 0. settings_description['relax_factor_lm'] = ('Relaxation factor for Lagrange Multipliers. ' '0 no relaxation. 1 full relaxation') settings_types['allow_skip_step'] = 'bool' settings_default['allow_skip_step'] = False settings_description['allow_skip_step'] = 'Allow skip step when NaN is found while solving the system' settings_types['zero_ini_dot_ddot'] = 'bool' settings_default['zero_ini_dot_ddot'] = False settings_description['zero_ini_dot_ddot'] = 'Set to zero the position and crv derivatives at the first time step' settings_types['fix_prescribed_quat_ini'] = 'bool' settings_default['fix_prescribed_quat_ini'] = False settings_description['fix_prescribed_quat_ini'] = 'Set to initial the quaternion for prescibed bodies' # initial speed direction is given in inertial FOR!!! also in a lot of cases coincident with global A frame settings_types['initial_velocity_direction'] = 'list(float)' settings_default['initial_velocity_direction'] = [-1., 0., 0.] settings_description[ 'initial_velocity_direction'] = 'Initial velocity of the reference node given in the inertial FOR' settings_types['initial_velocity'] = 'float' settings_default['initial_velocity'] = 0 settings_description['initial_velocity'] = 'Initial velocity magnitude of the reference node' # restart sim after dynamictrim settings_types['dyn_trim'] = 'bool' settings_default['dyn_trim'] = False settings_description['dyn_trim'] = 'flag for dyntrim prior to dyncoup' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None self.sys_size: typing.Optional[int] = None # total number of unknowns in the system self.num_lm_tot: typing.Optional[int] = None # total number of LMs in the system self.num_eq_tot: typing.Optional[int] = None # Total number of equations associated to the Lagrange multipliers self.lc_list: typing.Optional[list[lagrangeconstraints.Constraint]] = None self.num_lm_eq: typing.Optional[int] = None self.lambda_h: typing.Optional[np.ndarray] = None self.lambda_n: typing.Optional[np.ndarray] = None # function called to generate contributions of all LMs to equations self.lc_all_run: typing.Optional[typing.Callable] = None self.gamma: typing.Optional[int] = None self.beta: typing.Optional[int] = None self.prev_dq: typing.Optional[np.ndarray] = None self.time_integrator = None self.out_files = None # dict: containing output_variable:file_path if desired to write output def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, self.settings_options, no_ctype=True) # load info from dyn dictionary self.data.structure.add_unsteady_information(self.data.structure.dyn_dict, self.settings['num_steps']) if self.settings['initial_velocity']: new_direction = (self.data.structure.timestep_info[-1].cag() @ self.settings['initial_velocity_direction'] * self.settings['initial_velocity']) self.data.structure.timestep_info[-1].for_vel[:3] = new_direction num_body = self.data.structure.timestep_info[0].mb_FoR_vel.shape[0] for ibody in range(num_body): self.data.structure.timestep_info[-1].mb_FoR_vel[ibody, :] \ = self.data.structure.timestep_info[-1].for_vel # find total number of LC equations dict_of_lc = lagrangeconstraints.DICT_OF_LC lc_cls_list: list[typing.Type[lagrangeconstraints.Constraint]] = [] lc_settings: list[dict] = [] self.num_lm_tot = 0 for i in range(self.data.structure.ini_mb_dict['num_constraints']): lc_settings.append(self.data.structure.ini_mb_dict[f'constraint_{i:02d}']) lc_cls_list.append(dict_of_lc[lc_settings[-1]['behaviour']]) self.num_lm_tot += lc_cls_list[-1].get_n_lm() # find total number of equations mb_dict = self.data.structure.ini_mb_dict self.sys_size = self.data.structure.num_dof.value for ibody in range(self.data.structure.num_bodies): if mb_dict[f'body_{ibody:02d}']['FoR_movement'] == 'free': self.sys_size += 10 self.num_eq_tot = self.sys_size + self.num_lm_tot i_start_lc = 0 self.lc_list = [] for i_lc, lc_cls in enumerate(lc_cls_list): self.lc_list.append(lc_cls(self, i_start_lc, lc_settings[i_lc])) i_start_lc += self.lc_list[-1].num_lm # create a single function for all constraints self.lc_all_run = lagrangeconstraints.combine_constraints(self.lc_list) # lambda values self.lambda_h = np.zeros(self.num_lm_tot) self.lambda_n = np.zeros(self.num_lm_tot) self.prev_dq = np.zeros(self.sys_size + self.num_lm_tot) try: self.num_lm_eq = self.lc_list[0].num_lm_tot except IndexError: self.num_lm_eq = 0 self.settings['time_integrator_settings']['sys_size'] = self.sys_size self.settings['time_integrator_settings']['num_LM_eq'] = self.num_lm_eq # Initialise time integrator if not restart: self.time_integrator = solver_interface.initialise_solver(self.settings['time_integrator']) self.time_integrator.initialise(self.data, self.settings['time_integrator_settings'], restart=restart) def assembly_mb_eq_system(self, mb_beam, mb_tstep, ts, dt, lambda_h, lambda_n, mb_dict, mb_prescribed_dict): """ This function generates the matrix and vector associated to the linear system to solve a structural iteration It usses a Newmark-beta scheme for time integration. Being M, C and K the mass, damping and stiffness matrices of the system: .. math:: MB_Asys = mb_k + mb_c \frac{\gamma}{\beta dt} + \frac{1}{\beta dt^2} mb_m Args: mb_beam (list(:class:`~sharpy.structure.models.beam.Beam`)): each entry represents a body mb_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body ts (int): Time step number dt(int): time step lambda_ (np.ndarray): Lagrange Multipliers array lambda_dot (np.ndarray): Time derivarive of ``Lambda`` mb_dict (dict): Dictionary including the multibody information Returns: MB_Asys (np.ndarray): Matrix of the systems of equations mb_q (np.ndarray): Vector of the systems of equations """ mb_m = np.zeros((self.num_eq_tot, self.num_eq_tot)) mb_c = np.zeros((self.num_eq_tot, self.num_eq_tot)) mb_k = np.zeros((self.num_eq_tot, self.num_eq_tot)) mb_rhs = np.zeros(self.num_eq_tot) first_dof = 0 for ibody in range(len(mb_beam)): # Generate the matrices for each body if mb_beam[ibody].FoR_movement in ('prescribed', 'prescribed_trim'): last_dof = first_dof + mb_beam[ibody].num_dof.value m, c, k, rhs = xbeamlib.cbeam3_asbly_dynamic(mb_beam[ibody], mb_tstep[ibody], self.settings) elif mb_beam[ibody].FoR_movement == 'free': last_dof = first_dof + mb_beam[ibody].num_dof.value + 10 m, c, k, rhs = xbeamlib.xbeam3_asbly_dynamic(mb_beam[ibody], mb_tstep[ibody], self.settings) else: raise KeyError(f"Body FoR movement {mb_beam[ibody].FoR_movement} is invalid") mb_m[first_dof:last_dof, first_dof:last_dof] = m mb_c[first_dof:last_dof, first_dof:last_dof] = c mb_k[first_dof:last_dof, first_dof:last_dof] = k mb_rhs[first_dof:last_dof] = rhs first_dof = last_dof q = np.hstack([beam.q for beam in mb_tstep]) q_dot = np.hstack([beam.dqdt for beam in mb_tstep]) u = [] u_dot = [] for i_lc in range(len(self.lc_list)): try: ctrl_id = self.lc_list[i_lc].settings['controller_id'].decode('UTF-8') u.append(mb_prescribed_dict[ctrl_id]['psi']) u_dot.append(mb_prescribed_dict[ctrl_id]['psi_dot']) except KeyError: u.append(None) u_dot.append(None) lc_c, lc_k, lc_rhs = self.call_lm_generate(q, q_dot, u, u_dot, lambda_h, lambda_n) mb_c += lc_c mb_k += lc_k mb_rhs += lc_rhs return mb_m, mb_c, mb_k, mb_rhs # added to make profiling easier def call_lm_generate(self, *args): return self.lc_all_run(*args) def integrate_position(self, mb_beam, mb_tstep, dt): """ This function integrates the position of each local A FoR after the structural iteration has been solved. It uses a Newmark-beta approximation. Args: mb_beam (list(:class:`~sharpy.structure.models.beam.Beam`)): each entry represents a body mb_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body dt(int): time step """ vel = np.zeros(6) acc = np.zeros(6) for ibody in range(len(mb_tstep)): acc[:3] = ((0.5 - self.beta) * mb_beam[ibody].timestep_info.cga() @ mb_beam[ibody].timestep_info.for_acc[:3] + self.beta * mb_tstep[ibody].cga() @ mb_tstep[ibody].for_acc[:3]) vel[:3] = mb_beam[ibody].timestep_info.cga() @ mb_beam[ibody].timestep_info.for_vel[:3] mb_tstep[ibody].for_pos[:3] += dt * (vel[:3] + dt * acc[:3]) def extract_resultants(self, tstep): if tstep is None: tstep = self.data.structure.timestep_info[self.data.ts] steady, unsteady, grav = tstep.extract_resultants(self.data.structure, force_type=['steady', 'unsteady', 'grav']) totals = steady + unsteady + grav return totals[:3], totals[3:6] def run(self, **kwargs): structural_step = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) dt = settings_utils.set_value_or_default(kwargs, 'dt', self.settings['dt']) if structural_step.mb_dict is not None: mb_dict = structural_step.mb_dict else: mb_dict = self.data.structure.ini_mb_dict mb_prescribed_dict = structural_step.mb_prescribed_dict mb_beam, mb_tstep = mb.split_multibody(self.data.structure, structural_step, mb_dict, self.data.ts) if self.data.ts == 1 and self.settings['zero_ini_dot_ddot']: for ibody in range(len(mb_tstep)): mb_beam[ibody].ini_info.pos_dot.fill(0.) mb_beam[ibody].ini_info.pos_ddot.fill(0.) mb_beam[ibody].ini_info.psi_dot.fill(0.) mb_beam[ibody].ini_info.psi_dot_local.fill(0.) mb_beam[ibody].ini_info.psi_ddot.fill(0.) mb_tstep[ibody].pos_dot.fill(0.) mb_tstep[ibody].pos_ddot.fill(0.) mb_tstep[ibody].psi_dot.fill(0.) mb_tstep[ibody].psi_dot_local.fill(0.) mb_tstep[ibody].psi_ddot.fill(0.) # Predictor step q, dqdt, dqddt = mb.disp_and_accel2state(mb_beam, mb_tstep, self.lambda_h, self.lambda_n, self.sys_size, self.num_lm_eq) self.time_integrator.predictor(q, dqdt, dqddt) res_sys = 0. res_lm = 0. for iteration in range(self.settings['max_iterations']): if iteration == self.settings['max_iterations'] - 1: # Check if the maximum of iterations has been reached print(f'Solver did not converge in {iteration} iterations.\n res_sys = {res_sys} \n res_lm = {res_lm}') break # Update positions and velocities lambda_h, lambda_n = mb.state2disp_and_accel(q, dqdt, dqddt, mb_beam, mb_tstep, self.num_lm_eq) mb_m, mb_c, mb_k, mb_rhs = self.assembly_mb_eq_system(mb_beam, mb_tstep, self.data.ts, dt, lambda_h, lambda_n, mb_dict, mb_prescribed_dict) a_sys = self.time_integrator.build_matrix(mb_m, mb_c, mb_k) dq = np.linalg.solve(a_sys, -mb_rhs) # Relaxation relax_dq = np.zeros_like(dq) relax_dq[:self.sys_size] = dq[:self.sys_size].copy() relax_dq[self.sys_size:] = ((1. - self.settings['relax_factor_lm']) * dq[self.sys_size:] + self.settings['relax_factor_lm'] * self.prev_dq[self.sys_size:]) self.prev_dq = dq.copy() # Corrector step self.time_integrator.corrector(q, dqdt, dqddt, relax_dq) res_sys = np.max(np.abs(dq[:self.sys_size])) try: res_lm = np.max(np.abs(dq[self.sys_size:])) except ValueError: res_lm = 0. if iteration > 0 and (res_sys < self.settings['min_delta']) and (res_lm < self.settings['min_delta']): break lambda_h, lambda_n = mb.state2disp_and_accel(q, dqdt, dqddt, mb_beam, mb_tstep, self.num_lm_eq) for lc in self.lc_list: lc.postprocess(mb_beam, mb_tstep) # End of Newmark-beta iterations if self.settings['gravity_on']: for ibody in range(len(mb_beam)): xbeamlib.cbeam3_correct_gravity_forces(mb_beam[ibody], mb_tstep[ibody], self.settings) mb.merge_multibody(mb_tstep, mb_beam, self.data.structure, structural_step, mb_dict, dt) self.lambda_h = lambda_h self.lambda_n = lambda_n self.data.Lambda = lambda_h self.data.Lambda_dot = lambda_n return self.data def add_step(self): self.data.structure.next_step() def next_step(self): raise NotImplementedError ================================================ FILE: sharpy/solvers/nonlineardynamicprescribedstep.py ================================================ """ @modified Alfonso del Carre """ import sharpy.structure.utils.xbeamlib as xbeamlib from sharpy.utils.solver_interface import solver, BaseSolver, solver_from_string import sharpy.utils.settings as settings_utils _BaseStructural = solver_from_string('_BaseStructural') @solver class NonLinearDynamicPrescribedStep(_BaseStructural): """ Structural solver used for the dynamic simulation of clamped structures or those subject to a prescribed motion. This solver provides an interface to the structural library (``xbeam``) and updates the structural parameters for every k-th step in the FSI iteration. This solver can be called as part of a standalone structural simulation or as the structural solver of a coupled aeroelastic simulation. """ solver_id = 'NonLinearDynamicPrescribedStep' solver_classification = 'structural' settings_types = _BaseStructural.settings_types.copy() settings_default = _BaseStructural.settings_default.copy() settings_description = _BaseStructural.settings_description.copy() settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) # load info from dyn dictionary self.data.structure.add_unsteady_information(self.data.structure.dyn_dict, self.settings['num_steps']) def run(self, **kwargs): structural_step = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) dt = settings_utils.set_value_or_default(kwargs, 'dt', self.settings['dt']) if self.data.ts > 0: try: structural_step.for_vel[:] = self.data.structure.dynamic_input[self.data.ts - 1]['for_vel'] structural_step.for_acc[:] = self.data.structure.dynamic_input[self.data.ts - 1]['for_acc'] except IndexError: pass xbeamlib.cbeam3_step_nlndyn(self.data.structure, self.settings, self.data.ts, structural_step, dt=dt) self.data.structure.integrate_position(structural_step, dt) return self.data def add_step(self): self.data.structure.next_step() def next_step(self): pass def extract_resultants(self, tstep=None): if tstep is None: tstep = self.data.structure.timestep_info[self.data.ts] steady, unsteady, grav = tstep.extract_resultants(self.data.structure, force_type=['steady', 'unsteady', 'grav']) totals = steady + unsteady + grav return totals[0:3], totals[3:6] def update(self, tstep=None): self.create_q_vector(tstep) def create_q_vector(self, tstep=None): import sharpy.structure.utils.xbeamlib as xb if tstep is None: tstep = self.data.structure.timestep_info[-1] xb.xbeam_solv_disp2state(self.data.structure, tstep) ================================================ FILE: sharpy/solvers/nonlinearstatic.py ================================================ import numpy as np import sharpy.structure.utils.xbeamlib as xbeamlib import sharpy.utils.settings as settings_utils from sharpy.utils.solver_interface import solver, BaseSolver, solver_from_string _BaseStructural = solver_from_string('_BaseStructural') @solver class NonLinearStatic(_BaseStructural): """ Structural solver used for the static simulation of free-flying structures. This solver provides an interface to the structural library (``xbeam``) and updates the structural parameters for every k-th step of the FSI iteration. This solver can be called as part of a standalone structural simulation or as the structural solver of a coupled static aeroelastic simulation. """ solver_id = 'NonLinearStatic' solver_classification = 'structural' # settings list settings_types = _BaseStructural.settings_types.copy() settings_default = _BaseStructural.settings_default.copy() settings_description = _BaseStructural.settings_description.copy() settings_types['initial_position'] = 'list(float)' settings_default['initial_position'] = np.array([0.0, 0.0, 0.0]) settings_types['initial_velocity'] = 'list(float)' settings_default['initial_velocity'] = np.array([0., 0., 0., 0., 0., 0.]) settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, no_ctype=True) def run(self, **kwargs): self.data.structure.timestep_info[self.data.ts].for_pos[0:3] = self.settings['initial_position'] self.data.structure.timestep_info[self.data.ts].for_vel = self.settings['initial_velocity'].copy() xbeamlib.cbeam3_solv_nlnstatic(self.data.structure, self.settings, self.data.ts) self.extract_resultants() return self.data def next_step(self): self.data.structure.next_step() def extract_resultants(self, tstep=None): if tstep is None: tstep = self.data.structure.timestep_info[self.data.ts] steady, grav = tstep.extract_resultants(self.data.structure, force_type=['steady', 'grav']) totals = steady + grav return totals[0:3], totals[3:6] def update(self, tstep=None): self.create_q_vector(tstep) def create_q_vector(self, tstep=None): import sharpy.structure.utils.xbeamlib as xb if tstep is None: tstep = self.data.structure.timestep_info[-1] xb.xbeam_solv_disp2state(self.data.structure, tstep) ================================================ FILE: sharpy/solvers/nostructural.py ================================================ import numpy as np import sharpy.utils.settings as settings_utils from sharpy.utils.solver_interface import solver, BaseSolver, solver_from_string _BaseStructural = solver_from_string('_BaseStructural') @solver class NoStructural(_BaseStructural): """ Structural solver used for the static simulation of free-flying structures. This solver provides an interface to the structural library (``xbeam``) and updates the structural parameters for every k-th step of the FSI iteration. This solver can be called as part of a standalone structural simulation or as the structural solver of a coupled static aeroelastic simulation. """ solver_id = 'NoStructural' solver_classification = 'structural' # settings list settings_types = _BaseStructural.settings_types.copy() settings_default = _BaseStructural.settings_default.copy() settings_description = _BaseStructural.settings_description.copy() settings_types['initial_position'] = 'list(float)' settings_default['initial_position'] = np.array([0.0, 0.0, 0.0]) settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) def run(self, **kwargs): self.data.structure.timestep_info[self.data.ts].for_pos[0:3] = self.settings['initial_position'] self.extract_resultants() return self.data def next_step(self): self.data.structure.next_step() def extract_resultants(self, tstep=None): if tstep is None: tstep = self.data.structure.timestep_info[self.data.ts] steady, grav = tstep.extract_resultants(self.data.structure, force_type=['steady', 'grav']) totals = steady + grav return totals[0:3], totals[3:6] def update(self, tstep=None): self.create_q_vector(tstep) def create_q_vector(self, tstep=None): import sharpy.structure.utils.xbeamlib as xb if tstep is None: tstep = self.data.structure.timestep_info[-1] xb.xbeam_solv_disp2state(self.data.structure, tstep) ================================================ FILE: sharpy/solvers/prescribeduvlm.py ================================================ import sharpy.utils.settings as settings_utils from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.solver_interface as solver_interface import sharpy.utils.cout_utils as cout from sharpy.utils.constants import vortex_radius_def @solver class PrescribedUvlm(BaseSolver): """ This class runs a prescribed rigid body motion simulation of a rigid aerodynamic body. """ solver_id = 'PrescribedUvlm' solver_classification = 'Aero' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Write status to screen' settings_types['structural_solver'] = 'str' settings_default['structural_solver'] = None settings_description['structural_solver'] = 'Structural solver to use in the coupled simulation' settings_types['structural_solver_settings'] = 'dict' settings_default['structural_solver_settings'] = None settings_description['structural_solver_settings'] = 'Dictionary of settings for the structural solver' settings_types['aero_solver'] = 'str' settings_default['aero_solver'] = None settings_description['aero_solver'] = 'Aerodynamic solver to use in the coupled simulation' settings_types['aero_solver_settings'] = 'dict' settings_default['aero_solver_settings'] = None settings_description['aero_solver_settings'] = 'Dictionary of settings for the aerodynamic solver' settings_types['n_time_steps'] = 'int' settings_default['n_time_steps'] = None settings_description['n_time_steps'] = 'Number of time steps for the simulation' settings_types['dt'] = 'float' settings_default['dt'] = None settings_description['dt'] = 'Time step' settings_types['postprocessors'] = 'list(str)' settings_default['postprocessors'] = list() settings_description['postprocessors'] = 'List of the postprocessors to run at the end of every time step' settings_types['postprocessors_settings'] = 'dict' settings_default['postprocessors_settings'] = dict() settings_description['postprocessors_settings'] = 'Dictionary with the applicable settings for every ``postprocessor``. Every ``postprocessor`` needs its entry, even if empty' settings_types['cfl1'] = 'bool' settings_default['cfl1'] = True settings_description['cfl1'] = 'If it is ``True``, it assumes that the discretisation complies with CFL=1' settings_types['vortex_radius'] = 'float' settings_default['vortex_radius'] = vortex_radius_def settings_description['vortex_radius'] = 'Distance between points below which induction is not computed' settings_types['vortex_radius_wake_ind'] = 'float' settings_default['vortex_radius_wake_ind'] = vortex_radius_def settings_description['vortex_radius_wake_ind'] = 'Distance between points below which induction is not computed in the wake convection' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None self.structural_solver = None self.aero_solver = None self.previous_force = None self.dt = 0. self.postprocessors = dict() self.with_postprocessors = False def initialise(self, data, restart=False): self.data = data self.settings = data.settings[self.solver_id] settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) self.dt = self.settings['dt'] self.aero_solver = solver_interface.initialise_solver(self.settings['aero_solver']) self.aero_solver.initialise(self.data, self.settings['aero_solver_settings']) self.data = self.aero_solver.data # if there's data in timestep_info[>0], copy the last one to # timestep_info[0] and remove the rest self.cleanup_timestep_info() # initialise postprocessors self.postprocessors = dict() if len(self.settings['postprocessors']) > 0: self.with_postprocessors = True for postproc in self.settings['postprocessors']: self.postprocessors[postproc] = solver_interface.initialise_solver(postproc) self.postprocessors[postproc].initialise( self.data, self.settings['postprocessors_settings'][postproc], caller=self, restart=restart) self.residual_table = cout.TablePrinter(2, 14, ['g', 'f']) self.residual_table.field_length[0] = 6 self.residual_table.field_length[1] = 6 self.residual_table.print_header(['ts', 't']) def cleanup_timestep_info(self): if len(self.data.aero.timestep_info) > 1: # copy last info to first self.data.aero.timestep_info[0] = self.data.aero.timestep_info[-1] # delete all the rest while len(self.data.aero.timestep_info) - 1: del self.data.aero.timestep_info[-1] self.data.ts = 0 def increase_ts(self): self.data.structure.next_step() self.aero_solver.add_step() def run(self, **kwargs): structural_kstep = self.data.structure.ini_info.copy() # dynamic simulations start at tstep == 1, 0 is reserved for the initial state for self.data.ts in range(1, self.settings['n_time_steps'] + 1): aero_kstep = self.data.aero.timestep_info[-1].copy() structural_kstep = self.data.structure.timestep_info[-1].copy() ts = len(self.data.structure.timestep_info) - 1 if ts > 0: self.data.structure.timestep_info[ts].for_vel[:] = self.data.structure.dynamic_input[ts - 1]['for_vel'] self.data.structure.timestep_info[ts].for_acc[:] = self.data.structure.dynamic_input[ts - 1]['for_acc'] self.data.structure.next_step() self.data.structure.integrate_position(self.data.ts, self.settings['dt']) self.aero_solver.add_step() self.data.aero.timestep_info[-1] = aero_kstep.copy() self.aero_solver.update_custom_grid(self.data.structure.timestep_info[-1], self.data.aero.timestep_info[-1]) # run the solver self.data = self.aero_solver.run(self.data.aero.timestep_info[-1], self.data.structure.timestep_info[-1], self.data.aero.timestep_info[-2], convect_wake=True) self.residual_table.print_line([self.data.ts, self.data.ts*self.dt]) # run postprocessors if self.with_postprocessors: for postproc in self.postprocessors: self.data = self.postprocessors[postproc].run(online=True) return self.data ================================================ FILE: sharpy/solvers/rigiddynamiccoupledstep.py ================================================ import sharpy.structure.utils.xbeamlib as xbeamlib from sharpy.utils.solver_interface import solver, BaseSolver, solver_from_string import sharpy.utils.settings as settings_utils _BaseStructural = solver_from_string('_BaseStructural') @solver class RigidDynamicCoupledStep(_BaseStructural): """ Structural solver used for the dynamic simulation of free-flying rigid structures. This solver provides an interface to the structural library (``xbeam``) and updates the structural parameters for every k-th step in the FSI iteration. This solver can be called as part of a standalone structural simulation or as the structural solver of a coupled aeroelastic simulation. """ solver_id = 'RigidDynamicCoupledStep' solver_classification = 'structural' settings_types = _BaseStructural.settings_types.copy() settings_default = _BaseStructural.settings_default.copy() settings_description = _BaseStructural.settings_description.copy() settings_types['balancing'] = 'bool' settings_default['balancing'] = False settings_types['relaxation_factor'] = 'float' settings_default['relaxation_factor'] = 0.3 settings_description['relaxation factor'] = 'Relaxation factor' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) # load info from dyn dictionary self.data.structure.add_unsteady_information(self.data.structure.dyn_dict, self.settings['num_steps']) # generate q, dqdt and dqddt xbeamlib.xbeam_solv_disp2state(self.data.structure, self.data.structure.timestep_info[-1]) def run(self, **kwargs): structural_step = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) # TODO: previous_structural_step never used previous_structural_step = settings_utils.set_value_or_default(kwargs, 'previous_structural_step', self.data.structure.timestep_info[-1]) dt= settings_utils.set_value_or_default(kwargs, 'dt', self.settings['dt']) xbeamlib.xbeam_step_coupledrigid(self.data.structure, self.settings, self.data.ts, structural_step, dt=dt) self.extract_resultants(structural_step) self.data.structure.integrate_position(structural_step, dt) return self.data def add_step(self): self.data.structure.next_step() def next_step(self): pass def extract_resultants(self, tstep=None): if tstep is None: tstep = self.data.structure.timestep_info[self.data.ts] steady, unsteady, grav = tstep.extract_resultants(self.data.structure, force_type=['steady', 'unsteady', 'grav']) totals = steady + unsteady + grav return totals[0:3], totals[3:6] ================================================ FILE: sharpy/solvers/rigiddynamicprescribedstep.py ================================================ """ @modified Alfonso del Carre """ import numpy as np import sharpy.structure.utils.xbeamlib as xbeamlib from sharpy.utils.solver_interface import solver, BaseSolver, solver_from_string import sharpy.utils.settings as settings_utils import sharpy.utils.algebra as algebra _BaseStructural = solver_from_string('_BaseStructural') @solver class RigidDynamicPrescribedStep(BaseSolver): solver_id = 'RigidDynamicPrescribedStep' solver_classification = 'structural' settings_types = _BaseStructural.settings_types.copy() settings_default = _BaseStructural.settings_default.copy() settings_description = _BaseStructural.settings_description.copy() settings_types['dt'] = 'float' settings_default['dt'] = 0.01 settings_description['dt'] = 'Time step of simulation' settings_types['num_steps'] = 'int' settings_default['num_steps'] = 500 settings_description['num_steps'] = 'Number of timesteps to be run' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) # load info from dyn dictionary self.data.structure.add_unsteady_information(self.data.structure.dyn_dict, self.settings['num_steps']) def run(self, **kwargs): structural_step = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) dt= settings_utils.set_value_or_default(kwargs, 'dt', self.settings['dt']) if self.data.ts > 0: try: structural_step.for_vel[:] = self.data.structure.dynamic_input[self.data.ts - 1]['for_vel'] structural_step.for_acc[:] = self.data.structure.dynamic_input[self.data.ts - 1]['for_acc'] except IndexError: pass Temp = np.linalg.inv(np.eye(4) + 0.25*algebra.quadskew(structural_step.for_vel[3:6])*dt) structural_step.quat = np.dot(Temp, np.dot(np.eye(4) - 0.25*algebra.quadskew(structural_step.for_vel[3:6])*dt, structural_step.quat)) xbeamlib.cbeam3_solv_disp2state(self.data.structure, structural_step) self.extract_resultants(structural_step) if self.data.ts > 0: self.data.structure.integrate_position(structural_step, self.settings['dt']) return self.data def add_step(self): self.data.structure.next_step() def next_step(self): pass def extract_resultants(self, tstep=None): if tstep is None: tstep = self.data.structure.timestep_info[self.data.ts] steady, unsteady, grav = tstep.extract_resultants(self.data.structure, force_type=['steady', 'unsteady', 'grav']) totals = steady + unsteady + grav return totals[0:3], totals[3:6] def update(self, tstep=None): self.create_q_vector(tstep) def create_q_vector(self, tstep=None): import sharpy.structure.utils.xbeamlib as xb if tstep is None: tstep = self.data.structure.timestep_info[-1] xb.xbeam_solv_disp2state(self.data.structure, tstep) ================================================ FILE: sharpy/solvers/staticcoupled.py ================================================ import sys import numpy as np import sharpy.aero.utils.mapping as mapping import sharpy.utils.cout_utils as cout from sharpy.utils.solver_interface import solver, BaseSolver, initialise_solver import sharpy.utils.settings as settings_utils import sharpy.utils.algebra as algebra import sharpy.utils.generator_interface as gen_interface @solver class StaticCoupled(BaseSolver): """ This class is the main FSI driver for static simulations. It requires a ``structural_solver`` and a ``aero_solver`` to be defined. """ solver_id = 'StaticCoupled' solver_classification = 'Coupled' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Write status to screen' settings_types['structural_solver'] = 'str' settings_default['structural_solver'] = None settings_description['structural_solver'] = 'Structural solver to use in the coupled simulation' settings_types['structural_solver_settings'] = 'dict' settings_default['structural_solver_settings'] = None settings_description['structural_solver_settings'] = 'Dictionary of settings for the structural solver' settings_types['aero_solver'] = 'str' settings_default['aero_solver'] = None settings_description['aero_solver'] = 'Aerodynamic solver to use in the coupled simulation' settings_types['aero_solver_settings'] = 'dict' settings_default['aero_solver_settings'] = None settings_description['aero_solver_settings'] = 'Dictionary of settings for the aerodynamic solver' settings_types['max_iter'] = 'int' settings_default['max_iter'] = 100 settings_description['max_iter'] = 'Max iterations in the FSI loop' settings_types['n_load_steps'] = 'int' settings_default['n_load_steps'] = 0 settings_description['n_load_steps'] = 'Length of ramp for forces and gravity during FSI iteration' settings_types['tolerance'] = 'float' settings_default['tolerance'] = 1e-5 settings_description['tolerance'] = 'Convergence threshold for the FSI loop' settings_types['relaxation_factor'] = 'float' settings_default['relaxation_factor'] = 0. settings_description['relaxation_factor'] = 'Relaxation parameter in the FSI iteration. 0 is no relaxation and -> 1 is very relaxed' settings_types['correct_forces_method'] = 'str' settings_default['correct_forces_method'] = '' settings_description['correct_forces_method'] = 'Function used to correct aerodynamic forces. ' \ 'See :py:mod:`sharpy.generators.polaraeroforces`' settings_options['correct_forces_method'] = ['EfficiencyCorrection', 'PolarCorrection'] settings_types['correct_forces_settings'] = 'dict' settings_default['correct_forces_settings'] = {} settings_description['correct_forces_settings'] = 'Settings for corrected forces evaluation' settings_types['runtime_generators'] = 'dict' settings_default['runtime_generators'] = dict() settings_description['runtime_generators'] = 'The dictionary keys are the runtime generators to be used. ' \ 'The dictionary values are dictionaries with the settings ' \ 'needed by each generator.' settings_types['nonlifting_body_interactions'] = 'bool' settings_default['nonlifting_body_interactions'] = False settings_description['nonlifting_body_interactions'] = 'Consider forces induced by nonlifting bodies' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) def __init__(self): self.data = None self.settings = None self.structural_solver = None self.aero_solver = None self.previous_force = None self.residual_table = None self.correct_forces = False self.correct_forces_generator = None self.runtime_generators = dict() self.with_runtime_generators = False def initialise(self, data, input_dict=None, restart=False): self.data = data if input_dict is None: self.settings = data.settings[self.solver_id] else: self.settings = input_dict settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, options=self.settings_options, no_ctype=True) self.print_info = self.settings['print_info'] self.structural_solver = initialise_solver(self.settings['structural_solver']) self.structural_solver.initialise(self.data, self.settings['structural_solver_settings'], restart=restart) self.aero_solver = initialise_solver(self.settings['aero_solver']) self.aero_solver.initialise(self.structural_solver.data, self.settings['aero_solver_settings'], restart=restart) self.data = self.aero_solver.data if self.print_info: self.residual_table = cout.TablePrinter(9, 8, ['g', 'g', 'f', 'f', 'f', 'f', 'f', 'f', 'f']) self.residual_table.field_length[0] = 3 self.residual_table.field_length[1] = 3 self.residual_table.field_length[2] = 10 self.residual_table.print_header(['iter', 'step', 'log10(res)', 'Fx', 'Fy', 'Fz', 'Mx', 'My', 'Mz']) # Define the function to correct aerodynamic forces if self.settings['correct_forces_method'] != '': self.correct_forces = True self.correct_forces_generator = gen_interface.generator_from_string(self.settings['correct_forces_method'])() self.correct_forces_generator.initialise(in_dict=self.settings['correct_forces_settings'], aero=self.data.aero, structure=self.data.structure, rho=self.settings['aero_solver_settings']['rho'], vortex_radius=self.settings['aero_solver_settings']['vortex_radius'], output_folder = self.data.output_folder) # initialise runtime generators self.runtime_generators = dict() if self.settings['runtime_generators']: self.with_runtime_generators = True for rg_id, param in self.settings['runtime_generators'].items(): gen = gen_interface.generator_from_string(rg_id) self.runtime_generators[rg_id] = gen() self.runtime_generators[rg_id].initialise(param, data=self.data, restart=restart) def increase_ts(self): self.data.ts += 1 self.structural_solver.next_step() self.aero_solver.next_step() def cleanup_timestep_info(self): if max(len(self.data.aero.timestep_info), len(self.data.structure.timestep_info)) > 1: self.remove_old_timestep_info(self.data.structure.timestep_info) self.remove_old_timestep_info(self.data.aero.timestep_info) if self.settings['nonlifting_body_interactions']: self.remove_old_timestep_info(self.data.nonlifting_body.timestep_info) self.data.ts = 0 def remove_old_timestep_info(self, tstep_info): # copy last info to first tstep_info[0] = tstep_info[-1].copy() # delete all the rest while len(tstep_info) - 1: del tstep_info[-1] def run(self, **kwargs): for i_step in range(self.settings['n_load_steps'] + 1): if (i_step == self.settings['n_load_steps'] and self.settings['n_load_steps'] > 0): break # load step coefficient if not self.settings['n_load_steps'] == 0: load_step_multiplier = (i_step + 1.0)/self.settings['n_load_steps'] else: load_step_multiplier = 1.0 # new storage every load step if i_step > 0: self.increase_ts() for i_iter in range(self.settings['max_iter']): # run aero self.data = self.aero_solver.run() # map force struct_forces = mapping.aero2struct_force_mapping( self.data.aero.timestep_info[self.data.ts].forces, self.data.aero.struct2aero_mapping, self.data.aero.timestep_info[self.data.ts].zeta, self.data.structure.timestep_info[self.data.ts].pos, self.data.structure.timestep_info[self.data.ts].psi, self.data.structure.node_master_elem, self.data.structure.connectivities, self.data.structure.timestep_info[self.data.ts].cag(), self.data.aero.data_dict) if self.correct_forces: struct_forces = \ self.correct_forces_generator.generate(aero_kstep=self.data.aero.timestep_info[self.data.ts], structural_kstep=self.data.structure.timestep_info[self.data.ts], struct_forces=struct_forces, ts=0) # map nonlifting forces to structural nodes if self.settings['nonlifting_body_interactions']: struct_forces += mapping.aero2struct_force_mapping( self.data.nonlifting_body.timestep_info[self.data.ts].forces, self.data.nonlifting_body.struct2aero_mapping, self.data.nonlifting_body.timestep_info[self.data.ts].zeta, self.data.structure.timestep_info[self.data.ts].pos, self.data.structure.timestep_info[self.data.ts].psi, self.data.structure.node_master_elem, self.data.structure.connectivities, self.data.structure.timestep_info[self.data.ts].cag(), self.data.nonlifting_body.data_dict, skip_moments_generated_by_forces = True) self.data.aero.timestep_info[self.data.ts].aero_steady_forces_beam_dof = struct_forces self.data.structure.timestep_info[self.data.ts].postproc_node['aero_steady_forces'] = struct_forces # B # Add external forces if self.with_runtime_generators: self.data.structure.timestep_info[self.data.ts].runtime_steady_forces.fill(0.) self.data.structure.timestep_info[self.data.ts].runtime_unsteady_forces.fill(0.) params = dict() params['data'] = self.data params['struct_tstep'] = self.data.structure.timestep_info[self.data.ts] params['aero_tstep'] = self.data.aero.timestep_info[self.data.ts] params['fsi_substep'] = -i_iter for id, runtime_generator in self.runtime_generators.items(): runtime_generator.generate(params) struct_forces += self.data.structure.timestep_info[self.data.ts].runtime_steady_forces struct_forces += self.data.structure.timestep_info[self.data.ts].runtime_unsteady_forces if not self.settings['relaxation_factor'] == 0.: if i_iter == 0: self.previous_force = struct_forces.copy() temp = struct_forces.copy() struct_forces = ((1.0 - self.settings['relaxation_factor'])*struct_forces + self.settings['relaxation_factor']*self.previous_force) self.previous_force = temp # copy force in beam old_g = self.structural_solver.settings['gravity'] self.structural_solver.settings['gravity'] = old_g*load_step_multiplier temp1 = load_step_multiplier*(struct_forces + self.data.structure.ini_info.steady_applied_forces) self.data.structure.timestep_info[self.data.ts].steady_applied_forces[:] = temp1 # run beam self.data = self.structural_solver.run() self.structural_solver.settings['gravity'] = old_g (self.data.structure.timestep_info[self.data.ts].total_forces[0:3], self.data.structure.timestep_info[self.data.ts].total_forces[3:6]) = ( self.extract_resultants(self.data.structure.timestep_info[self.data.ts])) # update grid self.aero_solver.update_step() self.structural_solver.update(self.data.structure.timestep_info[self.data.ts]) # convergence if self.convergence(i_iter, i_step): # create q and dqdt vectors self.structural_solver.update(self.data.structure.timestep_info[self.data.ts]) self.cleanup_timestep_info() break return self.data def convergence(self, i_iter, i_step): if i_iter == self.settings['max_iter'] - 1: cout.cout_wrap('StaticCoupled did not converge!', 0) # quit(-1) return_value = None if i_iter == 0: self.initial_residual = np.linalg.norm(self.data.structure.timestep_info[self.data.ts].pos) self.previous_residual = self.initial_residual self.current_residual = self.initial_residual if self.print_info: forces = self.data.structure.timestep_info[self.data.ts].total_forces self.residual_table.print_line([i_iter, i_step, 0.0, forces[0], forces[1], forces[2], forces[3], forces[4], forces[5], ]) return False self.current_residual = np.linalg.norm(self.data.structure.timestep_info[self.data.ts].pos) if self.print_info: forces = self.data.structure.timestep_info[self.data.ts].total_forces res_print = np.NINF if (np.abs(self.current_residual - self.previous_residual) > sys.float_info.epsilon*10): res_print = np.log10(np.abs(self.current_residual - self.previous_residual)/self.initial_residual) self.residual_table.print_line([i_iter, i_step, res_print, forces[0], forces[1], forces[2], forces[3], forces[4], forces[5], ]) if return_value is None: if np.abs(self.current_residual - self.previous_residual)/self.initial_residual < self.settings['tolerance']: return_value = True else: self.previous_residual = self.current_residual return_value = False if return_value is None: return_value = False return return_value def change_trim(self, alpha, thrust, thrust_nodes, tail_deflection, tail_cs_index): # self.cleanup_timestep_info() self.data.structure.timestep_info = [] self.data.structure.timestep_info.append(self.data.structure.ini_info.copy()) aero_copy = self.data.aero.timestep_info[-1] self.data.aero.timestep_info = [] self.data.aero.timestep_info.append(aero_copy) self.data.ts = 0 # alpha orientation_quat = algebra.euler2quat(np.array([0.0, alpha, 0.0])) self.data.structure.timestep_info[0].quat[:] = orientation_quat[:] try: self.force_orientation except AttributeError: self.force_orientation = np.zeros((len(thrust_nodes), 3)) for i_node, node in enumerate(thrust_nodes): self.force_orientation[i_node, :] = ( algebra.unit_vector(self.data.structure.ini_info.steady_applied_forces[node, 0:3])) # thrust # thrust is scaled so that the direction of the forces is conserved # in all nodes. # the `thrust` parameter is the force PER node. # if there are two or more nodes in thrust_nodes, the total forces # is n_nodes_in_thrust_nodes*thrust # thrust forces have to be indicated in structure.ini_info for i_node, node in enumerate(thrust_nodes): self.data.structure.ini_info.steady_applied_forces[node, 0:3] = ( self.force_orientation[i_node, :]*thrust) self.data.structure.timestep_info[0].steady_applied_forces[node, 0:3] = ( self.force_orientation[i_node, :]*thrust) # tail deflection try: self.data.aero.data_dict['control_surface_deflection'][tail_cs_index] = tail_deflection except KeyError: raise Exception('This model has no control surfaces') except IndexError: raise Exception('The tail control surface index > number of surfaces') # update grid self.aero_solver.update_step() def extract_resultants(self, tstep=None): return self.structural_solver.extract_resultants(tstep) def teardown(self): self.structural_solver.teardown() self.aero_solver.teardown() if self.with_runtime_generators: for rg in self.runtime_generators.values(): rg.teardown() ================================================ FILE: sharpy/solvers/statictrim.py ================================================ import numpy as np import sharpy.utils.cout_utils as cout import sharpy.utils.solver_interface as solver_interface from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils import os @solver class StaticTrim(BaseSolver): """ The ``StaticTrim`` solver determines the longitudinal state of trim (equilibrium) for an aeroelastic system in static conditions. It wraps around the desired solver to yield the state of trim of the system, in most cases the :class:`~sharpy.solvers.staticcoupled.StaticCoupled` solver. It calculates the required angle of attack, elevator deflection and thrust required to achieve longitudinal equilibrium. The output angles are shown in degrees. The results from the trimming iteration can be saved to a text file by using the `save_info` option. """ solver_id = 'StaticTrim' solver_classification = 'Flight Dynamics' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Print info to screen' settings_types['solver'] = 'str' settings_default['solver'] = '' settings_description['solver'] = 'Solver to run in trim routine' settings_types['solver_settings'] = 'dict' settings_default['solver_settings'] = dict() settings_description['solver_settings'] = 'Solver settings dictionary' settings_types['max_iter'] = 'int' settings_default['max_iter'] = 100 settings_description['max_iter'] = 'Maximum number of iterations of trim routine' settings_types['fz_tolerance'] = 'float' settings_default['fz_tolerance'] = 0.01 settings_description['fz_tolerance'] = 'Tolerance in vertical force' settings_types['fx_tolerance'] = 'float' settings_default['fx_tolerance'] = 0.01 settings_description['fx_tolerance'] = 'Tolerance in horizontal force' settings_types['m_tolerance'] = 'float' settings_default['m_tolerance'] = 0.01 settings_description['m_tolerance'] = 'Tolerance in pitching moment' settings_types['tail_cs_index'] = ['int', 'list(int)'] settings_default['tail_cs_index'] = 0 settings_description['tail_cs_index'] = 'Index of control surfaces that move to achieve trim' settings_types['thrust_nodes'] = 'list(int)' settings_default['thrust_nodes'] = [0] settings_description['thrust_nodes'] = 'Nodes at which thrust is applied' settings_types['initial_alpha'] = 'float' settings_default['initial_alpha'] = 0. settings_description['initial_alpha'] = 'Initial angle of attack' settings_types['initial_deflection'] = 'float' settings_default['initial_deflection'] = 0. settings_description['initial_deflection'] = 'Initial control surface deflection' settings_types['initial_thrust'] = 'float' settings_default['initial_thrust'] = 0.0 settings_description['initial_thrust'] = 'Initial thrust setting' settings_types['initial_angle_eps'] = 'float' settings_default['initial_angle_eps'] = 0.05 settings_description['initial_angle_eps'] = 'Initial change of control surface deflection' settings_types['initial_thrust_eps'] = 'float' settings_default['initial_thrust_eps'] = 2. settings_description['initial_thrust_eps'] = 'Initial thrust setting change' settings_types['relaxation_factor'] = 'float' settings_default['relaxation_factor'] = 0.2 settings_description['relaxation_factor'] = 'Relaxation factor' settings_types['save_info'] = 'bool' settings_default['save_info'] = False settings_description['save_info'] = 'Save trim results to text file' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None self.solver = None # The order is # [0]: alpha/fz # [1]: alpha + delta (gamma)/moment # [2]: thrust/fx self.n_input = 3 self.i_iter = 0 self.input_history = [] self.output_history = [] self.gradient_history = [] self.trimmed_values = np.zeros((3,)) self.table = None self.folder = None def initialise(self, data, restart=False): self.data = data self.settings = data.settings[self.solver_id] settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) self.solver = solver_interface.initialise_solver(self.settings['solver']) self.solver.initialise(self.data, self.settings['solver_settings'], restart=restart) self.folder = data.output_folder + '/statictrim/' if not os.path.exists(self.folder): os.makedirs(self.folder) self.table = cout.TablePrinter(10, 8, ['g', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f'], filename=self.folder+'trim_iterations.txt') self.table.print_header(['iter', 'alpha[deg]', 'elev[deg]', 'thrust', 'Fx', 'Fy', 'Fz', 'Mx', 'My', 'Mz']) def increase_ts(self): self.data.ts += 1 self.structural_solver.next_step() self.aero_solver.next_step() def run(self, **kwargs): # In the event the modal solver has been run prior to StaticCoupled (i.e. to get undeformed modes), copy # results and then attach to the resulting timestep try: modal = self.data.structure.timestep_info[-1].modal.copy() modal_exists = True except AttributeError: modal_exists = False self.trim_algorithm() if modal_exists: self.data.structure.timestep_info[-1].modal = modal if self.settings['save_info']: np.savetxt(self.folder + '/trim_values.txt', self.trimmed_values) return self.data def convergence(self, fz, m, fx): return_value = np.array([False, False, False]) if np.abs(fz) < self.settings['fz_tolerance']: return_value[0] = True if np.abs(m) < self.settings['m_tolerance']: return_value[1] = True if np.abs(fx) < self.settings['fx_tolerance']: return_value[2] = True return return_value def trim_algorithm(self): """ Trim algorithm method The trim condition is found iteratively. Returns: np.array: array of trim values for angle of attack, control surface deflection and thrust. """ for self.i_iter in range(self.settings['max_iter'] + 1): if self.i_iter == self.settings['max_iter']: raise Exception('The Trim routine reached max iterations without convergence!') self.input_history.append([]) self.output_history.append([]) self.gradient_history.append([]) for i in range(self.n_input): self.input_history[self.i_iter].append(0) self.output_history[self.i_iter].append(0) self.gradient_history[self.i_iter].append(0) # the first iteration requires computing gradients if not self.i_iter: # add to input history the initial estimation self.input_history[self.i_iter][0] = self.settings['initial_alpha'] self.input_history[self.i_iter][1] = (self.settings['initial_deflection'] + self.settings['initial_alpha']) self.input_history[self.i_iter][2] = self.settings['initial_thrust'] # compute output (self.output_history[self.i_iter][0], self.output_history[self.i_iter][1], self.output_history[self.i_iter][2]) = self.evaluate(self.input_history[self.i_iter][0], self.input_history[self.i_iter][1], self.input_history[self.i_iter][2]) # check for convergence (in case initial values are ok) if all(self.convergence(self.output_history[self.i_iter][0], self.output_history[self.i_iter][1], self.output_history[self.i_iter][2])): self.trimmed_values = self.input_history[self.i_iter] return # compute gradients # dfz/dalpha (l, m, d) = self.evaluate(self.input_history[self.i_iter][0] + self.settings['initial_angle_eps'], self.input_history[self.i_iter][1], self.input_history[self.i_iter][2]) self.gradient_history[self.i_iter][0] = ((l - self.output_history[self.i_iter][0]) / self.settings['initial_angle_eps']) # dm/dgamma (l, m, d) = self.evaluate(self.input_history[self.i_iter][0], self.input_history[self.i_iter][1] + self.settings['initial_angle_eps'], self.input_history[self.i_iter][2]) self.gradient_history[self.i_iter][1] = ((m - self.output_history[self.i_iter][1]) / self.settings['initial_angle_eps']) # dfx/dthrust (l, m, d) = self.evaluate(self.input_history[self.i_iter][0], self.input_history[self.i_iter][1], self.input_history[self.i_iter][2] + self.settings['initial_thrust_eps']) self.gradient_history[self.i_iter][2] = ((d - self.output_history[self.i_iter][2]) / self.settings['initial_thrust_eps']) continue # if not all(np.isfinite(self.gradient_history[self.i_iter - 1])) # now back to normal evaluation (not only the i_iter == 0 case) # compute next alpha with the previous gradient # convergence = self.convergence(self.output_history[self.i_iter - 1][0], # self.output_history[self.i_iter - 1][1], # self.output_history[self.i_iter - 1][2]) convergence = np.full((3, ), False) if convergence[0]: # fz is converged, don't change it self.input_history[self.i_iter][0] = self.input_history[self.i_iter - 1][0] self.gradient_history[self.i_iter][0] = self.gradient_history[self.i_iter - 1][0] else: self.input_history[self.i_iter][0] = (self.input_history[self.i_iter - 1][0] - (self.output_history[self.i_iter - 1][0] / self.gradient_history[self.i_iter - 1][0])) if convergence[1]: # m is converged, don't change it self.input_history[self.i_iter][1] = self.input_history[self.i_iter - 1][1] self.gradient_history[self.i_iter][1] = self.gradient_history[self.i_iter - 1][1] else: # compute next gamma with the previous gradient self.input_history[self.i_iter][1] = (self.input_history[self.i_iter - 1][1] - (self.output_history[self.i_iter - 1][1] / self.gradient_history[self.i_iter - 1][1])) if convergence[2]: # fx is converged, don't change it self.input_history[self.i_iter][2] = self.input_history[self.i_iter - 1][2] self.gradient_history[self.i_iter][2] = self.gradient_history[self.i_iter - 1][2] else: # compute next gamma with the previous gradient self.input_history[self.i_iter][2] = (self.input_history[self.i_iter - 1][2] - (self.output_history[self.i_iter - 1][2] / self.gradient_history[self.i_iter - 1][2])) if self.settings['relaxation_factor']: for i_dim in range(3): self.input_history[self.i_iter][i_dim] = (self.input_history[self.i_iter][i_dim]*(1 - self.settings['relaxation_factor']) + self.input_history[self.i_iter][i_dim]*self.settings['relaxation_factor']) # evaluate (self.output_history[self.i_iter][0], self.output_history[self.i_iter][1], self.output_history[self.i_iter][2]) = self.evaluate(self.input_history[self.i_iter][0], self.input_history[self.i_iter][1], self.input_history[self.i_iter][2]) if not convergence[0]: self.gradient_history[self.i_iter][0] = ((self.output_history[self.i_iter][0] - self.output_history[self.i_iter - 1][0]) / (self.input_history[self.i_iter][0] - self.input_history[self.i_iter - 1][0])) if not convergence[1]: self.gradient_history[self.i_iter][1] = ((self.output_history[self.i_iter][1] - self.output_history[self.i_iter - 1][1]) / (self.input_history[self.i_iter][1] - self.input_history[self.i_iter - 1][1])) if not convergence[2]: self.gradient_history[self.i_iter][2] = ((self.output_history[self.i_iter][2] - self.output_history[self.i_iter - 1][2]) / (self.input_history[self.i_iter][2] - self.input_history[self.i_iter - 1][2])) # check convergence convergence = self.convergence(self.output_history[self.i_iter][0], self.output_history[self.i_iter][1], self.output_history[self.i_iter][2]) if all(convergence): self.trimmed_values = self.input_history[self.i_iter] self.table.close_file() return def evaluate(self, alpha, deflection_gamma, thrust): if not np.isfinite(alpha): raise ValueError("Alpha trim gradient is zero, resulting in division by zero.") if not np.isfinite(deflection_gamma): raise ValueError("Delta trim gradient is zero, resulting in division by zero.") if not np.isfinite(thrust): raise ValueError("Thrust trim gradient is zero, resulting in division by zero.") # modify the trim in the static_coupled solver self.solver.change_trim(alpha, thrust, self.settings['thrust_nodes'], deflection_gamma - alpha, self.settings['tail_cs_index']) # run the solver self.solver.run() # extract resultants forces, moments = self.solver.extract_resultants() forcez = forces[2] forcex = forces[0] moment = moments[1] self.table.print_line([self.i_iter, alpha*180/np.pi, (deflection_gamma - alpha)*180/np.pi, thrust, forces[0], forces[1], forces[2], moments[0], moments[1], moments[2]]) return forcez, moment, forcex ================================================ FILE: sharpy/solvers/staticuvlm.py ================================================ import sharpy.aero.utils.uvlmlib as uvlmlib import sharpy.utils.settings as settings_utils from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.generator_interface as gen_interface from sharpy.utils.constants import vortex_radius_def import sharpy.aero.utils.mapping as mapping @solver class StaticUvlm(BaseSolver): """ ``StaticUvlm`` solver class, inherited from ``BaseSolver`` Aerodynamic solver that runs a UVLM routine to solve the steady or unsteady aerodynamic problem. The aerodynamic problem is posed in the form of an ``Aerogrid`` object. Args: data (PreSharpy): object with problem data custom_settings (dict): custom settings that override the settings in the solver ``.txt`` file. None by default Attributes: settings (dict): Name-value pair of settings employed by solver. See Notes for valid combinations settings_types (dict): Acceptable data types for entries in ``settings`` settings_default (dict): Default values for the available ``settings`` data (PreSharpy): object containing the information of the problem velocity_generator(object): object containing the flow conditions information """ solver_id = 'StaticUvlm' solver_classification = 'aero' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Print info to screen' settings_types['horseshoe'] = 'bool' settings_default['horseshoe'] = False settings_description['horseshoe'] = 'Horseshoe wake modelling for steady simulations.' settings_types['nonlifting_body_interactions'] = 'bool' settings_default['nonlifting_body_interactions'] = False settings_description['nonlifting_body_interactions'] = 'Consider nonlifting body interactions' settings_types['only_nonlifting'] = 'bool' settings_default['only_nonlifting'] = False settings_description['only_nonlifting'] = 'Consider only nonlifting bodies' settings_types['phantom_wing_test'] = 'bool' settings_default['phantom_wing_test'] = False settings_description['phantom_wing_test'] = 'Debug option' settings_types['num_cores'] = 'int' settings_default['num_cores'] = 0 settings_description['num_cores'] = 'Number of cores to use in the VLM lib' settings_types['n_rollup'] = 'int' settings_default['n_rollup'] = 0 settings_description['n_rollup'] = 'Number of rollup iterations for free wake. Use at least ``n_rollup > 1.1*m_star``' settings_types['rollup_dt'] = 'float' settings_default['rollup_dt'] = 0.1 settings_description['rollup_dt'] = 'Pseudo time step for wake convection. Chose it so that it is similar to the unsteady time step' settings_types['rollup_aic_refresh'] = 'int' settings_default['rollup_aic_refresh'] = 1 settings_description['rollup_dt'] = 'Controls when the AIC matrix is refreshed during the wake rollup' settings_types['rollup_tolerance'] = 'float' settings_default['rollup_tolerance'] = 1e-4 settings_description['rollup_tolerance'] = 'Convergence criterium for rollup wake' settings_types['iterative_solver'] = 'bool' settings_default['iterative_solver'] = False settings_description['iterative_solver'] = 'Not in use' settings_types['iterative_tol'] = 'float' settings_default['iterative_tol'] = 1e-4 settings_description['iterative_tol'] = 'Not in use' settings_types['iterative_precond'] = 'bool' settings_default['iterative_precond'] = False settings_description['iterative_precond'] = 'Not in use' settings_types['velocity_field_generator'] = 'str' settings_default['velocity_field_generator'] = 'SteadyVelocityField' settings_description['velocity_field_generator'] = 'Name of the velocity field generator to be used in the simulation' settings_types['velocity_field_input'] = 'dict' settings_default['velocity_field_input'] = {} settings_description['velocity_field_input'] = 'Dictionary of settings for the velocity field generator' settings_types['rho'] = 'float' settings_default['rho'] = 1.225 settings_description['rho'] = 'Air density' settings_types['cfl1'] = 'bool' settings_default['cfl1'] = True settings_description['cfl1'] = 'If it is ``True``, it assumes that the discretisation complies with CFL=1' settings_types['vortex_radius'] = 'float' settings_default['vortex_radius'] = vortex_radius_def settings_description['vortex_radius'] = 'Distance between points below which induction is not computed' settings_types['vortex_radius_wake_ind'] = 'float' settings_default['vortex_radius_wake_ind'] = vortex_radius_def settings_description['vortex_radius_wake_ind'] = 'Distance between points below which induction is not computed in the wake convection' settings_types['rbm_vel_g'] = 'list(float)' settings_default['rbm_vel_g'] = [0., 0., 0., 0., 0., 0.] settings_description['rbm_vel_g'] = 'Rigid body velocity in G FoR' settings_types['centre_rot_g'] = 'list(float)' settings_default['centre_rot_g'] = [0., 0., 0.] settings_description['centre_rot_g'] = 'Centre of rotation in G FoR around which ``rbm_vel_g`` is applied' settings_types['map_forces_on_struct'] = 'bool' settings_default['map_forces_on_struct'] = False settings_description['map_forces_on_struct'] = 'Maps the forces on the structure at the end of the timestep. Only usefull if the solver is used outside StaticCoupled' settings_types['ignore_first_x_nodes_in_force_calculation'] = 'int' settings_default['ignore_first_x_nodes_in_force_calculation'] = 0 settings_description['ignore_first_x_nodes_in_force_calculation'] = 'Ignores the forces on the first user-specified number of nodes of all surfaces.' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): # settings list self.data = None self.settings = None self.velocity_generator = None def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, no_ctype=True) self.update_step() # init velocity generator velocity_generator_type = gen_interface.generator_from_string( self.settings['velocity_field_generator']) self.velocity_generator = velocity_generator_type() self.velocity_generator.initialise(self.settings['velocity_field_input'], restart=restart) def add_step(self): self.data.aero.add_timestep() if self.settings['nonlifting_body_interactions']: self.data.nonlifting_body.add_timestep() def update_grid(self, beam): if not self.settings['only_nonlifting']: self.data.aero.generate_zeta(beam, self.data.aero.aero_settings, -1, beam_ts=-1) if self.settings['nonlifting_body_interactions'] or self.settings['only_nonlifting']: self.data.nonlifting_body.generate_zeta(beam, self.data.nonlifting_body.aero_settings, -1, beam_ts=-1) def update_custom_grid(self, structure_tstep, aero_tstep, nonlifting_tstep=None): self.data.aero.generate_zeta_timestep_info(structure_tstep, aero_tstep, self.data.structure, self.data.aero.aero_settings, dt=self.settings['rollup_dt']) if self.settings['nonlifting_body_interactions']: self.data.nonlifting_body.generate_zeta_timestep_info(structure_tstep, nonlifting_tstep, self.data.structure, self.data.nonlifting_body.aero_settings) def run(self, **kwargs): structure_tstep = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[self.data.ts]) if not self.settings['only_nonlifting']: aero_tstep = settings_utils.set_value_or_default(kwargs, 'aero_step', self.data.aero.timestep_info[self.data.ts]) if not self.data.aero.timestep_info[self.data.ts].zeta: return self.data # generate the wake because the solid shape might change self.data.aero.wake_shape_generator.generate({'zeta': aero_tstep.zeta, 'zeta_star': aero_tstep.zeta_star, 'gamma': aero_tstep.gamma, 'gamma_star': aero_tstep.gamma_star, 'dist_to_orig': aero_tstep.dist_to_orig}) if self.settings['nonlifting_body_interactions']: # generate uext self.velocity_generator.generate({'zeta': self.data.nonlifting_body.timestep_info[self.data.ts].zeta, 'override': True, 'for_pos': structure_tstep.for_pos[0:3]}, self.data.nonlifting_body.timestep_info[self.data.ts].u_ext) # generate uext self.velocity_generator.generate({'zeta': self.data.aero.timestep_info[self.data.ts].zeta, 'override': True, 'for_pos': self.data.structure.timestep_info[self.data.ts].for_pos[0:3]}, self.data.aero.timestep_info[self.data.ts].u_ext) # grid orientation uvlmlib.vlm_solver_lifting_and_nonlifting_bodies(self.data.aero.timestep_info[self.data.ts], self.data.nonlifting_body.timestep_info[self.data.ts], self.settings) else: # generate uext self.velocity_generator.generate({'zeta': self.data.aero.timestep_info[self.data.ts].zeta, 'override': True, 'for_pos': self.data.structure.timestep_info[self.data.ts].for_pos[0:3]}, self.data.aero.timestep_info[self.data.ts].u_ext) # grid orientation uvlmlib.vlm_solver(self.data.aero.timestep_info[self.data.ts], self.settings) else: self.velocity_generator.generate({'zeta': self.data.nonlifting_body.timestep_info[self.data.ts].zeta, 'override': True, 'for_pos': self.data.structure.timestep_info[self.data.ts].for_pos[0:3]}, self.data.nonlifting_body.timestep_info[self.data.ts].u_ext) uvlmlib.vlm_solver_nonlifting_body(self.data.nonlifting_body.timestep_info[self.data.ts], self.settings) return self.data def next_step(self): """ Updates de aerogrid based on the info of the step, and increases the self.ts counter """ self.data.aero.add_timestep() if self.settings['nonlifting_body_interactions']: self.data.nonlifting_body.add_timestep() self.update_step() def update_step(self): if not self.settings['only_nonlifting']: self.data.aero.generate_zeta(self.data.structure, self.data.aero.aero_settings, self.data.ts) if self.settings['nonlifting_body_interactions'] or self.settings['only_nonlifting']: self.data.nonlifting_body.generate_zeta(self.data.structure, self.data.nonlifting_body.aero_settings, self.data.ts) ================================================ FILE: sharpy/solvers/steplinearuvlm.py ================================================ """ Time domain solver to integrate the linear UVLM aerodynamic system developed by S. Maraniello N Goizueta Nov 18 """ from sharpy.utils.solver_interface import BaseSolver, solver import numpy as np import sharpy.utils.settings as settings_utils import sharpy.utils.generator_interface as gen_interface import sharpy.utils.algebra as algebra import sharpy.linear.src.linuvlm as linuvlm from sharpy.utils.constants import vortex_radius_def @solver class StepLinearUVLM(BaseSolver): r""" Time domain aerodynamic solver that uses a linear UVLM formulation to be used with the :class:`solvers.DynamicCoupled` solver. To use this solver, the ``solver_id = StepLinearUVLM`` must be given as the name for the ``aero_solver`` is the case of an aeroelastic solver, where the setting below would be parsed through ``aero_solver_settings``. Notes: The ``integr_order`` variable refers to the finite differencing scheme used to calculate the bound circulation derivative with respect to time :math:`\dot{\mathbf{\Gamma}}`. A first order scheme is used when ``integr_order == 1`` .. math:: \dot{\mathbf{\Gamma}}^{n+1} = \frac{\mathbf{\Gamma}^{n+1}-\mathbf{\Gamma}^n}{\Delta t} If ``integr_order == 2`` a higher order scheme is used (but it isn't exactly second order accurate [1]). .. math:: \dot{\mathbf{\Gamma}}^{n+1} = \frac{3\mathbf{\Gamma}^{n+1}-4\mathbf{\Gamma}^n + \mathbf{\Gamma}^{n-1}} {2\Delta t} If ``track_body`` is ``True``, the UVLM is projected onto a frame ``U`` that is: * Coincident with ``G`` at the linearisation timestep. * Thence, rotates by the same quantity as the FoR ``A``. It is similar to a stability axes and is recommended any time rigid body dynamics are included. See Also: :class:`sharpy.sharpy.linear.assembler.linearuvlm.LinearUVLM` References: [1] Maraniello, S., & Palacios, R.. State-Space Realizations and Internal Balancing in Potential-Flow Aerodynamics with Arbitrary Kinematics. AIAA Journal, 57(6), 1–14. 2019. https://doi.org/10.2514/1.J058153 """ solver_id = 'StepLinearUVLM' solver_classification = 'aero' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['dt'] = 'float' settings_default['dt'] = 0.1 settings_description['dt'] = 'Time step' settings_types['integr_order'] = 'int' settings_default['integr_order'] = 2 settings_description['integr_order'] = 'Integration order of the circulation derivative. Either ``1`` or ``2``.' settings_types['ScalingDict'] = 'dict' settings_default['ScalingDict'] = dict() settings_description['ScalingDict'] = 'Dictionary of scaling factors to achieve normalised UVLM realisation.' settings_types['remove_predictor'] = 'bool' settings_default['remove_predictor'] = True settings_description['remove_predictor'] = 'Remove the predictor term from the UVLM equations' settings_types['use_sparse'] = 'bool' settings_default['use_sparse'] = True settings_description['use_sparse'] = 'Assemble UVLM plant matrix in sparse format' settings_types['density'] = 'float' settings_default['density'] = 1.225 settings_description['density'] = 'Air density' settings_types['track_body'] = 'bool' settings_default['track_body'] = True settings_description['track_body'] = 'UVLM inputs and outputs projected to coincide with lattice at linearisation' settings_types['track_body_number'] = 'int' settings_default['track_body_number'] = -1 settings_description['track_body_number'] = 'Frame of reference number to follow. If ``-1`` track ``A`` frame.' settings_types['velocity_field_generator'] = 'str' settings_default['velocity_field_generator'] = 'SteadyVelocityField' settings_description['velocity_field_generator'] = 'Name of the velocity field generator to be used in the ' \ 'simulation' settings_types['velocity_field_input'] = 'dict' settings_default['velocity_field_input'] = {} settings_description['velocity_field_input'] = 'Dictionary of settings for the velocity field generator' settings_types['vortex_radius'] = 'float' settings_default['vortex_radius'] = vortex_radius_def settings_description['vortex_radius'] = 'Distance between points below which induction is not computed' settings_types['vortex_radius_wake_ind'] = 'float' settings_default['vortex_radius_wake_ind'] = vortex_radius_def settings_description[ 'vortex_radius_wake_ind'] = 'Distance between points below which induction is not computed in the wake convection' settings_types['cfl1'] = 'bool' settings_default['cfl1'] = True settings_description['cfl1'] = 'If it is ``True``, it assumes that the discretisation complies with CFL=1' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) scaling_settings_types = dict() scaling_settings_default = dict() scaling_settings_description = dict() scaling_settings_types['length'] = 'float' scaling_settings_default['length'] = 1.0 scaling_settings_description['length'] = 'Reference length to be used for UVLM scaling' scaling_settings_types['speed'] = 'float' scaling_settings_default['speed'] = 1.0 scaling_settings_description['speed'] = 'Reference speed to be used for UVLM scaling' scaling_settings_types['density'] = 'float' scaling_settings_default['density'] = 1.0 scaling_settings_description['density'] = 'Reference density to be used for UVLM scaling' __doc__ += settings_table.generate(scaling_settings_types, scaling_settings_default, scaling_settings_description, header_line='The settings that ``ScalingDict`` ' 'accepts are the following:') def __init__(self): self.data = None self.settings = None self.lin_uvlm_system = None self.velocity_generator = None def initialise(self, data, custom_settings=None, restart=False): r""" Initialises the Linear UVLM aerodynamic solver and the chosen velocity generator. Settings are parsed into the standard SHARPy settings format for solvers. It then checks whether there is any previous information about the linearised system (in order for a solution to be restarted without overwriting the linearisation). If a linearised system does not exist, a linear UVLM system is created linearising about the current time step. The reference values for the input and output are transformed into column vectors :math:`\mathbf{u}` and :math:`\mathbf{y}`, respectively. The information pertaining to the linear system is stored in a dictionary ``self.data.aero.linear`` within the main ``data`` variable. Args: data (PreSharpy): class containing the problem information custom_settings (dict): custom settings dictionary """ self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, no_ctype=True) settings_utils.to_custom_types(self.settings['ScalingDict'], self.scaling_settings_types, self.scaling_settings_default, no_ctype=True) # Initialise velocity generator velocity_generator_type = gen_interface.generator_from_string(self.settings['velocity_field_generator']) self.velocity_generator = velocity_generator_type() self.velocity_generator.initialise(self.settings['velocity_field_input'], restart=restart) # Check whether linear UVLM has been initialised try: self.data.aero.linear except AttributeError: self.data.aero.linear = dict() aero_tstep = self.data.aero.timestep_info[-1] ### Record body orientation/velocities at time 0 # This option allows to rotate the linearised UVLM with the A frame # or a specific body (multi-body solution) if self.settings['track_body']: self.num_body_track = self.settings['track_body_number'] # track A frame if self.num_body_track == -1: self.quat0 = self.data.structure.timestep_info[-1].quat.copy() self.for_vel0 = self.data.structure.timestep_info[-1].for_vel.copy() else: # track a specific body self.quat0 = \ self.data.structure.timestep_info[-1].mb_quat[self.num_body_track, :].copy() self.for_vel0 = \ self.data.structure.timestep_info[-1].mb_FoR_vel[self.num_body_track, :].copy() # convert to G frame self.Cga0 = algebra.quat2rotation(self.quat0) self.Cga = self.Cga0.copy() self.for_vel0[:3] = self.Cga0.dot(self.for_vel0[:3]) self.for_vel0[3:] = self.Cga0.dot(self.for_vel0[3:]) else: # check/record initial rotation speed self.num_body_track = None self.quat0 = None self.Cag0 = None self.Cga = None self.for_vel0 = np.zeros((6,)) # TODO: verify of a better way to implement rho aero_tstep.rho = self.settings['density'] # Generate instance of linuvlm.Dynamic() lin_uvlm_system = linuvlm.DynamicBlock(aero_tstep, dynamic_settings=self.settings, for_vel=self.for_vel0) # add rotational speed for ii in range(lin_uvlm_system.MS.n_surf): lin_uvlm_system.MS.Surfs[ii].omega = self.for_vel0[3:] # Save reference values # System Inputs u_0 = self.pack_input_vector() # Linearised state dt = self.settings['dt'] x_0 = self.pack_state_vector(aero_tstep, None, dt, self.settings['integr_order']) # Reference forces f_0 = np.concatenate([aero_tstep.forces[ss][0:3].reshape(-1, order='C') for ss in range(aero_tstep.n_surf)]) # Assemble the state space system wake_prop_settings = {'dt': self.settings['dt'], 'ts': self.data.ts, 't': self.data.ts * self.settings['dt'], 'for_pos': self.data.structure.timestep_info[-1].for_pos, 'cfl1': self.settings['cfl1'], 'vel_gen': self.velocity_generator} lin_uvlm_system.assemble_ss(wake_prop_settings=wake_prop_settings) self.data.aero.linear['System'] = lin_uvlm_system self.data.aero.linear['SS'] = lin_uvlm_system.SS self.data.aero.linear['x_0'] = x_0 self.data.aero.linear['u_0'] = u_0 self.data.aero.linear['y_0'] = f_0 # TODO: Implement in AeroTimeStepInfo a way to store the state vectors def run(self, **kwargs): r""" Solve the linear aerodynamic UVLM model at the current time step ``n``. The step increment is solved as: .. math:: \mathbf{x}^n &= \mathbf{A\,x}^{n-1} + \mathbf{B\,u}^n \\ \mathbf{y}^n &= \mathbf{C\,x}^n + \mathbf{D\,u}^n A change of state is possible in order to solve the system without the predictor term. In which case the system is solved by: .. math:: \mathbf{h}^n &= \mathbf{A\,h}^{n-1} + \mathbf{B\,u}^{n-1} \\ \mathbf{y}^n &= \mathbf{C\,h}^n + \mathbf{D\,u}^n Variations are taken with respect to initial reference state. The state and input vectors for the linear UVLM system are of the form: If ``integr_order==1``: .. math:: \mathbf{x}_n = [\delta\mathbf{\Gamma}^T_n,\, \delta\mathbf{\Gamma_w}_n^T,\, \Delta t \,\delta\mathbf{\dot{\Gamma}}_n^T]^T Else, if ``integr_order==2``: .. math:: \mathbf{x}_n = [\delta\mathbf{\Gamma}_n^T,\, \delta\mathbf{\Gamma_w}_n^T,\, \Delta t \,\delta\mathbf{\dot{\Gamma}}_n^T,\, \delta\mathbf{\Gamma}_{n-1}^T]^T And the input vector: .. math:: \mathbf{u}_n = [\delta\mathbf{\zeta}_n^T,\, \delta\dot{\mathbf{\zeta}}_n^T,\,\delta\mathbf{u_{ext}}^T_n]^T where the subscript ``n`` refers to the time step. The linear UVLM system is then solved as detailed in :func:`sharpy.linear.src.linuvlm.Dynamic.solve_step`. The output is a column vector containing the aerodynamic forces at the panel vertices. To Do: option for impulsive start? Args: aero_tstep (AeroTimeStepInfo): object containing the aerodynamic data at the current time step structure_tstep (StructTimeStepInfo): object containing the structural data at the current time step convect_wake (bool): for backward compatibility only. The linear UVLM assumes a frozen wake geometry dt (float): time increment t (float): current time unsteady_contribution (bool): (backward compatibily). Unsteady aerodynamic effects are always included Returns: PreSharpy: updated ``self.data`` class with the new forces and circulation terms of the system """ aero_tstep = settings_utils.set_value_or_default(kwargs, 'aero_step', self.data.aero.timestep_info[-1]) structure_tstep = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) dt = settings_utils.set_value_or_default(kwargs, 'dt', self.settings['dt']) t = settings_utils.set_value_or_default(kwargs, 't', self.data.ts * dt) integr_order = self.settings['integr_order'] ### Define Input # Generate external velocity field u_ext self.velocity_generator.generate({'zeta': aero_tstep.zeta, 'override': True, 't': t, 'ts': self.data.ts, 'dt': dt, 'for_pos': structure_tstep.for_pos}, aero_tstep.u_ext) ### Proj from FoR G to linearisation frame # - proj happens in self.pack_input_vector and unpack_ss_vectors if self.settings['track_body']: # track A frame if self.num_body_track == -1: self.Cga = algebra.quat2rotation(structure_tstep.quat) else: # track a specific body self.Cga = algebra.quat2rotation( structure_tstep.mb_quat[self.num_body_track, :]) # Column vector that will be the input to the linearised UVLM system # Input is at time step n, since it is updated in the aeroelastic solver prior to aerodynamic solver u_n = self.pack_input_vector() du_n = u_n - self.data.aero.linear['u_0'] if self.settings['remove_predictor']: u_m1 = self.pack_input_vector() du_m1 = u_m1 - self.data.aero.linear['u_0'] else: du_m1 = None # Retrieve State vector at time n-1 if len(self.data.aero.timestep_info) < 2: x_m1 = self.pack_state_vector(aero_tstep, None, dt, integr_order) else: x_m1 = self.pack_state_vector(aero_tstep, self.data.aero.timestep_info[-2], dt, integr_order) # dx is at timestep n-1 dx_m1 = x_m1 - self.data.aero.linear['x_0'] ### Solve system - output is the variation in force dx_n, dy_n = self.data.aero.linear['System'].solve_step(dx_m1, du_m1, du_n, transform_state=True) x_n = self.data.aero.linear['x_0'] + dx_n y_n = self.data.aero.linear['y_0'] + dy_n # if self.settings['physical_model']: forces, gamma, gamma_dot, gamma_star = self.unpack_ss_vectors(y_n, x_n, u_n, aero_tstep) aero_tstep.forces = forces aero_tstep.gamma = gamma aero_tstep.gamma_dot = gamma_dot aero_tstep.gamma_star = gamma_star return self.data def add_step(self): self.data.aero.add_timestep() def update_grid(self, beam): self.data.aero.generate_zeta(beam, self.data.aero.aero_settings, -1, beam_ts=-1) def update_custom_grid(self, structure_tstep, aero_tstep): self.data.aero.generate_zeta_timestep_info(structure_tstep, aero_tstep, self.data.structure, self.data.aero.aero_settings) def unpack_ss_vectors(self, y_n, x_n, u_n, aero_tstep): r""" Transform column vectors used in the state space formulation into SHARPy format The column vectors are transformed into lists with one entry per aerodynamic surface. Each entry contains a matrix with the quantities at each grid vertex. .. math:: \mathbf{y}_n \longrightarrow \mathbf{f}_{aero} .. math:: \mathbf{x}_n \longrightarrow \mathbf{\Gamma}_n,\, \mathbf{\Gamma_w}_n,\, \mathbf{\dot{\Gamma}}_n If the ``track_body`` option is on, the output forces are projected from the linearization frame, to the G frame. Note that the linearisation frame is: a. equal to the FoR G at time 0 (linearisation point) b. rotates as the body frame specified in the ``track_body_number`` Args: y_n (np.ndarray): Column output vector of linear UVLM system x_n (np.ndarray): Column state vector of linear UVLM system u_n (np.ndarray): Column input vector of linear UVLM system aero_tstep (AeroTimeStepInfo): aerodynamic timestep information class instance Returns: tuple: Tuple containing: forces (list): Aerodynamic forces in a list with ``n_surf`` entries. Each entry is a ``(6, M+1, N+1)`` matrix, where the first 3 indices correspond to the components in ``x``, ``y`` and ``z``. The latter 3 are zero. gamma (list): Bound circulation list with ``n_surf`` entries. Circulation is stored in an ``(M+1, N+1)`` matrix, corresponding to the panel vertices. gamma_dot (list): Bound circulation derivative list with ``n_surf`` entries. Circulation derivative is stored in an ``(M+1, N+1)`` matrix, corresponding to the panel vertices. gamma_star (list): Wake (free) circulation list with ``n_surf`` entries. Wake circulation is stored in an ``(M_star+1, N+1)`` matrix, corresponding to the panel vertices of the wake. """ ### project forces from uvlm FoR to FoR G if self.settings['track_body']: Cg_uvlm = np.dot(self.Cga, self.Cga0.T) f_aero = y_n gamma_vec, gamma_star_vec, gamma_dot_vec = self.data.aero.linear['System'].unpack_state(x_n) # Reshape output into forces[i_surface] where forces[i_surface] is a (6,M+1,N+1) matrix and circulation terms # where gamma is a [i_surf](M+1, N+1) matrix forces = [] gamma = [] gamma_star = [] gamma_dot = [] worked_points = 0 worked_panels = 0 worked_wake_panels = 0 for i_surf in range(aero_tstep.n_surf): # Tuple with dimensions of the aerogrid zeta, which is the same shape for forces dimensions = aero_tstep.zeta[i_surf].shape dimensions_gamma = self.data.aero.aero_dimensions[i_surf] dimensions_wake = self.data.aero.aero_dimensions_star[i_surf] # Number of entries in zeta points_in_surface = aero_tstep.zeta[i_surf].size panels_in_surface = aero_tstep.gamma[i_surf].size panels_in_wake = aero_tstep.gamma_star[i_surf].size # Append reshaped forces to each entry in list (one for each surface) forces.append(f_aero[worked_points:worked_points + points_in_surface].reshape(dimensions, order='C')) ### project forces. # - forces are in UVLM linearisation frame. Hence, these are projected # into FoR (using rotation matrix Cag0 time 0) A and back to FoR G if self.settings['track_body']: for mm in range(dimensions[1]): for nn in range(dimensions[2]): forces[i_surf][:, mm, nn] = np.dot(Cg_uvlm, forces[i_surf][:, mm, nn]) # Add the null bottom 3 rows to to the forces entry forces[i_surf] = np.concatenate((forces[i_surf], np.zeros(dimensions))) # Reshape bound circulation terms gamma.append(gamma_vec[worked_panels:worked_panels + panels_in_surface].reshape( dimensions_gamma, order='C')) gamma_dot.append(gamma_dot_vec[worked_panels:worked_panels + panels_in_surface].reshape( dimensions_gamma, order='C')) # Reshape wake circulation terms gamma_star.append(gamma_star_vec[worked_wake_panels:worked_wake_panels + panels_in_wake].reshape( dimensions_wake, order='C')) worked_points += points_in_surface worked_panels += panels_in_surface worked_wake_panels += panels_in_wake return forces, gamma, gamma_dot, gamma_star def pack_input_vector(self): r""" Transform a SHARPy AeroTimestep instance into a column vector containing the input to the linear UVLM system. .. math:: [\zeta,\, \dot{\zeta}, u_{ext}] \longrightarrow \mathbf{u} If the ``track_body`` option is on, the function projects all the input into a frame that: a. is equal to the FoR G at time 0 (linearisation point) b. rotates as the body frame specified in the ``track_body_number`` Returns: np.ndarray: Input vector """ aero_tstep = self.data.aero.timestep_info[-1] ### re-compute projection in G frame as if A was not rotating # - u_n is in FoR G. Hence, this is project in FoR A and back to FoR G # using rotation matrix aat time 0 (as if FoR A was not rotating). if self.settings['track_body']: Cuvlm_g = np.dot(self.Cga0, self.Cga.T) zeta_uvlm, zeta_dot_uvlm, u_ext_uvlm = [], [], [] for i_surf in range(aero_tstep.n_surf): Mp1, Np1 = aero_tstep.dimensions[i_surf] + 1 zeta_uvlm.append(np.empty((3, Mp1, Np1))) zeta_dot_uvlm.append(np.empty((3, Mp1, Np1))) u_ext_uvlm.append(np.empty((3, Mp1, Np1))) for mm in range(Mp1): for nn in range(Np1): zeta_uvlm[i_surf][:, mm, nn] = \ np.dot(Cuvlm_g, aero_tstep.zeta[i_surf][:, mm, nn]) zeta_dot_uvlm[i_surf][:, mm, nn] = \ np.dot(Cuvlm_g, aero_tstep.zeta_dot[i_surf][:, mm, nn]) u_ext_uvlm[i_surf][:, mm, nn] = \ np.dot(Cuvlm_g, aero_tstep.u_ext[i_surf][:, mm, nn]) zeta = np.concatenate([zeta_uvlm[i_surf].reshape(-1, order='C') for i_surf in range(aero_tstep.n_surf)]) zeta_dot = np.concatenate([zeta_dot_uvlm[i_surf].reshape(-1, order='C') for i_surf in range(aero_tstep.n_surf)]) u_ext = np.concatenate([u_ext_uvlm[i_surf].reshape(-1, order='C') for i_surf in range(aero_tstep.n_surf)]) else: zeta = np.concatenate([aero_tstep.zeta[i_surf].reshape(-1, order='C') for i_surf in range(aero_tstep.n_surf)]) zeta_dot = np.concatenate([aero_tstep.zeta_dot[i_surf].reshape(-1, order='C') for i_surf in range(aero_tstep.n_surf)]) u_ext = np.concatenate([aero_tstep.u_ext[i_surf].reshape(-1, order='C') for i_surf in range(aero_tstep.n_surf)]) u = np.concatenate((zeta, zeta_dot, u_ext)) return u @staticmethod def pack_state_vector(aero_tstep, aero_tstep_m1, dt, integr_order): r""" Transform SHARPy Aerotimestep format into column vector containing the state information. The state vector is of a different form depending on the order of integration chosen. If a second order scheme is chosen, the state includes the bound circulation at the previous timestep, hence the timestep information for the previous timestep shall be parsed. The transformation is of the form: - If ``integr_order==1``: .. math:: \mathbf{x}_n = [\mathbf{\Gamma}^T_n,\, \mathbf{\Gamma_w}_n^T,\, \Delta t \,\mathbf{\dot{\Gamma}}_n^T]^T - Else, if ``integr_order==2``: .. math:: \mathbf{x}_n = [\mathbf{\Gamma}_n^T,\, \mathbf{\Gamma_w}_n^T,\, \Delta t \,\mathbf{\dot{\Gamma}}_n^T,\, \mathbf{\Gamma}_{n-1}^T]^T For the second order integration scheme, if the previous timestep information is not parsed, a first order stencil is employed to estimate the bound circulation at the previous timestep: .. math:: \mathbf{\Gamma}^{n-1} = \mathbf{\Gamma}^n - \Delta t \mathbf{\dot{\Gamma}}^n Args: aero_tstep (AeroTimeStepInfo): Aerodynamic timestep information at the current timestep ``n``. aero_tstep_m1 (AeroTimeStepInfo) Aerodynamic timestep information at the previous timestep ``n-1``. Returns: np.ndarray: State vector """ # Extract current state... gamma = np.concatenate([aero_tstep.gamma[ss].reshape(-1, order='C') for ss in range(aero_tstep.n_surf)]) gamma_star = np.concatenate([aero_tstep.gamma_star[ss].reshape(-1, order='C') for ss in range(aero_tstep.n_surf)]) gamma_dot = np.concatenate([aero_tstep.gamma_dot[ss].reshape(-1, order='C') for ss in range(aero_tstep.n_surf)]) if integr_order == 1: gamma_m1 = [] else: if aero_tstep_m1: gamma_m1 = np.concatenate([aero_tstep_m1.gamma[ss].reshape(-1, order='C') for ss in range(aero_tstep.n_surf)]) else: gamma_m1 = gamma - dt * gamma_dot x = np.concatenate((gamma, gamma_star, dt * gamma_dot, gamma_m1)) return x ================================================ FILE: sharpy/solvers/stepuvlm.py ================================================ import numpy as np import scipy.optimize import scipy.signal import sharpy.aero.utils.uvlmlib as uvlmlib import sharpy.utils.settings as settings_utils from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.generator_interface as gen_interface import sharpy.utils.cout_utils as cout from sharpy.utils.constants import vortex_radius_def @solver class StepUvlm(BaseSolver): """ StepUVLM is the main solver to use for unsteady aerodynamics. The desired flow field is injected into the simulation by means of a ``generator``. For a list of available velocity field generators see the documentation page on generators which can be found under SHARPy Source Code. Typical generators could be: * :class:`~.generators.steadyvelocityfield.SteadyVelocityField` * :class:`~.generators.gustvelocityfield.GustVelocityField` * :class:`~.generators.turbvelocityfield.TurbVelocityField` amongst others. """ solver_id = 'StepUvlm' solver_classification = 'aero' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Print info to screen' settings_types['num_cores'] = 'int' settings_default['num_cores'] = 0 settings_description['num_cores'] = 'Number of cores to use in the VLM lib' settings_types['n_time_steps'] = 'int' settings_default['n_time_steps'] = 100 settings_description['n_time_steps'] = 'Number of time steps to be run' settings_types['convection_scheme'] = 'int' settings_default['convection_scheme'] = 3 settings_description['convection_scheme'] = '``0``: fixed wake, ' \ '``2``: convected with background flow;' \ '``3``: full force-free wake' settings_options['convection_scheme'] = [0, 2, 3] settings_types['dt'] = 'float' settings_default['dt'] = 0.1 settings_description['dt'] = 'Time step' # the following settings are not in used but are required in place since they are called in uvlmlib settings_types['iterative_solver'] = 'bool' settings_default['iterative_solver'] = False settings_description['iterative_solver'] = 'Not in use' settings_types['iterative_tol'] = 'float' settings_default['iterative_tol'] = 1e-4 settings_description['iterative_tol'] = 'Not in use' settings_types['iterative_precond'] = 'bool' settings_default['iterative_precond'] = False settings_description['iterative_precond'] = 'Not in use' settings_types['velocity_field_generator'] = 'str' settings_default['velocity_field_generator'] = 'SteadyVelocityField' settings_description['velocity_field_generator'] = 'Name of the velocity field generator to be used in the ' \ 'simulation' settings_types['velocity_field_input'] = 'dict' settings_default['velocity_field_input'] = {} settings_description['velocity_field_input'] = 'Dictionary of settings for the velocity field generator' settings_types['gamma_dot_filtering'] = 'int' settings_default['gamma_dot_filtering'] = 0 settings_description['gamma_dot_filtering'] = 'Filtering parameter for the Welch filter for the Gamma_dot ' \ 'estimation. Used when ``unsteady_force_contribution`` is ``on``.' settings_types['rho'] = 'float' settings_default['rho'] = 1.225 settings_description['rho'] = 'Air density' settings_types['cfl1'] = 'bool' settings_default['cfl1'] = True settings_description['cfl1'] = 'If it is ``True``, it assumes that the discretisation complies with CFL=1' settings_types['vortex_radius'] = 'float' settings_default['vortex_radius'] = vortex_radius_def settings_description['vortex_radius'] = 'Distance between points below which induction is not computed' settings_types['vortex_radius_wake_ind'] = 'float' settings_default['vortex_radius_wake_ind'] = vortex_radius_def settings_description[ 'vortex_radius_wake_ind'] = 'Distance between points below which induction is not computed in the wake convection' settings_types['interp_coords'] = 'int' settings_default['interp_coords'] = 0 settings_description['interp_coords'] = 'Coordinates to use for wake description: cartesian(0) or cylindrical_z(1)' settings_options['interp_coords'] = [0, 1] settings_types['filter_method'] = 'int' settings_default['filter_method'] = 0 settings_description['filter_method'] = 'Method to filter the points: no filter (0) moving average(2)' settings_options['filter_method'] = [0, 2] # filter_method = 1 was dedicated to a splines filter. If it is needed in the future chack dev_alglib branch settings_types['interp_method'] = 'int' settings_default['interp_method'] = 0 settings_description[ 'interp_method'] = 'Method of interpolation: linear(0), parabolic(1), splines(2), slerp around z(3), slerp around yaw_slerp(4)' settings_options['interp_method'] = [0, 1, 2, 3, 4] settings_types['yaw_slerp'] = 'float' settings_default['yaw_slerp'] = 0 settings_description['yaw_slerp'] = 'Yaw angle in radians to be used when interp_metod == 4' settings_types['centre_rot'] = 'list(float)' settings_default['centre_rot'] = [0., 0., 0.] settings_description[ 'centre_rot'] = 'Coordinates of the centre of rotation to perform slerp interpolation or cylindrical coordinates' settings_types['quasi_steady'] = 'bool' settings_default['quasi_steady'] = False settings_description['quasi_steady'] = 'Use quasi-steady approximation in UVLM' settings_types['only_nonlifting'] = 'bool' settings_default['only_nonlifting'] = False settings_description['only_nonlifting'] = 'Consider nonlifting body interactions' settings_types['nonlifting_body_interactions'] = 'bool' settings_default['nonlifting_body_interactions'] = False settings_description['nonlifting_body_interactions'] = 'Consider nonlifting body interactions' settings_types['phantom_wing_test'] = 'bool' settings_default['phantom_wing_test'] = False settings_description['phantom_wing_test'] = 'Debug option' settings_types['centre_rot_g'] = 'list(float)' settings_default['centre_rot_g'] = [0., 0., 0.] settings_description['centre_rot_g'] = 'Centre of rotation in G FoR around which ``rbm_vel_g`` is applied' settings_types['ignore_first_x_nodes_in_force_calculation'] = 'int' settings_default['ignore_first_x_nodes_in_force_calculation'] = 0 settings_description[ 'ignore_first_x_nodes_in_force_calculation'] = 'Ignores the forces on the first user-specified number of nodes of all surfaces.' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) def __init__(self): self.data = None self.settings = None self.velocity_generator = None def initialise(self, data, custom_settings=None, restart=False): """ To be called just once per simulation. """ self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, self.settings_options) self.data.structure.add_unsteady_information( self.data.structure.dyn_dict, self.settings['n_time_steps']) # Filtering if self.settings['gamma_dot_filtering'] == 1: cout.cout_wrap( "gamma_dot_filtering cannot be one. Changing it to None", 2) self.settings['gamma_dot_filtering'] = None if self.settings['gamma_dot_filtering'] is not None: if self.settings['gamma_dot_filtering']: if not self.settings['gamma_dot_filtering'] % 2: cout.cout_wrap( "gamma_dot_filtering does not support even numbers." + "Changing " + str(self.settings['gamma_dot_filtering']) + " to " + str(self.settings['gamma_dot_filtering'] + 1), 2) self.settings['gamma_dot_filtering'] += 1 # init velocity generator velocity_generator_type = gen_interface.generator_from_string( self.settings['velocity_field_generator']) self.velocity_generator = velocity_generator_type() self.velocity_generator.initialise( self.settings['velocity_field_input'], restart=restart) def run(self, **kwargs): """ Runs a step of the aerodynamics as implemented in UVLM. """ # Default values aero_tstep = settings_utils.set_value_or_default(kwargs, 'aero_step', self.data.aero.timestep_info[-1]) structure_tstep = settings_utils.set_value_or_default(kwargs, 'structural_step', self.data.structure.timestep_info[-1]) convect_wake = settings_utils.set_value_or_default(kwargs, 'convect_wake', True) dt = settings_utils.set_value_or_default(kwargs, 'dt', self.settings['dt']) t = settings_utils.set_value_or_default(kwargs, 't', self.data.ts * dt) unsteady_contribution = settings_utils.set_value_or_default(kwargs, 'unsteady_contribution', False) if not aero_tstep.zeta: return self.data # generate uext self.velocity_generator.generate({'zeta': aero_tstep.zeta, 'override': True, 't': t, 'ts': self.data.ts, 'dt': dt, 'for_pos': structure_tstep.for_pos, 'is_wake': False}, aero_tstep.u_ext) if ((self.settings['convection_scheme'] > 1 and convect_wake) or (not self.settings['cfl1'])): # generate uext_star self.velocity_generator.generate({'zeta': aero_tstep.zeta_star, 'override': True, 'ts': self.data.ts, 'dt': dt, 't': t, 'for_pos': structure_tstep.for_pos, 'is_wake': True}, aero_tstep.u_ext_star) if self.settings['nonlifting_body_interactions']: nl_body_tstep = settings_utils.set_value_or_default(kwargs, 'nl_body_tstep', self.data.nonlifting_body.timestep_info[-1]) self.velocity_generator.generate({'zeta': nl_body_tstep.zeta, 'override': True, 'ts': self.data.ts, 'dt': dt, 't': t, 'for_pos': structure_tstep.for_pos, 'is_wake': False}, nl_body_tstep.u_ext) uvlmlib.uvlm_solver_lifting_and_nonlifting(self.data.ts, aero_tstep, nl_body_tstep, structure_tstep, self.settings, convect_wake=convect_wake, dt=dt) else: uvlmlib.uvlm_solver(self.data.ts, aero_tstep, structure_tstep, self.settings, convect_wake=convect_wake, dt=dt) if unsteady_contribution and not self.settings['quasi_steady']: # calculate unsteady (added mass) forces: self.data.aero.compute_gamma_dot(dt, aero_tstep, self.data.aero.timestep_info[-3:]) if self.settings['gamma_dot_filtering'] is None: self.filter_gamma_dot(aero_tstep, self.data.aero.timestep_info, None) elif self.settings['gamma_dot_filtering'] > 0: self.filter_gamma_dot( aero_tstep, self.data.aero.timestep_info, self.settings['gamma_dot_filtering']) uvlmlib.uvlm_calculate_unsteady_forces(aero_tstep, structure_tstep, self.settings, convect_wake=convect_wake, dt=dt) else: for i_surf in range(len(aero_tstep.gamma)): aero_tstep.gamma_dot[i_surf][:] = 0.0 return self.data def add_step(self): self.data.aero.add_timestep() if self.settings['nonlifting_body_interactions']: self.data.nonlifting_body.add_timestep() def update_grid(self, beam): self.data.aero.generate_zeta(beam, self.data.aero.aero_settings, -1, beam_ts=-1) if self.settings['nonlifting_body_interactions']: self.data.nonlifting_body.generate_zeta(beam, self.data.aero.aero_settings, -1, beam_ts=-1) def update_custom_grid(self, structure_tstep, aero_tstep, nl_body_tstep=None): self.data.aero.generate_zeta_timestep_info(structure_tstep, aero_tstep, self.data.structure, self.data.aero.aero_settings, dt=self.settings['dt']) if self.settings['nonlifting_body_interactions']: if nl_body_tstep is None: nl_body_tstep = self.data.nonlifting_body.timestep_info[-1] self.data.nonlifting_body.generate_zeta_timestep_info(structure_tstep, nl_body_tstep, self.data.structure, self.data.nonlifting_body.aero_settings, dt=self.settings['dt']) @staticmethod def filter_gamma_dot(tstep, history, filter_param): clean_history = [x for x in history if x is not None] series_length = len(clean_history) + 1 for i_surf in range(len(tstep.zeta)): n_rows, n_cols = tstep.gamma[i_surf].shape for i in range(n_rows): for j in range(n_cols): series = np.zeros((series_length,)) for it in range(series_length - 1): series[it] = clean_history[it].gamma_dot[i_surf][i, j] series[-1] = tstep.gamma_dot[i_surf][i, j] # filter tstep.gamma_dot[i_surf][i, j] = scipy.signal.wiener( series, filter_param)[-1] ================================================ FILE: sharpy/solvers/timeintegrators.py ================================================ import numpy as np import ctypes as ct import sharpy.utils.settings as settings_utils from sharpy.utils.solver_interface import solver @solver class _BaseTimeIntegrator(): """ Base structure for time integrators """ solver_id = '_BaseTimeIntegrator' solver_classification = 'time_integrator' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() def __init__(self): pass def initialise(self, data, custom_settings=None, restart=False): pass def predictor(self, q, dqdt, dqddt): pass def build_matrix(self, M, C, K): pass def corrector(self, q, dqdt, dqddt, Dq): pass @solver class NewmarkBeta(_BaseTimeIntegrator): """ Time integration according to the Newmark-beta scheme """ solver_id = 'NewmarkBeta' solver_classification = 'time_integrator' settings_types = _BaseTimeIntegrator.settings_types.copy() settings_default = _BaseTimeIntegrator.settings_default.copy() settings_description = _BaseTimeIntegrator.settings_description.copy() settings_options = _BaseTimeIntegrator.settings_options.copy() settings_types['dt'] = 'float' settings_default['dt'] = None settings_description['dt'] = 'Time step' settings_types['newmark_damp'] = 'float' settings_default['newmark_damp'] = 1e-4 settings_description['newmark_damp'] = 'Newmark damping coefficient' settings_types['sys_size'] = 'int' settings_default['sys_size'] = 0 settings_description['sys_size'] = 'Size of the system without constraints' settings_types['num_LM_eq'] = 'int' settings_default['num_LM_eq'] = 0 settings_description['num_LM_eq'] = 'Number of constraint equations' def __init__(self): self.sys_size = None self.num_LM_eq = None self.dt = None self.beta = None self.gamma = None def initialise(self, data, custom_settings=None, restart=False): if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, no_ctype=True) self.dt = self.settings['dt'] self.gamma = 0.5 + self.settings['newmark_damp'] self.beta = 0.25*(self.gamma + 0.5)*(self.gamma + 0.5) self.sys_size = self.settings['sys_size'] self.num_LM_eq = self.settings['num_LM_eq'] def predictor(self, q, dqdt, dqddt): sys_size = self.sys_size q[:sys_size] += self.dt*dqdt[:sys_size] + (0.5 - self.beta)*self.dt*self.dt*dqddt[:sys_size] dqdt[:sys_size] += (1.0 - self.gamma)*self.dt*dqddt[:sys_size] dqddt *= 0. # q[sys_size:] = q[sys_size:] dqdt[sys_size:] = dqdt[sys_size:] def build_matrix(self, M, C, K, Q, kBnh, LM_Q): sys_size = self.sys_size num_LM_eq = self.num_LM_eq Asys = np.zeros((sys_size + num_LM_eq, sys_size + num_LM_eq), dtype=ct.c_double, order='F') Qout = np.zeros((sys_size + num_LM_eq), dtype=ct.c_double, order='F') Asys[:sys_size, :sys_size] = K + C*self.gamma/(self.beta*self.dt) + M/(self.beta*self.dt*self.dt) Qout[:sys_size] = Q.copy() Asys[sys_size:, :sys_size] = (self.gamma/self.beta/self.dt)*kBnh Asys[:sys_size, sys_size:] = kBnh.T Qout[sys_size:] = LM_Q.copy() return Asys, Qout def corrector(self, q, dqdt, dqddt, Dq): sys_size = self.sys_size q[:sys_size] += Dq[:sys_size] dqdt[:sys_size] += self.gamma/(self.beta*self.dt)*Dq[:sys_size] dqddt[:sys_size] += 1.0/(self.beta*self.dt*self.dt)*Dq[:sys_size] dqdt[sys_size:] += Dq[sys_size:] @solver class GeneralisedAlpha(_BaseTimeIntegrator): """ Time integration according to the Generalised-Alpha scheme """ solver_id = 'GeneralisedAlpha' solver_classification = 'time_integrator' settings_types = _BaseTimeIntegrator.settings_types.copy() settings_default = _BaseTimeIntegrator.settings_default.copy() settings_description = _BaseTimeIntegrator.settings_description.copy() settings_options = _BaseTimeIntegrator.settings_options.copy() settings_types['dt'] = 'float' settings_default['dt'] = None settings_description['dt'] = 'Time step' settings_types['am'] = 'float' settings_default['am'] = 0. settings_description['am'] = 'alpha_M coefficient' settings_types['af'] = 'float' settings_default['af'] = 0.1 settings_description['af'] = 'alpha_F coefficient' settings_types['sys_size'] = 'int' settings_default['sys_size'] = 0 settings_description['sys_size'] = 'Size of the system without constraints' settings_types['num_LM_eq'] = 'int' settings_default['num_LM_eq'] = 0 settings_description['num_LM_eq'] = 'Number of contraint equations' def __init__(self): self.num_LM_eq = None self.sys_size = None self.om_af = None self.om_am = None self.dt = None self.am = None self.af = None self.gamma = None self.beta = None def initialise(self, data, custom_settings=None, restart=False): if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, no_ctype=True) self.dt = self.settings['dt'] self.am = self.settings['am'] self.af = self.settings['af'] self.om_am = 1. - self.am self.om_af = 1. - self.af self.gamma = 0.5 - self.am + self.af self.beta = 0.25*(1. - self.am + self.af)**2 self.sys_size = self.settings['sys_size'] self.num_LM_eq = self.settings['num_LM_eq'] def predictor(self, q, dqdt, dqddt): sys_size = self.sys_size q[:sys_size] += self.dt*dqdt[:sys_size] + (0.5 - self.beta)*self.dt*self.dt*dqddt[:sys_size] dqdt[:sys_size] += (1.0 - self.gamma)*self.dt*dqddt[:sys_size] dqddt *= 0. dqdt[sys_size:] = dqdt[sys_size:] def build_matrix(self, M, C, K, Q, kBnh, LM_Q): sys_size = self.sys_size num_LM_eq = self.num_LM_eq Asys = np.zeros((sys_size + num_LM_eq, sys_size + num_LM_eq), dtype=ct.c_double, order='F') Qout = np.zeros((sys_size + num_LM_eq), dtype=ct.c_double, order='F') Asys[:sys_size, :sys_size] = (self.om_af*K + self.gamma*self.om_af/self.beta/self.dt*C + self.om_am/(self.beta*self.dt*self.dt)*M) Qout[:sys_size] = Q.copy() Asys[sys_size:, :sys_size] = (self.gamma*self.om_af/self.beta/self.dt)*kBnh Asys[:sys_size, sys_size:] = kBnh.T Qout[sys_size:] = LM_Q.copy() return Asys, Qout def corrector(self, q, dqdt, dqddt, Dq): sys_size = self.sys_size q[:sys_size] += self.om_af*Dq[:sys_size] dqdt[:sys_size] += self.gamma*self.om_af/self.beta/self.dt*Dq[:sys_size] dqddt[:sys_size] += self.om_am/self.beta/self.dt**2*Dq[:sys_size] dqdt[sys_size:] += Dq[sys_size:] ================================================ FILE: sharpy/solvers/timeintegratorsjax.py ================================================ import numpy as np from abc import abstractmethod import typing import sharpy.utils.settings as settings_utils from sharpy.utils.solver_interface import solver arr: typing.Type = np.ndarray @solver class _BaseTimeIntegrator: """ Base structure for time integrators """ solver_id = '_BaseTimeIntegrator' solver_classification = 'time_integrator' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() def __init__(self): pass @abstractmethod def initialise(self, data, custom_settings=None, restart=False): pass @abstractmethod def predictor(self, q: arr, dqdt: arr, dqddt: arr): pass @abstractmethod def build_matrix(self, m: arr, c: arr, k: arr): pass @abstractmethod def corrector(self, q: arr, dqdt: arr, dqddt: arr, dq: arr): pass @solver class NewmarkBetaJAX(_BaseTimeIntegrator): """ Time integration according to the Newmark-beta scheme """ solver_id = 'NewmarkBetaJAX' solver_classification = 'time_integrator' settings_types = _BaseTimeIntegrator.settings_types.copy() settings_default = _BaseTimeIntegrator.settings_default.copy() settings_description = _BaseTimeIntegrator.settings_description.copy() settings_options = _BaseTimeIntegrator.settings_options.copy() settings_types['dt'] = 'float' settings_default['dt'] = None settings_description['dt'] = 'Time step' settings_types['newmark_damp'] = 'float' settings_default['newmark_damp'] = 1e-4 settings_description['newmark_damp'] = 'Newmark damping coefficient' settings_types['sys_size'] = 'int' settings_default['sys_size'] = 0 settings_description['sys_size'] = 'Size of the system without constraints' settings_types['num_LM_eq'] = 'int' settings_default['num_LM_eq'] = 0 settings_description['num_LM_eq'] = 'Number of constraint equations' def __init__(self): super().__init__() # I know the base class has no function here, but this makes Pycharm leave me alone self.dt = None self.beta = None self.gamma = None self.sys_size = None self.num_lm_eq = None self.settings = None def initialise(self, data, custom_settings=None, restart=False) -> None: if custom_settings is None: self.settings = data.input_settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, no_ctype=True) self.dt = self.settings['dt'] self.gamma = 0.5 + self.settings['newmark_damp'] self.beta = 0.25 * (self.gamma + 0.5) * (self.gamma + 0.5) self.sys_size = self.settings['sys_size'] self.num_lm_eq = self.settings['num_LM_eq'] def predictor(self, q, dqdt, dqddt): q[:self.sys_size] += (self.dt * dqdt[:self.sys_size] + (0.5 - self.beta) * self.dt * self.dt * dqddt[:self.sys_size]) dqdt[:self.sys_size] += (1. - self.gamma) * self.dt * dqddt[:self.sys_size] dqddt.fill(0.) def build_matrix(self, m: arr, c: arr, k: arr) -> arr: a_sys = np.zeros((self.sys_size + self.num_lm_eq, self.sys_size + self.num_lm_eq)) a_sys[:, :self.sys_size] = (k[:, :self.sys_size] + c[:, :self.sys_size] * self.gamma / (self.beta * self.dt) + m[:, :self.sys_size] / (self.beta * self.dt * self.dt)) a_sys[:, self.sys_size:] = k[:, self.sys_size:] + c[:, self.sys_size:] return a_sys def corrector(self, q: arr, dqdt: arr, dqddt: arr, dq: arr) -> None: q[:self.sys_size] += dq[:self.sys_size] dqdt[:self.sys_size] += self.gamma / (self.beta * self.dt) * dq[:self.sys_size] dqddt[:self.sys_size] += 1. / (self.beta * self.dt * self.dt) * dq[:self.sys_size] dqdt[self.sys_size:] += dq[self.sys_size:] @solver class GeneralisedAlphaJAX(_BaseTimeIntegrator): """ Time integration according to the Generalised-Alpha scheme """ solver_id = 'GeneralisedAlphaJAX' solver_classification = 'time_integrator' settings_types = _BaseTimeIntegrator.settings_types.copy() settings_default = _BaseTimeIntegrator.settings_default.copy() settings_description = _BaseTimeIntegrator.settings_description.copy() settings_options = _BaseTimeIntegrator.settings_options.copy() settings_types['dt'] = 'float' settings_default['dt'] = None settings_description['dt'] = 'Time step' settings_types['am'] = 'float' settings_default['am'] = 0. settings_description['am'] = 'alpha_M coefficient' settings_types['af'] = 'float' settings_default['af'] = 0.1 settings_description['af'] = 'alpha_F coefficient' settings_types['sys_size'] = 'int' settings_default['sys_size'] = 0 settings_description['sys_size'] = 'Size of the system without constraints' settings_types['num_LM_eq'] = 'int' settings_default['num_LM_eq'] = 0 settings_description['num_LM_eq'] = 'Number of contraint equations' def __init__(self): super().__init__() self.dt = None self.am = None self.af = None self.gamma = None self.beta = None self.om_am = None self.om_af = None self.sys_size = None self.num_lm_eq = None def initialise(self, data, custom_settings=None, restart=False) -> None: if custom_settings is None: self.settings = data.input_settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, no_ctype=True) self.dt = self.settings['dt'] self.am = self.settings['am'] self.af = self.settings['af'] self.om_am = 1. - self.am self.om_af = 1. - self.af self.gamma = 0.5 - self.am + self.af self.beta = 0.25 * (1. - self.am + self.af) ** 2 self.sys_size = self.settings['sys_size'] self.num_lm_eq = self.settings['num_LM_eq'] def predictor(self, q: arr, dqdt: arr, dqddt: arr): q[:self.sys_size] += (self.dt * dqdt[:self.sys_size] + (0.5 - self.beta) * self.dt * self.dt * dqddt[:self.sys_size]) dqdt[:self.sys_size] += (1. - self.gamma) * self.dt * dqddt[:self.sys_size] dqddt.fill(0.) def build_matrix(self, m: arr, c: arr, k: arr) -> arr: a_sys = np.zeros((self.sys_size + self.num_lm_eq, self.sys_size + self.num_lm_eq)) a_sys[:, :self.sys_size] = (self.om_af * k + self.gamma * self.om_af / (self.beta * self.dt) * c + self.om_am / (self.beta * self.dt * self.dt) * m) a_sys[:, self.sys_size:] = k[:, self.sys_size:] + c[:, self.sys_size:] return a_sys def corrector(self, q: arr, dqdt: arr, dqddt: arr, dq: arr) -> None: q[:self.sys_size] += self.om_af * dq[:self.sys_size] dqdt[:self.sys_size] += self.gamma * self.om_af / self.beta / self.dt * dq[:self.sys_size] dqddt[:self.sys_size] += self.om_am / self.beta / self.dt ** 2 * dq[:self.sys_size] dqdt[self.sys_size:] += dq[self.sys_size:] ================================================ FILE: sharpy/solvers/trim.py ================================================ import numpy as np import scipy.optimize import sharpy.utils.cout_utils as cout import sharpy.utils.solver_interface as solver_interface from sharpy.utils.solver_interface import solver, BaseSolver import sharpy.utils.settings as settings_utils import sharpy.utils.algebra as algebra @solver class Trim(BaseSolver): """ Trim routine with support for lateral dynamics. It usually struggles much more than the ``StaticTrim`` (only longitudinal) solver. We advise to start with ``StaticTrim`` even if you configuration is not totally symmetric. """ solver_id = 'Trim' solver_classification = 'Flight dynamics' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Print info to screen' settings_types['solver'] = 'str' settings_default['solver'] = '' settings_description['solver'] = 'Solver to run in trim routine' settings_types['solver_settings'] = 'dict' settings_default['solver_settings'] = dict() settings_description['solver_settings'] = 'Solver settings dictionary' settings_types['max_iter'] = 'int' settings_default['max_iter'] = 100 settings_description['max_iter'] = 'Maximum number of iterations of trim routine' settings_types['tolerance'] = 'float' settings_default['tolerance'] = 1e-4 settings_description['tolerance'] = 'Threshold for convergence of trim' settings_types['initial_alpha'] = 'float' settings_default['initial_alpha'] = 0. settings_description['initial_alpha'] = 'Initial angle of attack' settings_types['initial_beta'] = 'float' settings_default['initial_beta'] = 0. settings_description['initial_beta'] = 'Initial sideslip angle' settings_types['initial_roll'] = 'float' settings_default['initial_roll'] = 0 settings_description['initial_roll'] = 'Initial roll angle' settings_types['cs_indices'] = 'list(int)' settings_default['cs_indices'] = [] settings_description['cs_indices'] = 'Indices of control surfaces to be trimmed' settings_types['initial_cs_deflection'] = 'list(float)' settings_default['initial_cs_deflection'] = [] settings_description['initial_cs_deflection'] = 'Initial deflection of the control surfaces in order.' settings_types['thrust_nodes'] = 'list(int)' settings_default['thrust_nodes'] = [0] settings_description['thrust_nodes'] = 'Nodes at which thrust is applied' settings_types['initial_thrust'] = 'list(float)' settings_default['initial_thrust'] = [1.] settings_description['initial_thrust'] = 'Initial thrust setting' settings_types['thrust_direction'] = 'list(float)' settings_default['thrust_direction'] = [.0, 1.0, 0.0] settings_description['thrust_direction'] = 'Thrust direction setting' settings_types['special_case'] = 'dict' settings_default['special_case'] = dict() settings_description['special_case'] = 'Extra settings for specific cases such as differential thrust control' settings_types['refine_solution'] = 'bool' settings_default['refine_solution'] = False settings_description['refine_solution'] = 'If ``True`` and the optimiser routine allows for it, the optimiser will try to improve the solution with hybrid methods' settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None self.solver = None self.x_info = dict() self.initial_state = None self.with_special_case = False def initialise(self, data, restart=False): self.data = data self.settings = data.settings[self.solver_id] settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default) self.solver = solver_interface.initialise_solver(self.settings['solver']) self.solver.initialise(self.data, self.settings['solver_settings'], restart=restart) # generate x_info (which elements of the x array are what) counter = 0 self.x_info['n_variables'] = 0 # alpha self.x_info['i_alpha'] = counter counter += 1 # beta self.x_info['i_beta'] = counter counter += 1 # roll self.x_info['i_roll'] = counter counter += 1 # control surfaces n_control_surfaces = len(self.settings['cs_indices']) self.x_info['i_control_surfaces'] = [] # indices in the state vector self.x_info['control_surfaces_id'] = [] # indices of the trimmed control surfaces for i_cs in range(n_control_surfaces): self.x_info['i_control_surfaces'].append(counter) self.x_info['control_surfaces_id'].append(self.settings['cs_indices'][i_cs]) counter += 1 # thrust n_thrust_nodes = len(self.settings['thrust_nodes']) self.x_info['i_thrust'] = [] self.x_info['thrust_nodes'] = [] self.x_info['thrust_direction'] = [] for i_thrust in range(n_thrust_nodes): self.x_info['i_thrust'].append(counter) self.x_info['thrust_nodes'].append(self.settings['thrust_nodes'][i_thrust]) self.x_info['thrust_direction'].append(self.settings['thrust_direction']) counter += 1 self.x_info['n_variables'] = counter # special cases self.with_special_case = self.settings['special_case'] if self.with_special_case: if self.settings['special_case']['case_name'] == 'differential_thrust': self.x_info['special_case'] = 'differential_thrust' self.x_info['i_base_thrust'] = counter counter += 1 self.x_info['i_differential_parameter'] = counter counter += 1 self.x_info['initial_base_thrust'] = self.settings['special_case']['initial_base_thrust'] self.x_info['initial_differential_parameter'] = self.settings['special_case']['initial_differential_parameter'] self.x_info['base_thrust_nodes'] = [int(e) for e in self.settings['special_case']['base_thrust_nodes']] self.x_info['negative_thrust_nodes'] = [int(e) for e in self.settings['special_case']['negative_thrust_nodes']] self.x_info['positive_thrust_nodes'] = [int(e) for e in self.settings['special_case']['positive_thrust_nodes']] self.x_info['n_variables'] = counter # initial state vector self.initial_state = np.zeros(self.x_info['n_variables']) self.initial_state[self.x_info['i_alpha']] = self.settings['initial_alpha'] self.initial_state[self.x_info['i_beta']] = self.settings['initial_beta'] self.initial_state[self.x_info['i_roll']] = self.settings['initial_roll'] for i_cs in range(n_control_surfaces): self.initial_state[self.x_info['i_control_surfaces'][i_cs]] = self.settings['initial_cs_deflection'][i_cs] for i_thrust in range(n_thrust_nodes): self.initial_state[self.x_info['i_thrust'][i_thrust]] = self.settings['initial_thrust'][i_thrust] if self.with_special_case: if self.settings['special_case']['case_name'] == 'differential_thrust': self.initial_state[self.x_info['i_base_thrust']] = self.x_info['initial_base_thrust'] self.initial_state[self.x_info['i_differential_parameter']] = self.x_info['initial_differential_parameter'] # bounds # NOTE probably not necessary anymore, as Nelder-Mead method doesn't use them self.bounds = self.x_info['n_variables']*[None] for k, v in self.x_info.items(): if k == 'i_alpha': self.bounds[v] = (self.initial_state[self.x_info['i_alpha']] - 3*np.pi/180, self.initial_state[self.x_info['i_alpha']] + 3*np.pi/180) elif k == 'i_beta': self.bounds[v] = (self.initial_state[self.x_info['i_beta']] - 2*np.pi/180, self.initial_state[self.x_info['i_beta']] + 2*np.pi/180) elif k == 'i_roll': self.bounds[v] = (self.initial_state[self.x_info['i_roll']] - 2*np.pi/180, self.initial_state[self.x_info['i_roll']] + 2*np.pi/180) elif k == 'i_thrust': for ii, i in enumerate(v): self.bounds[i] = (self.initial_state[self.x_info['i_thrust'][ii]] - 2, self.initial_state[self.x_info['i_thrust'][ii]] + 2) elif k == 'i_control_surfaces': for ii, i in enumerate(v): self.bounds[i] = (self.initial_state[self.x_info['i_control_surfaces'][ii]] - 4*np.pi/180, self.initial_state[self.x_info['i_control_surfaces'][ii]] + 4*np.pi/180) elif k == 'i_base_thrust': if self.with_special_case: if self.settings['special_case']['case_name'] == 'differential_thrust': self.bounds[v] = (float(self.x_info['initial_base_thrust'])*0.5, float(self.x_info['initial_base_thrust'])*1.5) elif k == 'i_differential_parameter': if self.with_special_case: if self.settings['special_case']['case_name'] == 'differential_thrust': self.bounds[v] = (-0.5, 0.5) def increase_ts(self): self.data.ts += 1 self.structural_solver.next_step() self.aero_solver.next_step() def cleanup_timestep_info(self): if max(len(self.data.aero.timestep_info), len(self.data.structure.timestep_info)) > 1: # copy last info to first self.data.aero.timestep_info[0] = self.data.aero.timestep_info[-1] self.data.structure.timestep_info[0] = self.data.structure.timestep_info[-1] # delete all the rest while len(self.data.aero.timestep_info) - 1: del self.data.aero.timestep_info[-1] while len(self.data.structure.timestep_info) - 1: del self.data.structure.timestep_info[-1] self.data.ts = 0 def run(self): self.trim_algorithm() return self.data def trim_algorithm(self): # create bounds # call optimiser self.optimise(solver_wrapper, tolerance=self.settings['tolerance'], print_info=True, # method='BFGS') method='Nelder-Mead', # method='SLSQP', refine=self.settings['refine_solution']) # self.optimise(self.solver_wrapper, ) pass def optimise(self, func, tolerance, print_info, method, refine): args = (self.x_info, self, -2) solution = scipy.optimize.minimize(func, self.initial_state, args=args, method=method, options={'disp': print_info, 'maxfev': 1000, 'xatol': tolerance, 'fatol': 1e-4}) if refine: cout.cout_wrap('Refining results with a gradient-based method', 1) solution = scipy.optimize.minimize(func, solution.x, args=args, method='BFGS', options={'disp': print_info, 'eps': 0.05, 'maxfev': 5000, 'fatol': 1e-4}) cout.cout_wrap('Solution = ') cout.cout_wrap(solution.x) return solution def solver_wrapper(x, x_info, solver_data, i_dim=-1): if solver_data.settings['print_info']: cout.cout_wrap('x = ' + str(x), 1) # print('x = ', x) alpha = x[x_info['i_alpha']] beta = x[x_info['i_beta']] roll = x[x_info['i_roll']] # change input data solver_data.data.structure.timestep_info[solver_data.data.ts] = solver_data.data.structure.ini_info.copy() tstep = solver_data.data.structure.timestep_info[solver_data.data.ts] orientation_quat = algebra.euler2quat(np.array([roll, alpha, beta])) tstep.quat[:] = orientation_quat # control surface deflection for i_cs in range(len(x_info['i_control_surfaces'])): solver_data.data.aero.data_dict['control_surface_deflection'][x_info['control_surfaces_id'][i_cs]] = x[x_info['i_control_surfaces'][i_cs]] # thrust input tstep.steady_applied_forces[:] = 0.0 try: x_info['special_case'] except KeyError: for i_thrust in range(len(x_info['i_thrust'])): thrust = x[x_info['i_thrust'][i_thrust]] i_node = x_info['thrust_nodes'][i_thrust] solver_data.data.structure.ini_info.steady_applied_forces[i_node, 0:3] = thrust*x_info['thrust_direction'][i_thrust] else: if x_info['special_case'] == 'differential_thrust': base_thrust = x[x_info['i_base_thrust']] pos_thrust = base_thrust*(1.0 + x[x_info['i_differential_parameter']]) neg_thrust = -base_thrust*(1.0 - x[x_info['i_differential_parameter']]) for i_base_node in x_info['base_thrust_nodes']: solver_data.data.structure.ini_info.steady_applied_forces[i_base_node, 1] = base_thrust for i_pos_diff_node in x_info['positive_thrust_nodes']: solver_data.data.structure.ini_info.steady_applied_forces[i_pos_diff_node, 1] = pos_thrust for i_neg_diff_node in x_info['negative_thrust_nodes']: solver_data.data.structure.ini_info.steady_applied_forces[i_neg_diff_node, 1] = neg_thrust # run the solver solver_data.solver.run() # extract resultants forces, moments = solver_data.solver.extract_resultants() totals = np.zeros((6,)) totals[0:3] = forces totals[3:6] = moments if solver_data.settings['print_info']: cout.cout_wrap(' forces = ' + str(totals), 1) if i_dim >= 0: return totals[i_dim] elif i_dim == -1: return totals elif i_dim == -2: coeffs = np.array([1.0, 1.0, 1.0, 2, 2, 2]) if solver_data.settings['print_info']: cout.cout_wrap(' val = ' + str(np.dot(coeffs*totals, coeffs*totals)), 1) return np.dot(coeffs*totals, coeffs*totals) ================================================ FILE: sharpy/solvers/updatepickle.py ================================================ import numpy as np import sharpy.utils.settings as settings_utils from sharpy.utils.solver_interface import solver, BaseSolver @solver class UpdatePickle(BaseSolver): """ """ solver_id = 'UpdatePickle' settings_types = dict() settings_default = dict() settings_description = dict() settings_table = settings_utils.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.data = None self.settings = None def initialise(self, data, custom_settings=None, restart=False): self.data = data if custom_settings is None: self.settings = data.settings[self.solver_id] else: self.settings = custom_settings settings_utils.to_custom_types(self.settings, self.settings_types, self.settings_default, no_ctype=True) def run(self, **kwargs): for sts in self.data.structure.timestep_info: if sts is not None: sts.in_global_AFoR = True sts.runtime_steady_forces = np.zeros_like(sts.steady_applied_forces) sts.runtime_unsteady_forces = np.zeros_like(sts.steady_applied_forces) sts.psi_local = sts.psi.copy() sts.psi_dot_local = sts.psi_dot.copy() sts.mb_dquatdt = np.zeros_like(sts.mb_quat) return self.data ================================================ FILE: sharpy/structure/__init__.py ================================================ """Structural Packages """ ================================================ FILE: sharpy/structure/basestructure.py ================================================ from abc import ABCMeta, abstractmethod import sharpy.utils.cout_utils as cout import os class BaseStructure(metaclass=ABCMeta): @abstractmethod def generate(self, in_data, settings): pass ================================================ FILE: sharpy/structure/models/__init__.py ================================================ ================================================ FILE: sharpy/structure/models/beam.py ================================================ import ctypes as ct import numpy as np import copy from sharpy.structure.basestructure import BaseStructure import sharpy.structure.models.beamstructures as beamstructures import sharpy.utils.algebra as algebra from sharpy.utils.datastructures import StructTimeStepInfo import sharpy.utils.multibody as mb class Beam(BaseStructure): def __init__(self): self.settings = None # basic info self.num_node_elem = -1 self.num_node = -1 self.num_elem = -1 self.timestep_info = [] self.ini_info = None self.dynamic_input = [] self.connectivities = None self.elem_stiffness = None self.stiffness_db = None self.inv_stiffness_db = None self.n_stiff = 0 self.elem_mass = None self.mass_db = None self.n_mass = 0 self.frame_of_reference_delta = None self.structural_twist = None self.boundary_conditions = None self.beam_number = None self.lumped_mass = None self.lumped_mass_nodes = None self.lumped_mass_inertia = None self.lumped_mass_position = None self.lumped_mass_mat = None self.lumped_mass_mat_nodes = None self.n_lumped_mass = 0 self.steady_app_forces = None self.elements = [] self.master = None self.node_master_elem = None self.vdof = None self.fdof = None self.num_dof = 0 self.fortran = dict() # Multibody variabes self.ini_mb_dict = dict() self.body_number = None self.num_bodies = None self.FoR_movement = None self.global_nodes_num = None self.global_elems_num = None def generate(self, in_data, settings): self.settings = settings # read and store data # type of node self.num_node_elem = in_data['num_node_elem'] # node info self.num_node = in_data['num_node'] self.num_elem = in_data['num_elem'] # Body number try: self.body_number = in_data['body_number'].copy() self.num_bodies = np.max(self.body_number) + 1 except KeyError: self.body_number = np.zeros((self.num_elem, ), dtype=int) self.num_bodies = 1 # boundary conditions self.boundary_conditions = in_data['boundary_conditions'].copy() self.generate_dof_arrays() # ini info self.ini_info = StructTimeStepInfo(self.num_node, self.num_elem, self.num_node_elem, num_dof=self.num_dof, num_bodies=self.num_bodies) # mutibody: FoR information try: for ibody in range(self.num_bodies): self.ini_info.mb_FoR_pos[ibody,:] = self.ini_mb_dict["body_%02d" % ibody]["FoR_position"].copy() self.ini_info.mb_FoR_vel[ibody,:] = self.ini_mb_dict["body_%02d" % ibody]["FoR_velocity"].copy() self.ini_info.mb_FoR_acc[ibody,:] = self.ini_mb_dict["body_%02d" % ibody]["FoR_acceleration"].copy() self.ini_info.mb_quat[ibody,:] = self.ini_mb_dict["body_%02d" % ibody]["quat"].copy() self.ini_info.mb_dict = copy.deepcopy(self.ini_mb_dict) except KeyError: self.ini_info.mb_FoR_pos[0,:] = self.ini_info.for_pos self.ini_info.mb_FoR_vel[0,:] = self.ini_info.for_vel self.ini_info.mb_FoR_acc[0,:] = self.ini_info.for_acc self.ini_info.mb_quat[0,:] = self.ini_info.quat # attention, it has to be copied, not only referenced self.ini_info.pos = in_data['coordinates'].astype(dtype=ct.c_double, order='F') # connectivity information self.connectivities = in_data['connectivities'].astype(dtype=ct.c_int, order='F') self.global_elems_num = np.arange(self.num_elem) self.global_nodes_num = np.arange(self.num_node) # stiffness data self.elem_stiffness = in_data['elem_stiffness'].copy() self.stiffness_db = in_data['stiffness_db'].copy() (self.n_stiff, _, _) = self.stiffness_db.shape self.inv_stiffness_db = np.zeros_like(self.stiffness_db, dtype=ct.c_double, order='F') for i in range(self.n_stiff): self.inv_stiffness_db[i, :, :] = np.linalg.inv(self.stiffness_db[i, :, :]) # mass data self.elem_mass = in_data['elem_mass'].copy() self.mass_db = in_data['mass_db'].copy() (self.n_mass, _, _) = self.mass_db.shape # frame of reference delta self.frame_of_reference_delta = in_data['frame_of_reference_delta'].copy() # structural twist self.structural_twist = in_data['structural_twist'].copy() # beam number for every elem try: self.beam_number = in_data['beam_number'].copy() except KeyError: self.beam_number = np.zeros((self.num_elem, ), dtype=int) # applied forces try: in_data['app_forces'][self.num_node - 1, 5] except IndexError: in_data['app_forces'] = np.zeros((self.num_node, 6), dtype=ct.c_double, order='F') self.steady_app_forces = in_data['app_forces'].astype(dtype=ct.c_double, order='F') # generate the Element array for ielem in range(self.num_elem): self.elements.append( beamstructures.Element( ielem, self.num_node_elem, self.connectivities[ielem, :], self.ini_info.pos[self.connectivities[ielem, :], :], self.frame_of_reference_delta[ielem, :, :], self.structural_twist[ielem, :], self.beam_number[ielem], self.elem_stiffness[ielem], self.elem_mass[ielem])) # now we need to add the attributes like mass and stiffness index for ielem in range(self.num_elem): dictionary = dict() dictionary['stiffness_index'] = self.elem_stiffness[ielem] dictionary['mass_index'] = self.elem_mass[ielem] self.elements[ielem].add_attributes(dictionary) # psi calculation self.generate_psi() # master-slave structure self.generate_master_structure() # the timestep_info[0] is the steady state or initial state for unsteady solutions self.ini_info.steady_applied_forces = self.steady_app_forces.astype(dtype=ct.c_double, order='F') # rigid body rotations self.ini_info.quat = self.settings['orientation'].astype(dtype=ct.c_double, order='F') self.ini_info.for_pos[0:3] = self.settings['for_pos'].astype(dtype=ct.c_double, order='F') self.timestep_info.append(self.ini_info.copy()) self.timestep_info[-1].steady_applied_forces = self.steady_app_forces.astype(dtype=ct.c_double, order='F') # lumped masses self.n_lumped_mass = 0 try: self.lumped_mass = in_data['lumped_mass'].copy() except KeyError: self.lumped_mass = None else: self.lumped_mass_nodes = in_data['lumped_mass_nodes'].copy() self.lumped_mass_inertia = in_data['lumped_mass_inertia'].copy() self.lumped_mass_position = in_data['lumped_mass_position'].copy() self.n_lumped_mass += self.lumped_mass_position.shape[0] # lumped masses given as matrices try: self.lumped_mass_mat = in_data['lumped_mass_mat'].copy() except KeyError: self.lumped_mass_mat = None else: self.lumped_mass_mat_nodes = in_data['lumped_mass_mat_nodes'].copy() self.n_lumped_mass += self.lumped_mass_mat.shape[0] # lumped masses to element mass if self.lumped_mass is not None or self.lumped_mass_mat is not None: self.lump_masses() # self.generate_dof_arrays() self.generate_fortran() def generate_psi(self): # it will just generate the CRV for all the nodes of the element self.ini_info.psi = np.zeros((self.num_elem, 3, 3), dtype=ct.c_double, order='F') for elem in self.elements: self.ini_info.psi[elem.ielem, :, :] = elem.psi_ini def add_unsteady_information(self, dyn_dict, num_steps): # data storage for time dependant input for it in range(num_steps): self.dynamic_input.append(dict()) try: for it in range(num_steps): self.dynamic_input[it]['dynamic_forces'] = dyn_dict['dynamic_forces'][it, :, :] except KeyError: for it in range(num_steps): self.dynamic_input[it]['dynamic_forces'] = np.zeros((self.num_node, 6), dtype=ct.c_double, order='F') try: for it in range(num_steps): self.dynamic_input[it]['for_pos'] = dyn_dict['for_pos'][it, :] except KeyError: for it in range(num_steps): self.dynamic_input[it]['for_pos'] = np.zeros((6, ), dtype=ct.c_double, order='F') try: for it in range(num_steps): self.dynamic_input[it]['for_vel'] = dyn_dict['for_vel'][it, :] except KeyError: for it in range(num_steps): self.dynamic_input[it]['for_vel'] = np.zeros((6, ), dtype=ct.c_double, order='F') try: for it in range(num_steps): self.dynamic_input[it]['for_acc'] = dyn_dict['for_acc'][it, :] except KeyError: for it in range(num_steps): self.dynamic_input[it]['for_acc'] = np.zeros((6, ), dtype=ct.c_double, order='F') def generate_dof_arrays(self): self.vdof = np.zeros((self.num_node,), dtype=ct.c_int, order='F') - 1 self.fdof = np.zeros((self.num_node,), dtype=ct.c_int, order='F') - 1 vcounter = -1 fcounter = -1 for inode in range(self.num_node): if self.boundary_conditions[inode] == 0: vcounter += 1 fcounter += 1 self.vdof[inode] = vcounter self.fdof[inode] = fcounter elif self.boundary_conditions[inode] == -1: vcounter += 1 self.vdof[inode] = vcounter elif self.boundary_conditions[inode] == 1: fcounter += 1 self.fdof[inode] = fcounter self.num_dof = ct.c_int((vcounter + 1)*6) def generate_mass_matrix(self, mass, position, inertia): inertia_tensor = np.zeros((6, 6)) pos_skew = algebra.skew(position) inertia_tensor[0:3, 0:3] = mass*np.eye(3) inertia_tensor[0:3, 3:6] = -mass*pos_skew inertia_tensor[3:6, 0:3] = mass*pos_skew inertia_tensor[3:6, 3:6] = inertia + mass*(np.dot(pos_skew.T, pos_skew)) return inertia_tensor def lump_masses(self): if self.lumped_mass is not None: for i_lumped in range(self.lumped_mass.shape[0]): r = self.lumped_mass_position[i_lumped, :] m = self.lumped_mass[i_lumped] j = self.lumped_mass_inertia[i_lumped, :, :] inertia_tensor = self.generate_mass_matrix(m, r, j) i_lumped_node = self.lumped_mass_nodes[i_lumped] self.add_lumped_mass_to_element(i_lumped_node, inertia_tensor) if self.lumped_mass_mat is not None: for i_lumped in range(self.lumped_mass_mat.shape[0]): inertia_tensor = self.lumped_mass_mat[i_lumped, :, :] i_lumped_node = self.lumped_mass_mat_nodes[i_lumped] self.add_lumped_mass_to_element(i_lumped_node, inertia_tensor) return def add_lumped_mass_to_element(self, i_lumped_node, inertia_tensor, replace=False): i_lumped_master_elem, i_lumped_master_node_local = self.node_master_elem[i_lumped_node] if self.elements[i_lumped_master_elem].rbmass is None: # allocate memory self.elements[i_lumped_master_elem].rbmass = np.zeros(( self.elements[i_lumped_master_elem].max_nodes_elem, 6, 6)) if replace: self.elements[i_lumped_master_elem].rbmass[i_lumped_master_node_local, :, :] = ( inertia_tensor) else: self.elements[i_lumped_master_elem].rbmass[i_lumped_master_node_local, :, :] += ( inertia_tensor) # += necessary in case multiple masses defined per node def generate_master_structure(self): self.master = np.zeros((self.num_elem, self.num_node_elem, 2), dtype=int) - 1 for i_elem in range(self.num_elem): for i_node_local in range(self.elements[i_elem].n_nodes): # for i_node_local in self.elements[i_elem].ordering: if not i_elem and not i_node_local: continue j_elem = 0 while self.master[i_elem, i_node_local, 0] == -1 and j_elem <= i_elem: # for j_node_local in self.elements[j_elem].ordering: for j_node_local in range(self.elements[j_elem].n_nodes): # for j_node_local in self.elements[j_elem].ordering: if (self.connectivities[i_elem, i_node_local] == self.connectivities[j_elem, j_node_local]): self.master[i_elem, i_node_local, :] = [j_elem, j_node_local] j_elem += 1 self.generate_node_master_elem() # a = 1 def add_timestep(self, timestep_info): if len(timestep_info) == 0: # copy from ini_info timestep_info.append(self.ini_info.copy()) else: timestep_info.append(self.timestep_info[-1].copy()) def next_step(self): self.add_timestep(self.timestep_info) def generate_node_master_elem(self): """ Returns a matrix indicating the master element for a given node :return: """ self.node_master_elem = np.zeros((self.num_node, 2), dtype=ct.c_int, order='F') - 1 for i_elem in range(self.num_elem): for i_node_local in range(self.elements[i_elem].n_nodes): if self.master[i_elem, i_node_local, 0] == -1: if self.node_master_elem[self.connectivities[i_elem, i_node_local], 0] < 0: self.node_master_elem[self.connectivities[i_elem, i_node_local], 0] = i_elem self.node_master_elem[self.connectivities[i_elem, i_node_local], 1] = i_node_local else: master_elem = self.master[i_elem, i_node_local, 0] master_node = self.master[i_elem, i_node_local, 1] if self.node_master_elem[self.connectivities[i_elem, i_node_local], 0] < 0: self.node_master_elem[self.connectivities[i_elem, i_node_local], 0] = master_elem self.node_master_elem[self.connectivities[i_elem, i_node_local], 1] = master_node def generate_fortran(self): # steady, no time-dependant information self.fortran['num_nodes'] = np.zeros((self.num_elem,), dtype=ct.c_int, order='F') for elem in self.elements: self.fortran['num_nodes'][elem.ielem] = elem.n_nodes self.fortran['num_mem'] = np.zeros_like(self.fortran['num_nodes'], dtype=ct.c_int) for elem in self.elements: self.fortran['num_mem'][elem.ielem] = elem.num_mem self.fortran['connectivities'] = self.connectivities.astype(ct.c_int, order='F') + 1 self.fortran['master'] = self.master.astype(dtype=ct.c_int, order='F') + 1 self.fortran['node_master_elem'] = self.node_master_elem.astype(dtype=ct.c_int, order='F') + 1 self.fortran['length'] = np.zeros_like(self.fortran['num_nodes'], dtype=ct.c_double, order='F') for elem in self.elements: self.fortran['length'][elem.ielem] = elem.length self.fortran['mass'] = self.mass_db.astype(ct.c_double, order='F') self.fortran['stiffness'] = self.stiffness_db.astype(ct.c_double, order='F') self.fortran['inv_stiffness'] = self.inv_stiffness_db.astype(ct.c_double, order='F') self.fortran['mass_indices'] = self.elem_mass.astype(ct.c_int, order='F') + 1 self.fortran['stiffness_indices'] = self.elem_stiffness.astype(ct.c_int, order='F') + 1 self.fortran['frame_of_reference_delta'] = self.frame_of_reference_delta.astype(ct.c_double, order='F') self.fortran['vdof'] = self.vdof.astype(ct.c_int, order='F') + 1 self.fortran['fdof'] = self.fdof.astype(ct.c_int, order='F') + 1 # self.fortran['steady_applied_forces'] = self.steady_app_forces.astype(dtype=ct.c_double, order='F') # undeformed structure matrices self.fortran['pos_ini'] = self.ini_info.pos.astype(dtype=ct.c_double, order='F') self.fortran['psi_ini'] = self.ini_info.psi.astype(dtype=ct.c_double, order='F') max_nodes_elem = self.elements[0].max_nodes_elem rbmass_temp = np.zeros((self.num_elem, max_nodes_elem, 6, 6)) for elem in self.elements: for inode in range(elem.n_nodes): if elem.rbmass is not None: rbmass_temp[elem.ielem, inode, :, :] += elem.rbmass[inode, :, :] self.fortran['rbmass'] = rbmass_temp.astype(dtype=ct.c_double, order='F') if self.settings['unsteady']: pass def integrate_position(self, ts, dt): try: ts.for_pos[0:3] += ( dt*np.dot(ts.cga(), ts.for_vel[0:3])) except AttributeError: self.timestep_info[ts].for_pos[0:3] += ( dt*np.dot(self.timestep_info[ts].cga(), self.timestep_info[ts].for_vel[0:3])) def nodal_premultiply_inv_T_transpose(self, nodal, tstep, filter=np.array([True]*6)): nodal_t = nodal.copy(order='F') for i_node in range(self.num_node): # get master elem and i_local_node i_master_elem, i_local_node = self.node_master_elem[i_node, :] crv = tstep.psi[i_master_elem, i_local_node, :] inv_tanT = algebra.crv2invtant(crv) temp = np.zeros((6,)) temp[0:3] = np.dot(inv_tanT, nodal[i_node, 0:3]) temp[3:6] = np.dot(inv_tanT, nodal[i_node, 3:6]) for i in range(6): if filter[i]: nodal_t[i_node, i] = temp[i] return nodal_t def get_body(self, ibody): """ get_body Extract the body number ``ibody`` from a multibody system This function returns a :class:`~sharpy.structure.models.beam.Beam` class (``ibody_beam``) that only includes the body number ``ibody`` of the original system Args: self(:class:`~sharpy.structure.models.beam.Beam`): structural information of the multibody system ibody(int): body number to be extracted Returns: ibody_beam(:class:`~sharpy.structure.models.beam.Beam`): structural information of the isolated body """ ibody_beam = Beam() # Define the nodes and elements belonging to the body ibody_beam.global_elems_num, ibody_beam.global_nodes_num = mb.get_elems_nodes_list(self, ibody) # Renaming for clarity ibody_elements = ibody_beam.global_elems_num ibody_nodes = ibody_beam.global_nodes_num # Assign all the properties to the new StructTimeStepInfo ibody_beam.settings = self.settings.copy() ibody_beam.num_node_elem = self.num_node_elem.astype(dtype=ct.c_int, order='F', copy=True) ibody_beam.num_node = len(ibody_beam.global_nodes_num) ibody_beam.num_elem = len(ibody_beam.global_elems_num) ibody_beam.connectivities = self.connectivities[ibody_elements,:] # Renumber the connectivities int_list_nodes = np.arange(0, ibody_beam.num_node, 1) for ielem in range(ibody_beam.num_elem): for inode_in_elem in range(ibody_beam.num_node_elem): ibody_beam.connectivities[ielem, inode_in_elem] = int_list_nodes[ np.argwhere(ibody_nodes == ibody_beam.connectivities[ielem, inode_in_elem])[0][0]] # TODO: I could copy only the needed stiffness and masses to save storage ibody_beam.elem_stiffness = self.elem_stiffness[ibody_elements].astype(dtype=ct.c_int, order='F', copy=True) ibody_beam.stiffness_db = self.stiffness_db.astype(dtype=ct.c_double, order='F', copy=True) ibody_beam.inv_stiffness_db = self.inv_stiffness_db.astype(dtype=ct.c_double, order='F', copy=True) ibody_beam.n_stiff = self.n_stiff ibody_beam.elem_mass = self.elem_mass[ibody_elements].astype(dtype=ct.c_int, order='F', copy=True) ibody_beam.mass_db = self.mass_db.astype(dtype=ct.c_double, order='F', copy=True) ibody_beam.n_mass = self.n_mass ibody_beam.frame_of_reference_delta = self.frame_of_reference_delta[ibody_elements,:,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_beam.structural_twist = self.structural_twist[ibody_elements, :].astype(dtype=ct.c_double, order='F', copy=True) ibody_beam.boundary_conditions = self.boundary_conditions[ibody_nodes].astype(dtype=ct.c_int, order='F', copy=True) ibody_beam.beam_number = self.beam_number[ibody_elements].astype(dtype=ct.c_int, order='F', copy=True) if not self.lumped_mass_nodes is None: is_first = True ibody_beam.n_lumped_mass = 0 for inode in range(len(self.lumped_mass_nodes)): if self.lumped_mass_nodes[inode] in ibody_nodes: if is_first: is_first = False ibody_beam.lumped_mass_nodes = int_list_nodes[ibody_nodes == self.lumped_mass_nodes[inode]] ibody_beam.lumped_mass = np.array([self.lumped_mass[inode]]) ibody_beam.lumped_mass_inertia = np.array([self.lumped_mass_inertia[inode]]) ibody_beam.lumped_mass_position = np.array([self.lumped_mass_position[inode]]) ibody_beam.n_lumped_mass += 1 else: ibody_beam.lumped_mass_nodes = np.concatenate((ibody_beam.lumped_mass_nodes ,int_list_nodes[ibody_nodes == self.lumped_mass_nodes[inode]]), axis=0) ibody_beam.lumped_mass = np.concatenate((ibody_beam.lumped_mass ,np.array([self.lumped_mass[inode]])), axis=0) ibody_beam.lumped_mass_inertia = np.concatenate((ibody_beam.lumped_mass_inertia ,np.array([self.lumped_mass_inertia[inode]])), axis=0) ibody_beam.lumped_mass_position = np.concatenate((ibody_beam.lumped_mass_position ,np.array([self.lumped_mass_position[inode]])), axis=0) ibody_beam.n_lumped_mass += 1 if not self.lumped_mass_mat is None: is_first = True for inode in range(len(self.lumped_mass_mat_nodes)): if self.lumped_mass_mat_nodes[inode] in ibody_nodes: if is_first: is_first = False ibody_beam.lumped_mass_mat_nodes = int_list_nodes[ibody_nodes == self.lumped_mass_mat_nodes[inode]] ibody_beam.lumped_mass_mat = np.array([self.lumped_mass_mat[inode, :, :]]) else: ibody_beam.lumped_mass_mat_nodes = np.concatenate((ibody_beam.lumped_mass_mat_nodes , int_list_nodes[ibody_nodes == self.lumped_mass_mat_nodes[inode]]), axis=0) ibody_beam.lumped_mass_mat = np.concatenate((ibody_beam.lumped_mass_mat ,np.array([self.lumped_mass_mat[inode, :, :]])), axis=0) ibody_beam.steady_app_forces = self.steady_app_forces[ibody_nodes,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_beam.num_bodies = 1 ibody_beam.body_number = self.body_number[ibody_elements].astype(dtype=ct.c_int, order='F', copy=True) ibody_beam.generate_dof_arrays() ibody_beam.ini_info = self.ini_info.get_body(self, ibody_beam.num_dof, ibody) ibody_beam.timestep_info = self.timestep_info[-1].get_body(self, ibody_beam.num_dof, ibody) # generate the Element array for ielem in range(ibody_beam.num_elem): ibody_beam.elements.append( beamstructures.Element( ielem, ibody_beam.num_node_elem, ibody_beam.connectivities[ielem, :], ibody_beam.ini_info.pos[ibody_beam.connectivities[ielem, :], :], ibody_beam.frame_of_reference_delta[ielem, :, :], ibody_beam.structural_twist[ielem, :], ibody_beam.beam_number[ielem], ibody_beam.elem_stiffness[ielem], ibody_beam.elem_mass[ielem])) # now we need to add the attributes like mass and stiffness index for ielem in range(ibody_beam.num_elem): dictionary = dict() dictionary['stiffness_index'] = ibody_beam.elem_stiffness[ielem] dictionary['mass_index'] = ibody_beam.elem_mass[ielem] ibody_beam.elements[ielem].add_attributes(dictionary) ibody_beam.generate_master_structure() if ibody_beam.lumped_mass is not None or ibody_beam.lumped_mass_mat is not None: ibody_beam.lump_masses() ibody_beam.generate_fortran() return ibody_beam ================================================ FILE: sharpy/structure/models/beamstructures.py ================================================ # Alfonso del Carre import numpy as np import sharpy.utils.algebra as algebra class Element(object): """ This class stores all the required data for the definition of a linear or quadratic beam element. """ ordering = [0, 2, 1] max_nodes_elem = 3 def __init__(self, ielem, n_nodes, global_connectivities, coordinates, frame_of_reference_delta, structural_twist, num_mem, stiff_index, mass_index): # store info in instance # global element number self.ielem = ielem # number of nodes per elem self.n_nodes = n_nodes if self.max_nodes_elem < self.n_nodes: raise AttributeError('Elements with more than 3 nodes are not allowed') # global connectivities (global node numbers) self.global_connectivities = global_connectivities self.reordered_global_connectivities = global_connectivities[self.ordering] # coordinates of the nodes in a-frame (body-fixed frame) self.coordinates_def = coordinates.copy() # frame of reference points self.frame_of_reference_delta = frame_of_reference_delta # structural twist self.structural_twist = structural_twist # number in memory (for fortran routines) self.num_mem = num_mem # stiffness and mass matrices indices (stored in parent beam class) self.stiff_index = stiff_index self.mass_index = mass_index # placeholder for RBMass self.rbmass = None # np.zeros((self.max_nodes_elem, 6, 6)) self.update(self.coordinates_def) def update(self, coordinates_def, psi_def=None): self.coordinates_def = coordinates_def.copy() if psi_def is not None: # element orientation self.psi_def = psi_def.copy() # element length self.calculate_length() if psi_def is None: # ini conditions, initial crv has to be calculated # we need to define the FoR z direction for every beam element v1, v2, v3 = self.get_triad() self.psi_ini = algebra.triad2crv_vec(v1, v2, v3) self.psi_def = self.psi_ini.copy() # copy all the info to _ini fields self.coordinates_ini = self.coordinates_def.copy() def calculate_length(self): # TODO implement length based on integration self.length = np.linalg.norm(self.coordinates_def[0, :] - self.coordinates_def[1, :]) def add_attributes(self, dictionary): for key, value in dictionary.items(): setattr(self, key, value) def generate_curve(self, n_elem_curve, defor=False): curve = np.zeros((n_elem_curve, 3)) t_vec = np.linspace(0, 2, n_elem_curve) for i in range(n_elem_curve): t = t_vec[i] for idim in range(3): if defor: polyfit, _, _ = algebra.get_polyfit(self.coordinates_def, self.ordering) else: polyfit, _, _ = algebra.get_polyfit(self.coordinates_ini, self.ordering) polyf = np.poly1d(polyfit[idim]) curve[i, idim] = (polyf(t)) return curve def get_triad(self): """ Generates two unit vectors in body FoR that define the local FoR for a beam element. These vectors are calculated using `frame_of_reference_delta` :return: """ # now, calculate tangent vector (and coefficients of the polynomial # fit just in case) tangent, polyfit = algebra.tangent_vector( self.coordinates_def, Element.ordering) normal = np.zeros_like(tangent) binormal = np.zeros_like(tangent) # v_vector is the vector with origin the FoR node and delta # equals frame_of_reference_delta for inode in range(self.n_nodes): v_vector = self.frame_of_reference_delta[inode, :] normal[inode, :] = algebra.unit_vector(np.cross( tangent[inode, :], v_vector ) ) binormal[inode, :] = -algebra.unit_vector(np.cross( tangent[inode, :], normal[inode, :] ) ) # we apply twist now for inode in range(self.n_nodes): if not self.structural_twist[inode] == 0.0: rotation_mat = algebra.rotation_matrix_around_axis(tangent[inode, :], self.structural_twist[inode]) normal[inode, :] = np.dot(rotation_mat, normal[inode, :]) binormal[inode, :] = np.dot(rotation_mat, binormal[inode, :]) return tangent, binormal, normal def deformed_triad(self, psi=None): if psi is None: return algebra.crv2triad_vec(self.psi_def) else: return algebra.crv2triad_vec(psi) ================================================ FILE: sharpy/structure/utils/__init__.py ================================================ ================================================ FILE: sharpy/structure/utils/lagrangeconstraints.py ================================================ """ LagrangeConstraints library Library used to create the matrices associated to boundary conditions through the method of Lagrange Multipliers. The source code includes four different sections. * Basic structures: basic functions and variables needed to organise the library with different Lagrange Constraints to enhance the interaction with this library. * Auxiliar functions: basic queries that are performed repeatedly. * Equations: functions that generate the equations associated to the constraint of basic degrees of freedom. * Lagrange Constraints: different available Lagrange Constraints. They tipically use the basic functions in "Equations" to assembly the required set of equations. Attributes: dict_of_lc (dict): Dictionary including the available Lagrange Contraint identifier (``_lc_id``) and the associated ``BaseLagrangeConstraint`` class Notes: To use this library: import sharpy.structure.utils.lagrangeconstraints as lagrangeconstraints Args: lc_list (list): list of all the defined contraints MBdict (dict): dictionary with the MultiBody and LagrangeMultipliers information MB_beam (list): list of :class:`~sharpy.structure.models.beam.Beam` of each of the bodies that form the system MB_tstep (list): list of :class:`~sharpy.utils.datastructures.StructTimeStepInfo` of each of the bodies that form the system num_LM_eq (int): number of new equations needed to define the boundary boundary conditions sys_size (int): total number of degrees of freedom of the multibody system dt (float): time step Lambda (np.ndarray): list of Lagrange multipliers values Lambda_dot (np.ndarray): list of the first derivative of the Lagrange multipliers values dynamic_or_static (str): string defining if the computation is dynamic or static LM_C (np.ndarray): Damping matrix associated to the Lagrange Multipliers equations LM_K (np.ndarray): Stiffness matrix associated to the Lagrange Multipliers equations LM_Q (np.ndarray): Vector of independent terms associated to the Lagrange Multipliers equations """ from abc import ABCMeta, abstractmethod import sharpy.utils.cout_utils as cout import os import ctypes as ct import numpy as np import sharpy.utils.algebra as ag from sharpy.utils.settings import set_value_or_default ############################################################################### # Basic structures ############################################################################### dict_of_lc = {} lc = {} # for internal working # decorator def lagrangeconstraint(arg): """ Decorator used to create the dictionary (``dict_of_lc``) that links constraints id (``_lc_id``) to the associated ``BaseLagrangeConstraint`` class """ global dict_of_lc try: arg._lc_id except AttributeError: raise AttributeError('Class defined as lagrange constraint has no _lc_id attribute') dict_of_lc[arg._lc_id] = arg return arg def print_available_lc(): """ Prints the available Lagrange Constraints """ cout.cout_wrap('The available lagrange constraints on this session are:', 2) for name, i_lc in dict_of_lc.items(): cout.cout_wrap('%s ' % i_lc._lc_id, 2) def lc_from_string(string): """ Returns the ``BaseLagrangeConstraint`` class associated to a constraint id (``_lc_id``) """ return dict_of_lc[string] def lc_list_from_path(cwd): onlyfiles = [f for f in os.listdir(cwd) if os.path.isfile(os.path.join(cwd, f))] for i_file in range(len(onlyfiles)): if ".py" in onlyfiles[i_file]: if onlyfiles[i_file] == "__init__.py": onlyfiles[i_file] = "" continue onlyfiles[i_file] = onlyfiles[i_file].replace('.py', '') else: onlyfiles[i_file] = "" files = [file for file in onlyfiles if not file == ""] return files def initialise_lc(lc_name, print_info=True): """ Initialises the Lagrange Constraints """ if print_info: cout.cout_wrap('Generating an instance of %s' % lc_name, 2) cls_type = lc_from_string(lc_name) lc = cls_type() return lc class BaseLagrangeConstraint(metaclass=ABCMeta): __doc__ = """ BaseLagrangeConstraint Base class for LagrangeConstraints showing the methods required. They will be inherited by all the Lagrange Constraints Attributes: _n_eq (int): Number of equations required by a LagrangeConstraint _ieq (int): Number of the first equation associated to the Lagrange Constraint in the whole set of Lagrange equations """ _lc_id = 'BaseLagrangeConstraint' def __init__(self): """ Initialisation """ self._n_eq = None self._ieq = None @abstractmethod def get_n_eq(self): """ Returns the number of equations required by the Lagrange Constraint """ return self._n_eq @abstractmethod # def initialise(self, **kwargs): def initialise(self, MBdict_entry, ieq): """ Initialisation """ self._ieq = ieq return self._ieq + self._n_eq @abstractmethod # def staticmat(self, **kwargs): def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): """ Generates the structural matrices (damping, stiffness) and the independent vector associated to the LagrangeConstraint in a static simulation """ return np.zeros((6, 6)) @abstractmethod # def dynamicmat(self, **kwargs): def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): """ Generates the structural matrices (damping, stiffness) and the independent vector associated to the LagrangeConstraint in a dynamic simulation """ return np.zeros((10, 10)) @abstractmethod # def staticpost(self, **kwargs): def staticpost(self, lc_list, MB_beam, MB_tstep): """ Postprocess operations needed by the LagrangeConstraint in a static simulation """ return @abstractmethod # def dynamicpost(self, **kwargs): def dynamicpost(self, lc_list, MB_beam, MB_tstep): """ Postprocess operations needed by the LagrangeConstraint in a dynamic simulation """ return ################################################################################ # Auxiliar functions ################################################################################ def define_node_dof(MB_beam, node_body, num_node): """ define_node_dof Define the position of the first degree of freedom associated to a certain node Args: MB_beam(list): list of :class:`~sharpy.structure.models.beam.Beam` node_body(int): body to which the node belongs num_node(int): number os the node within the body Returns: node_dof(int): first degree of freedom associated to the node """ node_dof = 0 for ibody in range(node_body): node_dof += MB_beam[ibody].num_dof.value if MB_beam[ibody].FoR_movement == 'free': node_dof += 10 node_dof += 6 * MB_beam[node_body].vdof[num_node] return node_dof def define_FoR_dof(MB_beam, FoR_body): """ define_FoR_dof Define the position of the first degree of freedom associated to a certain frame of reference Args: MB_beam(list): list of :class:`~sharpy.structure.models.beam.Beam` node_body(int): body to which the node belongs num_node(int): number os the node within the body Returns: node_dof(int): first degree of freedom associated to the node """ FoR_dof = 0 for ibody in range(FoR_body): FoR_dof += MB_beam[ibody].num_dof.value if MB_beam[ibody].FoR_movement == 'free': FoR_dof += 10 FoR_dof += MB_beam[FoR_body].num_dof.value return FoR_dof ################################################################################ # Equations ################################################################################ def equal_pos_node_FoR(MB_tstep, MB_beam, FoR_body, node_body, inode_in_body, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda, scalingFactor, penaltyFactor, ieq, LM_K, LM_C, LM_Q): """ This function generates the stiffness and damping matrices and the independent vector associated to a constraint that imposes equal positions between a node and a frame of reference See ``LagrangeConstraints`` for the description of variables Args: node_FoR_dof (int): position of the first degree of freedom of the FoR to which the "node" belongs node_dof (int): position of the first degree of freedom associated to the "node" FoR_body (int): body number of the "FoR" FoR_dof (int): position of the first degree of freedom associated to the "FoR" Note: this equation constitutes a holonomic constraint which is not currently supported. Check ``equal_lin_vel_node_FoR`` """ cout.cout_wrap( "WARNING: this equation constitutes a holonomic constraint which is not currently supported. Check ``equal_lin_vel_node_FoR``", 3) num_LM_eq_specific = 3 B = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Simplify notation node_cga = MB_tstep[node_body].cga() node_pos = MB_tstep[node_body].pos[inode_in_body, :] node_FoR_pos = MB_tstep[node_body].for_pos[0:3] FoR_pos = MB_tstep[FoR_body].for_pos[0:3] # if MB_beam[node_body].FoR_movement == 'free': B[:, node_FoR_dof:node_FoR_dof + 3] = np.eye(3) B[:, node_dof:node_dof + 3] = node_cga B[:, FoR_dof:FoR_dof + 3] = -np.eye(3) LM_K[sys_size + ieq: sys_size + ieq + num_LM_eq_specific, :sys_size] += scalingFactor * B LM_K[:sys_size, sys_size + ieq: sys_size + ieq + num_LM_eq_specific] += scalingFactor * B.T LM_Q[:sys_size] += scalingFactor * B.T @ Lambda[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += scalingFactor * (node_FoR_pos + node_cga @ node_pos - FoR_pos) LM_C[node_dof:node_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] += scalingFactor * ag.der_CquatT_by_v( MB_tstep[node_body].quat, Lambda[ieq: ieq + num_LM_eq_specific]) if penaltyFactor: q = np.zeros((sys_size,)) q[node_FoR_dof:node_FoR_dof + 3] = node_FoR_pos q[node_dof:node_dof + 3] = node_pos q[FoR_dof:FoR_dof + 3] = FoR_pos LM_Q[:sys_size] += penaltyFactor * B.T @ B @ q LM_K[node_FoR_dof:node_FoR_dof + 3, node_FoR_dof:node_FoR_dof + 3] += penaltyFactor * np.eye(3) LM_K[node_FoR_dof:node_FoR_dof + 3, node_dof:node_dof + 3] += penaltyFactor * node_cga LM_K[node_FoR_dof:node_FoR_dof + 3, FoR_dof:FoR_dof + 3] += -penaltyFactor * np.eye(3) LM_C[node_FoR_dof:node_FoR_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] += penaltyFactor * ag.der_Cquat_by_v( MB_tstep[node_body].quat, node_pos) LM_K[node_dof:node_dof + 3, node_FoR_dof:node_FoR_dof + 3] += penaltyFactor * node_cga.T LM_K[node_dof:node_dof + 3, node_dof:node_dof + 3] += penaltyFactor * np.eye(3) LM_K[node_dof:node_dof + 3, FoR_dof:FoR_dof + 3] += -penaltyFactor * node_cga.T LM_C[node_dof:node_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] += penaltyFactor * ( ag.der_CquatT_by_v(MB_tstep[node_body].quat, node_FoR_pos - FoR_pos)) LM_K[FoR_dof:FoR_dof + 3, node_FoR_dof:node_FoR_dof + 3] += -penaltyFactor * np.eye(3) LM_K[FoR_dof:FoR_dof + 3, node_dof:node_dof + 3] += -penaltyFactor * node_cga.T LM_K[FoR_dof:FoR_dof + 3, FoR_dof:FoR_dof + 3] += penaltyFactor * np.eye(3) LM_C[FoR_dof:FoR_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] += -penaltyFactor * ag.der_Cquat_by_v( MB_tstep[node_body].quat, node_pos) ieq += 3 return ieq def equal_lin_vel_node_FoR(MB_tstep, MB_beam, FoR_body, node_body, node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, scalingFactor, penaltyFactor, ieq, LM_K, LM_C, LM_Q, rel_posB=np.zeros((3))): """ This function generates the stiffness and damping matrices and the independent vector associated to a constraint that imposes equal linear velocities between a node and a frame of reference See ``LagrangeConstraints`` for the description of variables Args: node_number (int): number of the "node" within its own body node_body (int): body number of the "node" node_FoR_dof (int): position of the first degree of freedom of the FoR to which the "node" belongs node_dof (int): position of the first degree of freedom associated to the "node" FoR_body (int): body number of the "FoR" FoR_dof (int): position of the first degree of freedom associated to the "FoR" rel_posB (float np.array): relative position between the node and the FoR (in the node B FoR) """ num_LM_eq_specific = 3 Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Simplify notation node_cga = MB_tstep[node_body].cga() node_FoR_va = MB_tstep[node_body].for_vel[0:3] node_FoR_wa = MB_tstep[node_body].for_vel[3:6] ielem, inode_in_elem = MB_beam[node_body].node_master_elem[node_number] psi = MB_tstep[node_body].psi[ielem, inode_in_elem, :] node_cab = ag.crv2rotation(psi) node_Ra = MB_tstep[node_body].pos[node_number, :] + node_cab @ rel_posB node_dot_Ra = MB_tstep[node_body].pos_dot[node_number, :] FoR_cga = MB_tstep[FoR_body].cga() FoR_va = MB_tstep[FoR_body].for_vel[0:3] Bnh[:, FoR_dof:FoR_dof + 3] = FoR_cga Bnh[:, node_dof:node_dof + 3] = -1. * node_cga if MB_beam[node_body].FoR_movement == 'free': Bnh[:, node_FoR_dof:node_FoR_dof + 3] = -1. * node_cga Bnh[:, node_FoR_dof + 3:node_FoR_dof + 6] = node_cga @ ag.skew(node_Ra) LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += scalingFactor * Bnh.T LM_Q[:sys_size] += scalingFactor * Bnh.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += scalingFactor * (FoR_cga @ FoR_va - node_cga @ (node_dot_Ra + node_FoR_va - ag.skew(node_Ra) @ node_FoR_wa)) LM_C[FoR_dof:FoR_dof + 3, FoR_dof + 6:FoR_dof + 10] \ += scalingFactor * ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, Lambda_dot[ieq:ieq + num_LM_eq_specific]) if MB_beam[node_body].FoR_movement == 'free': LM_C[node_dof:node_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] -= scalingFactor * ag.der_CquatT_by_v( MB_tstep[node_body].quat, Lambda_dot[ieq:ieq + num_LM_eq_specific]) LM_C[node_FoR_dof:node_FoR_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] -= scalingFactor * ag.der_CquatT_by_v( MB_tstep[node_body].quat, Lambda_dot[ieq:ieq + num_LM_eq_specific]) LM_C[node_FoR_dof + 3:node_FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += (scalingFactor * ag.skew(node_Ra).T @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, Lambda_dot[ieq:ieq + num_LM_eq_specific])) # non-trivial - verified by hand (involves multiple transformations, Dynamics of Flexible Aircraft Appen. C) LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof:node_dof + 3] += scalingFactor * ag.skew( node_cga.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific]) if penaltyFactor: if MB_beam[node_body].FoR_movement == 'free': # TODO: follow general approach to derive terms - first 4*4 terms, then LMC derivatives, then LMK derivatives - this is why penalty didn't work! # Simplify notation node_cga = MB_tstep[node_body].cga() FoR_cga = MB_tstep[FoR_body].cga() psi_dot = MB_tstep[node_body].psi_dot[ielem, inode_in_elem, :] q = np.zeros((sys_size)) q[FoR_dof:FoR_dof + 3] = FoR_va q[node_dof:node_dof + 3] = node_dot_Ra q[node_dof + 3:node_dof + 6] = psi_dot q[node_FoR_dof:node_FoR_dof + 3] = node_FoR_va q[node_FoR_dof + 3:node_FoR_dof + 6] = node_FoR_wa LM_Q[:sys_size] += penaltyFactor * Bnh.T @ Bnh @ q # # 16 canonical terms for (abcd)^T(abcd) LM_C[:sys_size, :sys_size] += penaltyFactor * Bnh.T @ Bnh # other LM_C derivatives for c dependencies in x1 and x2 # term 1-x1 - \frac{\partial}{\partial x_1}(a^Taq_1 + a^Tbq2 + a^Tcq3 + a^Tdq4) # da^Tdxaq_1 + a^Tdadxaq_1 + da^Tdxbq_2 + a^Tdbdxq_2 + da^Tdxcq_3 + a^Tdcdxq_3 + da^Tdxdq_4 mat = -np.eye(3) vec = -node_cga @ node_dot_Ra LM_C[node_dof:node_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) mat = node_cga.T vec = node_dot_Ra LM_C[node_dof:node_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = -np.eye(3) vec = -node_cga @ node_FoR_va LM_C[node_dof:node_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) mat = node_cga.T vec = node_FoR_va LM_C[node_dof:node_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = -np.eye(3) vec = node_cga @ ag.skew(node_Ra) @ node_FoR_wa LM_C[node_dof:node_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) mat = -node_cga.T vec = ag.skew(node_Ra) @ node_FoR_wa LM_C[node_dof:node_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = -np.eye(3) vec = FoR_cga @ FoR_va LM_C[node_dof:node_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) # term 2-x1 - \frac{\partial}{\partial x_1}(b^Taq_1 + b^Tbq2 + b^Tcq3 + b^Tdq4) # db^Tdxaq_1 + b^Tdadxaq_1 + db^Tdxbq_2 + b^Tdbdxq_2 + db^Tdxcq_3 + b^Tdcdxq_3 + db^Tdxdq_4 mat = -np.eye(3) vec = -node_cga @ node_dot_Ra LM_C[node_FoR_dof:node_FoR_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) mat = node_cga.T vec = node_dot_Ra LM_C[node_FoR_dof:node_FoR_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = -np.eye(3) vec = -node_cga @ node_FoR_va LM_C[node_FoR_dof:node_FoR_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) mat = node_cga.T vec = node_FoR_va LM_C[node_FoR_dof:node_FoR_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = -np.eye(3) vec = node_cga @ ag.skew(node_Ra) @ node_FoR_wa LM_C[node_FoR_dof:node_FoR_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) mat = -node_cga.T vec = ag.skew(node_Ra) @ node_FoR_wa LM_C[node_FoR_dof:node_FoR_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = -np.eye(3) vec = FoR_cga @ FoR_va LM_C[node_FoR_dof:node_FoR_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) # term 3-x1 - \frac{\partial}{\partial x_1}(c^Taq_1 + c^Tbq2 + c^Tcq3 + c^Tdq4) # dc^Tdxaq_1 + c^Tdadxaq_1 + dc^Tdxbq_2 + c^Tdbdxq_2 + dc^Tdxcq_3 + c^Tdcdxq_3 + dc^Tdxdq_4 mat = ag.skew(node_Ra).T vec = -node_cga @ node_dot_Ra LM_C[node_FoR_dof + 3:node_FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) mat = -ag.skew(node_Ra).T @ node_cga.T vec = node_dot_Ra LM_C[node_FoR_dof + 3:node_FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = ag.skew(node_Ra).T vec = -node_cga @ node_FoR_va LM_C[node_FoR_dof + 3:node_FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) mat = -ag.skew(node_Ra).T @ node_cga.T vec = node_FoR_va LM_C[node_FoR_dof + 3:node_FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = ag.skew(node_Ra).T vec = node_cga @ ag.skew(node_Ra) @ node_FoR_wa LM_C[node_FoR_dof + 3:node_FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) mat = ag.skew(node_Ra).T @ node_cga.T vec = ag.skew(node_Ra) @ node_FoR_wa LM_C[node_FoR_dof + 3:node_FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = ag.skew(node_Ra).T vec = FoR_cga @ FoR_va LM_C[node_FoR_dof + 3:node_FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) # term 4-x1 - \frac{\partial}{\partial x_1}(d^Taq_1 + d^Tbq2 + d^Tcq3 + d^Tdq4) # d^Tdadxaq_1 + d^Tdbdxq_2 + d^Tdcdxq_3 mat = -FoR_cga.T vec = node_dot_Ra LM_C[FoR_dof:FoR_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = -FoR_cga.T vec = node_FoR_va LM_C[FoR_dof:FoR_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = FoR_cga.T vec = ag.skew(node_Ra) @ node_FoR_wa LM_C[FoR_dof:FoR_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) # term 1-x2 - \frac{\partial}{\partial x_2}(a^Taq_1 + a^Tbq2 + a^Tcq3 + a^Tdq4) # a^Tdddxq_4 mat = -node_cga.T vec = FoR_va LM_C[node_dof:node_dof + 3, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[FoR_body].quat, vec) # term 2-x2 - \frac{\partial}{\partial x_2}(b^Taq_1 + b^Tbq2 + b^Tcq3 + b^Tdq4) # b^Tdddxq_4 mat = -node_cga.T vec = FoR_va LM_C[node_FoR_dof:node_FoR_dof + 3, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[FoR_body].quat, vec) # term 3-x2 - \frac{\partial}{\partial x_2}(c^Taq_1 + c^Tbq2 + c^Tcq3 + c^Tdq4) # c^Tdddxq_4 mat = ag.skew(node_Ra).T @ node_cga.T vec = FoR_va LM_C[node_FoR_dof + 3:node_FoR_dof + 6, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[FoR_body].quat, vec) # term 4-x2 - \frac{\partial}{\partial x_2}(d^Taq_1 + d^Tbq2 + d^Tcq3 + d^Tdq4) # dd^Tdxaq_1 + dd^Tdxbq_2 + dd^Tdxcq_3 + dd^Tdxdq_4 + d^Tdddxq_4 mat = np.eye(3) vec = -node_cga @ node_dot_Ra LM_C[FoR_dof:FoR_dof + 3, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, vec) mat = np.eye(3) vec = -node_cga @ node_FoR_va LM_C[FoR_dof:FoR_dof + 3, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, vec) mat = np.eye(3) vec = node_cga @ ag.skew(node_Ra) @ node_FoR_wa LM_C[FoR_dof:FoR_dof + 3, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, vec) mat = np.eye(3) vec = FoR_cga @ FoR_va LM_C[FoR_dof:FoR_dof + 3, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, vec) mat = FoR_cga.T vec = FoR_va LM_C[FoR_dof:FoR_dof + 3, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[FoR_body].quat, vec) # other LM_K derivatives for a/b/c/d dependencies in Ra # term 1-Ra - \frac{\partial}{\partial Ra}(a^Taq_1 + a^Tbq2 + a^Tcq3 + a^Tdq4) # a^Tdcdrq_3 mat = -node_cga.T @ node_cga vec = node_FoR_wa LM_K[node_dof:node_dof + 3, node_dof:node_dof + 3] \ += penaltyFactor * mat @ ag.der_skewp_v(ag.skew(node_Ra), vec) # term 2-Ra - \frac{\partial}{\partial Ra}(b^Taq_1 + b^Tbq2 + b^Tcq3 + b^Tdq4) # b^Tdcdrq_3 mat = -node_cga.T @ node_cga vec = node_FoR_wa LM_K[node_FoR_dof:node_FoR_dof + 3, node_dof:node_dof + 3] \ += penaltyFactor * mat @ ag.der_skewp_v(ag.skew(node_Ra), vec) # term 3-Ra - \frac{\partial}{\partial Ra}(c^Taq_1 + c^Tbq2 + c^Tcq3 + c^Tdq4) # dc^Tdraq_1 + dc^Tdrbq_2 + dc^Tdrcq_3 + c^Tdcdrq_3 + dc^Tdrdq_4 mat = np.eye(3) vec = node_cga.T @ -node_cga @ node_dot_Ra LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof:node_dof + 3] \ += penaltyFactor * mat @ ag.der_skewpT_v(ag.skew(node_Ra), vec) mat = np.eye(3) vec = node_cga.T @ -node_cga @ node_FoR_va LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof:node_dof + 3] \ += penaltyFactor * mat @ ag.der_skewpT_v(ag.skew(node_Ra), vec) mat = np.eye(3) vec = node_cga.T @ node_cga @ ag.skew(node_Ra) @ node_FoR_wa LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof:node_dof + 3] \ += penaltyFactor * mat @ ag.der_skewpT_v(ag.skew(node_Ra), vec) mat = ag.skew(node_Ra).T @ node_cga.T @ node_cga vec = node_FoR_wa LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof:node_dof + 3] \ += penaltyFactor * mat @ ag.der_skewp_v(ag.skew(node_Ra), vec) mat = np.eye(3) vec = node_cga.T @ FoR_cga @ FoR_va LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof:node_dof + 3] \ += penaltyFactor * mat @ ag.der_skewpT_v(ag.skew(node_Ra), vec) # term 4-Ra - \frac{\partial}{\partial Ra}(d^Taq_1 + d^Tbq2 + d^Tcq3 + d^Tdq4) # d^Tdcdrq_3 mat = FoR_cga.T @ node_cga vec = node_FoR_wa LM_K[FoR_dof:FoR_dof + 3, node_dof:node_dof + 3] \ += penaltyFactor * mat @ ag.der_skewp_v(ag.skew(node_Ra), vec) # a^T -node_cga.T # a -node_cga # b^T -node_cga.T # b -node_cga # c^T ag.skew(node_Ra).T, node_cga.T # c node_cga, ag.skew(node_Ra) # d^T FoR_cga.T # d FoR_cga # q1 node_dof:node_dof+3 node_dot_Ra # q2 node_FoR_dof:node_FoR_dof+3 node_FoR_va # q3 node_FoR_dof+3:node_FoR_dof+6 node_FoR_wa # q4 FoR_dof:FoR_dof+3 FoR_va else: # TODO: follow general approach to derive terms - first 4*4 terms, then LMC derivatives, # then LMK derivatives - this is why penalty didn't work! # if A1 is clamped, then remove the related DoFs from the description above (commented sections below) # Simplify notation node_cga = MB_tstep[node_body].cga() FoR_cga = MB_tstep[FoR_body].cga() psi_dot = MB_tstep[node_body].psi_dot[ielem, inode_in_elem, :] q = np.zeros((sys_size)) q[FoR_dof:FoR_dof + 3] = FoR_va q[node_dof:node_dof + 3] = node_dot_Ra q[node_dof + 3:node_dof + 6] = psi_dot LM_Q[:sys_size] += penaltyFactor * Bnh.T @ Bnh @ q # # 16 canonical terms for (abcd)^T(abcd) LM_C[:sys_size, :sys_size] += penaltyFactor * Bnh.T @ Bnh # other LM_C derivatives for c dependencies in x1 and x2 # term 1-x1 - \frac{\partial}{\partial x_1}(a^Taq_1 + a^Tbq2 + a^Tcq3 + a^Tdq4) # da^Tdxaq_1 + a^Tdadxaq_1 + da^Tdxbq_2 + a^Tdbdxq_2 + da^Tdxcq_3 + a^Tdcdxq_3 + da^Tdxdq_4 mat = -np.eye(3) vec = -node_cga @ node_dot_Ra LM_C[node_dof:node_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, ec) mat = node_cga.T vec = node_dot_Ra LM_C[node_dof:node_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = -np.eye(3) vec = FoR_cga @ FoR_va LM_C[node_dof:node_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) # term 4-x1 - \frac{\partial}{\partial x_1}(d^Taq_1 + d^Tbq2 + d^Tcq3 + d^Tdq4) # d^Tdadxaq_1 + d^Tdbdxq_2 + d^Tdcdxq_3 mat = -FoR_cga.T vec = node_dot_Ra LM_C[FoR_dof:FoR_dof + 3, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) # term 1-x2 - \frac{\partial}{\partial x_2}(a^Taq_1 + a^Tbq2 + a^Tcq3 + a^Tdq4) # a^Tdddxq_4 mat = -node_cga.T vec = FoR_va LM_C[node_dof:node_dof + 3, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[FoR_body].quat, vec) # term 4-x2 - \frac{\partial}{\partial x_2}(d^Taq_1 + d^Tbq2 + d^Tcq3 + d^Tdq4) # dd^Tdxaq_1 + dd^Tdxbq_2 + dd^Tdxcq_3 + dd^Tdxdq_4 + d^Tdddxq_4 mat = np.eye(3) vec = -node_cga @ node_dot_Ra LM_C[FoR_dof:FoR_dof + 3, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, vec) mat = np.eye(3) vec = FoR_cga @ FoR_va LM_C[FoR_dof:FoR_dof + 3, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, vec) mat = FoR_cga.T vec = FoR_va LM_C[FoR_dof:FoR_dof + 3, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[FoR_body].quat, vec) # a^T -node_cga.T # a -node_cga # b^T -node_cga.T # b -node_cga # c^T ag.skew(node_Ra).T, node_cga.T # c node_cga, ag.skew(node_Ra) # d^T FoR_cga.T # d FoR_cga # q1 node_dof:node_dof+3 node_dot_Ra # q2 node_FoR_dof:node_FoR_dof+3 node_FoR_va # q3 node_FoR_dof+3:node_FoR_dof+6 node_FoR_wa # q4 FoR_dof:FoR_dof+3 FoR_va ieq += 3 return ieq def rel_rot_vel_node_FoR(MB_tstep, MB_beam, FoR_body, node_body, node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, scalingFactor, penaltyFactor, ieq, LM_K, LM_C, LM_Q, rel_vel=np.zeros((3))): """ This function generates the stiffness and damping matrices and the independent vector associated to a constraint that imposes equal rotation velocities between a node and a frame of reference See ``LagrangeConstraints`` for the description of variables Args: node_number (int): number of the "node" within its own body node_body (int): body number of the "node" node_FoR_dof (int): position of the first degree of freedom of the FoR to which the "node" belongs node_dof (int): position of the first degree of freedom associated to the "node" FoR_body (int): body number of the "FoR" FoR_dof (int): position of the first degree of freedom associated to the "FoR" rel_vel (np.array): relative velocity FoR-node """ num_LM_eq_specific = 3 Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Simplify notation ielem, inode_in_elem = MB_beam[node_body].node_master_elem[node_number] node_cga = MB_tstep[node_body].cga() node_FoR_wa = MB_tstep[node_body].for_vel[3:6] psi = MB_tstep[node_body].psi[ielem, inode_in_elem, :] cab = ag.crv2rotation(psi) tan = ag.crv2tan(psi) FoR_cga = MB_tstep[FoR_body].cga() FoR_wa = MB_tstep[FoR_body].for_vel[3:6] Bnh[:, node_dof + 3:node_dof + 6] += tan.copy() Bnh[:, FoR_dof + 3:FoR_dof + 6] -= cab.T @ node_cga.T @ FoR_cga if MB_beam[node_body].FoR_movement == 'free': Bnh[:, node_FoR_dof + 3:node_FoR_dof + 6] += cab.T LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += scalingFactor * Bnh.T LM_Q[:sys_size] += scalingFactor * Bnh.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += scalingFactor * (tan @ MB_tstep[node_body].psi_dot[ielem, inode_in_elem, :] + cab.T @ node_FoR_wa - cab.T @ node_cga.T @ FoR_cga @ FoR_wa + rel_vel) LM_K[node_dof + 3:node_dof + 6, node_dof + 3:node_dof + 6] \ += scalingFactor * ag.der_TanT_by_xv(psi, Lambda_dot[ieq:ieq + num_LM_eq_specific]) if MB_beam[node_body].FoR_movement == 'free': LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof + 3:node_dof + 6] \ += scalingFactor * ag.der_Ccrv_by_v(psi, Lambda_dot[ieq:ieq + num_LM_eq_specific]) LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ -= scalingFactor * ag.der_Ccrv_by_v(psi, node_cga @ FoR_cga.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific]) LM_C[FoR_dof + 3:FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ -= scalingFactor * cab @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, FoR_cga.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific]) LM_C[FoR_dof + 3:FoR_dof + 6, FoR_dof + 6:FoR_dof + 10] \ -= scalingFactor * cab @ node_cga @ ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, Lambda_dot[ieq:ieq + num_LM_eq_specific]) ieq += 3 return ieq def def_rot_axis_FoR_wrt_node_general(MB_tstep, MB_beam, FoR_body, node_body, node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, rot_axisB, rot_axisA2, scalingFactor, penaltyFactor, ieq, LM_K, LM_C, LM_Q, indep): """ This function generates the stiffness and damping matrices and the independent vector associated to a joint that forces the rotation axis of a FoR to be parallel to a certain direction. This direction is defined in the B FoR of a node and, thus, might change along the simulation. See ``LagrangeConstraints`` for the description of variables Args: rot_axisB (np.ndarray): Rotation axis with respect to the node B FoR rot_axisA2 (np.ndarray): Rotation axis with respect to the node A2 FoR indep (np.ndarray): Number of the equations that are used as independent node_number (int): number of the "node" within its own body node_body (int): body number of the "node" node_FoR_dof (int): position of the first degree of freedom of the FoR to which the "node" belongs node_dof (int): position of the first degree of freedom associated to the "node" FoR_body (int): body number of the "FoR" FoR_dof (int): position of the first degree of freedom associated to the "FoR" """ ielem, inode_in_elem = MB_beam[node_body].node_master_elem[node_number] # Simplify notation cab = ag.crv2rotation(MB_tstep[node_body].psi[ielem, inode_in_elem, :]) node_cga = MB_tstep[node_body].cga() FoR_cga = MB_tstep[FoR_body].cga() FoR_wa = MB_tstep[FoR_body].for_vel[3:6] node_wa = MB_tstep[node_body].for_vel[3:6] psi = MB_tstep[node_body].psi[ielem, inode_in_elem, :] psi_dot = MB_tstep[node_body].psi_dot[ielem, inode_in_elem, :] if MB_beam[node_body].FoR_movement == 'free': if not indep: aux_Bnh = cab.T @ node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) # indep = None n0 = np.linalg.norm(aux_Bnh[0, :]) n1 = np.linalg.norm(aux_Bnh[1, :]) n2 = np.linalg.norm(aux_Bnh[2, :]) if ((n0 < n1) and (n0 < n2)): indep[:] = [1, 2] elif ((n1 < n0) and (n1 < n2)): indep[:] = [0, 2] elif ((n2 < n0) and (n2 < n1)): indep[:] = [0, 1] new_Lambda_dot = np.zeros(3) new_Lambda_dot[indep[0]] = Lambda_dot[ieq] new_Lambda_dot[indep[1]] = Lambda_dot[ieq + 1] num_LM_eq_specific = 2 Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') Bnh[:, FoR_dof + 3:FoR_dof + 6] -= (cab.T @ node_cga.T @ FoR_cga @ ag.skew(rot_axisA2))[indep, :] Bnh[:, node_dof + 3:node_dof + 6] += (ag.skew(rot_axisB) @ ag.crv2tan(psi))[indep, :] Bnh[:, node_FoR_dof + 3:node_FoR_dof + 6] += (ag.skew(rot_axisB) @ cab.T)[indep, :] # Constrain angular velocities LM_Q[:sys_size] += scalingFactor * Bnh.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ -= scalingFactor * (cab.T @ node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa)[indep] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += scalingFactor * (ag.skew(rot_axisB) @ ag.crv2tan(psi) @ psi_dot)[indep] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += scalingFactor * (ag.skew(rot_axisB) @ cab.T @ MB_tstep[node_body].for_vel[3:6])[indep] # # for initial omega A2 LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += scalingFactor * Bnh.T # term 3 x1 LM_C[FoR_dof + 3:FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += scalingFactor * ag.skew(rot_axisA2) @ FoR_cga.T @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, cab @ new_Lambda_dot) # term 3 x2 LM_C[FoR_dof + 3:FoR_dof + 6, FoR_dof + 6:FoR_dof + 10] \ += scalingFactor * ag.skew(rot_axisA2) @ ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, node_cga @ cab @ new_Lambda_dot) # term 3 K(psi) LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += scalingFactor * ag.skew(rot_axisA2) @ FoR_cga.T @ node_cga @ ag.der_CcrvT_by_v(psi, new_Lambda_dot) LM_K[node_dof + 3:node_dof + 6, node_dof + 3:node_dof + 6] \ -= scalingFactor * ag.der_TanT_by_xv(psi, ag.skew(rot_axisB) @ new_Lambda_dot) # term 1 LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof + 3:node_dof + 6] \ -= scalingFactor * ag.der_Ccrv_by_v(psi, ag.skew(rot_axisB) @ new_Lambda_dot) else: if not indep: aux_Bnh = cab.T @ node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) # indep = None n0 = np.linalg.norm(aux_Bnh[0, :]) n1 = np.linalg.norm(aux_Bnh[1, :]) n2 = np.linalg.norm(aux_Bnh[2, :]) if ((n0 < n1) and (n0 < n2)): indep[:] = [1, 2] elif ((n1 < n0) and (n1 < n2)): indep[:] = [0, 2] elif ((n2 < n0) and (n2 < n1)): indep[:] = [0, 1] new_Lambda_dot = np.zeros(3) new_Lambda_dot[indep[0]] = Lambda_dot[ieq] new_Lambda_dot[indep[1]] = Lambda_dot[ieq + 1] num_LM_eq_specific = 2 Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') Bnh[:, FoR_dof + 3:FoR_dof + 6] -= (cab.T @ node_cga.T @ FoR_cga @ ag.skew(rot_axisA2))[indep, :] Bnh[:, node_dof + 3:node_dof + 6] += (ag.skew(rot_axisB) @ ag.crv2tan(psi))[indep, :] # Constrain angular velocities LM_Q[:sys_size] += scalingFactor * Bnh.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ -= scalingFactor * (cab.T @ node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa)[indep] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += scalingFactor * (ag.skew(rot_axisB) @ ag.crv2tan(psi) @ psi_dot)[indep] LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += scalingFactor * Bnh.T # term 3 x2 LM_C[FoR_dof + 3:FoR_dof + 6, FoR_dof + 6:FoR_dof + 10] \ += scalingFactor * ag.skew(rot_axisA2) @ ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, node_cga @ cab @ new_Lambda_dot) # term 3 K(psi) LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += scalingFactor * ag.skew(rot_axisA2) @ FoR_cga.T @ node_cga, ag.der_CcrvT_by_v(psi, new_Lambda_dot) # term 2 LM_K[node_dof + 3:node_dof + 6, node_dof + 3:node_dof + 6] \ -= scalingFactor * ag.der_TanT_by_xv(psi, ag.skew(rot_axisB) @ new_Lambda_dot) # TODO: penalty factor formulation to be verified if penaltyFactor: if MB_beam[node_body].FoR_movement == 'free': q = np.zeros((sys_size,)) q[FoR_dof + 3:FoR_dof + 6] = FoR_wa q[node_dof + 3:node_dof + 6] = psi_dot q[node_FoR_dof + 3:node_FoR_dof + 6] = node_wa LM_Q[:sys_size] += penaltyFactor * Bnh.T @ Bnh @ q LM_C[:sys_size, :sys_size] += penaltyFactor * Bnh.T @ Bnh # other LM_K derivatives for a/b/c dependencies in C(psi) and T(psi) # term 2-psi - \frac{\partial}{\partial psi}(a^2q_2 + abq_5 + acq_7) # da^Tdpsiaq_2 + a^Tdadpsiq_2 + da^Tdpsibq_5 + a^Tdbdpsiq_5 + da^Tdpsicq_7 + a^Tdcdpsiq_7 mat = np.eye(3) vec = ag.skew(rot_axisB).T @ ag.skew(rot_axisB) @ cab.T @ node_wa LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_Ccrv_by_v(psi, vec) mat = cab @ ag.skew(rot_axisB).T @ ag.skew(rot_axisB) vec = node_wa LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_CcrvT_by_v(psi, vec) mat = np.eye(3) vec = ag.skew(rot_axisB).T @ ag.skew(rot_axisB) @ ag.crv2tan(psi) @ psi_dot LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_Ccrv_by_v(psi, vec) mat = cab @ ag.skew(rot_axisB).T @ ag.skew(rot_axisB) vec = psi_dot LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_Tan_by_xv(psi, vec) mat = np.eye(3) vec = ag.skew(rot_axisB).T @ -cab.T @ node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_Ccrv_by_v(psi, vec) mat = -cab @ ag.skew(rot_axisB).T vec = node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_CcrvT_by_v(psi, vec) # term 5-psi - \frac{\partial}{\partial psi}(ba q_2 + b^2 q_5 + bc q_7) # db^Tdpsiaq_2 + b^Tdadpsiq_2 + db^Tdpsibq_5 + b^Tdbdpsiq_5 + db^Tdpsicq_7 + b^Tdcdpsiq_7 mat = np.eye(3) vec = ag.skew(rot_axisB).T @ ag.skew(rot_axisB) @ cab.T @ node_wa LM_K[node_dof + 3:node_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_TanT_by_xv(psi, vec) mat = ag.crv2tan(psi).T @ ag.skew(rot_axisB).T @ ag.skew(rot_axisB) vec = node_wa LM_K[node_dof + 3:node_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_CcrvT_by_v(psi, vec) mat = np.eye(3) vec = ag.skew(rot_axisB).T @ ag.skew(rot_axisB) @ ag.crv2tan(psi) @ psi_dot LM_K[node_dof + 3:node_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_TanT_by_xv(psi, vec) mat = ag.crv2tan(psi).T @ ag.skew(rot_axisB).T @ ag.skew(rot_axisB) vec = psi_dot LM_K[node_dof + 3:node_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_Tan_by_xv(psi, vec) mat = np.eye(3) vec = ag.skew(rot_axisB).T @ -cab.T @ node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa LM_K[node_dof + 3:node_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_TanT_by_xv(psi, vec) mat = -ag.crv2tan(psi).T @ ag.skew(rot_axisB).T vec = node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa LM_K[node_dof + 3:node_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_CcrvT_by_v(psi, vec) # term 7-psi - \frac{\partial}{\partial psi}(ca q_2 + cb q_5 + c^2 q_7) # dc^Tdpsiaq_2 + c^Tdadpsiq_2 + dc^Tdpsibq_5 + c^Tdbdpsiq_5 + dc^Tdpsicq_7 + c^Tdcdpsiq_7 mat = -ag.skew(rot_axisA2).T @ FoR_cga.T @ node_cga vec = ag.skew(rot_axisB) @ cab.T @ node_wa LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_Ccrv_by_v(psi, vec) mat = -ag.skew(rot_axisA2).T @ FoR_cga.T @ node_cga @ cab @ ag.skew(rot_axisB) vec = node_wa LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_CcrvT_by_v(psi, vec) mat = -ag.skew(rot_axisA2).T @ FoR_cga.T @ node_cga vec = ag.skew(rot_axisB) @ ag.crv2tan(psi) @ psi_dot LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_Ccrv_by_v(psi, vec) mat = -ag.skew(rot_axisA2).T @ FoR_cga.T @ node_cga @ cab @ ag.skew(rot_axisB) vec = psi_dot LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_Tan_by_xv(psi, vec) mat = -ag.skew(rot_axisA2).T @ FoR_cga.T @ node_cga vec = -cab.T @ node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_Ccrv_by_v(psi, vec) mat = ag.skew(rot_axisA2).T @ FoR_cga.T @ node_cga @ cab vec = node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_CcrvT_by_v(psi, vec) # a^T cab, ag.skew(rot_axisB).T # a ag.skew(rot_axisB), cab.T # b^T ag.crv2tan(psi).T, ag.skew(rot_axisB).T # b ag.skew(rot_axisB), ag.crv2tan(psi) # c^T -ag.skew(rot_axisA2).T, FoR_cga.T, node_cga, cab # c -cab.T, node_cga.T, FoR_cga, ag.skew(rot_axisA2) else: q = np.zeros((sys_size,)) q[FoR_dof + 3:FoR_dof + 6] = MB_tstep[FoR_body].for_vel[3:6] q[node_dof + 3:node_dof + 6] = psi_dot LM_Q[:sys_size] += penaltyFactor * Bnh.T @ Bnh @ q LM_C[:sys_size, :sys_size] += penaltyFactor * Bnh.T @ Bnh # term 5-x1 - \frac{\partial}{\partial x1}(b^2 q_5 + bc q_7) # b^Tdcdx1q_7 mat = ag.crv2tan(psi).T @ ag.skew(rot_axisB).T @ -cab.T vec = FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa LM_C[node_dof + 3:node_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) # term 5-x2 - \frac{\partial}{\partial x2}(b^2 q_5 + bc q_7) # b^Tdcdx2q_7 mat = ag.crv2tan(psi).T @ ag.skew(rot_axisB).T @ -cab.T @ node_cga.T vec = ag.skew(rot_axisA2) @ FoR_wa LM_C[node_dof + 3:node_dof + 6, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[FoR_body].quat, vec) # term 7-x1 - \frac{\partial}{\partial x1}(cb q_5 + c^2 q_7) # dc^Tdx1aq_2 -> 0!!! + dc^Tdx1bq_5 + dc^Tdx1cq_7 + c^Tdcdx1q_7 mat = -ag.skew(rot_axisA2).T @ FoR_cga.T vec = cab @ ag.skew(rot_axisB) @ ag.crv2tan(psi) @ psi_dot LM_C[FoR_dof + 3:FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = -ag.skew(rot_axisA2).T @ FoR_cga.T vec = cab @ -cab.T @ node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa LM_C[FoR_dof + 3:FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = -ag.skew(rot_axisA2).T @ FoR_cga.T @ node_cga @ cab @ -cab.T vec = FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa LM_C[FoR_dof + 3:FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) # term 7-x2 - \frac{\partial}{\partial x2}(cb q_5 + c^2 q_7) # dc^Tdx2aq_2 -> 0!!! + dc^Tdx2bq_5 + dc^Tdx2cq_7 + c^Tdcdx2q_7 mat = -ag.skew(rot_axisA2).T vec = node_cga @ cab @ ag.skew(rot_axisB) @ ag.crv2tan(psi) @ psi_dot LM_C[FoR_dof + 3:FoR_dof + 6, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, vec) mat = -ag.skew(rot_axisA2).T vec = node_cga @ cab @ -cab.T @ node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa LM_C[FoR_dof + 3:FoR_dof + 6, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, vec) mat = -ag.skew(rot_axisA2).T @ FoR_cga.T @ node_cga @ cab @ -cab.T @ node_cga.T vec = ag.skew(rot_axisA2) @ FoR_wa LM_C[FoR_dof + 3:FoR_dof + 6, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[FoR_body].quat, vec) # other LM_K derivatives for a/b/c dependencies in C(psi) and T(psi) mat = np.eye(3) vec = ag.skew(rot_axisB).T @ ag.skew(rot_axisB) @ ag.crv2tan(psi) @ psi_dot LM_K[node_dof + 3:node_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_TanT_by_xv(psi, vec) mat = ag.crv2tan(psi).T @ ag.skew(rot_axisB).T @ ag.skew(rot_axisB) vec = psi_dot LM_K[node_dof + 3:node_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_Tan_by_xv(psi, vec) mat = np.eye(3) vec = ag.skew(rot_axisB).T @ -cab.T @ node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa LM_K[node_dof + 3:node_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_TanT_by_xv(psi, vec) mat = -ag.crv2tan(psi).T @ ag.skew(rot_axisB).T vec = node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa LM_K[node_dof + 3:node_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_CcrvT_by_v(psi, vec) # term 7-psi - \frac{\partial}{\partial psi}(cb q_5 + c^2 q_7) # dc^Tdpsiaq_2 -> 0!!! + c^Tdadpsiq_2 -> 0!!! + dc^Tdpsibq_5 + c^Tdbdpsiq_5 + dc^Tdpsicq_7 + c^Tdcdpsiq_7 mat = -ag.skew(rot_axisA2).T @ FoR_cga.T @ node_cga vec = ag.skew(rot_axisB) @ ag.crv2tan(psi) @ psi_dot LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_Ccrv_by_v(psi, vec) mat = -ag.skew(rot_axisA2).T @ FoR_cga.T @ node_cga @ cab @ ag.skew(rot_axisB) vec = psi_dot LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_Tan_by_xv(psi, vec) mat = -ag.skew(rot_axisA2).T @ FoR_cga.T @ node_cga vec = -cab.T @ node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_Ccrv_by_v(psi, vec) mat = ag.skew(rot_axisA2).T @ FoR_cga.T @ node_cga @ cab vec = node_cga.T @ FoR_cga @ ag.skew(rot_axisA2) @ FoR_wa LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_CcrvT_by_v(psi, vec) # a^T cab, ag.skew(rot_axisB).T # a ag.skew(rot_axisB), cab.T # b^T ag.crv2tan(psi).T, ag.skew(rot_axisB).T # b ag.skew(rot_axisB), ag.crv2tan(psi) # c^T -ag.skew(rot_axisA2).T, FoR_cga.T, node_cga, cab # c -cab.T, node_cga.T, FoR_cga, ag.skew(rot_axisA2) ieq += 2 return ieq def def_rot_axis_FoR_wrt_node_xyz(MB_tstep, MB_beam, FoR_body, node_body, node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, rot_axisB, scalingFactor, penaltyFactor, ieq, LM_K, LM_C, LM_Q, zero_comp): """ This function generates the stiffness and damping matrices and the independent vector associated to a joint that forces the rotation axis of a FoR to be parallel to a certain direction. This direction is defined in the B FoR of a node and parallel to x, y or z See ``LagrangeConstraints`` for the description of variables Args: rot_axisB (np.ndarray): Rotation axis with respect to the node B FoR indep (np.ndarray): Number of the equations that are used as independent node_number (int): number of the "node" within its own body node_body (int): body number of the "node" node_FoR_dof (int): position of the first degree of freedom of the FoR to which the "node" belongs node_dof (int): position of the first degree of freedom associated to the "node" FoR_body (int): body number of the "FoR" FoR_dof (int): position of the first degree of freedom associated to the "FoR" """ ielem, inode_in_elem = MB_beam[node_body].node_master_elem[node_number] num_LM_eq_specific = 2 Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Simplify notation cab = ag.crv2rotation(MB_tstep[node_body].psi[ielem, inode_in_elem, :]) node_cga = MB_tstep[node_body].cga() FoR_cga = MB_tstep[FoR_body].cga() FoR_wa = MB_tstep[FoR_body].for_vel[3:6] psi = MB_tstep[node_body].psi[ielem, inode_in_elem, :] psi_dot = MB_tstep[node_body].psi_dot[ielem, inode_in_elem, :] # Components to be zero Z = np.zeros((2, 3)) Z[:, zero_comp] = np.eye(2) Bnh[:, FoR_dof + 3:FoR_dof + 6] += Z @ cab.T @ node_cga.T @ FoR_cga Bnh[:, node_dof + 3:node_dof + 6] -= Z @ ag.crv2tan(psi) Bnh[:, node_FoR_dof + 3:node_FoR_dof + 6] -= Z @ cab.T # Constrain angular velocities LM_Q[:sys_size] += scalingFactor * Bnh.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += scalingFactor * Z @ cab.T @ node_cga.T @ FoR_cga @ FoR_wa LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ -= scalingFactor * Z @ ag.crv2tan(psi) @ psi_dot LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ -= scalingFactor * Z @ cab.T @ MB_tstep[node_body].for_vel[3:6] LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += scalingFactor * Bnh.T vec = node_cga @ cab @ Z.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_C[FoR_dof + 3:FoR_dof + 6, FoR_dof + 6:FoR_dof + 10] \ += scalingFactor * ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, vec) if MB_beam[node_body].FoR_movement == 'free': vec = cab @ Z.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_C[FoR_dof + 3:FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += scalingFactor * FoR_cga.T @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += scalingFactor * FoR_cga.T @ node_cga @ ag.der_Ccrv_by_v(MB_tstep[node_body].psi[ielem, inode_in_elem, :], Z.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific]) LM_K[node_dof + 3:node_dof + 6, node_dof + 3:node_dof + 6] \ -= scalingFactor * ag.der_TanT_by_xv(psi, Z.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific]) LM_K[node_FoR_dof + 3:node_FoR_dof + 6, node_dof + 3:node_dof + 6] \ -= scalingFactor * ag.der_Ccrv_by_v(psi, Z.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific]) if penaltyFactor: q = np.zeros((sys_size,)) q[FoR_dof + 3:FoR_dof + 6] = FoR_wa LM_Q[:sys_size] += penaltyFactor * Bnh.T @ Bnh @ q LM_C[:sys_size, :sys_size] += penaltyFactor * Bnh.T @ Bnh ZTZ = Z.T @ Z # Derivatives with the quaternion of the FoR vec = node_cga @ cab @ ZTZ @ cab.T @ node_cga.T @ FoR_cga @ FoR_wa LM_C[FoR_dof + 3:FoR_dof + 6, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, vec) mat = FoR_cga.T @ node_cga @ cab @ ZTZ @ cab.T @ node_cga.T LM_C[FoR_dof + 3:FoR_dof + 6, FoR_dof + 6:FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_Cquat_by_v(MB_tstep[FoR_body].quat, FoR_wa) if MB_beam[node_body].FoR_movement == 'free': # Derivatives with the quaternion of the FoR of the node vec = cab @ ZTZ @ cab.T @ node_cga.T @ FoR_cga @ FoR_wa LM_C[FoR_dof + 3:FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * FoR_cga.T @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) mat = FoR_cga.T @ node_cga @ cab @ ZTZ @ cab.T vec = FoR_cga @ FoR_wa LM_C[FoR_dof + 3:FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += penaltyFactor * mat @ ag.der_CquatT_by_v(MB_tstep[node_body].quat, vec) # Derivatives with the CRV mat = FoR_cga.T @ node_cga vec = ZTZ @ cab.T @ node_cga.T @ FoR_cga @ FoR_wa LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_Ccrv_by_v(MB_tstep[node_body].psi[ielem, inode_in_elem, :], vec) mat = FoR_cga.T @ node_cga @ cab @ ZTZ vec = node_cga.T @ FoR_cga @ FoR_wa LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += penaltyFactor * mat @ ag.der_CcrvT_by_v(MB_tstep[node_body].psi[ielem, inode_in_elem, :], vec) ieq += 2 return ieq def def_rot_vel_mod_FoR_wrt_node(MB_tstep, MB_beam, FoR_body, node_body, node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, nonzero_comp, rot_vel, scalingFactor, penaltyFactor, ieq, LM_K, LM_C, LM_Q): """ This function generates the stiffness and damping matrices and the independent vector associated to a joint that forces the rotation velocity of a FoR with respect to a node See ``LagrangeConstraints`` for the description of variables Args: nonzero_comp (int): Component of the rotation axis with respect to the node B FoR which is non-zero rot_vel (float): Rotation velocity node_number (int): number of the "node" within its own body node_body (int): body number of the "node" node_FoR_dof (int): position of the first degree of freedom of the FoR to which the "node" belongs node_dof (int): position of the first degree of freedom associated to the "node" FoR_body (int): body number of the "FoR" FoR_dof (int): position of the first degree of freedom associated to the "FoR" """ ielem, inode_in_elem = MB_beam[node_body].node_master_elem[node_number] num_LM_eq_specific = 1 Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Simplify notation cab = ag.crv2rotation(MB_tstep[node_body].psi[ielem, inode_in_elem, :]) node_cga = MB_tstep[node_body].cga() FoR_cga = MB_tstep[FoR_body].cga() FoR_wa = MB_tstep[FoR_body].for_vel[3:6] # Components to be zero Znon = np.zeros((1, 3)) Znon[:, nonzero_comp] = 1 Bnh[:, FoR_dof + 3:FoR_dof + 6] += Znon @ cab.T @ node_cga.T @ FoR_cga # Constrain angular velocities LM_Q[:sys_size] += scalingFactor * Bnh.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += scalingFactor * Znon @ cab.T @ node_cga.T @ FoR_cga @ FoR_wa LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] -= scalingFactor * rot_vel LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += scalingFactor * Bnh.T vec = node_cga @ cab @ Znon.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_C[FoR_dof + 3:FoR_dof + 6, FoR_dof + 6:FoR_dof + 10] \ += scalingFactor * ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, vec) if MB_beam[node_body].FoR_movement == 'free': vec = cab @ Znon.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_C[FoR_dof + 3:FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += scalingFactor * FoR_cga.T @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, vec) LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += scalingFactor * FoR_cga.T @ node_cga @ ag.der_Ccrv_by_v(MB_tstep[node_body].psi[ielem, inode_in_elem, :], Znon.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific]) ieq += 1 return ieq def def_rot_vect_FoR_wrt_node(MB_tstep, MB_beam, FoR_body, node_body, node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, rot_vect, scalingFactor, penaltyFactor, ieq, LM_K, LM_C, LM_Q): """ This function fixes the rotation velocity VECTOR of a FOR equal to a velocity vector defined in the B FoR of a node This function is a new implementation that combines and simplifies the use of 'def_rot_vel_mod_FoR_wrt_node' and 'def_rot_axis_FoR_wrt_node' together """ num_LM_eq_specific = 3 Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Simplify notation ielem, inode_in_elem = MB_beam[node_body].node_master_elem[node_number] node_cga = MB_tstep[node_body].cga() cab = ag.crv2rotation(MB_tstep[node_body].psi[ielem, inode_in_elem, :]) FoR_cga = MB_tstep[FoR_body].cga() FoR_wa = MB_tstep[FoR_body].for_vel[3:6] Bnh[:, FoR_dof + 3:FoR_dof + 6] = cab.T @ node_cga.T @ FoR_cga # Constrain angular velocities LM_Q[:sys_size] += scalingFactor * Bnh.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += scalingFactor * (Bnh[:, FoR_dof + 3:FoR_dof + 6] @ FoR_wa - rot_vect) LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += scalingFactor * Bnh.T if MB_beam[node_body].FoR_movement == 'free': LM_C[FoR_dof + 3:FoR_dof + 6, node_FoR_dof + 6:node_FoR_dof + 10] \ += scalingFactor * FoR_cga.T @ ag.der_Cquat_by_v(MB_tstep[node_body].quat, cab @ Lambda_dot[ieq:ieq + num_LM_eq_specific]) LM_C[FoR_dof + 3:FoR_dof + 6, FoR_dof + 6:FoR_dof + 10] \ += scalingFactor * ag.der_CquatT_by_v(MB_tstep[FoR_body].quat, node_cga @ cab @ Lambda_dot[ieq:ieq + num_LM_eq_specific]) LM_K[FoR_dof + 3:FoR_dof + 6, node_dof + 3:node_dof + 6] \ += scalingFactor * FoR_cga.T @ node_cga @ ag.der_Ccrv_by_v(MB_tstep[node_body].psi[ielem, inode_in_elem, :], Lambda_dot[ieq:ieq + num_LM_eq_specific]) if penaltyFactor: LM_C[FoR_dof + 3:FoR_dof + 6, FoR_dof + 3:FoR_dof + 6] += penaltyFactor * np.eye(3) q = np.zeros(sys_size) q[FoR_dof + 3:FoR_dof + 6] = FoR_wa LM_Q[:sys_size] += penaltyFactor * Bnh.T @ Bnh @ q ieq += 3 return ieq ################################################################################ # Lagrange constraints ################################################################################ @lagrangeconstraint class hinge_node_FoR(BaseLagrangeConstraint): __doc__ = """ hinge_node_FoR This constraint forces a hinge behaviour between a node and a FoR See ``LagrangeConstraints`` for the description of variables Attributes: node_number (int): number of the "node" within its own body node_body (int): body number of the "node" FoR_body (int): body number of the "FoR" rot_axisB (np.ndarray): Rotation axis with respect to the node B FoR rot_axisA2 (np.ndarray): Rotation axis with respect to the node A2 FoR """ _lc_id = 'hinge_node_FoR' def __init__(self): self.required_parameters = ['node_in_body', 'body', 'body_FoR', 'rot_axisB'] self._n_eq = 5 def get_n_eq(self): return self._n_eq def initialise(self, MBdict_entry, ieq, print_info=True): self.node_number = MBdict_entry['node_in_body'] self.node_body = MBdict_entry['body'] self.FoR_body = MBdict_entry['body_FoR'] self.rot_axisB = MBdict_entry['rot_axisB'] self.rot_axisA2 = set_value_or_default(MBdict_entry, "rot_axisA2", self.rot_axisB) self._ieq = ieq self.scalingFactor = set_value_or_default(MBdict_entry, "scalingFactor", 1.) self.penaltyFactor = set_value_or_default(MBdict_entry, "penaltyFactor", 0.) self.indep = [] if (self.rot_axisB[[1, 2]] == 0).all(): self.rot_dir = 'x' self.zero_comp = np.array([1, 2], dtype=int) elif (self.rot_axisB[[0, 2]] == 0).all(): self.rot_dir = 'y' self.zero_comp = np.array([0, 2], dtype=int) elif (self.rot_axisB[[0, 1]] == 0).all(): self.rot_dir = 'z' self.zero_comp = np.array([0, 1], dtype=int) else: # raise NotImplementedError("Hinges should be parallel to the xB, yB or zB of the reference node") self.rot_dir = 'general' self.indep = [] return self._ieq + self._n_eq def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): return def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): # Define the position of the first degree of freedom associated to the node node_dof = define_node_dof(MB_beam, self.node_body, self.node_number) node_FoR_dof = define_FoR_dof(MB_beam, self.node_body) FoR_dof = define_FoR_dof(MB_beam, self.FoR_body) ieq = self._ieq # Define the equations ieq = equal_lin_vel_node_FoR(MB_tstep, MB_beam, self.FoR_body, self.node_body, self.node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, self.scalingFactor, self.penaltyFactor, ieq, LM_K, LM_C, LM_Q) ieq = def_rot_axis_FoR_wrt_node_general(MB_tstep, MB_beam, self.FoR_body, self.node_body, self.node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, self.rot_axisB, self.rot_axisA2, self.scalingFactor, self.penaltyFactor, ieq, LM_K, LM_C, LM_Q, self.indep) return def staticpost(self, lc_list, MB_beam, MB_tstep): return def dynamicpost(self, lc_list, MB_beam, MB_tstep): MB_tstep[self.FoR_body].for_pos[:3] \ = (MB_tstep[self.node_body].cga() @ MB_tstep[self.node_body].pos[self.node_number, :] + MB_tstep[self.node_body].for_pos[:3]) return @lagrangeconstraint class hinge_node_FoR_constant_vel(BaseLagrangeConstraint): __doc__ = """ hinge_node_FoR_constant_vel This constraint forces a hinge behaviour between a node and a FoR and a constant rotation velocity at the join See ``LagrangeConstraints`` for the description of variables Attributes: node_number (int): number of the "node" within its own body node_body (int): body number of the "node" FoR_body (int): body number of the "FoR" rot_vect (np.ndarray): Rotation velocity vector in the node B FoR rel_posB (np.ndarray): Relative position between the node and the frame of reference in the node B FoR """ _lc_id = 'hinge_node_FoR_constant_vel' def __init__(self): self.required_parameters = ['node_in_body', 'body', 'body_FoR', 'rot_vect', 'rel_posB'] self._n_eq = 6 def get_n_eq(self): return self._n_eq def initialise(self, MBdict_entry, ieq, print_info=True): self.node_number = MBdict_entry['node_in_body'] self.node_body = MBdict_entry['body'] self.FoR_body = MBdict_entry['body_FoR'] self.rel_posB = MBdict_entry['rel_posB'] self._ieq = ieq self.indep = [] self.scalingFactor = set_value_or_default(MBdict_entry, "scalingFactor", 1.) self.penaltyFactor = set_value_or_default(MBdict_entry, "penaltyFactor", 0.) self.rot_axisB = ag.unit_vector(MBdict_entry['rot_vect']) if (self.rot_axisB[[1, 2]] == 0).all(): self.rot_dir = 'x' self.zero_comp = np.array([1, 2], dtype=int) self.nonzero_comp = 0 elif (self.rot_axisB[[0, 2]] == 0).all(): self.rot_dir = 'y' self.zero_comp = np.array([0, 2], dtype=int) self.nonzero_comp = 1 elif (self.rot_axisB[[0, 1]] == 0).all(): self.rot_dir = 'z' self.zero_comp = np.array([0, 1], dtype=int) self.nonzero_comp = 2 else: raise NotImplementedError("Hinges should be parallel to the xB, yB or zB of the reference node") self.set_rot_vel(MBdict_entry['rot_vect'][self.nonzero_comp]) return self._ieq + self._n_eq def set_rot_vel(self, rot_vel): self.rot_vel = rot_vel def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): return def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): # Define the position of the first degree of freedom associated to the node node_dof = define_node_dof(MB_beam, self.node_body, self.node_number) node_FoR_dof = define_FoR_dof(MB_beam, self.node_body) FoR_dof = define_FoR_dof(MB_beam, self.FoR_body) ieq = self._ieq # Define the equations ieq = equal_lin_vel_node_FoR(MB_tstep, MB_beam, self.FoR_body, self.node_body, self.node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, self.scalingFactor, self.penaltyFactor, ieq, LM_K, LM_C, LM_Q, rel_posB=self.rel_posB) ieq = def_rot_axis_FoR_wrt_node_xyz(MB_tstep, MB_beam, self.FoR_body, self.node_body, self.node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, self.rot_axisB, self.scalingFactor, self.penaltyFactor, ieq, LM_K, LM_C, LM_Q, self.zero_comp) ieq = def_rot_vel_mod_FoR_wrt_node(MB_tstep, MB_beam, self.FoR_body, self.node_body, self.node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, self.nonzero_comp, self.rot_vel, self.scalingFactor, self.penaltyFactor, ieq, LM_K, LM_C, LM_Q) return def staticpost(self, lc_list, MB_beam, MB_tstep): return def dynamicpost(self, lc_list, MB_beam, MB_tstep): ielem, inode_in_elem = MB_beam[self.node_body].node_master_elem[self.node_number] node_cga = MB_tstep[self.node_body].cga() cab = ag.crv2rotation(MB_tstep[self.node_body].psi[ielem, inode_in_elem, :]) MB_tstep[self.FoR_body].for_pos[:3] = (node_cga @ MB_tstep[self.node_body].pos[self.node_number, :] + cab @ self.rel_posB + MB_tstep[self.node_body].for_pos[0:3]) return @lagrangeconstraint class hinge_node_FoR_pitch(BaseLagrangeConstraint): __doc__ = """ hinge_node_FoR_pitch This constraint forces a hinge behaviour between a node and a FoR and a rotation velocity at the joint See ``LagrangeConstraints`` for the description of variables Attributes: node_number (int): number of the "node" within its own body node_body (int): body number of the "node" FoR_body (int): body number of the "FoR" rot_vect (np.ndarray): Rotation velocity vector in the node B FoR rel_posB (np.ndarray): Relative position between the node and the frame of reference in the node B FoR """ _lc_id = 'hinge_node_FoR_pitch' def __init__(self): self.required_parameters = ['node_in_body', 'body', 'body_FoR', 'rotor_vel', 'rel_posB'] self._n_eq = 6 def get_n_eq(self): return self._n_eq def initialise(self, MBdict_entry, ieq, print_info=True): self.node_number = MBdict_entry['node_in_body'] self.node_body = MBdict_entry['body'] self.FoR_body = MBdict_entry['body_FoR'] self.rel_posB = MBdict_entry['rel_posB'] self._ieq = ieq self.indep = [] self.scalingFactor = set_value_or_default(MBdict_entry, "scalingFactor", 1.) self.penaltyFactor = set_value_or_default(MBdict_entry, "penaltyFactor", 0.) self.set_rotor_vel(MBdict_entry['rotor_vel']) pitch_vel = set_value_or_default(MBdict_entry, "pitch_vel", 0.) self.set_pitch_vel(pitch_vel) return self._ieq + self._n_eq def set_rotor_vel(self, rotor_vel): self.rotor_vel = rotor_vel def set_pitch_vel(self, pitch_vel): self.pitch_vel = pitch_vel def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): return def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): # Define the position of the first degree of freedom associated to the node node_dof = define_node_dof(MB_beam, self.node_body, self.node_number) node_FoR_dof = define_FoR_dof(MB_beam, self.node_body) FoR_dof = define_FoR_dof(MB_beam, self.FoR_body) ieq = self._ieq # Compute relative velocity ielem, inode_in_elem = MB_beam[self.node_body].node_master_elem[self.node_number] node_cga = MB_tstep[self.node_body].cga() cab = ag.crv2rotation(MB_tstep[self.node_body].psi[ielem, inode_in_elem, :]) FoR_cga = MB_tstep[self.FoR_body].cga() # rel_vel in B FoR rel_vel = np.array([0., 0., self.rotor_vel]) rel_vel += cab.T @ node_cga.T @ FoR_cga @ np.array([self.pitch_vel, 0., 0.]) # Define the equations ieq = equal_lin_vel_node_FoR(MB_tstep, MB_beam, self.FoR_body, self.node_body, self.node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, self.scalingFactor, self.penaltyFactor, ieq, LM_K, LM_C, LM_Q, rel_posB=self.rel_posB) ieq = rel_rot_vel_node_FoR(MB_tstep, MB_beam, self.FoR_body, self.node_body, self.node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, self.scalingFactor, self.penaltyFactor, ieq, LM_K, LM_C, LM_Q, rel_vel=rel_vel) return def staticpost(self, lc_list, MB_beam, MB_tstep): return def dynamicpost(self, lc_list, MB_beam, MB_tstep): ielem, inode_in_elem = MB_beam[self.node_body].node_master_elem[self.node_number] node_cga = MB_tstep[self.node_body].cga() cab = ag.crv2rotation(MB_tstep[self.node_body].psi[ielem, inode_in_elem, :]) MB_tstep[self.FoR_body].for_pos[:3] = node_cga @ (MB_tstep[self.node_body].pos[self.node_number, :] + cab @ self.rel_posB) + MB_tstep[self.node_body].for_pos[:3] return @lagrangeconstraint class spherical_node_FoR(BaseLagrangeConstraint): __doc__ = """ spherical_node_FoR This constraint forces a spherical join between a node and a FoR See ``LagrangeConstraints`` for the description of variables Attributes: node_number (int): number of the "node" within its own body node_body (int): body number of the "node" FoR_body (int): body number of the "FoR" """ _lc_id = 'spherical_node_FoR' def __init__(self): self.required_parameters = ['node_in_body', 'body', 'body_FoR'] self._n_eq = 3 def get_n_eq(self): return self._n_eq def initialise(self, MBdict_entry, ieq, print_info=True): self.node_number = MBdict_entry['node_in_body'] self.node_body = MBdict_entry['body'] self.FoR_body = MBdict_entry['body_FoR'] self._ieq = ieq self.scalingFactor = set_value_or_default(MBdict_entry, "scalingFactor", 1.) self.penaltyFactor = set_value_or_default(MBdict_entry, "penaltyFactor", 0.) return self._ieq + self._n_eq def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): return def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): # Define the position of the first degree of freedom associated to the node node_dof = define_node_dof(MB_beam, self.node_body, self.node_number) node_FoR_dof = define_FoR_dof(MB_beam, self.node_body) FoR_dof = define_FoR_dof(MB_beam, self.FoR_body) ieq = self._ieq # Define the equations ieq = equal_lin_vel_node_FoR(MB_tstep, MB_beam, self.FoR_body, self.node_body, self.node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, self.scalingFactor, self.penaltyFactor, ieq, LM_K, LM_C, LM_Q) return def staticpost(self, lc_list, MB_beam, MB_tstep): return def dynamicpost(self, lc_list, MB_beam, MB_tstep): MB_tstep[self.FoR_body].for_pos[:3] = (MB_tstep[self.node_body].cga() @ MB_tstep[self.node_body].pos[self.node_number, :] + MB_tstep[self.node_body].for_pos[:3]) return @lagrangeconstraint class free(BaseLagrangeConstraint): _lc_id = 'free' __doc__ = _lc_id def __init__(self): self.required_parameters = [] self._n_eq = 0 def get_n_eq(self): return self._n_eq def initialise(self, MBdict_entry, ieq, print_info=True): self._ieq = ieq return self._ieq + self._n_eq def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): return def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): return def staticpost(self, lc_list, MB_beam, MB_tstep): return def dynamicpost(self, lc_list, MB_beam, MB_tstep): return @lagrangeconstraint class spherical_FoR(BaseLagrangeConstraint): __doc__ = """ spherical_FoR This constraint forces a spherical join at a FoR See ``LagrangeConstraints`` for the description of variables Attributes: body_FoR (int): body number of the "FoR" """ _lc_id = 'spherical_FoR' def __init__(self): self.required_parameters = ['body_FoR'] self._n_eq = 3 def get_n_eq(self): return self._n_eq def initialise(self, MBdict_entry, ieq, print_info=True): self.body_FoR = MBdict_entry['body_FoR'] self._ieq = ieq self.scalingFactor = set_value_or_default(MBdict_entry, "scalingFactor", 1.) self.penaltyFactor = set_value_or_default(MBdict_entry, "penaltyFactor", 0.) return self._ieq + self._n_eq def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): return def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): num_LM_eq_specific = self._n_eq Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Define the position of the first degree of freedom associated to the FoR FoR_dof = define_FoR_dof(MB_beam, self.body_FoR) ieq = self._ieq Bnh[:3, FoR_dof:FoR_dof + 3] = np.eye(3) LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += self.scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += self.scalingFactor * Bnh.T LM_Q[:sys_size] += self.scalingFactor * Bnh.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + 3] \ += self.scalingFactor * MB_tstep[self.body_FoR].for_vel[:3].astype(dtype=ct.c_double, copy=True, order='F') ieq += 3 return def staticpost(self, lc_list, MB_beam, MB_tstep): return def dynamicpost(self, lc_list, MB_beam, MB_tstep): return @lagrangeconstraint class hinge_FoR(BaseLagrangeConstraint): __doc__ = """ hinge_FoR This constraint forces a hinge at a FoR See ``LagrangeConstraints`` for the description of variables Attributes: body_FoR (int): body number of the "FoR" rot_axis_AFoR (np.ndarray): Rotation axis with respect to the node A FoR """ _lc_id = 'hinge_FoR' def __init__(self): self.required_parameters = ['body_FoR', 'rot_axis_AFoR'] self._n_eq = 5 def get_n_eq(self): return self._n_eq def initialise(self, MBdict_entry, ieq, print_info=True): self.body_FoR = MBdict_entry['body_FoR'] self.rot_axis = MBdict_entry['rot_axis_AFoR'] self._ieq = ieq self.scalingFactor = set_value_or_default(MBdict_entry, "scalingFactor", 1.) self.penaltyFactor = set_value_or_default(MBdict_entry, "penaltyFactor", 0.) if (self.rot_axis[[1, 2]] == 0).all(): self.rot_dir = 'x' self.zero_comp = np.array([1, 2], dtype=int) elif (self.rot_axis[[0, 2]] == 0).all(): self.rot_dir = 'y' self.zero_comp = np.array([0, 2], dtype=int) elif (self.rot_axis[[0, 1]] == 0).all(): self.rot_dir = 'z' self.zero_comp = np.array([0, 1], dtype=int) else: self.rot_dir = 'general' return self._ieq + self._n_eq def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): return def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): num_LM_eq_specific = self._n_eq Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Define the position of the first degree of freedom associated to the FoR FoR_dof = define_FoR_dof(MB_beam, self.body_FoR) ieq = self._ieq Bnh[:3, FoR_dof:FoR_dof + 3] = np.eye(3) # TODO: general logic removed since that implies local beam direction coincident with global axis direction skew_rot_axis = ag.skew(self.rot_axis) n0 = np.linalg.norm(skew_rot_axis[0, :]) n1 = np.linalg.norm(skew_rot_axis[1, :]) n2 = np.linalg.norm(skew_rot_axis[2, :]) if ((n0 < n1) and (n0 < n2)): row0 = 1 row1 = 2 elif ((n1 < n0) and (n1 < n2)): row0 = 0 row1 = 2 elif ((n2 < n0) and (n2 < n1)): row0 = 0 row1 = 1 Bnh[3:5, FoR_dof + 3:FoR_dof + 6] = skew_rot_axis[[row0, row1], :] LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += self.scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += self.scalingFactor * Bnh.T LM_Q[:sys_size] += self.scalingFactor * Bnh.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + 3] \ += self.scalingFactor * MB_tstep[self.body_FoR].for_vel[:3].astype(dtype=ct.c_double, copy=True, order='F') # TODO: general logic removed since that implies local beam direction coincident with global axis direction LM_Q[sys_size + ieq + 3:sys_size + ieq + 5] \ += self.scalingFactor * skew_rot_axis[[row0, row1], :] @ MB_tstep[self.body_FoR].for_vel[3:6] if self.penaltyFactor: LM_Q[FoR_dof:FoR_dof + 3] += self.penaltyFactor * MB_tstep[self.body_FoR].for_vel[:3] LM_C[FoR_dof:FoR_dof + 3, FoR_dof:FoR_dof + 3] += self.penaltyFactor * np.eye(3) # TODO: general logic removed since that implies local beam direction coincident with global axis direction sq_rot_axis = ag.skew(self.rot_axis).T @ ag.skew(self.rot_axis) LM_Q[FoR_dof + 3:FoR_dof + 6] \ += self.penaltyFactor * sq_rot_axis @ MB_tstep[self.body_FoR].for_vel[3:6] LM_C[FoR_dof + 3:FoR_dof + 6, FoR_dof + 3:FoR_dof + 6] += self.penaltyFactor * sq_rot_axis ieq += 5 return def staticpost(self, lc_list, MB_beam, MB_tstep): return def dynamicpost(self, lc_list, MB_beam, MB_tstep): return @lagrangeconstraint class hinge_FoR_wrtG(BaseLagrangeConstraint): __doc__ = """ hinge_FoR_wrtG This constraint forces a hinge at a FoR See ``LagrangeConstraints`` for the description of variables Attributes: body_FoR (int): body number of the "FoR" rot_axis_AFoR (np.ndarray): Rotation axis with respect to the node G FoR """ _lc_id = 'hinge_FoR_wrtG' def __init__(self): self.required_parameters = ['body_FoR', 'rot_axis_AFoR'] self._n_eq = 5 def get_n_eq(self): return self._n_eq def initialise(self, MBdict_entry, ieq, print_info=True): self.body_FoR = MBdict_entry['body_FoR'] self.rot_axis = MBdict_entry['rot_axis_AFoR'] self._ieq = ieq self.scalingFactor = set_value_or_default(MBdict_entry, "scalingFactor", 1.) self.penaltyFactor = set_value_or_default(MBdict_entry, "penaltyFactor", 0.) return self._ieq + self._n_eq def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): return def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): num_LM_eq_specific = self._n_eq Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') B = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Define the position of the first degree of freedom associated to the FoR FoR_dof = define_FoR_dof(MB_beam, self.body_FoR) ieq = self._ieq Bnh[:3, FoR_dof:FoR_dof + 3] = MB_tstep[self.body_FoR].cga() # Only two of these equations are linearly independent skew_rot_axis = ag.skew(self.rot_axis) n0 = np.linalg.norm(skew_rot_axis[0, :]) n1 = np.linalg.norm(skew_rot_axis[1, :]) n2 = np.linalg.norm(skew_rot_axis[2, :]) if ((n0 < n1) and (n0 < n2)): row0 = 1 row1 = 2 elif ((n1 < n0) and (n1 < n2)): row0 = 0 row1 = 2 elif ((n2 < n0) and (n2 < n1)): row0 = 0 row1 = 1 Bnh[3:5, FoR_dof + 3:FoR_dof + 6] = skew_rot_axis[[row0, row1], :] LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += self.scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += self.scalingFactor * Bnh.T LM_C[FoR_dof:FoR_dof + 3, FoR_dof + 6:FoR_dof + 10] \ += self.scalingFactor * ag.der_CquatT_by_v(MB_tstep[self.body_FoR].quat, Lambda_dot[ieq:ieq + 3]) LM_Q[:sys_size] += self.scalingFactor * Bnh.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + 3] \ += self.scalingFactor * MB_tstep[self.body_FoR].cga() @ MB_tstep[self.body_FoR].for_vel[:3] LM_Q[sys_size + ieq + 3:sys_size + ieq + 5] \ += self.scalingFactor * skew_rot_axis[[row0, row1], :] @ MB_tstep[self.body_FoR].for_vel[3:6] ieq += 5 return def staticpost(self, lc_list, MB_beam, MB_tstep): return def dynamicpost(self, lc_list, MB_beam, MB_tstep): return @lagrangeconstraint class fully_constrained_node_FoR(BaseLagrangeConstraint): __doc__ = """ fully_constrained_node_FoR This constraint forces linear and angular displacements between a node and a FoR to be the same See ``LagrangeConstraints`` for the description of variables Attributes: node_number (int): number of the "node" within its own body node_body (int): body number of the "node" FoR_body (int): body number of the "FoR" """ _lc_id = 'fully_constrained_node_FoR' def __init__(self): self.required_parameters = ['node_in_body', 'body', 'body_FoR', 'rel_posB'] self._n_eq = 6 def get_n_eq(self): return self._n_eq def initialise(self, MBdict_entry, ieq, print_info=True): self.node_number = MBdict_entry['node_in_body'] self.node_body = MBdict_entry['body'] self.FoR_body = MBdict_entry['body_FoR'] self.rel_posB = MBdict_entry['rel_posB'] self._ieq = ieq self.scalingFactor = set_value_or_default(MBdict_entry, "scalingFactor", 1.) self.penaltyFactor = set_value_or_default(MBdict_entry, "penaltyFactor", 0.) return self._ieq + self._n_eq def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): return def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): # Define the position of the first degree of freedom associated to the node node_dof = define_node_dof(MB_beam, self.node_body, self.node_number) node_FoR_dof = define_FoR_dof(MB_beam, self.node_body) FoR_dof = define_FoR_dof(MB_beam, self.FoR_body) ieq = self._ieq # Define the equations ieq = equal_lin_vel_node_FoR(MB_tstep, MB_beam, self.FoR_body, self.node_body, self.node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, self.scalingFactor, self.penaltyFactor, ieq, LM_K, LM_C, LM_Q, rel_posB=self.rel_posB) ieq = rel_rot_vel_node_FoR(MB_tstep, MB_beam, self.FoR_body, self.node_body, self.node_number, node_FoR_dof, node_dof, FoR_dof, sys_size, Lambda_dot, self.scalingFactor, self.penaltyFactor, ieq, LM_K, LM_C, LM_Q, rel_vel=np.zeros(3)) return def staticpost(self, lc_list, MB_beam, MB_tstep): return def dynamicpost(self, lc_list, MB_beam, MB_tstep): ielem, inode_in_elem = MB_beam[self.node_body].node_master_elem[self.node_number] node_cga = MB_tstep[self.node_body].cga() cab = ag.crv2rotation(MB_tstep[self.node_body].psi[ielem, inode_in_elem, :]) MB_tstep[self.FoR_body].for_pos[:3] = node_cga @ (MB_tstep[self.node_body].pos[self.node_number, :] + cab @ self.rel_posB) + MB_tstep[self.node_body].for_pos[:3] return @lagrangeconstraint class constant_rot_vel_FoR(BaseLagrangeConstraint): __doc__ = """ constant_rot_vel_FoR This constraint forces a constant rotation velocity of a FoR See ``LagrangeConstraints`` for the description of variables Attributes: FoR_body (int): body number of the "FoR" """ _lc_id = 'constant_rot_vel_FoR' def __init__(self): self.required_parameters = ['FoR_body', 'rot_vel'] self._n_eq = 3 def get_n_eq(self): return self._n_eq def initialise(self, MBdict_entry, ieq, print_info=True): self.rot_vel = MBdict_entry['rot_vel'] self.FoR_body = MBdict_entry['FoR_body'] self._ieq = ieq self.scalingFactor = set_value_or_default(MBdict_entry, "scalingFactor", 1.) self.penaltyFactor = set_value_or_default(MBdict_entry, "penaltyFactor", 0.) return self._ieq + self._n_eq def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): return def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): num_LM_eq_specific = self._n_eq Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Define the position of the first degree of freedom associated to the FoR FoR_dof = define_FoR_dof(MB_beam, self.FoR_body) ieq = self._ieq Bnh[:3, FoR_dof + 3:FoR_dof + 6] = np.eye(3) LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += self.scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += self.scalingFactor * Bnh.T LM_Q[:sys_size] += self.scalingFactor * Bnh.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += self.scalingFactor * (MB_tstep[self.FoR_body].for_vel[3:6] - self.rot_vel) ieq += 3 return def staticpost(self, lc_list, MB_beam, MB_tstep): return def dynamicpost(self, lc_list, MB_beam, MB_tstep): return @lagrangeconstraint class constant_vel_FoR(BaseLagrangeConstraint): __doc__ = """ constant_vel_FoR This constraint forces a constant velocity of a FoR See ``LagrangeConstraints`` for the description of variables Attributes: FoR_body (int): body number of the "FoR" vel (np.ndarray): 6 components of the desired velocity """ _lc_id = 'constant_vel_FoR' def __init__(self): self.required_parameters = ['FoR_body', 'vel'] self._n_eq = 6 def get_n_eq(self): return self._n_eq def initialise(self, MBdict_entry, ieq, print_info=True): self.vel = MBdict_entry['vel'] self.FoR_body = MBdict_entry['FoR_body'] self._ieq = ieq self.scalingFactor = set_value_or_default(MBdict_entry, "scalingFactor", 1.) self.penaltyFactor = set_value_or_default(MBdict_entry, "penaltyFactor", 0.) return self._ieq + self._n_eq def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): return def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): num_LM_eq_specific = self._n_eq Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Define the position of the first degree of freedom associated to the FoR FoR_dof = define_FoR_dof(MB_beam, self.FoR_body) ieq = self._ieq Bnh[:num_LM_eq_specific, FoR_dof:FoR_dof + 6] = np.eye(6) LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += self.scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += self.scalingFactor * Bnh.T LM_Q[:sys_size] += self.scalingFactor * Bnh.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += self.scalingFactor * (MB_tstep[self.FoR_body].for_vel - self.vel) ieq += 6 return def staticpost(self, lc_list, MB_beam, MB_tstep): return def dynamicpost(self, lc_list, MB_beam, MB_tstep): return @lagrangeconstraint class zero_lin_vel_sine_rot_vel_FoR(BaseLagrangeConstraint): __doc__ = """ zero_lin_vel_sine_rot_vel_FoR Zero linear velocity and sinusoidal rotation velocity FoR See ``LagrangeConstraints`` for the description of variables Attributes: FoR_body (int): body number of the "FoR" vel_amp (float): Rotation velocity amplitude omega (float): Frequency of the sinusoidally-varying rotation velocity xyz (string): Axis with the sine velocity """ _lc_id = 'zero_lin_vel_sine_rot_vel_FoR' def __init__(self): self.required_parameters = ['FoR_body', 'vel_amp', 'omega', 'xyz'] self._n_eq = 6 def get_n_eq(self): return self._n_eq def initialise(self, MBdict_entry, ieq, print_info=True): self.FoR_body = MBdict_entry['FoR_body'] self.vel_amp = MBdict_entry['vel_amp'] self.omega = MBdict_entry['omega'] if MBdict_entry['xyz'] == 'x': self.xyz_index = 0 elif MBdict_entry['xyz'] == 'y': self.xyz_index = 1 elif MBdict_entry['xyz'] == 'z': self.xyz_index = 2 else: raise NotImplementedError("FoR rotation velocity shouldd be parallel to x, y or z") self._ieq = ieq self.scalingFactor = set_value_or_default(MBdict_entry, "scalingFactor", 1.) self.penaltyFactor = set_value_or_default(MBdict_entry, "penaltyFactor", 0.) return self._ieq + self._n_eq def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): return def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): num_LM_eq_specific = self._n_eq Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Define the position of the first degree of freedom associated to the FoR FoR_dof = define_FoR_dof(MB_beam, self.FoR_body) ieq = self._ieq vel = np.zeros(6) vel[3 + self.xyz_index] = self.vel_amp * np.sin(self.omega * ts * dt) Bnh[:num_LM_eq_specific, FoR_dof:FoR_dof + 6] = np.eye(6) LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += self.scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += self.scalingFactor * Bnh.T LM_Q[:sys_size] += self.scalingFactor * Bnh.T, Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += self.scalingFactor * (MB_tstep[self.FoR_body].for_vel - vel) ieq += 6 return def staticpost(self, lc_list, MB_beam, MB_tstep): return def dynamicpost(self, lc_list, MB_beam, MB_tstep): return @lagrangeconstraint class lin_vel_node_wrtA(BaseLagrangeConstraint): __doc__ = """ lin_vel_node_wrtA This constraint forces the linear velocity of a node to have a certain value with respect to the A FoR See ``LagrangeConstraints`` for the description of variables Attributes: node_number (int): number of the "node" within its own body body_number (int): body number of the "node" vel (np.ndarray): 6 components of the desired velocity with respect to the A FoR """ _lc_id = 'lin_vel_node_wrtA' def __init__(self): self.required_parameters = ['velocity', 'body_number', 'node_number'] self._n_eq = 3 def get_n_eq(self): return self._n_eq def initialise(self, MBdict_entry, ieq, print_info=True): self.vel = MBdict_entry['velocity'] self.body_number = MBdict_entry['body_number'] self.node_number = MBdict_entry['node_number'] self._ieq = ieq self.scalingFactor = set_value_or_default(MBdict_entry, "scalingFactor", 1.) self.penaltyFactor = set_value_or_default(MBdict_entry, "penaltyFactor", 0.) return self._ieq + self._n_eq def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): num_LM_eq_specific = self._n_eq B = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Define the position of the first degree of freedom associated to the FoR node_dof = define_node_dof(MB_beam, self.body_number, self.node_number) ieq = self._ieq B[:num_LM_eq_specific, node_dof:node_dof + 3] = np.eye(3) LM_K[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += self.scalingFactor * B LM_K[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += self.scalingFactor * B.T LM_Q[:sys_size] += self.scalingFactor * B.T @ Lambda[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += self.scalingFactor * (MB_tstep[self.body_number].pos[self.node_number, :] - MB_beam[self.body_number].ini_info.pos[self.node_number, :]) ieq += 3 return def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): if len(self.vel.shape) > 1: current_vel = self.vel[ts - 1, :] else: current_vel = self.vel num_LM_eq_specific = self._n_eq Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Define the position of the first degree of freedom associated to the FoR node_dof = define_node_dof(MB_beam, self.body_number, self.node_number) ieq = self._ieq Bnh[:num_LM_eq_specific, node_dof:node_dof + 3] = np.eye(3) LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += self.scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += self.scalingFactor * Bnh.T LM_Q[:sys_size] += self.scalingFactor * Bnh.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += self.scalingFactor * (MB_tstep[self.body_number].pos_dot[self.node_number, :] - current_vel) ieq += 3 return def staticpost(self, lc_list, MB_beam, MB_tstep): return def dynamicpost(self, lc_list, MB_beam, MB_tstep): return @lagrangeconstraint class lin_vel_node_wrtG(BaseLagrangeConstraint): __doc__ = """ lin_vel_node_wrtG This constraint forces the linear velocity of a node to have a certain value with respect to the G FoR See ``LagrangeConstraints`` for the description of variables Attributes: node_number (int): number of the "node" within its own body body_number (int): body number of the "node" vel (np.ndarray): 6 components of the desired velocity with respect to the G FoR """ _lc_id = 'lin_vel_node_wrtG' def __init__(self): self.required_parameters = ['velocity', 'body_number', 'node_number'] self._n_eq = 3 def get_n_eq(self): return self._n_eq def initialise(self, MBdict_entry, ieq, print_info=True): self.vel = MBdict_entry['velocity'] self.body_number = MBdict_entry['body_number'] self.node_number = MBdict_entry['node_number'] self._ieq = ieq self.scalingFactor = set_value_or_default(MBdict_entry, "scalingFactor", 1.) self.penaltyFactor = set_value_or_default(MBdict_entry, "penaltyFactor", 0.) return self._ieq + self._n_eq def staticmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): num_LM_eq_specific = self._n_eq B = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Define the position of the first degree of freedom associated to the FoR node_dof = define_node_dof(MB_beam, self.body_number, self.node_number) ieq = self._ieq B[:num_LM_eq_specific, node_dof:node_dof + 3] = MB_tstep[self.body_number].cga() LM_K[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += self.scalingFactor * B LM_K[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += self.scalingFactor * B.T LM_Q[:sys_size] += self.scalingFactor * B.T @ Lambda[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += (self.scalingFactor * MB_tstep[self.body_number].cga() @ MB_tstep[self.body_number].pos[self.node_number, :] + MB_tstep[self.body_number].for_pos) LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ -= (self.scalingFactor * MB_beam[self.body_number].ini_info.cga() @ MB_beam[self.body_number].ini_info.pos[self.node_number, :] + MB_beam[self.body_number].ini_info.for_pos) ieq += 3 return def dynamicmat(self, LM_C, LM_K, LM_Q, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot): if len(self.vel.shape) > 1: current_vel = self.vel[ts - 1, :] else: current_vel = self.vel num_LM_eq_specific = self._n_eq Bnh = np.zeros((num_LM_eq_specific, sys_size), dtype=ct.c_double, order='F') # Define the position of the first degree of freedom associated to the FoR FoR_dof = define_FoR_dof(MB_beam, self.body_number) node_dof = define_node_dof(MB_beam, self.body_number, self.node_number) ieq = self._ieq if MB_beam[self.body_number].FoR_movement == 'free': Bnh[:num_LM_eq_specific, FoR_dof:FoR_dof + 3] = MB_tstep[self.body_number].cga() Bnh[:num_LM_eq_specific, FoR_dof + 3:FoR_dof + 6] \ = -MB_tstep[self.body_number].cga() @ ag.skew(MB_tstep[self.body_number].pos[self.node_number, :]) Bnh[:num_LM_eq_specific, node_dof:node_dof + 3] = MB_tstep[self.body_number].cga() LM_C[sys_size + ieq:sys_size + ieq + num_LM_eq_specific, :sys_size] += self.scalingFactor * Bnh LM_C[:sys_size, sys_size + ieq:sys_size + ieq + num_LM_eq_specific] += self.scalingFactor * Bnh.T if MB_beam[self.body_number].FoR_movement == 'free': LM_C[FoR_dof:FoR_dof + 3, FoR_dof + 6:FoR_dof + 10] += self.scalingFactor * ag.der_CquatT_by_v( MB_tstep[self.body_number].quat, Lambda_dot[ieq:ieq + num_LM_eq_specific]) LM_C[node_dof:node_dof + 3, FoR_dof + 6:FoR_dof + 10] += self.scalingFactor * ag.der_CquatT_by_v( MB_tstep[self.body_number].quat, Lambda_dot[ieq:ieq + num_LM_eq_specific]) LM_C[FoR_dof + 3:FoR_dof + 6, FoR_dof + 6:FoR_dof + 10] \ += (self.scalingFactor * ag.skew(MB_tstep[self.body_number].pos[self.node_number, :]) @ ag.der_CquatT_by_v(MB_tstep[self.body_number].quat, Lambda_dot[ieq:ieq + num_LM_eq_specific])) LM_K[FoR_dof + 3:FoR_dof + 6, node_dof:node_dof + 3] \ -= self.scalingFactor * ag.skew(MB_tstep[self.body_number].cga().T @ Lambda_dot[ieq:ieq + num_LM_eq_specific]) LM_Q[:sys_size] += self.scalingFactor * Bnh.T @ Lambda_dot[ieq:ieq + num_LM_eq_specific] LM_Q[sys_size + ieq:sys_size + ieq + num_LM_eq_specific] \ += self.scalingFactor * (MB_tstep[self.body_number].cga() @ (MB_tstep[self.body_number].for_vel[:3] + ag.skew(MB_tstep[self.body_number].for_vel[3:6]) @ MB_tstep[self.body_number].pos[self.node_number, :] + MB_tstep[self.body_number].pos_dot[self.node_number, :]) - current_vel) ieq += 3 return def staticpost(self, lc_list, MB_beam, MB_tstep): return def dynamicpost(self, lc_list, MB_beam, MB_tstep): return ################################################################################ # Funtions to interact with this Library ################################################################################ def initialize_constraints(MBdict): index_eq = 0 num_constraints = MBdict['num_constraints'] lc_list = list() # Read the dictionary and create the constraints for iconstraint in range(num_constraints): lc_list.append(lc_from_string(MBdict["constraint_%02d" % iconstraint]['behaviour'])()) MBdict_entry = MBdict["constraint_%02d" % iconstraint] if "penaltyFactor" in MBdict_entry.keys(): if not MBdict_entry['penaltyFactor'] == 0.: print("Penalty method not completely implemented for Lagrange Constraints") index_eq = lc_list[-1].initialise(MBdict_entry, index_eq) return lc_list def define_num_LM_eq(lc_list): """ define_num_LM_eq Define the number of equations needed to define the boundary boundary conditions Args: lc_list(): list of all the defined contraints Returns: num_LM_eq(int): number of new equations needed to define the boundary boundary conditions Examples: num_LM_eq = lagrangeconstraints.define_num_LM_eq(lc_list) Notes: """ num_LM_eq = 0 # Compute the number of equations for lc in lc_list: num_LM_eq += lc.get_n_eq() return num_LM_eq def generate_lagrange_matrix(lc_list, MB_beam, MB_tstep, ts, num_LM_eq, sys_size, dt, Lambda, Lambda_dot, dynamic_or_static): """ generate_lagrange_matrix Generates the matrices associated to the Lagrange multipliers boundary conditions Args: lc_list(): list of all the defined contraints MBdict(dict): dictionary with the MultiBody and LagrangeMultipliers information MB_beam(list): list of 'beams' of each of the bodies that form the system MB_tstep(list): list of 'StructTimeStepInfo' of each of the bodies that form the system num_LM_eq(int): number of new equations needed to define the boundary boundary conditions sys_size(int): total number of degrees of freedom of the multibody system dt(float): time step Lambda(np.ndarray): list of Lagrange multipliers values Lambda_dot(np.ndarray): list of the first derivative of the Lagrange multipliers values dynamic_or_static (str): string defining if the computation is dynamic or static Returns: LM_C (np.ndarray): Damping matrix associated to the Lagrange Multipliers equations LM_K (np.ndarray): Stiffness matrix associated to the Lagrange Multipliers equations LM_Q (np.ndarray): Vector of independent terms associated to the Lagrange Multipliers equations """ # Initialize matrices LM_C = np.zeros((sys_size + num_LM_eq, sys_size + num_LM_eq), dtype=ct.c_double, order='F') LM_K = np.zeros((sys_size + num_LM_eq, sys_size + num_LM_eq), dtype=ct.c_double, order='F') LM_Q = np.zeros((sys_size + num_LM_eq,), dtype=ct.c_double, order='F') # Define the matrices associated to the constratints # TODO: Is there a better way to deal with ieq? for lc in lc_list: if dynamic_or_static.lower() == "static": lc.staticmat(LM_C=LM_C, LM_K=LM_K, LM_Q=LM_Q, MB_beam=MB_beam, MB_tstep=MB_tstep, ts=ts, num_LM_eq=num_LM_eq, sys_size=sys_size, dt=dt, Lambda=Lambda, Lambda_dot=Lambda_dot) elif dynamic_or_static.lower() == "dynamic": lc.dynamicmat(LM_C=LM_C, LM_K=LM_K, LM_Q=LM_Q, MB_beam=MB_beam, MB_tstep=MB_tstep, ts=ts, num_LM_eq=num_LM_eq, sys_size=sys_size, dt=dt, Lambda=Lambda, Lambda_dot=Lambda_dot) return LM_C, LM_K, LM_Q def postprocess(lc_list, MB_beam, MB_tstep, dynamic_or_static): """ Run the postprocess of all the Lagrange Constraints in the system """ for lc in lc_list: if dynamic_or_static.lower() == "static": lc.staticpost(lc_list=lc_list, MB_beam=MB_beam, MB_tstep=MB_tstep) elif dynamic_or_static.lower() == "dynamic": lc.dynamicpost(lc_list=lc_list, MB_beam=MB_beam, MB_tstep=MB_tstep) return def remove_constraint(MBdict, constraint): """ Removes a constraint from the list. This function is thought to release constraints at some point during a dynamic simulation """ try: del (MBdict[constraint]) MBdict['num_constraints'] -= 1 except KeyError: pass ################################################################################ print_available_lc() ================================================ FILE: sharpy/structure/utils/lagrangeconstraintsjax.py ================================================ from typing import Callable, Any, Optional, Type, cast from abc import ABC import numpy as np import scipy as sp import jax import jax.scipy.spatial.transform import jax.numpy as jnp from jax.numpy import ndarray as jarr # global constants jax.config.update("jax_enable_x64", True) DICT_OF_LC = dict() USE_JIT = True # type definition for b subfunctions (Constraint, i_lm, b_mat, q, q_dot, u, u_dot) b_type: Type = Optional[Callable[[Any, slice, jarr, jarr, jarr, jarr, jarr], jarr]] # type definitions for b functions, ordering is (q, q_dot, u, u_dot, lmh, lmn) func_type: Type = Callable[[jarr, jarr, jarr, jarr, jarr, jarr], jarr] # redefine jax.scipy rotation class to use [real, i, j, k] ordering by default class Rot(jax.scipy.spatial.transform.Rotation): @classmethod def from_quat(cls, quat: jarr): return super().from_quat(jnp.array((*quat[1:4], quat[0]))) def as_quat(self, canonical=True) -> jarr: return super().as_quat(canonical=canonical) # decorator populating DICT_OF_LC as {lc_id: Constraint}, not type hinted as preceeds Constraint declaration def constraint(constraint_): global DICT_OF_LC if constraint_.lc_id is not None: DICT_OF_LC[constraint_.lc_id] = constraint_ else: raise AttributeError('Class defined as lagrange constraint has no lc_id attribute') return constraint_ # JAX vector skew def skew(vec: jarr) -> jarr: if vec.shape != (3,) or jnp.iscomplexobj(vec): raise ValueError("Incompatible input vector dimention or data type") return jnp.array(((0., -vec[2], vec[1]), (vec[2], 0., -vec[0]), (-vec[1], vec[0], 0.))) # JAX cartesian rotation vector to rotation matrix as version from SciPy's Rotation class doesn't differentiate well def crv2rot(crv: jarr) -> jarr: ang = jnp.linalg.norm(crv) crv_skew = skew(crv) return jax.lax.cond(ang > 1e-15, lambda: jnp.eye(3) + jnp.sin(ang) / ang * crv_skew + (1 - jnp.cos(ang)) / ang ** 2 * crv_skew @ crv_skew, lambda: jnp.eye(3) + crv_skew + 0.5 * crv_skew @ crv_skew) # JAX cartesian rotation vector tangent operator def crv2tan(psi: jarr) -> jarr: ang = jnp.linalg.norm(psi) psi_skew = skew(psi) return jax.lax.cond(ang > 1e-8, lambda: jnp.eye(3) + (jnp.cos(ang) - 1.) / ang ** 2 * psi_skew + (ang - jnp.sin(ang)) / ang ** 3 * psi_skew @ psi_skew, lambda: jnp.eye(3) - 0.5 * psi_skew + psi_skew @ psi_skew / 6.) @constraint class Constraint(ABC): # this might make it a bit faster, might be overkill __slots__ = ('settings', 's_fact', 'p_fact', 'use_p_fact', 'jac_func', 'num_h_funcs', 'num_n_funcs', 'i_first_lm', 'num_lmh', 'num_lmn', 'num_lm', 'num_lm_tot', 'num_bodys', 'is_free', 'num_elem_body', 'num_node_body', 'num_dof_body', 'num_dof_tot', 'num_eq_tot', 'i_sys', 'i_lm_tot', 'i_lmh_eq', 'i_lmn_eq', 'i_lm_eq', 'i_v0', 'i_omega0', 'i_quat0', 'i_v1', 'i_omega1', 'i_quat1', 'i_r0', 'i_psi0', 'q_i_global', 'num_active_q', 'bh', 'bn', 'gn', 'c', 'd', 'bht', 'bnt', 'bht_lmh', 'bnt_lmn', 'p_func_h', 'p_func_n', 'run') lc_id = '_base_constraint' required_params: tuple[str, ...] = tuple() # required parameters for the given constraint bh_funcs: tuple[b_type, ...] = list() # tuple of holonomic B matrix funcs bn_funcs: tuple[b_type | None, ...] = tuple() # tuple of non holonomic B matrix funcs, or None for zero matrix gn_funcs: tuple[b_type | None, ...] = tuple() # tuple of non holonomic g matrix funcs, or None for zero matrix num_lmh_eq: tuple[int, ...] = tuple() # number of LMs for each holonomic constraint num_lmn_eq: tuple[int, ...] = tuple() # number of LMs for each nonholonomic constraint postproc_funcs: tuple[Callable, ...] = tuple() # postprocessing functions to be run def __init__(self, data, i_lm: int, constraint_settings: dict): # case data, index of first LM, input settings self.settings = constraint_settings # input constraint parameters # scaling factor, with 1/dt^2 as default self.s_fact = constraint_settings.get('scaling_factor', data.data.settings['DynamicCoupled']['dt'] ** -2) self.p_fact = constraint_settings.get('penalty_factor', 0.) # penalty factor for constraint self.use_p_fact = abs(self.p_fact) > 1e-6 # if true, penalty equations are included in run function # choose either forward or reverse mode autodifferentiation # I believe that only forward works as of current, and for this case the difference should be negligible match data.settings['jacobian_method']: case 'forward': self.jac_func = jax.jacfwd case 'reverse': self.jac_func = jax.jacrev self.num_h_funcs = len(self.bh_funcs) # number of holonomic functions (this constraint) self.num_n_funcs = len(self.bn_funcs) # number of non holonomic functions (this constraint) self.i_first_lm = i_lm # index of first LM (this constraint) self.num_lmh = sum(self.num_lmh_eq) # total number of holonomic LMs (this constraint) self.num_lmn = sum(self.num_lmn_eq) # total number of non holonomic LMs (this constraint) self.num_lm = self.num_lmh + self.num_lmn # total number of LMs (this constraint) self.num_lm_tot = data.num_lm_tot # total numer of LMs (all constraints) self.num_bodys = data.data.structure.ini_mb_dict['num_bodies'] # number of bodies self.is_free = [data.data.structure.ini_mb_dict[f'body_{i:02d}']['FoR_movement'] == 'free' for i in range(self.num_bodys)] # boolean for beam freedom condition self.num_elem_body = [list(data.data.structure.body_number).count(i) for i in range(self.num_bodys)] # number of elements per body self.num_node_body = [self.num_elem_body[i] * (data.data.structure.num_node_elem - 1) + 1 for i in range(self.num_bodys)] # number of nodes per body self.num_dof_body = [(self.num_node_body[i] - 1) * 6 + self.is_free[i] * 10 for i in range(self.num_bodys)] # number of DoFs per body self.num_dof_tot = data.sys_size # number of equations representing the unconstrained system self.num_eq_tot = self.num_dof_tot + self.num_lm_tot # number of equations representing the constrainted system self.i_sys = slice(0, self.num_dof_tot) # index of equations representing the unconstrained system self.i_lm_tot = jnp.arange(self.num_dof_tot, self.num_dof_tot + self.num_lm_tot) # index of LMs in full system self.i_lmh_eq: list[slice] = [] # list of slices of holonomic LMs for this constraint self.i_lmn_eq: list[slice] = [] # list of slices of non holonomic LMs for this constraint i_eq = self.i_first_lm for i in range(len(self.num_lmh_eq)): self.i_lmh_eq.append(slice(i_eq, i_eq + self.num_lmh_eq[i])) i_eq += self.num_lmh_eq[i] for i in range(len(self.num_lmn_eq)): self.i_lmn_eq.append(slice(i_eq, i_eq + self.num_lmn_eq[i])) i_eq += self.num_lmn_eq[i] self.i_lm_eq = self.i_lmh_eq + self.i_lmn_eq # list of slices of LMs for this constraint, holonomic first # the below indexes are slices of q and q_dot in the subset of active q terms self.i_v0: Optional[slice] = None # index of body 0 linear velocity self.i_omega0: Optional[slice] = None # index of body 0 rotational velocity self.i_quat0: Optional[slice] = None # index of body 0 orientation quaternion self.i_v1: Optional[slice] = None # index of body 1 linear velocity self.i_omega1: Optional[slice] = None # index of body 1 rotational velocity self.i_quat1: Optional[slice] = None # index of body 1 orientation quaternion self.i_r0: Optional[slice] = None # index of body 0 node position self.i_psi0: Optional[slice] = None # index of body 0 node orientation self.q_i_global: Optional[jnp.ndarray] = None # index of q used in constraints (global) self.num_active_q: Optional[int] = None # number of elements of q required in constraint self.create_index() # assign all required indexes self.bh: func_type = self.create_bh() # overall holonomic b function self.bn: func_type = self.create_bn() # overall nonholonomic b function self.gn: func_type = self.create_gn() # overall nonholonomic g function # cannot type hint lambda expressions, so casting the type will do self.c: func_type = cast(func_type, lambda *args: self.bh(*args) @ args[0]) # C function self.d: func_type = cast(func_type, lambda *args: self.bn(*args) @ args[1] + self.gn(*args)) # D function self.bht: func_type = cast(func_type, lambda *args: self.bh(*args).T) # B_h^T self.bnt: func_type = cast(func_type, lambda *args: self.bn(*args).T) # B_n^T self.bht_lmh: func_type = cast(func_type, lambda *args: self.bht(*args) @ args[4]) # B_h^T @ lm_h self.bnt_lmn: func_type = cast(func_type, lambda *args: self.bnt(*args) @ args[5]) # B_n^T @ lm_n self.p_func_h: func_type = cast(func_type, lambda *args: self.bht(*args) @ self.c(*args)) self.p_func_n: func_type = cast(func_type, lambda *args: self.bnt(*args) @ self.d(*args)) # create function which returns contribution to structural equations self.run: Callable[[jarr, jarr, jarr, jarr, jarr, jarr], tuple[jarr, jarr, jarr]] = self.create_run() def postprocess(self, mb_beam, mb_tstep) -> None: for func in self.postproc_funcs: func(self, mb_beam, mb_tstep) @classmethod def get_n_lm(cls) -> int: return sum(cls.num_lmh_eq) + sum(cls.num_lmn_eq) def create_index(self) -> None: # check all required parameters are in settings for param in self.required_params: if param not in self.settings.keys(): raise KeyError(f"Parameter {param} is undefined in constraint settings") i_global = [] # index in full q i_count = 0 # start of current slice if 'body' in self.required_params: i_for0 = self.settings['body'] i_start_for0 = sum(self.num_dof_body[:i_for0 + 1]) - 10 self.i_v0 = np.arange(0, 3, dtype=int) self.i_omega0 = np.arange(3, 6, dtype=int) self.i_quat0 = np.arange(6, 10, dtype=int) i_global.append(np.arange(i_start_for0, i_start_for0 + 10)) i_count += 10 if 'node_in_body' in self.required_params: i_node = self.settings['node_in_body'] self.i_r0 = np.arange(i_count, i_count + 3, dtype=int) self.i_psi0 = np.arange(i_count + 3, i_count + 6, dtype=int) i_global.append(jnp.arange((i_node - 1) * 6, (i_node - 1) * 6 + 6)) i_count += 6 if 'body_FoR' in self.required_params: i_for1 = self.settings['body_FoR'] i_start_for1 = sum(self.num_dof_body[:i_for1 + 1]) - 10 self.i_v1 = np.arange(i_count, i_count + 3, dtype=int) self.i_omega1 = np.arange(i_count + 3, i_count + 6, dtype=int) self.i_quat1 = np.arange(i_count + 6, i_count + 10, dtype=int) i_global.append(np.arange(i_start_for1, i_start_for1 + 10)) i_count += 10 self.q_i_global = jnp.hstack(i_global) self.num_active_q = self.q_i_global.shape[0] def create_bh(self) -> func_type: def bh(*args: jarr) -> jarr: bh_mat = jnp.zeros((self.num_lm_tot, self.num_active_q)) for i_func, bh_func in enumerate(self.bh_funcs): bh_mat = bh_func(self, self.i_lmh_eq[i_func], bh_mat, *args[:4]) return bh_mat return bh def create_bn(self) -> func_type: def bn(*args: jarr) -> jarr: bn_mat = jnp.zeros((self.num_lm_tot, self.num_active_q)) for i_func, bn_func in enumerate(self.bn_funcs): if bn_func is not None: bn_mat = bn_func(self, self.i_lmn_eq[i_func], bn_mat, *args[:4]) return bn_mat return bn def create_gn(self) -> func_type: def gn(*args: jarr) -> jarr: gn_mat = jnp.zeros(self.num_lm_tot) for i_func, gn_func in enumerate(self.gn_funcs): if gn_func is not None: gn_mat = gn_func(self, self.i_lmn_eq[i_func], gn_mat, *args[:4]) return gn_mat return gn def create_run(self): def run(q: jarr, q_dot: jarr, u: jarr, u_dot: jarr, lmh: jarr, lmn: jarr) -> tuple[jarr, jarr, jarr]: args = (q, q_dot, u, u_dot, lmh, lmn) c = jnp.zeros((self.num_eq_tot, self.num_eq_tot)) c = c.at[jnp.ix_(self.q_i_global, self.q_i_global)].add(self.jac_func(self.bht_lmh, 1)(*args)) c = c.at[jnp.ix_(self.q_i_global, self.q_i_global)].add(self.jac_func(self.bnt_lmn, 1)(*args)) c = c.at[jnp.ix_(self.q_i_global, self.i_lm_tot)].add(self.bnt(*args)) c = c.at[jnp.ix_(self.i_lm_tot, self.q_i_global)].add(self.s_fact * (self.jac_func(self.c, 1)(*args))) c = c.at[jnp.ix_(self.i_lm_tot, self.q_i_global)].add(self.s_fact * (self.jac_func(self.d, 1)(*args))) k = jnp.zeros((self.num_eq_tot, self.num_eq_tot)) k = k.at[jnp.ix_(self.q_i_global, self.q_i_global)].add(self.jac_func(self.bht_lmh, 0)(*args)) k = k.at[jnp.ix_(self.q_i_global, self.q_i_global)].add(self.jac_func(self.bnt_lmn, 0)(*args)) k = k.at[jnp.ix_(self.q_i_global, self.i_lm_tot)].add(self.bht(*args)) k = k.at[jnp.ix_(self.i_lm_tot, self.q_i_global)].add(self.s_fact * (self.jac_func(self.c, 0)(*args))) k = k.at[jnp.ix_(self.i_lm_tot, self.q_i_global)].add(self.s_fact * (self.jac_func(self.d, 0)(*args))) rhs = jnp.zeros(self.num_eq_tot) rhs = rhs.at[self.q_i_global].add(self.bht_lmh(*args) + self.bnt_lmn(*args)) rhs = rhs.at[self.i_lm_tot].add(self.s_fact * (self.c(*args) + self.d(*args))) if self.use_p_fact: c = c.at[jnp.ix_(self.q_i_global, self.q_i_global)].add(self.p_fact * (self.jac_func(self.p_func_h, 1)(*args))) c = c.at[jnp.ix_(self.q_i_global, self.q_i_global)].add(self.p_fact * (self.jac_func(self.p_func_n, 1)(*args))) k = k.at[jnp.ix_(self.q_i_global, self.q_i_global)].add(self.p_fact * (self.jac_func(self.p_func_h, 0)(*args))) k = k.at[jnp.ix_(self.q_i_global, self.q_i_global)].add(self.p_fact * (self.jac_func(self.p_func_n, 0)(*args))) rhs = rhs.at[self.q_i_global].add(self.p_fact * (self.p_func_h(*args) + self.p_func_n(*args))) return c, k, rhs return run def combine_constraints(csts: list[Constraint]) -> Callable: def combined_run(q: jarr, q_dot: jarr, u: list[None | jarr, ...], u_dot: list[None | jarr, ...], lmh: jarr, lmn: jarr) -> tuple[jarr, jarr, jarr]: q_i = [cst.q_i_global for cst in csts] n_cst = len(csts) out_mats = [csts[i_cst].run(q[q_i[i_cst]], q_dot[q_i[i_cst]], u[i_cst], u_dot[i_cst], lmh, lmn) for i_cst in range(n_cst)] c = jnp.sum(jnp.array([out_mats[i][0] for i in range(n_cst)]), axis=0) k = jnp.sum(jnp.array([out_mats[i][1] for i in range(n_cst)]), axis=0) rhs = jnp.sum(jnp.array([out_mats[i][2] for i in range(n_cst)]), axis=0) return c, k, rhs return jax.jit(combined_run) if USE_JIT else combined_run class BaseFunc(ABC): n_eq = 3 class CstFuncs(ABC): class EqualNodeFoR(BaseFunc): @staticmethod def b_lin_vel(constraint_: Constraint, i_lm: slice, b: jarr, q: jarr, q_dot: jarr, u: jarr, u_dot: jarr) \ -> jarr: b = b.at[i_lm, constraint_.i_r0].add( -Rot.from_quat(q_dot[constraint_.i_quat0]).as_matrix()) b = b.at[i_lm, constraint_.i_v0].add( -Rot.from_quat(q_dot[constraint_.i_quat0]).as_matrix()) b = b.at[i_lm, constraint_.i_v1].add( Rot.from_quat(q_dot[constraint_.i_quat1]).as_matrix()) b = b.at[i_lm, constraint_.i_omega0].add( Rot.from_quat(q_dot[constraint_.i_quat0]).as_matrix() @ skew(q[constraint_.i_r0])) return b @staticmethod def b_ang_vel(constraint_: Constraint, i_lm: slice, b: jarr, q: jarr, q_dot: jarr, u: jarr, u_dot: jarr) \ -> jarr: b = b.at[i_lm, constraint_.i_psi0].add(crv2tan(q[constraint_.i_psi0])) b = b.at[i_lm, constraint_.i_omega1].add(-crv2rot(q[constraint_.i_psi0]).T @ Rot.from_quat(q_dot[constraint_.i_quat0]).as_matrix().T @ Rot.from_quat(q_dot[constraint_.i_quat1]).as_matrix()) b = b.at[i_lm, constraint_.i_omega0].add(crv2rot(q[constraint_.i_psi0]).T) return b class ControlFoR(BaseFunc): @staticmethod def b_ang_vel(constraint_: Constraint, i_lm: slice, b: jarr, q: jarr, q_dot: jarr, u: jarr, u_dot: jarr) \ -> jarr: return b.at[i_lm, constraint_.i_omega1].add(jnp.eye(3)) @staticmethod def g_ang_vel(constraint_: Constraint, i_lm: slice, g: jarr, q: jarr, q_dot: jarr, u: jarr, u_dot: jarr) \ -> jarr: return g.at[i_lm].add(-crv2tan(u) @ u_dot) class ControlNodeFoR(EqualNodeFoR): @staticmethod def g_ang_vel(constraint_: Constraint, i_lm: slice, g: jarr, q: jarr, q_dot: jarr, u: jarr, u_dot: jarr) \ -> jarr: return g.at[i_lm].add(crv2rot(u).T @ crv2tan(u) @ u_dot) class ZeroFoR(BaseFunc): @staticmethod def b_lin_vel(constraint_: Constraint, i_lm: slice, b: jarr, q: jarr, q_dot: jarr, u: jarr, u_dot: jarr) \ -> jarr: return b.at[i_lm, constraint_.i_v1].add(jnp.eye(3)) @staticmethod def b_ang_vel(constraint_: Constraint, i_lm: slice, b: jarr, q: jarr, q_dot: jarr, u: jarr, u_dot: jarr) \ -> jarr: return b.at[i_lm, constraint_.i_omega1].add(jnp.eye(3)) class HingeNodeFoR(BaseFunc): n_eq = 2 @staticmethod def b_ang_vel(constraint_: Constraint, i_lm: slice, b: jarr, q: jarr, q_dot: jarr, u: jarr, u_dot: jarr) \ -> jarr: r_ax_a = jnp.array(constraint_.settings['rot_axisA2']) r_ax_a /= jnp.linalg.norm(r_ax_a) a_skew = skew(r_ax_a) r_ax_b = jnp.array(constraint_.settings['rot_axisB']) r_ax_b /= jnp.linalg.norm(r_ax_b) b_skew = skew(r_ax_b) rot_psi = crv2rot(q[constraint_.i_psi0]) aux = (rot_psi.T @ Rot.from_quat(q[constraint_.i_quat0]).as_matrix().T @ Rot.from_quat(q[constraint_.i_quat1]).as_matrix()) @ a_skew aux_norms = jnp.linalg.norm(aux, axis=1) dirs = jnp.array(((1, 2), (0, 2), (0, 1)))[jnp.argmin(aux_norms)] b = b.at[i_lm, constraint_.i_omega0].add((b_skew @ rot_psi.T)[dirs, :]) b = b.at[i_lm, constraint_.i_psi0].add((b_skew @ crv2tan(q[constraint_.i_psi0]))[dirs, :]) b = b.at[i_lm, constraint_.i_omega1].add(-aux[dirs, :]) return b class HingeFoR(BaseFunc): n_eq = 2 @classmethod def b_ang_vel(cls, constraint_: Constraint, i_lm: slice, b: jarr, q: jarr, q_dot: jarr, u: jarr, u_dot: jarr) \ -> jarr: axis = jnp.array(constraint_.settings['rot_axis_AFoR']) axis = axis / jnp.linalg.norm(axis) axis_skew = skew(axis) dirs = jnp.array(((1, 2), (0, 2), (0, 1)))[jnp.argmax(axis)] # directions for axis matrix i_omega1_indep = jnp.arange(constraint_.i_omega1[0], constraint_.i_omega1[2] + 1)[dirs] b = jax.lax.cond(jnp.abs(jnp.linalg.norm(axis) - 1.) < 1e-6, lambda: b.at[i_lm, i_omega1_indep].add(jnp.eye(2)), lambda: b.at[i_lm, constraint_.i_omega1].add(axis_skew[dirs, :])) return b def move_for_to_node(constraint_: Constraint, mb_beam, mb_tstep) -> None: i_body_node = constraint_.settings['body'] # beam number which contains node i_node = constraint_.settings['node_in_body'] # node number in beam i_body_for = constraint_.settings['body_FoR'] rel_pos_b = constraint_.settings.get('rel_posB', np.zeros(3)) i_elem, i_node_in_elem = mb_beam[i_body_node].node_master_elem[i_node] c_ga = mb_tstep[i_body_node].cga() c_ab = sp.spatial.transform.Rotation.from_rotvec(mb_tstep[i_body_node].psi[i_elem, i_node_in_elem, :]).as_matrix() mb_tstep[i_body_for].for_pos[:3] = (c_ga @ (mb_tstep[i_body_node].pos[i_node, :] + c_ab @ rel_pos_b) + mb_tstep[i_body_node].for_pos[:3]) @constraint class FullyConstrainedNodeFoR(Constraint): lc_id = 'fully_constrained_node_FoR' required_params = ('node_in_body', 'body', 'body_FoR') bn_funcs = (CstFuncs.EqualNodeFoR.b_lin_vel, CstFuncs.EqualNodeFoR.b_ang_vel) gn_funcs = (None, None) num_lmn_eq = (CstFuncs.EqualNodeFoR.n_eq, CstFuncs.EqualNodeFoR.n_eq) postproc_funcs = (move_for_to_node,) @constraint class FullyConstrainedFoR(Constraint): lc_id = 'fully_constrained_FoR' required_params = ('body_FoR',) bn_funcs = (CstFuncs.ZeroFoR.b_lin_vel, CstFuncs.ZeroFoR.b_ang_vel) gn_funcs = (None, None) num_lmn_eq = (CstFuncs.ZeroFoR.n_eq, CstFuncs.ZeroFoR.n_eq) @constraint class SphericalFoR(Constraint): lc_id = 'spherical_FoR' required_params = ('body_FoR',) bn_funcs = (CstFuncs.ZeroFoR.b_lin_vel,) gn_funcs = (None,) num_lmn_eq = (CstFuncs.ZeroFoR.n_eq,) @constraint class Free(Constraint): lc_id = 'free' @constraint class ControlledRotNodeFoR(Constraint): lc_id = 'control_node_FoR_rot_vel' required_params = ('controller_id', 'node_in_body', 'body', 'body_FoR') bn_funcs = (CstFuncs.ControlNodeFoR.b_lin_vel, CstFuncs.ControlNodeFoR.b_ang_vel) gn_funcs = (None, CstFuncs.ControlNodeFoR.g_ang_vel) num_lmn_eq = (CstFuncs.ControlNodeFoR.n_eq, CstFuncs.ControlNodeFoR.n_eq) postproc_funcs = (move_for_to_node,) @constraint class ControlledRotFoR(Constraint): lc_id = 'control_rot_vel_FoR' required_params = ('controller_id', 'body_FoR') bn_funcs = (CstFuncs.ZeroFoR.b_lin_vel, CstFuncs.ControlFoR.b_ang_vel) gn_funcs = (None, CstFuncs.ControlFoR.g_ang_vel) num_lmn_eq = (CstFuncs.ZeroFoR.n_eq, CstFuncs.ControlFoR.n_eq) @constraint class HingeFoR(Constraint): lc_id = 'hinge_FoR' required_params = ('body_FoR', 'rot_axis_AFoR') bn_funcs = (CstFuncs.ZeroFoR.b_lin_vel, CstFuncs.HingeFoR.b_ang_vel) gn_funcs = (None, None) num_lmn_eq = (CstFuncs.ZeroFoR.n_eq, CstFuncs.HingeFoR.n_eq) @constraint class SphericalNodeFor(Constraint): lc_id = 'spherical_node_FoR' required_params = ('body', 'body_FoR', 'node_in_body') bn_funcs = (CstFuncs.EqualNodeFoR.b_lin_vel, ) gn_funcs = (None, ) num_lmn_eq = (CstFuncs.EqualNodeFoR.n_eq, ) postproc_funcs = (move_for_to_node,) @constraint class HingeNodeFoR(Constraint): lc_id = 'hinge_node_FoR' required_params = ('body', 'body_FoR', 'node_in_body', 'rot_axisA2', 'rot_axisB') bn_funcs = (CstFuncs.EqualNodeFoR.b_lin_vel, CstFuncs.HingeNodeFoR.b_ang_vel) gn_funcs = (None, None) num_lmn_eq = (CstFuncs.EqualNodeFoR.n_eq, CstFuncs.HingeNodeFoR.n_eq) postproc_funcs = (move_for_to_node,) if __name__ == '__main__': print("Available constraints:") for constraint in DICT_OF_LC.values(): print(constraint.lc_id) ================================================ FILE: sharpy/structure/utils/modalutils.py ================================================ import numpy as np import sharpy.utils.cout_utils as cout import sharpy.utils.algebra as algebra from sharpy.utils.plotutils import plot_frame_to_vtk def frequency_damping(eigenvalue): omega_n = np.abs(eigenvalue) omega_d = np.abs(eigenvalue.imag) f_n = omega_n / 2 / np.pi f_d = omega_d / 2 / np.pi if f_d < 1e-8: damping_ratio = 1. period = np.inf else: damping_ratio = -eigenvalue.real / omega_n period = 1 / f_d return omega_n, omega_d, damping_ratio, f_n, f_d, period class EigenvalueTable(cout.TablePrinter): def __init__(self, filename=None): super().__init__(7, 12, ['g', 'f', 'f', 'f', 'f', 'f', 'f'], filename) self.headers = ['mode', 'eval_real', 'eval_imag', 'freq_n (Hz)', 'freq_d (Hz)', 'damping', 'period (s)'] def print_evals(self, eigenvalues): for i in range(len(eigenvalues)): omega_n, omega_d, damping_ratio, f_n, f_d, period = frequency_damping(eigenvalues[i]) self.print_line([i, eigenvalues[i].real, eigenvalues[i].imag, f_n, f_d, damping_ratio, period]) def cg(M, use_euler=False): if use_euler: Mrr = M[-9:, -9:] else: Mrr = M[-10:, -10:] return -np.array([Mrr[2, 4], Mrr[0, 5], Mrr[1, 3]]) / Mrr[0, 0] def scale_mode(data, eigenvector, rot_max_deg=15.0, perc_max=0.15): """ Scales the eigenvector such that: 1) the maximum change in component of the beam cartesian rotation vector is equal to rot_max_deg degrees. 2) the maximum translational displacement does not exceed perc_max the maximum nodal position. Warning: If the eigenvector is in state-space form, only the first half of the eigenvector is scanned for determining the scaling. """ ### initialise struct = data.structure tsstr = data.structure.timestep_info[data.ts] jj = 0 # structural dofs index RotMax = 0.0 RaMax = 0.0 dRaMax = 0.0 for node_glob in range(struct.num_node): ### detect bc at node (and no. of dofs) bc_here = struct.boundary_conditions[node_glob] if bc_here == 1: # clamp dofs_here = 0 continue elif bc_here == -1 or bc_here == 0: dofs_here = 6 jj_tra = [jj, jj + 1, jj + 2] jj_rot = [jj + 3, jj + 4, jj + 5] jj += dofs_here # check for max rotation RotMaxHere = np.max(np.abs(eigenvector[jj_rot].real)) if RotMaxHere > RotMax: RotMax = RotMaxHere # check for maximum position RaNorm = np.linalg.norm(tsstr.pos[node_glob, :]) if RaNorm > RaMax: RaMax = RaNorm # check for maximum displacement dRaNorm = np.linalg.norm(eigenvector[jj_tra].real) if dRaNorm > dRaMax: dRaMax = dRaNorm RotMaxDeg = RotMax * 180 / np.pi if RotMaxDeg > 1e-4: fact = rot_max_deg / RotMaxDeg if dRaMax * fact > perc_max * RaMax: fact = perc_max * RaMax / dRaMax else: fact = perc_max * RaMax / dRaMax # correct factor to ensure max disp is perc return fact def get_mode_zeta(data, eigvect): """ Retrieves the UVLM grid nodal displacements associated to the eigenvector ``eigvect`` """ ### initialise aero = data.aero struct = data.structure tsaero = data.aero.timestep_info[data.ts] tsstr = data.structure.timestep_info[data.ts] try: num_dof = struct.num_dof.value except AttributeError: num_dof = struct.num_dof eigvect = eigvect[:num_dof] zeta_mode = [] for ss in range(aero.n_surf): zeta_mode.append(tsaero.zeta[ss].copy()) jj = 0 # structural dofs index Cga0 = algebra.quat2rotation(tsstr.quat) Cag0 = Cga0.T for node_glob in range(struct.num_node): ### detect bc at node (and no. of dofs) bc_here = struct.boundary_conditions[node_glob] if bc_here == 1: # clamp dofs_here = 0 continue elif bc_here == -1 or bc_here == 0: dofs_here = 6 jj_tra = [jj, jj + 1, jj + 2] jj_rot = [jj + 3, jj + 4, jj + 5] jj += dofs_here # retrieve element and local index ee, node_loc = struct.node_master_elem[node_glob, :] # get original position and crv Ra0 = tsstr.pos[node_glob, :] psi0 = tsstr.psi[ee, node_loc, :] Rg0 = np.dot(Cga0, Ra0) Cab0 = algebra.crv2rotation(psi0) Cbg0 = np.dot(Cab0.T, Cag0) # update position and crv of mode Ra = tsstr.pos[node_glob, :] + eigvect[jj_tra] psi = tsstr.psi[ee, node_loc, :] + eigvect[jj_rot] Rg = np.dot(Cga0, Ra) Cab = algebra.crv2rotation(psi) Cbg = np.dot(Cab.T, Cag0) ### str -> aero mapping # some nodes may be linked to multiple surfaces... for str2aero_here in aero.struct2aero_mapping[node_glob]: # detect surface/span-wise coordinate (ss,nn) nn, ss = str2aero_here['i_n'], str2aero_here['i_surf'] # print('%.2d,%.2d'%(nn,ss)) # surface panelling M = aero.dimensions[ss][0] N = aero.dimensions[ss][1] for mm in range(M + 1): # get position of vertex in B FoR zetag0 = tsaero.zeta[ss][:, mm, nn] # in G FoR, w.r.t. origin A-G Xb = np.dot(Cbg0, zetag0 - Rg0) # in B FoR, w.r.t. origin B # update vertex position zeta_mode[ss][:, mm, nn] = Rg + np.dot(np.dot(Cga0, Cab), Xb) return zeta_mode def write_zeta_vtk(zeta, zeta_ref, filename_root): """ Given a list of arrays representing the coordinates of a set of n_surf UVLM lattices and organised as: zeta[n_surf][3,M+1,N=1] this function writes a vtk for each of the n_surf surfaces. Args: zeta (np.array): lattice coordinates to plot zeta_ref (np.array): reference lattice used to compute the magnitude of displacements filename_root (str): initial part of filename (full path) without file extension """ for i_surf in range(len(zeta)): filename = f"{filename_root}_{i_surf:02d}.vtu" this_zeta = np.swapaxes(zeta[i_surf], 0, -1) this_zeta_ref = np.swapaxes(zeta_ref[i_surf], 0, -1) disp = np.linalg.norm(this_zeta - this_zeta_ref, axis=-1) panel_surf_id = np.ones((this_zeta.shape[0]-1, this_zeta.shape[1]-1), dtype=int) * i_surf plot_frame_to_vtk(this_zeta, filename, node_scalar_data={'point_displacement_magnitude': disp}, cell_scalar_data={'panel_surface_id': panel_surf_id}) def write_modes_vtk(data, eigenvectors, num_lambda, filename_root, rot_max_deg=15., perc_max=0.15, ts=-1): """ Writes a vtk file for each of the first ``num_lambda`` eigenvectors. When these are associated to the state-space form of the structural equations, only the displacement field is saved. """ # initialise struct = data.structure tsaero = data.aero.timestep_info[ts] num_dof = struct.num_dof.value eigenvectors = eigenvectors[:num_dof, :] # skip rigid body modes num_rigid_body = 10 if data.settings['Modal']['rigid_body_modes'] else 0 for mode in range(num_rigid_body, num_lambda): # scale eigenvector eigvec = eigenvectors[:num_dof, mode] fact = scale_mode(data, eigvec, rot_max_deg, perc_max) eigvec = eigvec * fact zeta_mode = get_mode_zeta(data, eigvec) write_zeta_vtk(zeta_mode, tsaero.zeta, f"{filename_root}_{mode:06d}") def free_modes_principal_axes(phi, mass_matrix, use_euler=False, **kwargs): """ Transforms the rigid body modes defined at with the A frame as reference to the centre of mass position and aligned with the principal axes of inertia. Args: phi (np.array): Eigenvectors defined at the ``A`` frame. mass_matrix (np.array): System mass matrix use_euler (bool): Use Euler rotation parametrisation rather than quaternions. Keyword Args: return_transform (bool): Return tuple containing transformed modes and the transformation from the ``A`` frame to the ``P`` frame. Returns: np.array: Mass normalised modes with rigid modes defined at the centre of gravity and aligned with the principal axes of inertia. References: Marc Artola, 2020 """ if use_euler: num_rigid_modes = 9 else: num_rigid_modes = 10 r_cg = cg(mass_matrix, use_euler) # centre of gravity mrr = mass_matrix[-num_rigid_modes:-num_rigid_modes + 6, -num_rigid_modes:-num_rigid_modes + 6] m = mrr[0, 0] # mass # principal axes of inertia matrix and transformation matrix j_cm, t_rb = principal_axes_inertia(mrr[-3:, -3:], r_cg, m) # rigid body mass matrix about CM and inertia in principal axes m_cm = np.eye(6) * m m_cm[-3:, -3:] = np.diag(j_cm) # rigid body modes about CG - mass normalised rb_cm = np.eye(6) rb_cm /= np.sqrt(np.diag(rb_cm.T.dot(m_cm.dot(rb_cm)))) # transform to A frame reference position trb_diag = np.zeros((6, 6)) # matrix with (t_rb, t_rb) in the diagonal trb_diag[:3, :3] = t_rb trb_diag[-3:, -3:] = t_rb rb_a = np.block([[np.eye(3), algebra.skew(r_cg)], [np.zeros((3, 3)), np.eye(3)]]).dot(trb_diag.dot(rb_cm)) phit = np.block([np.zeros((phi.shape[0], num_rigid_modes)), phi[:, num_rigid_modes:]]) phit[-num_rigid_modes:-num_rigid_modes + 6, :6] = rb_a phit[-num_rigid_modes + 6:, 6:num_rigid_modes] = np.eye(num_rigid_modes - 6) # euler or quaternion modes if kwargs.get('return_transform', False): return phit, t_rb, np.block([[np.eye(3), algebra.skew(r_cg)], [np.zeros((3, 3)), np.eye(3)]]).dot(trb_diag) else: return phit def principal_axes_inertia(j_a, r_cg, m): r""" Transform the inertia tensor :math:`\boldsymbol{j}_a` defined about the ``A`` frame of reference to the centre of gravity and aligned with the principal axes of inertia. The inertia tensor about the centre of gravity is obtained using the parallel axes theorem .. math:: \boldsymbol{j}_{cm} = \boldsymbol{j}_a + \tilde{r}_{cg}\tilde{r}_{cg}m and rotated such that it is aligned with its eigenvectors and thus represents the inertia tensor about the principal axes of inertia .. math:: \boldsymbol{j}_p = T_{pa}^\top \boldsymbol{j}_{cm} T^{pa} where :math:`T^{pa}` is the transformation matrix from the ``A`` frame to the principal axes ``P`` frame. Args: j_a (np.array): Inertia tensor defined about the ``A`` frame. r_cg (np.array): Centre of gravity position defined in ``A`` coordinates. m (float): Mass. Returns: tuple: Containing :math:`\boldsymbol{j}_p` and :math:`T^{pa}` """ j_p, t_pa = np.linalg.eig(j_a + algebra.skew(r_cg) @ (algebra.skew(r_cg)) * m) t_pa, j_p = order_eigenvectors(t_pa, j_p) return j_p, t_pa def mode_sign_convention(bocos, eigenvectors, rigid_body_motion=False, use_euler=False): """ When comparing against different cases, it is important that the modes share a common sign convention. In this case, modes will be arranged such that the z-coordinate of the first free end is positive. If the z-coordinate is 0, then the y-coordinate is forced to be positive, then x, followed by the CRV in y, x and z. Returns: np.ndarray: Eigenvectors following the aforementioned sign convention. """ if use_euler: num_rigid_modes = 9 else: num_rigid_modes = 10 if rigid_body_motion: eigenvectors = order_rigid_body_modes(eigenvectors, use_euler) # A frame reference z_coord = -num_rigid_modes + 2 y_coord = -num_rigid_modes + 1 x_coord = -num_rigid_modes + 0 mz_coord = -num_rigid_modes + 5 my_coord = -num_rigid_modes + 4 mx_coord = -num_rigid_modes + 3 else: first_free_end_node = np.where(bocos == -1)[0][0] z_coord = 6 * (first_free_end_node - 1) + 2 y_coord = 6 * (first_free_end_node - 1) + 1 x_coord = 6 * (first_free_end_node - 1) + 0 my_coord = 6 * (first_free_end_node - 1) + 4 mz_coord = 6 * (first_free_end_node - 1) + 5 mx_coord = 6 * (first_free_end_node - 1) + 3 for i in range(0, eigenvectors.shape[1]): if np.abs(eigenvectors[z_coord, i]) > 1e-8: eigenvectors[:, i] = np.sign(eigenvectors[z_coord, i]) * eigenvectors[:, i] elif np.abs(eigenvectors[y_coord, i]) > 1e-8: eigenvectors[:, i] = np.sign(eigenvectors[y_coord, i]) * eigenvectors[:, i] elif np.abs(eigenvectors[x_coord, i]) > 1e-8: eigenvectors[:, i] = np.sign(eigenvectors[x_coord, i]) * eigenvectors[:, i] elif np.abs(eigenvectors[my_coord, i]) > 1e-8: eigenvectors[:, i] = np.sign(eigenvectors[my_coord, i]) * eigenvectors[:, i] elif np.abs(eigenvectors[mx_coord, i]) > 1e-8: eigenvectors[:, i] = np.sign(eigenvectors[mx_coord, i]) * eigenvectors[:, i] elif np.abs(eigenvectors[mz_coord, i]) > 1e-8: eigenvectors[:, i] = np.sign(eigenvectors[mz_coord, i]) * eigenvectors[:, i] else: if rigid_body_motion: if not np.max(np.abs(eigenvectors[-num_rigid_modes+6:, i])) == 1.0: # orientation mode, either euler/quat cout.cout_wrap('Implementing mode sign convention. Mode {:g} component at the A frame is 0.'.format(i), 3) else: # cout.cout_wrap('Mode component at the first free end (node {:g}) is 0.'.format(first_free_end_node), 3) # this will be the case for symmetric clamped structures, where modes will be present for the left and # right wings. Method should be called again when symmetric modes are removed. pass return eigenvectors def order_rigid_body_modes(eigenvectors, use_euler): if use_euler: num_rigid_modes = 9 else: num_rigid_modes = 10 phi_rr = np.zeros((num_rigid_modes, num_rigid_modes)) num_node = eigenvectors.shape[0] for i in range(num_rigid_modes): index_max_node = np.where(eigenvectors[:, i] == np.max(eigenvectors[:, i]))[0][0] index_mode = num_rigid_modes - (num_node - index_max_node) phi_rr[:, index_mode] = eigenvectors[-num_rigid_modes:, i] eigenvectors[-num_rigid_modes:, :num_rigid_modes] = phi_rr return eigenvectors def order_eigenvectors(eigenvectors, eigenvalues): ordered_eigenvectors = np.zeros_like(eigenvectors) new_order = [] for i in range(eigenvectors.shape[1]): index_max_node = np.where(np.abs(eigenvectors[:, i]) == np.max(np.abs(eigenvectors[:, i])))[0][0] ordered_eigenvectors[:, index_max_node] = eigenvectors[:, i] * np.sign(eigenvectors[index_max_node, i]) new_order.append(index_max_node) try: eigenvalues.shape[1] except IndexError: new_eigenvalues = eigenvalues[new_order] else: new_eigenvalues = eigenvalues[:, new_order] return ordered_eigenvectors, new_eigenvalues def scale_mass_normalised_modes(eigenvectors, mass_matrix): r""" Scales eigenvector matrix such that the modes are mass normalised: .. math:: \phi^\top\boldsymbol{M}\phi = \boldsymbol{I} and .. math:: \phi^\top\boldsymbol{K}\phi = \mathrm{diag}(\omega^2) Args: eigenvectors (np.array): Eigenvector matrix. mass_matrix (np.array): Mass matrix. Returns: np.array: Mass-normalised eigenvectors. """ # mass normalise (diagonalises M and K) dfact = np.diag(np.dot(eigenvectors.T, np.dot(mass_matrix, eigenvectors))) eigenvectors = (1./np.sqrt(dfact))*eigenvectors return eigenvectors def assert_orthogonal_eigenvectors(u, v, decimal, raise_error=False): """ Checks orthogonality between eigenvectors Args: u (np.ndarray): Eigenvector 1. v (np.ndarray): Eigenvector 2. decimal (int): Number of decimal points to compare raise_error (bool): Raise an error or print a warning Raises: AssertionError: if ``raise_error == True`` it raises an error. """ try: np.testing.assert_almost_equal(u.dot(v), 0, decimal=decimal, err_msg='Eigenvectors not orthogonal') # random eigenvector to test orthonality except AssertionError as e: if raise_error: raise e else: cout.cout_wrap('Eigenvectors not orthogonal', 3) def assert_modes_mass_normalised(phi, m, tolerance, raise_error=False): """ Asserts the eigenvectors result in an identity modal mass matrix. Args: phi (np.ndarray): Eigenvector matrix m (np.ndarray): Mass matrix tolerance (float): Absolute tolerance. raise_error (bool): Raise ``AssertionError`` if modes not mass normalised. Returns: AssertionError: if ``raise_error == True`` it raises an error. """ modal_mass = phi.T.dot(m.dot(phi)) try: np.testing.assert_allclose(modal_mass - np.eye(modal_mass.shape[0]), np.zeros_like(modal_mass), atol=tolerance, err_msg='Eigenvectors are not mass normalised') except AssertionError as e: if raise_error: raise e else: cout.cout_wrap('Eigenvectors are not mass normalised', 3) def modes_to_cg_ref(phi, M, rigid_body_motion=False, use_euler=False): r""" Returns the rigid body modes defined with respect to the centre of gravity The transformation from the modes defined at the FoR A origin, :math:`\boldsymbol{\Phi}`, to the modes defined using the centre of gravity as a reference is .. math:: \boldsymbol{\Phi}_{rr,CG}|_{TRA} = \boldsymbol{\Phi}_{RR}|_{TRA} + \tilde{\mathbf{r}}_{CG} \boldsymbol{\Phi}_{RR}|_{ROT} .. math:: \boldsymbol{\Phi}_{rr,CG}|_{ROT} = \boldsymbol{\Phi}_{RR}|_{ROT} Returns: (np.array): Transformed eigenvectors """ # if not rigid_body_motion: # return phi # NG - 26/7/19 This is the transformation being performed by K_vec # Leaving this here for now in case it becomes necessary # .. math:: \boldsymbol{\Phi}_{ss,CG}|_{TRA} = \boldsymbol{\Phi}_{SS}|_{TRA} +\boldsymbol{\Phi}_{RS}|_{TRA} - # \tilde{\mathbf{r}}_{A}\boldsymbol{\Phi}_{RS}|_{ROT} # # .. math:: \boldsymbol{\Phi}_{ss,CG}|_{ROT} = \boldsymbol{\Phi}_{SS}|_{ROT} # + (\mathbf{T}(\boldsymbol{\Psi})^\top)^{-1}\boldsymbol{\Phi}_{RS}|_{ROT} # pos = self.data.structure.timestep_info[self.data.ts].pos r_cg = cg(M) # jj = 0 K_vec = np.zeros((phi.shape[0], phi.shape[0])) # jj_for_vel = range(self.data.structure.num_dof.value, self.data.structure.num_dof.value + 3) # jj_for_rot = range(self.data.structure.num_dof.value + 3, self.data.structure.num_dof.value + 6) # for node_glob in range(self.data.structure.num_node): # ### detect bc at node (and no. of dofs) # bc_here = self.data.structure.boundary_conditions[node_glob] # # if bc_here == 1: # clamp (only rigid-body) # dofs_here = 0 # jj_tra, jj_rot = [], [] # continue # # elif bc_here == -1 or bc_here == 0: # (rigid+flex body) # dofs_here = 6 # jj_tra = 6 * self.data.structure.vdof[node_glob] + np.array([0, 1, 2], dtype=int) # jj_rot = 6 * self.data.structure.vdof[node_glob] + np.array([3, 4, 5], dtype=int) # # jj_tra=[jj ,jj+1,jj+2] # # jj_rot=[jj+3,jj+4,jj+5] # else: # raise NameError('Invalid boundary condition (%d) at node %d!' \ # % (bc_here, node_glob)) # # jj += dofs_here # # ee, node_loc = self.data.structure.node_master_elem[node_glob, :] # psi = self.data.structure.timestep_info[self.data.ts].psi[ee, node_loc, :] # # Ra = pos[node_glob, :] # in A FoR with respect to G # # K_vec[np.ix_(jj_tra, jj_tra)] += np.eye(3) # K_vec[np.ix_(jj_tra, jj_for_vel)] += np.eye(3) # K_vec[np.ix_(jj_tra, jj_for_rot)] -= algebra.skew(Ra) # # K_vec[np.ix_(jj_rot, jj_rot)] += np.eye(3) # K_vec[np.ix_(jj_rot, jj_for_rot)] += np.linalg.inv(algebra.crv2tan(psi).T) # NG - 26/7/19 - Transformation of the rigid part of the elastic modes ended up not being necessary but leaving # here in case it becomes useful in the future (using K_vec) # Rigid-Rigid modes transform if use_euler: num_rig_dof = 9 else: num_rig_dof = 10 Krr = np.eye(num_rig_dof) Krr[np.ix_([0, 1, 2], [3, 4, 5])] += algebra.skew(r_cg) # Assemble transformed modes phirr = Krr.dot(phi[-num_rig_dof:, :num_rig_dof]) # phiss = K_vec.dot(phi[:, 10:]) # Get rigid body modes to be positive in translation and rotation for i in range(num_rig_dof): ind = np.argmax(np.abs(phirr[:, i])) phirr[:, i] = np.sign(phirr[ind, i]) * phirr[:, i] phit = np.block([np.zeros((phi.shape[0], num_rig_dof)), phi[:, num_rig_dof:]]) phit[-num_rig_dof:, :num_rig_dof] = phirr return phit ================================================ FILE: sharpy/structure/utils/xbeamlib.py ================================================ import ctypes as ct import numpy as np import scipy as sc import scipy.integrate import sharpy.utils.algebra as algebra import sharpy.utils.ctypes_utils as ct_utils from sharpy.utils.sharpydir import SharpyDir # from sharpy.utils.datastructures import StructTimeStepInfo import sharpy.utils.cout_utils as cout try: xbeamlib = ct_utils.import_ctypes_lib(SharpyDir + '/xbeam', 'libxbeam') except OSError: xbeamlib = ct_utils.import_ctypes_lib(SharpyDir + '/lib/xbeam/lib', 'libxbeam') # ctypes pointer types doubleP = ct.POINTER(ct.c_double) intP = ct.POINTER(ct.c_int) charP = ct.POINTER(ct.c_char_p) class Xbopts(ct.Structure): """Structure skeleton for options input in xbeam """ _fields_ = [("FollowerForce", ct.c_bool), ("FollowerForceRig", ct.c_bool), ("PrintInfo", ct.c_bool), ("OutInBframe", ct.c_bool), ("OutInaframe", ct.c_bool), ("ElemProj", ct.c_int), ("MaxIterations", ct.c_int), ("NumLoadSteps", ct.c_int), ("NumGauss", ct.c_int), ("Solution", ct.c_int), ("DeltaCurved", ct.c_double), ("MinDelta", ct.c_double), ("abs_threshold", ct.c_double), ("NewmarkDamp", ct.c_double), ("gravity_on", ct.c_bool), ("gravity", ct.c_double), ("gravity_dir_x", ct.c_double), ("gravity_dir_y", ct.c_double), ("gravity_dir_z", ct.c_double), ("balancing", ct.c_bool), ("relaxation_factor", ct.c_double), ] def __init__(self): ct.Structure.__init__(self) self.FollowerForce = ct.c_bool(True) self.FollowerForceRig = ct.c_bool(True) self.PrintInfo = ct.c_bool(True) self.OutInBframe = ct.c_bool(False) self.OutInaframe = ct.c_bool(True) self.ElemProj = ct.c_int(0) self.MaxIterations = ct.c_int(99) self.NumLoadSteps = ct.c_int(5) self.NumGauss = ct.c_int(0) self.Solution = ct.c_int(111) self.DeltaCurved = ct.c_double(1.0e-2) self.MinDelta = ct.c_double(1.0e-8) self.abs_threshold = ct.c_double(1.0e-13) self.NewmarkDamp = ct.c_double(0.0) self.gravity_on = ct.c_bool(False) self.gravity = ct.c_double(0.0) self.gravity_dir_x = ct.c_double(0.0) self.gravity_dir_y = ct.c_double(0.0) self.gravity_dir_z = ct.c_double(1.0) self.balancing = ct.c_bool(False) self.relaxation_factor = ct.c_double(0.3) def cbeam3_solv_nlnstatic(beam, settings, ts): """@brief Python wrapper for f_cbeam3_solv_nlnstatic Alfonso del Carre """ f_cbeam3_solv_nlnstatic = xbeamlib.cbeam3_solv_nlnstatic_python f_cbeam3_solv_nlnstatic.restype = None n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) n_mass = ct.c_int(beam.n_mass) n_stiff = ct.c_int(beam.n_stiff) xbopts = Xbopts() xbopts.PrintInfo = ct.c_bool(settings['print_info']) xbopts.Solution = ct.c_int(112) xbopts.MaxIterations = ct.c_int(settings['max_iterations']) xbopts.NumLoadSteps = ct.c_int(settings['num_load_steps']) xbopts.DeltaCurved = ct.c_double(settings['delta_curved']) xbopts.MinDelta = ct.c_double(settings['min_delta']) xbopts.abs_threshold = ct.c_double(settings['abs_threshold']) xbopts.gravity_on = ct.c_bool(settings['gravity_on']) xbopts.gravity = ct.c_double(settings['gravity']) gravity_vector = np.dot(beam.timestep_info[ts].cag(), settings['gravity_dir']) xbopts.gravity_dir_x = ct.c_double(gravity_vector[0]) xbopts.gravity_dir_y = ct.c_double(gravity_vector[1]) xbopts.gravity_dir_z = ct.c_double(gravity_vector[2]) xbopts.relaxation_factor = ct.c_double(settings['relaxation_factor']) # here we only need to set the flags at True, all the forces are follower xbopts.FollowerForce = ct.c_bool(True) xbopts.FollowerForceRig = ct.c_bool(True) f_cbeam3_solv_nlnstatic(ct.byref(n_elem), ct.byref(n_nodes), beam.fortran['num_nodes'].ctypes.data_as(intP), beam.fortran['num_mem'].ctypes.data_as(intP), beam.fortran['connectivities'].ctypes.data_as(intP), beam.fortran['master'].ctypes.data_as(intP), ct.byref(n_mass), beam.fortran['mass'].ctypes.data_as(doubleP), beam.fortran['mass_indices'].ctypes.data_as(intP), ct.byref(n_stiff), beam.fortran['stiffness'].ctypes.data_as(doubleP), beam.fortran['inv_stiffness'].ctypes.data_as(doubleP), beam.fortran['stiffness_indices'].ctypes.data_as(intP), beam.fortran['frame_of_reference_delta'].ctypes.data_as(doubleP), beam.fortran['rbmass'].ctypes.data_as(doubleP), beam.fortran['node_master_elem'].ctypes.data_as(intP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['fdof'].ctypes.data_as(intP), ct.byref(xbopts), beam.ini_info.pos.ctypes.data_as(doubleP), beam.ini_info.psi.ctypes.data_as(doubleP), beam.timestep_info[ts].pos.ctypes.data_as(doubleP), beam.timestep_info[ts].psi.ctypes.data_as(doubleP), beam.timestep_info[ts].steady_applied_forces.ctypes.data_as(doubleP), beam.timestep_info[ts].gravity_forces.ctypes.data_as(doubleP) ) def cbeam3_loads(beam, timestep): """Python wrapper for f_cbeam3_loads Args: beam (sharpy.structure.models.beam.Beam): Structural info class timestep (sharpy.utils.datastructures.StructTimeStepInfo): Structural time step class Returns: tuple: Tuple containing the ``strains`` and ``loads``. """ f_cbeam3_loads = xbeamlib.cbeam3_loads f_cbeam3_loads.restype = None n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) n_stiff = ct.c_int(beam.n_stiff) strain = np.zeros((n_elem.value, 6), dtype=ct.c_double, order='F') loads = np.zeros((n_elem.value, 6), dtype=ct.c_double, order='F') f_cbeam3_loads(ct.byref(n_elem), ct.byref(n_nodes), beam.fortran['connectivities'].ctypes.data_as(intP), beam.ini_info.pos.ctypes.data_as(doubleP), timestep.pos.ctypes.data_as(doubleP), beam.ini_info.psi.ctypes.data_as(doubleP), timestep.psi.ctypes.data_as(doubleP), beam.fortran['stiffness_indices'].ctypes.data_as(intP), ct.byref(n_stiff), beam.fortran['stiffness'].ctypes.data_as(doubleP), strain.ctypes.data_as(doubleP), loads.ctypes.data_as(doubleP)) return strain, loads def cbeam3_solv_nlndyn(beam, settings): f_cbeam3_solv_nlndyn = xbeamlib.cbeam3_solv_nlndyn_python f_cbeam3_solv_nlndyn.restype = None n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) n_mass = ct.c_int(beam.n_mass) n_stiff = ct.c_int(beam.n_stiff) dt = settings['dt'] n_tsteps = settings['num_steps'] time = np.zeros((n_tsteps,), dtype=ct.c_double, order='F') for i in range(n_tsteps): time[i] = i*dt # deformation history matrices pos_def_history = np.zeros((n_tsteps, beam.num_node, 3), order='F') pos_dot_def_history = np.zeros((n_tsteps, beam.num_node, 3), order='F') psi_def_history = np.zeros((n_tsteps, beam.num_elem, 3, 3), order='F') psi_dot_def_history = np.zeros((n_tsteps, beam.num_elem, 3, 3), order='F') n_tsteps = ct.c_int(n_tsteps) xbopts = Xbopts() xbopts.PrintInfo = ct.c_bool(settings['print_info']) xbopts.Solution = ct.c_int(312) # xbopts.OutInaframe = ct.c_bool(settings['out_a_frame']) # xbopts.OutInBframe = ct.c_bool(settings['out_b_frame']) # xbopts.ElemProj = settings['elem_proj'] xbopts.MaxIterations = ct.c_int(settings['max_iterations']) xbopts.NumLoadSteps = ct.c_int(settings['num_load_steps']) xbopts.NumGauss = ct.c_int(0) xbopts.DeltaCurved = ct.c_double(settings['delta_curved']) xbopts.MinDelta = ct.c_double(settings['min_delta']) xbopts.abs_threshold = ct.c_double(settings['abs_threshold']) xbopts.NewmarkDamp = ct.c_double(settings['newmark_damp']) xbopts.gravity_on = ct.c_bool(settings['gravity_on']) xbopts.gravity = ct.c_double(settings['gravity']) xbopts.gravity_dir_x = ct.c_double(settings['gravity_dir'][0]) xbopts.gravity_dir_y = ct.c_double(settings['gravity_dir'][1]) xbopts.gravity_dir_z = ct.c_double(settings['gravity_dir'][2]) xbopts.relaxation_factor = ct.c_double(settings['relaxation_factor']) # here we only need to set the flags at True, all the forces are follower xbopts.FollowerForce = ct.c_bool(True) xbopts.FollowerForceRig = ct.c_bool(True) f_cbeam3_solv_nlndyn(ct.byref(n_elem), ct.byref(n_nodes), ct.byref(n_tsteps), time.ctypes.data_as(doubleP), beam.fortran['num_nodes'].ctypes.data_as(intP), beam.fortran['num_mem'].ctypes.data_as(intP), beam.fortran['connectivities'].ctypes.data_as(intP), beam.fortran['master'].ctypes.data_as(intP), ct.byref(n_mass), beam.fortran['mass'].ctypes.data_as(doubleP), beam.fortran['mass_indices'].ctypes.data_as(intP), ct.byref(n_stiff), beam.fortran['stiffness'].ctypes.data_as(doubleP), beam.fortran['inv_stiffness'].ctypes.data_as(doubleP), beam.fortran['stiffness_indices'].ctypes.data_as(intP), beam.fortran['frame_of_reference_delta'].ctypes.data_as(doubleP), beam.fortran['rbmass'].ctypes.data_as(doubleP), beam.fortran['node_master_elem'].ctypes.data_as(intP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['fdof'].ctypes.data_as(intP), ct.byref(xbopts), beam.ini_info.pos.ctypes.data_as(doubleP), beam.ini_info.psi.ctypes.data_as(doubleP), beam.timestep_info[0].pos.ctypes.data_as(doubleP), beam.timestep_info[0].psi.ctypes.data_as(doubleP), beam.timestep_info[0].steady_applied_forces.ctypes.data_as(doubleP), dynamic_forces.ctypes.data_as(doubleP), beam.forced_vel_fortran.ctypes.data_as(doubleP), beam.forced_acc_fortran.ctypes.data_as(doubleP), pos_def_history.ctypes.data_as(doubleP), psi_def_history.ctypes.data_as(doubleP), pos_dot_def_history.ctypes.data_as(doubleP), psi_dot_def_history.ctypes.data_as(doubleP) ) for i in range(1, n_tsteps.value): beam.add_timestep() beam.timestep_info[i].pos[:] = pos_def_history[i, :] beam.timestep_info[i].psi[:] = psi_def_history[i, :] beam.timestep_info[i].pos_dot[:] = pos_dot_def_history[i, :] beam.timestep_info[i].psi_dot[:] = psi_dot_def_history[i, :] def cbeam3_step_nlndyn(beam, settings, ts, tstep=None, dt=None): f_cbeam3_solv_nlndyn_step = xbeamlib.cbeam3_solv_nlndyn_step_python f_cbeam3_solv_nlndyn_step.restype = None if tstep is None: tstep = beam.timestep_info[-1] n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) n_mass = ct.c_int(beam.n_mass) n_stiff = ct.c_int(beam.n_stiff) num_dof = ct.c_int(len(tstep.q) - 10) xbopts = Xbopts() xbopts.PrintInfo = ct.c_bool(settings['print_info']) xbopts.Solution = ct.c_int(312) xbopts.MaxIterations = ct.c_int(settings['max_iterations']) xbopts.NumLoadSteps = ct.c_int(settings['num_load_steps']) xbopts.NumGauss = ct.c_int(0) xbopts.DeltaCurved = ct.c_double(settings['delta_curved']) xbopts.MinDelta = ct.c_double(settings['min_delta']) xbopts.abs_threshold = ct.c_double(settings['abs_threshold']) xbopts.NewmarkDamp = ct.c_double(settings['newmark_damp']) xbopts.gravity_on = ct.c_bool(settings['gravity_on']) xbopts.gravity = ct.c_double(settings['gravity']) xbopts.gravity_dir_x = ct.c_double(settings['gravity_dir'][0]) xbopts.gravity_dir_y = ct.c_double(settings['gravity_dir'][1]) xbopts.gravity_dir_z = ct.c_double(settings['gravity_dir'][2]) xbopts.relaxation_factor = ct.c_double(settings['relaxation_factor']) # here we only need to set the flags at True, all the forces are follower xbopts.FollowerForce = ct.c_bool(True) xbopts.FollowerForceRig = ct.c_bool(True) if dt is None: in_dt = ct.c_double(settings['dt']) else: in_dt = ct.c_double(dt) f_cbeam3_solv_nlndyn_step(ct.byref(num_dof), ct.byref(n_elem), ct.byref(n_nodes), ct.byref(in_dt), beam.fortran['num_nodes'].ctypes.data_as(intP), beam.fortran['num_mem'].ctypes.data_as(intP), beam.fortran['connectivities'].ctypes.data_as(intP), beam.fortran['master'].ctypes.data_as(intP), ct.byref(n_mass), beam.fortran['mass'].ctypes.data_as(doubleP), beam.fortran['mass_indices'].ctypes.data_as(intP), ct.byref(n_stiff), beam.fortran['stiffness'].ctypes.data_as(doubleP), beam.fortran['inv_stiffness'].ctypes.data_as(doubleP), beam.fortran['stiffness_indices'].ctypes.data_as(intP), beam.fortran['frame_of_reference_delta'].ctypes.data_as(doubleP), beam.fortran['rbmass'].ctypes.data_as(doubleP), beam.fortran['node_master_elem'].ctypes.data_as(intP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['fdof'].ctypes.data_as(intP), ct.byref(xbopts), beam.ini_info.pos.ctypes.data_as(doubleP), beam.ini_info.psi.ctypes.data_as(doubleP), tstep.pos.ctypes.data_as(doubleP), tstep.pos_dot.ctypes.data_as(doubleP), tstep.pos_ddot.ctypes.data_as(doubleP), tstep.psi.ctypes.data_as(doubleP), tstep.psi_dot.ctypes.data_as(doubleP), tstep.psi_ddot.ctypes.data_as(doubleP), tstep.steady_applied_forces.ctypes.data_as(doubleP), tstep.unsteady_applied_forces.ctypes.data_as(doubleP), tstep.gravity_forces.ctypes.data_as(doubleP), tstep.quat.ctypes.data_as(doubleP), tstep.for_vel.ctypes.data_as(doubleP), tstep.for_acc.ctypes.data_as(doubleP), tstep.q.ctypes.data_as(doubleP), tstep.dqdt.ctypes.data_as(doubleP) ) xbeam_solv_state2disp(beam, tstep, cbeam3=True) f_xbeam_solv_couplednlndyn = xbeamlib.xbeam_solv_couplednlndyn_python f_xbeam_solv_couplednlndyn.restype = None def xbeam_solv_couplednlndyn(beam, settings): n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) n_mass = ct.c_int(beam.n_mass) n_stiff = ct.c_int(beam.n_stiff) dt = settings['dt'] n_tsteps = settings['num_steps'] time = np.zeros((n_tsteps,), dtype=ct.c_double, order='F') for i in range(n_tsteps): time[i] = i*dt # deformation history matrices for_vel = np.zeros((n_tsteps + 1, 6), order='F') for_acc = np.zeros((n_tsteps + 1, 6), order='F') quat_history = np.zeros((n_tsteps, 4), order='F') quat_history[0, 0] = 1.0 quat_history[0, :] = beam.timestep_info[0].quat[:] dt = ct.c_double(dt) n_tsteps = ct.c_int(n_tsteps) xbopts = Xbopts() xbopts.PrintInfo = ct.c_bool(settings['print_info']) xbopts.Solution = ct.c_int(910) xbopts.OutInaframe = ct.c_bool(True) xbopts.MaxIterations = ct.c_int(settings['max_iterations']) xbopts.NumLoadSteps = ct.c_int(settings['num_load_steps']) # xbopts.NumGauss = ct.c_int(0) xbopts.DeltaCurved = ct.c_double(settings['delta_curved']) xbopts.MinDelta = ct.c_double(settings['min_delta']) xbopts.abs_threshold = ct.c_double(settings['abs_threshold']) xbopts.NewmarkDamp = ct.c_double(settings['newmark_damp']) xbopts.gravity_on = ct.c_bool(settings['gravity_on']) xbopts.gravity = ct.c_double(settings['gravity']) xbopts.gravity_dir_x = ct.c_double(settings['gravity_dir'][0]) xbopts.gravity_dir_y = ct.c_double(settings['gravity_dir'][1]) xbopts.gravity_dir_z = ct.c_double(settings['gravity_dir'][2]) xbopts.relaxation_factor = ct.c_double(settings['relaxation_factor']) xbopts.gravity_dir_x = ct.c_double(settings['gravity_dir'][0]) xbopts.gravity_dir_y = ct.c_double(settings['gravity_dir'][1]) xbopts.gravity_dir_z = ct.c_double(settings['gravity_dir'][2]) pos_def_history = np.zeros((n_tsteps.value, beam.num_node, 3), order='F', dtype=ct.c_double) pos_dot_def_history = np.zeros((n_tsteps.value, beam.num_node, 3), order='F', dtype=ct.c_double) psi_def_history = np.zeros((n_tsteps.value, beam.num_elem, 3, 3), order='F', dtype=ct.c_double) psi_dot_def_history = np.zeros((n_tsteps.value, beam.num_elem, 3, 3), order='F', dtype=ct.c_double) dynamic_force = np.zeros((n_nodes.value, 6, n_tsteps.value), dtype=ct.c_double, order='F') for it in range(n_tsteps.value): dynamic_force[:, :, it] = beam.dynamic_input[it]['dynamic_forces'] # status flag success = ct.c_bool(True) # here we only need to set the flags at True, all the forces are follower xbopts.FollowerForce = ct.c_bool(True) xbopts.FollowerForceRig = ct.c_bool(True) import time as ti start_time = ti.time() f_xbeam_solv_couplednlndyn(ct.byref(n_elem), ct.byref(n_nodes), ct.byref(n_tsteps), time.ctypes.data_as(doubleP), beam.fortran['num_nodes'].ctypes.data_as(intP), beam.fortran['num_mem'].ctypes.data_as(intP), beam.fortran['connectivities'].ctypes.data_as(intP), beam.fortran['master'].ctypes.data_as(intP), ct.byref(n_mass), beam.fortran['mass'].ctypes.data_as(doubleP), beam.fortran['mass_indices'].ctypes.data_as(intP), ct.byref(n_stiff), beam.fortran['stiffness'].ctypes.data_as(doubleP), beam.fortran['inv_stiffness'].ctypes.data_as(doubleP), beam.fortran['stiffness_indices'].ctypes.data_as(intP), beam.fortran['frame_of_reference_delta'].ctypes.data_as(doubleP), beam.fortran['rbmass'].ctypes.data_as(doubleP), beam.fortran['node_master_elem'].ctypes.data_as(intP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['fdof'].ctypes.data_as(intP), ct.byref(xbopts), beam.ini_info.pos.ctypes.data_as(doubleP), beam.ini_info.psi.ctypes.data_as(doubleP), beam.ini_info.steady_applied_forces.ctypes.data_as(doubleP), dynamic_force.ctypes.data_as(doubleP), for_vel.ctypes.data_as(doubleP), for_acc.ctypes.data_as(doubleP), pos_def_history.ctypes.data_as(doubleP), psi_def_history.ctypes.data_as(doubleP), pos_dot_def_history.ctypes.data_as(doubleP), psi_dot_def_history.ctypes.data_as(doubleP), quat_history.ctypes.data_as(doubleP), ct.byref(success)) cout.cout_wrap("\n--- %s seconds ---" % (ti.time() - start_time), 1) if not success: raise Exception('couplednlndyn did not converge') for_pos = np.zeros_like(for_vel) for_pos[:, 0] = sc.integrate.cumtrapz(for_vel[:, 0], dx=dt.value, initial=0) for_pos[:, 1] = sc.integrate.cumtrapz(for_vel[:, 1], dx=dt.value, initial=0) for_pos[:, 2] = sc.integrate.cumtrapz(for_vel[:, 2], dx=dt.value, initial=0) glob_pos_def = np.zeros_like(pos_def_history) for it in range(n_tsteps.value): rot = algebra.quat2rotation(quat_history[it, :]) for inode in range(beam.num_node): glob_pos_def[it, inode, :] = np.dot(rot.T, pos_def_history[it, inode, :]) for i in range(n_tsteps.value - 1): beam.timestep_info[i + 1].pos[:] = pos_def_history[i+1, :] beam.timestep_info[i + 1].psi[:] = psi_def_history[i+1, :] beam.timestep_info[i + 1].pos_dot[:] = pos_dot_def_history[i+1, :] beam.timestep_info[i + 1].psi_dot[:] = psi_dot_def_history[i+1, :] beam.timestep_info[i + 1].quat[:] = quat_history[i+1, :] # beam.timestep_info[i + 1].for_pos[:] = for_pos[i+1, :] beam.timestep_info[i + 1].for_vel[:] = for_vel[i+1, :] for it in range(n_tsteps.value - 1): beam.integrate_position(it + 1, dt.value) def xbeam_step_couplednlndyn(beam, settings, ts, tstep=None, dt=None): # library load f_xbeam_solv_nlndyn_step_python = xbeamlib.xbeam_solv_nlndyn_step_python f_xbeam_solv_nlndyn_step_python.restype = None if tstep is None: tstep = beam.timestep_info[-1] # initialisation n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) n_mass = ct.c_int(beam.n_mass) n_stiff = ct.c_int(beam.n_stiff) xbopts = Xbopts() xbopts.PrintInfo = ct.c_bool(settings['print_info']) xbopts.MaxIterations = ct.c_int(settings['max_iterations']) xbopts.NumLoadSteps = ct.c_int(settings['num_load_steps']) xbopts.DeltaCurved = ct.c_double(settings['delta_curved']) xbopts.MinDelta = ct.c_double(settings['min_delta']) xbopts.abs_threshold = ct.c_double(settings['abs_threshold']) xbopts.NewmarkDamp = ct.c_double(settings['newmark_damp']) xbopts.gravity_on = ct.c_bool(settings['gravity_on']) xbopts.gravity = ct.c_double(settings['gravity']) xbopts.balancing = ct.c_bool(settings['balancing']) xbopts.gravity_dir_x = ct.c_double(settings['gravity_dir'][0]) xbopts.gravity_dir_y = ct.c_double(settings['gravity_dir'][1]) xbopts.gravity_dir_z = ct.c_double(settings['gravity_dir'][2]) xbopts.relaxation_factor = ct.c_double(settings['relaxation_factor']) if dt is None: in_dt = ct.c_double(settings['dt']) else: in_dt = ct.c_double(dt) ctypes_ts = ct.c_int(ts) numdof = ct.c_int(beam.num_dof.value) f_xbeam_solv_nlndyn_step_python(ct.byref(numdof), ct.byref(ctypes_ts), ct.byref(n_elem), ct.byref(n_nodes), ct.byref(in_dt), beam.fortran['num_nodes'].ctypes.data_as(intP), beam.fortran['num_mem'].ctypes.data_as(intP), beam.fortran['connectivities'].ctypes.data_as(intP), beam.fortran['master'].ctypes.data_as(intP), ct.byref(n_mass), beam.fortran['mass'].ctypes.data_as(doubleP), beam.fortran['mass_indices'].ctypes.data_as(intP), ct.byref(n_stiff), beam.fortran['stiffness'].ctypes.data_as(doubleP), beam.fortran['inv_stiffness'].ctypes.data_as(doubleP), beam.fortran['stiffness_indices'].ctypes.data_as(intP), beam.fortran['frame_of_reference_delta'].ctypes.data_as(doubleP), beam.fortran['rbmass'].ctypes.data_as(doubleP), beam.fortran['node_master_elem'].ctypes.data_as(intP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['fdof'].ctypes.data_as(intP), ct.byref(xbopts), beam.ini_info.pos.ctypes.data_as(doubleP), beam.ini_info.psi.ctypes.data_as(doubleP), tstep.pos.ctypes.data_as(doubleP), tstep.pos_dot.ctypes.data_as(doubleP), tstep.pos_ddot.ctypes.data_as(doubleP), tstep.psi.ctypes.data_as(doubleP), tstep.psi_dot.ctypes.data_as(doubleP), tstep.psi_ddot.ctypes.data_as(doubleP), tstep.steady_applied_forces.ctypes.data_as(doubleP), tstep.unsteady_applied_forces.ctypes.data_as(doubleP), tstep.gravity_forces.ctypes.data_as(doubleP), tstep.quat.ctypes.data_as(doubleP), tstep.for_vel.ctypes.data_as(doubleP), tstep.for_acc.ctypes.data_as(doubleP), tstep.q.ctypes.data_as(doubleP), tstep.dqdt.ctypes.data_as(doubleP), tstep.dqddt.ctypes.data_as(doubleP)) def xbeam_init_couplednlndyn(beam, settings, ts, dt=None): # library load f_xbeam_solv_nlndyn_init_python = xbeamlib.xbeam_solv_nlndyn_init_python f_xbeam_solv_nlndyn_init_python.restype = None # initialisation n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) n_mass = ct.c_int(beam.n_mass) n_stiff = ct.c_int(beam.n_stiff) xbopts = Xbopts() xbopts.PrintInfo = ct.c_bool(settings['print_info']) xbopts.MaxIterations = ct.c_int(settings['max_iterations']) xbopts.NumLoadSteps = ct.c_int(settings['num_load_steps']) xbopts.DeltaCurved = ct.c_double(settings['delta_curved']) xbopts.MinDelta = ct.c_double(settings['min_delta']) xbopts.abs_threshold = ct.c_double(settings['abs_threshold']) xbopts.NewmarkDamp = ct.c_double(settings['newmark_damp']) xbopts.gravity_on = ct.c_bool(settings['gravity_on']) xbopts.gravity = ct.c_double(settings['gravity']) xbopts.gravity_dir_x = ct.c_double(settings['gravity_dir'][0]) xbopts.gravity_dir_y = ct.c_double(settings['gravity_dir'][1]) xbopts.gravity_dir_z = ct.c_double(settings['gravity_dir'][2]) in_dt = ct.c_double(dt) if ts < 0: ctypes_ts = ct.c_int(0) else: ctypes_ts = ct.c_int(ts) numdof = ct.c_int(beam.num_dof.value) f_xbeam_solv_nlndyn_init_python(ct.byref(numdof), ct.byref(ctypes_ts), ct.byref(n_elem), ct.byref(n_nodes), ct.byref(ct.c_double(settings['dt'])), beam.fortran['num_nodes'].ctypes.data_as(intP), beam.fortran['num_mem'].ctypes.data_as(intP), beam.fortran['connectivities'].ctypes.data_as(intP), beam.fortran['master'].ctypes.data_as(intP), ct.byref(n_mass), beam.fortran['mass'].ctypes.data_as(doubleP), beam.fortran['mass_indices'].ctypes.data_as(intP), ct.byref(n_stiff), beam.fortran['stiffness'].ctypes.data_as(doubleP), beam.fortran['inv_stiffness'].ctypes.data_as(doubleP), beam.fortran['stiffness_indices'].ctypes.data_as(intP), beam.fortran['frame_of_reference_delta'].ctypes.data_as(doubleP), beam.fortran['rbmass'].ctypes.data_as(doubleP), beam.fortran['node_master_elem'].ctypes.data_as(intP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['fdof'].ctypes.data_as(intP), ct.byref(xbopts), beam.ini_info.pos.ctypes.data_as(doubleP), beam.ini_info.psi.ctypes.data_as(doubleP), beam.timestep_info[ts].pos.ctypes.data_as(doubleP), beam.timestep_info[ts].pos_dot.ctypes.data_as(doubleP), beam.timestep_info[ts].psi.ctypes.data_as(doubleP), beam.timestep_info[ts].psi_dot.ctypes.data_as(doubleP), beam.timestep_info[ts].steady_applied_forces.ctypes.data_as(doubleP), beam.timestep_info[ts].unsteady_applied_forces.ctypes.data_as(doubleP), beam.timestep_info[ts].quat.ctypes.data_as(doubleP), beam.timestep_info[ts].for_vel.ctypes.data_as(doubleP), beam.timestep_info[ts].for_acc.ctypes.data_as(doubleP), beam.timestep_info[ts].q.ctypes.data_as(doubleP), beam.timestep_info[ts].dqdt.ctypes.data_as(doubleP), beam.timestep_info[ts].dqddt.ctypes.data_as(doubleP)) def xbeam_solv_state2disp(beam, tstep, cbeam3=False): numdof = beam.num_dof.value cbeam3_solv_state2disp(beam, tstep) if not cbeam3: tstep.for_pos[0:3] = tstep.q[numdof:numdof+3].astype(dtype=ct.c_double, order='F', copy=True) tstep.for_vel = tstep.dqdt[numdof:numdof+6].astype(dtype=ct.c_double, order='F', copy=True) # tstep.for_acc = tstep.dqddt[numdof:numdof+6].astype(dtype=ct.c_double, order='F', copy=True) tstep.quat = algebra.unit_vector(tstep.dqdt[numdof+6:]).astype(dtype=ct.c_double, order='F', copy=True) def xbeam_solv_state2accel(beam, tstep, cbeam3=False): numdof = beam.num_dof.value cbeam3_solv_state2accel(beam, tstep) if not cbeam3: tstep.for_acc = tstep.dqddt[numdof:numdof+6].astype(dtype=ct.c_double, order='F', copy=True) def cbeam3_solv_state2disp(beam, tstep): # library load f_cbeam3_solv_state2disp = xbeamlib.cbeam3_solv_state2disp_python f_cbeam3_solv_state2disp.restype = None # initialisation n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) numdof = ct.c_int(beam.num_dof.value) f_cbeam3_solv_state2disp( ct.byref(n_elem), ct.byref(n_nodes), ct.byref(numdof), beam.ini_info.pos.ctypes.data_as(doubleP), beam.ini_info.psi.ctypes.data_as(doubleP), tstep.pos.ctypes.data_as(doubleP), tstep.psi.ctypes.data_as(doubleP), tstep.pos_dot.ctypes.data_as(doubleP), tstep.psi_dot.ctypes.data_as(doubleP), beam.fortran['node_master_elem'].ctypes.data_as(intP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['num_nodes'].ctypes.data_as(intP), beam.fortran['master'].ctypes.data_as(intP), tstep.q.ctypes.data_as(doubleP), tstep.dqdt.ctypes.data_as(doubleP)) def cbeam3_solv_state2accel(beam, tstep): # library load f_cbeam3_solv_state2disp = xbeamlib.cbeam3_solv_state2disp_python f_cbeam3_solv_state2disp.restype = None # initialisation n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) numdof = ct.c_int(beam.num_dof.value) dummy_pos = np.zeros_like(tstep.pos) dummy_psi = np.zeros_like(tstep.psi) dummy_ini_pos = np.zeros_like(tstep.pos) dummy_ini_psi = np.zeros_like(tstep.psi) dummy_q = np.zeros_like(tstep.q) f_cbeam3_solv_state2disp( ct.byref(n_elem), ct.byref(n_nodes), ct.byref(numdof), dummy_ini_pos.ctypes.data_as(doubleP), dummy_ini_psi.ctypes.data_as(doubleP), dummy_pos.ctypes.data_as(doubleP), dummy_psi.ctypes.data_as(doubleP), tstep.pos_ddot.ctypes.data_as(doubleP), tstep.psi_ddot.ctypes.data_as(doubleP), beam.fortran['node_master_elem'].ctypes.data_as(intP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['num_nodes'].ctypes.data_as(intP), beam.fortran['master'].ctypes.data_as(intP), dummy_q.ctypes.data_as(doubleP), tstep.dqddt.ctypes.data_as(doubleP)) def xbeam_solv_disp2state(beam, tstep): numdof = beam.num_dof.value cbeam3_solv_disp2state(beam, tstep) tstep.q[numdof:numdof+3] = tstep.for_pos[0:3] tstep.dqdt[numdof:numdof+6] = tstep.for_vel # tstep.dqddt[numdof:numdof+6] = tstep.for_acc tstep.dqdt[numdof+6:] = algebra.unit_vector(tstep.quat) # tstep.dqdt[numdof+6:] = tstep.quat # tstep.dqdt[numdof+6:] = algebra.unit_quat(tstep.quat) def xbeam_solv_accel2state(beam, tstep): numdof = beam.num_dof.value cbeam3_solv_accel2state(beam, tstep) tstep.dqddt[numdof:numdof+6] = tstep.for_acc def cbeam3_solv_disp2state(beam, tstep): # library load f_cbeam3_solv_disp2state = xbeamlib.cbeam3_solv_disp2state_python f_cbeam3_solv_disp2state.restype = None # initialisation n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) numdof = ct.c_int(beam.num_dof.value) f_cbeam3_solv_disp2state( ct.byref(n_elem), ct.byref(n_nodes), ct.byref(numdof), tstep.pos.ctypes.data_as(doubleP), tstep.psi.ctypes.data_as(doubleP), tstep.pos_dot.ctypes.data_as(doubleP), tstep.psi_dot.ctypes.data_as(doubleP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['node_master_elem'].ctypes.data_as(intP), tstep.q.ctypes.data_as(doubleP), tstep.dqdt.ctypes.data_as(doubleP)) def cbeam3_solv_accel2state(beam, tstep): # library load f_cbeam3_solv_disp2state = xbeamlib.cbeam3_solv_disp2state_python f_cbeam3_solv_disp2state.restype = None # initialisation n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) numdof = ct.c_int(beam.num_dof.value) dummy_pos = np.zeros_like(tstep.pos) dummy_psi = np.zeros_like(tstep.psi) dummy_q = np.zeros_like(tstep.q) # Reused f_cbeam3_solv_disp2state( ct.byref(n_elem), ct.byref(n_nodes), ct.byref(numdof), dummy_pos.ctypes.data_as(doubleP), dummy_psi.ctypes.data_as(doubleP), tstep.pos_ddot.ctypes.data_as(doubleP), tstep.psi_ddot.ctypes.data_as(doubleP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['node_master_elem'].ctypes.data_as(intP), dummy_q.ctypes.data_as(doubleP), tstep.dqddt.ctypes.data_as(doubleP)) def cbeam3_solv_modal(beam, settings, ts, FullMglobal, FullCglobal, FullKglobal): """ cbeam3_solv_modal Generates the system matrices for modal analysis Args: beam(Beam): beam information settings(settings): ts(int): timestep FullMglobal(numpy array): Mass matrix FullCglobal(numpy array): Damping matrix FullKglobal(numpy array): Stiffness matrix Returns: FullMglobal(numpy array): Mass matrix FullCglobal(numpy array): Damping matrix FullKglobal(numpy array): Stiffness matrix Examples: Notes: """ f_cbeam3_solv_modal = xbeamlib.cbeam3_solv_modal_python f_cbeam3_solv_modal.restype = None n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) n_mass = ct.c_int(beam.n_mass) n_stiff = ct.c_int(beam.n_stiff) num_dof = ct.c_int(beam.num_dof.value) xbopts = Xbopts() xbopts.PrintInfo = ct.c_bool(settings['print_info']) xbopts.Solution = ct.c_int(312) # xbopts.OutInaframe = ct.c_bool(settings['out_a_frame']) # xbopts.OutInBframe = ct.c_bool(settings['out_b_frame']) # xbopts.ElemProj = settings['elem_proj'] # xbopts.MaxIterations = settings['max_iterations'] # xbopts.NumLoadSteps = settings['num_load_steps'] xbopts.NumGauss = ct.c_int(0) xbopts.DeltaCurved = ct.c_double(settings['delta_curved']) # xbopts.MinDelta = settings['min_delta'] # xbopts.NewmarkDamp = settings['newmark_damp'] # xbopts.gravity_on = settings['gravity_on'] # xbopts.gravity = settings['gravity'] # xbopts.gravity_dir_x = ct.c_double(settings['gravity_dir'][0]) # xbopts.gravity_dir_y = ct.c_double(settings['gravity_dir'][1]) # xbopts.gravity_dir_z = ct.c_double(settings['gravity_dir'][2]) # print("ts: ",ts) # print("FoR vel: ", beam.timestep_info[ts].for_vel) # print("initial position: ", beam.ini_info.pos) # print("initial rotation: ", beam.ini_info.psi) # print("position: ", beam.timestep_info[ts].pos) # print("rotation: ", beam.timestep_info[ts].psi) # print("mass: ", beam.fortran['mass']) # print("mass: ", beam.fortran['stiffness']) # print("mass: ", beam.fortran['inv_stiffness']) f_cbeam3_solv_modal(ct.byref(num_dof), ct.byref(n_elem), ct.byref(n_nodes), beam.fortran['num_nodes'].ctypes.data_as(intP), beam.fortran['num_mem'].ctypes.data_as(intP), beam.fortran['connectivities'].ctypes.data_as(intP), beam.fortran['master'].ctypes.data_as(intP), ct.byref(n_mass), beam.fortran['mass'].ctypes.data_as(doubleP), beam.fortran['mass_indices'].ctypes.data_as(intP), ct.byref(n_stiff), beam.fortran['stiffness'].ctypes.data_as(doubleP), beam.fortran['inv_stiffness'].ctypes.data_as(doubleP), beam.fortran['stiffness_indices'].ctypes.data_as(intP), beam.fortran['frame_of_reference_delta'].ctypes.data_as(doubleP), beam.fortran['rbmass'].ctypes.data_as(doubleP), beam.fortran['node_master_elem'].ctypes.data_as(intP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['fdof'].ctypes.data_as(intP), ct.byref(xbopts), beam.ini_info.pos.ctypes.data_as(doubleP), beam.ini_info.psi.ctypes.data_as(doubleP), beam.timestep_info[ts].pos.ctypes.data_as(doubleP), beam.timestep_info[ts].psi.ctypes.data_as(doubleP), beam.timestep_info[ts].for_vel.ctypes.data_as(doubleP), FullMglobal.ctypes.data_as(doubleP), FullCglobal.ctypes.data_as(doubleP), FullKglobal.ctypes.data_as(doubleP)) def cbeam3_asbly_dynamic(beam, tstep, settings): """ cbeam3_asbly_dynamic Generates the system matrices for a nonlinear dynamic structure with prescribed FoR motions Args: beam(Beam): beam information tstep(StructTimeStepInfo): time step information settings(settings): Returns: Mglobal(numpy array): Mass matrix Cglobal(numpy array): Damping matrix Kglobal(numpy array): Stiffness matrix Qglobal(numpy array): Vector of independent terms Examples: Notes: """ # library load f_cbeam3_asbly_dynamic_python = xbeamlib.cbeam3_asbly_dynamic_python f_cbeam3_asbly_dynamic_python.restype = None # initialisation n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) num_dof = beam.num_dof.value n_mass = ct.c_int(beam.n_mass) n_stiff = ct.c_int(beam.n_stiff) dt = ct.c_double(settings['dt']) # Options xbopts = Xbopts() xbopts.PrintInfo = ct.c_bool(settings['print_info']) xbopts.Solution = ct.c_int(312) xbopts.MaxIterations = ct.c_int(settings['max_iterations']) xbopts.NumLoadSteps = ct.c_int(settings['num_load_steps']) xbopts.NumGauss = ct.c_int(0) xbopts.DeltaCurved = ct.c_double(settings['delta_curved']) xbopts.MinDelta = ct.c_double(settings['min_delta']) xbopts.abs_threshold = ct.c_double(settings['abs_threshold']) xbopts.NewmarkDamp = ct.c_double(settings['newmark_damp']) xbopts.gravity_on = ct.c_bool(settings['gravity_on']) xbopts.gravity = ct.c_double(settings['gravity']) xbopts.gravity_dir_x = ct.c_double(settings['gravity_dir'][0]) xbopts.gravity_dir_y = ct.c_double(settings['gravity_dir'][1]) xbopts.gravity_dir_z = ct.c_double(settings['gravity_dir'][2]) # Initialize matrices Mglobal = np.zeros((num_dof, num_dof), dtype=ct.c_double, order='F') Cglobal = np.zeros((num_dof, num_dof), dtype=ct.c_double, order='F') Kglobal = np.zeros((num_dof, num_dof), dtype=ct.c_double, order='F') Qglobal = np.zeros((num_dof, ), dtype=ct.c_double, order='F') f_cbeam3_asbly_dynamic_python(ct.byref(ct.c_int(num_dof)), ct.byref(n_nodes), ct.byref(n_elem), ct.byref(dt), beam.ini_info.pos.ctypes.data_as(doubleP), beam.ini_info.psi.ctypes.data_as(doubleP), tstep.pos.ctypes.data_as(doubleP), tstep.pos_dot.ctypes.data_as(doubleP), tstep.pos_ddot.ctypes.data_as(doubleP), tstep.psi.ctypes.data_as(doubleP), tstep.psi_dot.ctypes.data_as(doubleP), tstep.psi_ddot.ctypes.data_as(doubleP), tstep.steady_applied_forces.ctypes.data_as(doubleP), tstep.unsteady_applied_forces.ctypes.data_as(doubleP), tstep.for_vel.ctypes.data_as(doubleP), tstep.for_acc.ctypes.data_as(doubleP), beam.fortran['num_nodes'].ctypes.data_as(intP), beam.fortran['num_mem'].ctypes.data_as(intP), beam.fortran['connectivities'].ctypes.data_as(intP), beam.fortran['master'].ctypes.data_as(intP), ct.byref(n_mass), beam.fortran['mass'].ctypes.data_as(doubleP), beam.fortran['mass_indices'].ctypes.data_as(intP), ct.byref(n_stiff), beam.fortran['stiffness'].ctypes.data_as(doubleP), beam.fortran['inv_stiffness'].ctypes.data_as(doubleP), beam.fortran['stiffness_indices'].ctypes.data_as(intP), beam.fortran['frame_of_reference_delta'].ctypes.data_as(doubleP), beam.fortran['rbmass'].ctypes.data_as(doubleP), beam.fortran['node_master_elem'].ctypes.data_as(intP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['fdof'].ctypes.data_as(intP), ct.byref(xbopts), # CAREFUL, this is dXddt, with num_dof elements, # not num_dof + 10 tstep.dqddt.ctypes.data_as(doubleP), tstep.quat.ctypes.data_as(doubleP), tstep.gravity_forces.ctypes.data_as(doubleP), Mglobal.ctypes.data_as(doubleP), Cglobal.ctypes.data_as(doubleP), Kglobal.ctypes.data_as(doubleP), Qglobal.ctypes.data_as(doubleP)) return Mglobal, Cglobal, Kglobal, Qglobal def xbeam3_asbly_dynamic(beam, tstep, settings): """ xbeam3_asbly_dynamic Generates the system matrices for a nonlinear dynamic structure with free FoR motions Args: beam(Beam): beam information tstep(StructTimeStepInfo): time step information settings(settings): Returns: Mglobal(numpy array): Mass matrix Cglobal(numpy array): Damping matrix Kglobal(numpy array): Stiffness matrix Qglobal(numpy array): Vector of independent terms Examples: Notes: """ # library load f_xbeam3_asbly_dynamic_python = xbeamlib.xbeam3_asbly_dynamic_python f_xbeam3_asbly_dynamic_python.restype = None # initialisation n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) num_dof = beam.num_dof.value n_mass = ct.c_int(beam.n_mass) n_stiff = ct.c_int(beam.n_stiff) dt = ct.c_double(settings['dt']) # Options xbopts = Xbopts() xbopts.PrintInfo = ct.c_bool(settings['print_info']) xbopts.Solution = ct.c_int(312) xbopts.MaxIterations = ct.c_int(settings['max_iterations']) xbopts.NumLoadSteps = ct.c_int(settings['num_load_steps']) xbopts.NumGauss = ct.c_int(0) xbopts.DeltaCurved = ct.c_double(settings['delta_curved']) xbopts.MinDelta = ct.c_double(settings['min_delta']) xbopts.abs_threshold = ct.c_double(settings['abs_threshold']) xbopts.NewmarkDamp = ct.c_double(settings['newmark_damp']) xbopts.gravity_on = ct.c_bool(settings['gravity_on']) xbopts.gravity = ct.c_double(settings['gravity']) xbopts.gravity_dir_x = ct.c_double(settings['gravity_dir'][0]) xbopts.gravity_dir_y = ct.c_double(settings['gravity_dir'][1]) xbopts.gravity_dir_z = ct.c_double(settings['gravity_dir'][2]) # Initialize matrices Mtotal = np.zeros((num_dof+10, num_dof+10), dtype=ct.c_double, order='F') Ctotal = np.zeros((num_dof+10, num_dof+10), dtype=ct.c_double, order='F') Ktotal = np.zeros((num_dof+10, num_dof+10), dtype=ct.c_double, order='F') Qtotal = np.zeros((num_dof+10, ), dtype=ct.c_double, order='F') f_xbeam3_asbly_dynamic_python(ct.byref(ct.c_int(num_dof)), ct.byref(n_nodes), ct.byref(n_elem), ct.byref(dt), beam.ini_info.pos.ctypes.data_as(doubleP), beam.ini_info.psi.ctypes.data_as(doubleP), tstep.pos.ctypes.data_as(doubleP), tstep.pos_dot.ctypes.data_as(doubleP), tstep.pos_ddot.ctypes.data_as(doubleP), tstep.psi.ctypes.data_as(doubleP), tstep.psi_dot.ctypes.data_as(doubleP), tstep.psi_ddot.ctypes.data_as(doubleP), tstep.steady_applied_forces.ctypes.data_as(doubleP), tstep.unsteady_applied_forces.ctypes.data_as(doubleP), tstep.for_vel.ctypes.data_as(doubleP), tstep.for_acc.ctypes.data_as(doubleP), # ct.byref(in_dt), beam.fortran['num_nodes'].ctypes.data_as(intP), beam.fortran['num_mem'].ctypes.data_as(intP), beam.fortran['connectivities'].ctypes.data_as(intP), beam.fortran['master'].ctypes.data_as(intP), ct.byref(n_mass), beam.fortran['mass'].ctypes.data_as(doubleP), beam.fortran['mass_indices'].ctypes.data_as(intP), ct.byref(n_stiff), beam.fortran['stiffness'].ctypes.data_as(doubleP), beam.fortran['inv_stiffness'].ctypes.data_as(doubleP), beam.fortran['stiffness_indices'].ctypes.data_as(intP), beam.fortran['frame_of_reference_delta'].ctypes.data_as(doubleP), beam.fortran['rbmass'].ctypes.data_as(doubleP), beam.fortran['node_master_elem'].ctypes.data_as(intP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['fdof'].ctypes.data_as(intP), ct.byref(xbopts), tstep.quat.ctypes.data_as(doubleP), tstep.q.ctypes.data_as(doubleP), tstep.dqdt.ctypes.data_as(doubleP), tstep.dqddt.ctypes.data_as(doubleP), tstep.gravity_forces.ctypes.data_as(doubleP), Mtotal.ctypes.data_as(doubleP), Ctotal.ctypes.data_as(doubleP), Ktotal.ctypes.data_as(doubleP), Qtotal.ctypes.data_as(doubleP)) return Mtotal, Ctotal, Ktotal, Qtotal def cbeam3_correct_gravity_forces(beam, tstep, settings): """ cbeam3_correct_gravity_forces Corrects the gravity forces orientation after a time step Args: beam(Beam): beam information tstep(StructTimeStepInfo): time step information settings(settings): """ # library load f_cbeam3_correct_gravity_forces_python = xbeamlib.cbeam3_correct_gravity_forces_python f_cbeam3_correct_gravity_forces_python.restype = None # initialisation n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) n_mass = ct.c_int(beam.n_mass) n_stiff = ct.c_int(beam.n_stiff) f_cbeam3_correct_gravity_forces_python(ct.byref(n_nodes), ct.byref(n_elem), beam.ini_info.psi.ctypes.data_as(doubleP), tstep.psi.ctypes.data_as(doubleP), beam.fortran['num_nodes'].ctypes.data_as(intP), beam.fortran['num_mem'].ctypes.data_as(intP), beam.fortran['connectivities'].ctypes.data_as(intP), beam.fortran['master'].ctypes.data_as(intP), ct.byref(n_mass), beam.fortran['mass'].ctypes.data_as(doubleP), beam.fortran['mass_indices'].ctypes.data_as(intP), ct.byref(n_stiff), beam.fortran['stiffness'].ctypes.data_as(doubleP), beam.fortran['inv_stiffness'].ctypes.data_as(doubleP), beam.fortran['stiffness_indices'].ctypes.data_as(intP), beam.fortran['frame_of_reference_delta'].ctypes.data_as(doubleP), beam.fortran['rbmass'].ctypes.data_as(doubleP), beam.fortran['node_master_elem'].ctypes.data_as(intP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['fdof'].ctypes.data_as(intP), tstep.gravity_forces.ctypes.data_as(doubleP)) def cbeam3_asbly_static(beam, tstep, settings, iLoadStep): """ cbeam3_asbly_static Generates the system matrices for a nonlinear static structure Args: beam(Beam): beam information tstep(StructTimeStepInfo): time step information settings(settings): Returns: Kglobal(numpy array): Stiffness matrix Qglobal(numpy array): Vector of independent terms Examples: Notes: """ # library load f_cbeam3_asbly_static_python = xbeamlib.cbeam3_asbly_static_python f_cbeam3_asbly_static_python.restype = None # initialisation n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) num_dof = beam.num_dof.value n_mass = ct.c_int(beam.n_mass) n_stiff = ct.c_int(beam.n_stiff) # dt = settings['dt'] # Options xbopts = Xbopts() xbopts.PrintInfo = ct.c_bool(settings['print_info']) # xbopts.Solution = ct.c_int(312) # xbopts.MaxIterations = settings['max_iterations'] xbopts.NumLoadSteps = ct.c_int(settings['num_load_steps'] + 1) # xbopts.NumGauss = ct.c_int(0) # xbopts.DeltaCurved = settings['delta_curved'] # xbopts.MinDelta = settings['min_delta'] # xbopts.NewmarkDamp = settings['newmark_damp'] xbopts.gravity_on = ct.c_bool(settings['gravity_on']) xbopts.gravity = ct.c_double(settings['gravity']) gravity_vector = np.dot(beam.timestep_info[ts].cag(), settings['gravity_dir']) xbopts.gravity_dir_x = ct.c_double(gravity_vector[0]) xbopts.gravity_dir_y = ct.c_double(gravity_vector[1]) xbopts.gravity_dir_z = ct.c_double(gravity_vector[2]) # Initialize matrices # Mglobal = np.zeros((num_dof, num_dof), dtype=ct.c_double, order='F') # Cglobal = np.zeros((num_dof, num_dof), dtype=ct.c_double, order='F') Kglobal = np.zeros((num_dof, num_dof), dtype=ct.c_double, order='F') Qglobal = np.zeros((num_dof, ), dtype=ct.c_double, order='F') f_cbeam3_asbly_static_python(ct.byref(ct.c_int(num_dof)), ct.byref(n_nodes), ct.byref(n_elem), beam.ini_info.pos.ctypes.data_as(doubleP), beam.ini_info.psi.ctypes.data_as(doubleP), tstep.pos.ctypes.data_as(doubleP), tstep.psi.ctypes.data_as(doubleP), tstep.steady_applied_forces.ctypes.data_as(doubleP), beam.fortran['num_nodes'].ctypes.data_as(intP), beam.fortran['num_mem'].ctypes.data_as(intP), beam.fortran['connectivities'].ctypes.data_as(intP), beam.fortran['master'].ctypes.data_as(intP), ct.byref(n_mass), beam.fortran['mass'].ctypes.data_as(doubleP), beam.fortran['mass_indices'].ctypes.data_as(intP), ct.byref(n_stiff), beam.fortran['stiffness'].ctypes.data_as(doubleP), beam.fortran['inv_stiffness'].ctypes.data_as(doubleP), beam.fortran['stiffness_indices'].ctypes.data_as(intP), beam.fortran['frame_of_reference_delta'].ctypes.data_as(doubleP), beam.fortran['rbmass'].ctypes.data_as(doubleP), beam.fortran['node_master_elem'].ctypes.data_as(intP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['fdof'].ctypes.data_as(intP), ct.byref(xbopts), tstep.gravity_forces.ctypes.data_as(doubleP), Kglobal.ctypes.data_as(doubleP), Qglobal.ctypes.data_as(doubleP), ct.byref(ct.c_int(iLoadStep))) return Kglobal, Qglobal def xbeam_step_coupledrigid(beam, settings, ts, tstep=None, dt=None): # library load f_xbeam_solv_rigid_step_python = xbeamlib.xbeam_solv_coupledrigid_step_python f_xbeam_solv_rigid_step_python.restype = None if tstep is None: tstep = beam.timestep_info[-1] # initialisation n_elem = ct.c_int(beam.num_elem) n_nodes = ct.c_int(beam.num_node) n_mass = ct.c_int(beam.n_mass) n_stiff = ct.c_int(beam.n_stiff) xbopts = Xbopts() xbopts.PrintInfo = ct.c_bool(settings['print_info']) xbopts.MaxIterations = ct.c_int(settings['max_iterations']) xbopts.NumLoadSteps = ct.c_int(settings['num_load_steps']) xbopts.DeltaCurved = ct.c_double(settings['delta_curved']) xbopts.MinDelta = ct.c_double(settings['min_delta']) xbopts.abs_threshold = ct.c_double(settings['abs_threshold']) xbopts.NewmarkDamp = ct.c_double(settings['newmark_damp']) xbopts.gravity_on = ct.c_bool(settings['gravity_on']) xbopts.gravity = ct.c_double(settings['gravity']) xbopts.balancing = ct.c_bool(settings['balancing']) xbopts.gravity_dir_x = ct.c_double(settings['gravity_dir'][0]) xbopts.gravity_dir_y = ct.c_double(settings['gravity_dir'][1]) xbopts.gravity_dir_z = ct.c_double(settings['gravity_dir'][2]) xbopts.relaxation_factor = ct.c_double(settings['relaxation_factor']) if dt is None: in_dt = ct.c_double(settings['dt']) else: in_dt = ct.c_double(dt) ctypes_ts = ct.c_int(ts) numdof = ct.c_int(beam.num_dof.value) f_xbeam_solv_rigid_step_python(ct.byref(numdof), ct.byref(ctypes_ts), ct.byref(n_elem), ct.byref(n_nodes), ct.byref(in_dt), beam.fortran['num_nodes'].ctypes.data_as(intP), beam.fortran['num_mem'].ctypes.data_as(intP), beam.fortran['connectivities'].ctypes.data_as(intP), beam.fortran['master'].ctypes.data_as(intP), ct.byref(n_mass), beam.fortran['mass'].ctypes.data_as(doubleP), beam.fortran['mass_indices'].ctypes.data_as(intP), ct.byref(n_stiff), beam.fortran['stiffness'].ctypes.data_as(doubleP), beam.fortran['inv_stiffness'].ctypes.data_as(doubleP), beam.fortran['stiffness_indices'].ctypes.data_as(intP), beam.fortran['frame_of_reference_delta'].ctypes.data_as(doubleP), beam.fortran['rbmass'].ctypes.data_as(doubleP), beam.fortran['node_master_elem'].ctypes.data_as(intP), beam.fortran['vdof'].ctypes.data_as(intP), beam.fortran['fdof'].ctypes.data_as(intP), ct.byref(xbopts), tstep.pos.ctypes.data_as(doubleP), tstep.psi.ctypes.data_as(doubleP), tstep.steady_applied_forces.ctypes.data_as(doubleP), tstep.unsteady_applied_forces.ctypes.data_as(doubleP), tstep.gravity_forces.ctypes.data_as(doubleP), tstep.quat.ctypes.data_as(doubleP), tstep.for_vel.ctypes.data_as(doubleP), tstep.for_acc.ctypes.data_as(doubleP), tstep.q[-10:].ctypes.data_as(doubleP), tstep.dqdt[-10:].ctypes.data_as(doubleP), tstep.dqddt[-10:].ctypes.data_as(doubleP)) ================================================ FILE: sharpy/utils/__init__.py ================================================ """Utilities""" ================================================ FILE: sharpy/utils/algebra.py ================================================ """ Algebra package Extensive library with geometrical and algebraic operations Note: Tests can be found in ``tests/utils/algebra_test`` """ import numpy as np from warnings import warn ####### # functions for back compatibility def quat2rot(quat): warn('quat2rot(quat) is obsolete! Use quat2rotation(quat).T instead!', stacklevel=2) return quat2rotation(quat).T def crv2rot(psi): warn('crv2rot(psi) is obsolete! Use crv2rotation(psi) instead!', stacklevel=2) return crv2rotation(psi) def rot2crv(rot): warn('rot2crv(rot) is obsolete! Use rotation2crv(rot.T) instead!', stacklevel=2) return rotation2crv(rot.T) def triad2rot(xb, yb, zb): warn('triad2rot(xb,yb,zb) is obsolete! Use triad2rotation(xb,yb,zb).T instead!', stacklevel=2) return triad2rotation(xb, yb, zb).T def mat2quat(rot): """ Rotation matrix to quaternion function. Warnings: This function is deprecated and now longer supported. Please use ``algebra.rotation2quat(rot.T)`` instead. Args: rot: Rotation matrix Returns: np.array: equivalent quaternion """ warn('mat2quat(rot) is obsolete! Use rotation2quat(rot.T) instead!', stacklevel=2) return rotation2quat(rot.T) def tangent_vector(in_coord, ordering=None): r""" Tangent vector calculation for 2+ noded elements. Calculates the tangent vector interpolating every dimension separately. It uses a (n_nodes - 1) degree polynomial, and the differentiation is analytical. Calculation method: 1. A n_nodes-1 polynomial is fitted through the nodes per dimension. 2. Those polynomials are analytically differentiated with respect to the node index 3. The tangent vector is given by: .. math:: \vec{t} = \frac{s_x'\vec{i} + s_y'\vec{j} + s_z'\vec{k}}{\left| s_x'\vec{i} + s_y'\vec{j} + s_z'\vec{k}\right|} where :math:`'` notes the differentiation with respect to the index number Args: in_coord (np.ndarray): array of coordinates of the nodes. Dimensions = ``[n_nodes, ndim]`` Notes: Dimensions are treated independent from each other, interpolating polynomials are computed individually. """ n_nodes, ndim = in_coord.shape if ordering is None: if n_nodes == 2: ordering = [0, n_nodes - 1] elif n_nodes == 3: ordering = [0, 2, 1] else: raise NotImplementedError('Elements with more than 3 nodes are not supported') polyfit_vec, polyfit_der_vec, coord = get_polyfit(in_coord, ordering) # tangent vector calculation # \vec{t} = \frac{fx'i + fy'j + fz'k}/mod(...) tangent = np.zeros_like(coord) for inode in range(n_nodes): vector = [] for idim in range(ndim): vector.append((polyfit_der_vec[idim])(inode)) vector = np.array(vector) vector /= np.linalg.norm(vector) tangent[inode, :] = vector # check orientation of tangent vector fake_tangent = np.zeros_like(tangent) for inode in range(n_nodes): if inode == n_nodes - 1: # use previous vector fake_tangent[inode, :] = fake_tangent[inode - 1, :] continue fake_tangent[inode, :] = coord[inode + 1, :] - coord[inode, :] for inode in range(n_nodes): if np.dot(tangent[inode, :], fake_tangent[inode, :]) < 0: tangent[inode, :] *= -1 return tangent, polyfit_vec def get_polyfit(in_coord, ordering): coord = in_coord.copy() n_nodes, ndim = coord.shape for index in range(n_nodes): order = ordering[index] coord[index, :] = in_coord[order, :] polynomial_degree = n_nodes - 1 # first, the polynomial fit. # we are going to differentiate wrt the indices ([0, 1, 2] for a 3-node) polyfit_vec = [] # we are going to store here the coefficients of the polyfit for idim in range(ndim): polyfit_vec.append(np.polyfit(range(n_nodes), coord[:, idim], polynomial_degree)) # differentiation polyfit_der_vec = [] for idim in range(ndim): polyfit_der_vec.append(np.poly1d(np.polyder(polyfit_vec[idim]))) return polyfit_vec, polyfit_der_vec, coord def unit_vector(vector): r""" Transforms the input vector into a unit vector .. math:: \mathbf{\hat{v}} = \frac{\mathbf{v}}{\|\mathbf{v}\|} Args: vector (np.array): vector to normalise Returns: np.array: unit vector """ if np.linalg.norm(vector) < 1e-6: return np.zeros_like(vector) return vector / np.linalg.norm(vector) def rotation_matrix_around_axis(axis, angle): axis = unit_vector(axis) rot = np.cos(angle) * np.eye(3) rot += np.sin(angle) * skew(axis) rot += (1 - np.cos(angle)) * np.outer(axis, axis) return rot def skew(vector): r""" Returns a skew symmetric matrix such that .. math:: \boldsymbol{v} \times \boldsymbol{u} = \tilde{\boldsymbol{v}}{\boldsymbol{u} where .. math:: \tilde{\boldsymbol{v}} = \begin{bmatrix} 0 & -v_z & v_y \\ v_z & 0 & -v_x \\ -v_y & v_x & 0 \end{bmatrix}. Args: vector (np.ndarray): 3-dimensional vector Returns: np.array: Skew-symmetric matrix. """ if not vector.size == 3: raise ValueError('The input vector is not 3D') matrix = np.zeros((3, 3)) matrix[1, 2] = -vector[0] matrix[2, 0] = -vector[1] matrix[0, 1] = -vector[2] matrix[2, 1] = vector[0] matrix[0, 2] = vector[1] matrix[1, 0] = vector[2] return matrix def quadskew(vector): """ Generates the matrix needed to obtain the quaternion in the following time step through integration of the FoR angular velocity. Args: vector (np.array): FoR angular velocity Notes: The angular velocity is assumed to be constant in the time interval Equivalent to lib_xbeam function Quaternion ODE to compute orientation of body-fixed frame a See Shearer and Cesnik (2007) for definition Returns: np.array: matrix """ if not vector.size == 3: raise ValueError('The input vector is not 3D') matrix = np.zeros((4, 4)) matrix[0, 1:4] = vector matrix[1:4, 0] = -vector matrix[1:4, 1:4] = skew(vector) return matrix def triad2rotation(xb, yb, zb): """ If the input triad is the "b" coord system given in "a" frame, (the vectors of the triad are xb, yb, zb), this function returns Rab, ie the rotation matrix required to rotate the FoR A onto B. :param xb: :param yb: :param zb: :return: rotation matrix Rab """ return np.column_stack((xb, yb, zb)) def rot_matrix_2d(angle): return np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]]) def angle_between_vectors(vec_a, vec_b): angle = np.arctan2(np.linalg.norm(np.cross(vec_a, vec_b)), np.dot(vec_a, vec_b)) return angle def angle_between_vectors_sign(vec_a, vec_b, plane_normal=np.array([0, 0, 1])): angle = np.arctan2(np.linalg.norm(np.cross(vec_a, vec_b)), np.dot(vec_a, vec_b)) if np.dot(plane_normal, np.cross(vec_a, vec_b)) < 0: angle *= -1 return angle def angle_between_vector_and_plane(vector, plane_normal): angle = np.arcsin((np.linalg.norm(np.dot(vector, plane_normal))) / (np.linalg.norm(vector) * np.linalg.norm(plane_normal))) return angle def panel_area(A, B, C, D): """ Calculates the area of a quadrilateral panel from the corner points A,B,C, and D using Bertschneider's formula Args: A (np.ndarray): Coordinates of point 1 B (np.ndarray): Coordinates of point 2 C (np.ndarray): Coordinates of point 3 D (np.ndarray): Coordinates of point 4 Returns: float: Area of quadrilateral panel """ Theta_1 = angle_between_vectors(A - B, A - D) Theta_2 = angle_between_vectors(B - C, B - D) a = np.linalg.norm(D - A) b = np.linalg.norm(A - B) c = np.linalg.norm(B - C) d = np.linalg.norm(C - D) s = (a + b + c + d) / 2 area = np.sqrt((s - a) * (s - b) * (s - c) * (s - d) - a * b * c * d * np.cos(0.5 * (Theta_1 + Theta_2)) ** 2) return area def rotation2quat(Cab): r""" Given a rotation matrix :math:`C^{AB}` rotating the frame A onto B, the function returns the minimal "positive angle" quaternion representing this rotation, where the quaternion, :math:`\vec{\chi}` is defined as: .. math:: \vec{\chi}= \left[\cos\left(\frac{\psi}{2}\right),\, \sin\left(\frac{\psi}{2}\right)\mathbf{\hat{n}}\right] Args: Cab (np.array): rotation matrix :math:`C^{AB}` from frame A to B Returns: np.array: equivalent quaternion :math:`\vec{\chi}` Notes: This is the inverse of ``algebra.quat2rotation`` for Cartesian rotation vectors associated to rotations in the range :math:`[-\pi,\pi]`, i.e.: ``fv == algebra.rotation2crv(algebra.crv2rotation(fv))`` where ``fv`` represents the Cartesian Rotation Vector, :math:`\vec{\psi}` defined as: .. math:: \vec{\psi} = \psi\,\mathbf{\hat{n}} such that :math:`\mathbf{\hat{n}}` is a unit vector and the scalar :math:`\psi` is in the range :math:`[-\pi,\,\pi]`. """ s = np.zeros((4, 4)) s[0, 0] = 1.0 + np.trace(Cab) s[0, 1:] = matrix2skewvec(Cab) s[1, 0] = Cab[2, 1] - Cab[1, 2] s[1, 1] = 1.0 + Cab[0, 0] - Cab[1, 1] - Cab[2, 2] s[1, 2] = Cab[0, 1] + Cab[1, 0] s[1, 3] = Cab[0, 2] + Cab[2, 0] s[2, 0] = Cab[0, 2] - Cab[2, 0] s[2, 1] = Cab[1, 0] + Cab[0, 1] s[2, 2] = 1.0 - Cab[0, 0] + Cab[1, 1] - Cab[2, 2] s[2, 3] = Cab[1, 2] + Cab[2, 1] s[3, 0] = Cab[1, 0] - Cab[0, 1] s[3, 1] = Cab[0, 2] + Cab[2, 0] s[3, 2] = Cab[1, 2] + Cab[2, 1] s[3, 3] = 1.0 - Cab[0, 0] - Cab[1, 1] + Cab[2, 2] smax = np.max(np.diag(s)) ismax = np.argmax(np.diag(s)) # compute quaternion angles quat = np.zeros((4,)) quat[ismax] = 0.5 * np.sqrt(smax) for i in range(4): if i == ismax: continue quat[i] = 0.25 * s[ismax, i] / quat[ismax] return quat_bound(quat) def quat_bound(quat): r""" Given a quaternion, :math:`\vec{\chi}`, associated to a rotation of angle :math:`\psi` about an axis :math:`\mathbf{\hat{n}}`, the function "bounds" the quaternion, i.e. sets the rotation axis :math:`\mathbf{\hat{n}}` such that :math:`\psi` in :math:`[-\pi,\pi]`. Notes: As quaternions are defined as: .. math:: \vec{\chi}= \left[\cos\left(\frac{\psi}{2}\right),\, \sin\left(\frac{\psi}{2}\right)\mathbf{\hat{n}}\right] this is equivalent to enforcing :math:`\chi_0\ge0`. Args: quat (np.array): quaternion to bound Returns: np.array: bounded quaternion """ if quat[0] < 0: quat *= -1. return quat def matrix2skewvec(matrix): vector = np.array([matrix[2, 1] - matrix[1, 2], matrix[0, 2] - matrix[2, 0], matrix[1, 0] - matrix[0, 1]]) return vector def quat2crv(quat): crv_norm = 2.0 * np.arccos(max(-1.0, min(quat[0], 1.0))) # normal vector if abs(crv_norm) < 1e-15: psi = np.zeros((3,)) else: psi = crv_norm * quat[1:4] / np.sin(crv_norm * 0.5) return psi def crv2quat(psi): r""" Converts a Cartesian rotation vector, .. math:: \vec{\psi} = \psi\,\mathbf{\hat{n}} into a "minimal rotation" quaternion, i.e. being the quaternion, :math:`\vec{\chi}`, defined as: .. math:: \vec{\chi}= \left[\cos\left(\frac{\psi}{2}\right),\, \sin\left(\frac{\psi}{2}\right)\mathbf{\hat{n}}\right] the rotation axis, :math:`\mathbf{\hat{n}}` is such that the rotation angle, :math:`\psi`, is in :math:`[-\pi,\,\pi]` or, equivalently, :math:`\chi_0\ge0`. Args: psi (np.array): Cartesian Rotation Vector, CRV: :math:`\vec{\psi} = \psi\,\mathbf{\hat{n}}`. Returns: np.array: equivalent quaternion :math:`\vec{\chi}` """ # minimise crv rotation psi_new = crv_bounds(psi) fi = np.linalg.norm(psi_new) if fi > 1e-15: nv = psi_new / fi else: nv = psi_new quat = np.zeros((4,)) quat[0] = np.cos(.5 * fi) quat[1:] = np.sin(.5 * fi) * nv return quat def crv_bounds(crv_ini): r""" Forces the Cartesian rotation vector norm, :math:`\|\vec{\psi}\|`, to be in the range :math:`[-\pi,\pi]`, i.e. determines the rotation axis orientation, :math:`\mathbf{\hat{n}}`, so as to ensure "minimal rotation". Args: crv_ini (np.array): Cartesian rotation vector, :math:`\vec{\psi}` Returns: np.array: modified and bounded, equivalent Cartesian rotation vector """ crv = crv_ini.copy() # original norm norm_ini = np.linalg.norm(crv_ini) # force the norm to be in [-pi, pi] norm = norm_ini - 2.0 * np.pi * int(norm_ini / (2 * np.pi)) if norm == 0.0: crv *= 0.0 else: if norm > np.pi: norm -= 2.0 * np.pi elif norm < -np.pi: norm += 2.0 * np.pi crv *= (norm / norm_ini) return crv # return crv_ini def triad2crv(xb, yb, zb): return rotation2crv(triad2rotation(xb, yb, zb)) def crv2triad(psi): rot_matrix = crv2rotation(psi) return rot_matrix[:, 0], rot_matrix[:, 1], rot_matrix[:, 2] def crv2rotation(psi): r""" Given a Cartesian rotation vector, :math:`\boldsymbol{\Psi}`, the function produces the rotation matrix required to rotate a vector according to :math:`\boldsymbol{\Psi}`. The rotation matrix is given by .. math:: \mathbf{R} = \mathbf{I} + \frac{\sin||\boldsymbol{\Psi}||}{||\boldsymbol{\Psi}||} \tilde{\boldsymbol{\Psi}} + \frac{1-\cos{||\boldsymbol{\Psi}||}}{||\boldsymbol{\Psi}||^2}\tilde{\boldsymbol{\Psi}} \tilde{\boldsymbol{\Psi}} To avoid the singularity when :math:`||\boldsymbol{\Psi}||=0`, the series expansion is used .. math:: \mathbf{R} = \mathbf{I} + \tilde{\boldsymbol{\Psi}} + \frac{1}{2!}\tilde{\boldsymbol{\Psi}}^2. Args: psi (np.array): Cartesian rotation vector :math:`\boldsymbol{\Psi}`. Returns: np.array: equivalent rotation matrix References: Geradin and Cardona, Flexible Multibody Dynamics: A finite element approach. Chapter 4 """ norm_psi = np.linalg.norm(psi) if norm_psi < 1e-15: skew_psi = skew(psi) rot_matrix = np.eye(3) + skew_psi + 0.5 * np.dot(skew_psi, skew_psi) else: normal = psi / norm_psi skew_normal = skew(normal) rot_matrix = np.eye(3) rot_matrix += np.sin(norm_psi) * skew_normal rot_matrix += (1.0 - np.cos(norm_psi)) * np.dot(skew_normal, skew_normal) return rot_matrix def rotation2crv(Cab): r""" Given a rotation matrix :math:`C^{AB}` rotating the frame A onto B, the function returns the minimal size Cartesian rotation vector, :math:`\vec{\psi}` representing this rotation. Args: Cab (np.array): rotation matrix :math:`C^{AB}` Returns: np.array: equivalent Cartesian rotation vector, :math:`\vec{\psi}`. Notes: this is the inverse of ``algebra.crv2rotation`` for Cartesian rotation vectors associated to rotations in the range :math:`[-\pi,\,\pi]`, i.e.: ``fv == algebra.rotation2crv(algebra.crv2rotation(fv))`` for each Cartesian rotation vector of the form :math:`\vec{\psi} = \psi\,\mathbf{\hat{n}}` represented as ``fv=a*nv`` such that ``nv`` is a unit vector and the scalar ``a`` is in the range :math:`[-\pi,\,\pi]`. """ if np.linalg.norm(Cab) < 1e-6: raise AttributeError('Element Vector V is not orthogonal to reference line (51105)') quat = rotation2quat(Cab) psi = quat2crv(quat) if np.linalg.norm(Cab) < 1.0e-15: psi[0] = Cab[1, 2] psi[1] = Cab[2, 0] psi[2] = Cab[0, 1] return crv_bounds(psi) def crv2tan(psi): r""" Returns the tangential operator, :math:`\mathbf{T}(\boldsymbol{\Psi})`, that is a function of the Cartesian Rotation Vector, :math:`\boldsymbol{\Psi}`. .. math:: \boldsymbol{T}(\boldsymbol{\Psi}) = \mathbf{I} + \left(\frac{\cos ||\boldsymbol{\Psi}|| - 1}{||\boldsymbol{\Psi}||^2}\right)\tilde{\boldsymbol{\Psi}} + \left(1 - \frac{\sin||\boldsymbol{\Psi}||}{||\boldsymbol{\Psi}||}\right) \frac{\tilde{\boldsymbol{\Psi}}\tilde{\boldsymbol{\Psi}}}{||\boldsymbol{\Psi}||^2} When the norm of the CRV approaches 0, the series expansion expression is used in-lieu of the above expression .. math:: \boldsymbol{T}(\boldsymbol{\Psi}) = \mathbf{I} -\frac{1}{2!}\tilde{\boldsymbol{\Psi}} + \frac{1}{3!}\tilde{\boldsymbol{\Psi}}^2 Args: psi (np.array): Cartesian Rotation Vector, :math:`\boldsymbol{\Psi}`. Returns: np.array: Tangential operator References: Geradin and Cardona. Flexible Multibody Dynamics: A Finite Element Approach. Chapter 4. """ norm_psi = np.linalg.norm(psi) psi_skew = skew(psi) eps = 1e-8 if norm_psi < eps: return np.eye(3) - 0.5 * psi_skew + 1.0 / 6.0 * np.dot(psi_skew, psi_skew) else: k1 = (np.cos(norm_psi) - 1.0) / (norm_psi * norm_psi) k2 = (1.0 - np.sin(norm_psi) / norm_psi) / (norm_psi * norm_psi) return np.eye(3) + k1 * psi_skew + k2 * np.dot(psi_skew, psi_skew) def crv2invtant(psi): tan = crv2tan(psi).T return np.linalg.inv(tan) def triad2crv_vec(v1, v2, v3): n_nodes, _ = v1.shape crv_vec = np.zeros((n_nodes, 3)) for inode in range(n_nodes): crv_vec[inode, :] = triad2crv(v1[inode, :], v2[inode, :], v3[inode, :]) return crv_vec def crv2triad_vec(crv_vec): n_nodes, _ = crv_vec.shape v1 = np.zeros((n_nodes, 3)) v2 = np.zeros((n_nodes, 3)) v3 = np.zeros((n_nodes, 3)) for inode in range(n_nodes): v1[inode, :], v2[inode, :], v3[inode, :] = crv2triad(crv_vec[inode, :]) return v1, v2, v3 def quat2rotation(q1): r"""Calculate rotation matrix based on quaternions. If B is a FoR obtained rotating a FoR A by an angle :math:`\phi` about an axis :math:`\mathbf{n}` (recall :math:`\mathbf{n}` will be invariant during the rotation), and :math:`\mathbf{q}` is the related quaternion, :math:`\mathbf{q}(\phi,\mathbf{n})`, the function will return the matrix :math:`C^{AB}` such that: - :math:`C^{AB}` rotates FoR A onto FoR B. - :math:`C^{AB}` transforms the coordinates of a vector defined in B component to A components i.e. :math:`\mathbf{v}^A = C^{AB}(\mathbf{q})\mathbf{v}^B`. .. math:: C^{AB}(\mathbf{q}) = \begin{pmatrix} q_0^2 + q_1^2 - q_2^2 -q_3^2 & 2(q_1 q_2 - q_0 q_3) & 2(q_1 q_3 + q_0 q_2) \\ 2(q_1 q_2 + q_0 q_3) & q_0^2 - q_1^2 + q_2^2 - q_3^2 & 2(q_2 q_3 - q_0 q_1) \\ 2(q_1 q_3 - q_0 q_2) & 2(q_2 q_3 + q_0 q_1) & q_0^2 -q_1^2 -q_2^2 +q_3^2 \end{pmatrix} Notes: The inverse rotation is defined as the transpose of the matrix :math:`C^{BA} = C^{{AB}^T}`. In typical SHARPy applications, the quaternion relation between the A and G frames is expressed as :math:`C^{GA}(\mathbf{q})`, and in the context of this function it corresponds to: >>> C_ga = quat2rotation(q1) >>> C_ag = quat2rotation.T(q1) Args: q (np.ndarray): Quaternion :math:`\mathbf{q}(\phi, \mathbf{n})`. Returns: np.ndarray: :math:`C^{AB}` rotation matrix from FoR B to FoR A. References: Stevens, L. Aircraft Control and Simulation. 1985. pg 41 """ q = q1.copy(order='F') q /= np.linalg.norm(q) rot_mat = np.zeros((3, 3), order='F') rot_mat[0, 0] = q[0] ** 2 + q[1] ** 2 - q[2] ** 2 - q[3] ** 2 rot_mat[1, 1] = q[0] ** 2 - q[1] ** 2 + q[2] ** 2 - q[3] ** 2 rot_mat[2, 2] = q[0] ** 2 - q[1] ** 2 - q[2] ** 2 + q[3] ** 2 rot_mat[1, 0] = 2. * (q[1] * q[2] + q[0] * q[3]) rot_mat[0, 1] = 2. * (q[1] * q[2] - q[0] * q[3]) rot_mat[2, 0] = 2. * (q[1] * q[3] - q[0] * q[2]) rot_mat[0, 2] = 2. * (q[1] * q[3] + q[0] * q[2]) rot_mat[2, 1] = 2. * (q[2] * q[3] + q[0] * q[1]) rot_mat[1, 2] = 2. * (q[2] * q[3] - q[0] * q[1]) return rot_mat def rot_skew(vec): from warnings import warn warn("use 'skew' function instead of 'rot_skew'") return skew(vec) def rotation3d_x(angle): r""" Rotation matrix about the x axis by the input angle :math:`\Phi` .. math:: \mathbf{\tau}_x = \begin{bmatrix} 1 & 0 & 0 \\ 0 & \cos(\Phi) & -\sin(\Phi) \\ 0 & \sin(\Phi) & \cos(\Phi) \end{bmatrix} Args: angle (float): angle of rotation in radians about the x axis Returns: np.array: 3x3 rotation matrix about the x axis """ c = np.cos(angle) s = np.sin(angle) mat = np.zeros((3, 3)) mat[0, :] = [1., 0., 0.] mat[1, :] = [0., c, -s] mat[2, :] = [0., s, c] return mat def rotation3d_y(angle): r""" Rotation matrix about the y axis by the input angle :math:`\Theta` .. math:: \mathbf{\tau}_y = \begin{bmatrix} \cos(\Theta) & 0 & -\sin(\Theta) \\ 0 & 1 & 0 \\ \sin(\Theta) & 0 & \cos(\Theta) \end{bmatrix} Args: angle (float): angle of rotation in radians about the y axis Returns: np.array: 3x3 rotation matrix about the y axis """ c = np.cos(angle) s = np.sin(angle) mat = np.zeros((3, 3)) mat[0, :] = [c, 0., s] mat[1, :] = [0., 1., 0.] mat[2, :] = [-s, 0., c] return mat def rotation3d_z(angle): r""" Rotation matrix about the z axis by the input angle :math:`\Psi` .. math:: \mathbf{\tau}_z = \begin{bmatrix} \cos(\Psi) & -\sin(\Psi) & 0 \\ \sin(\Psi) & \cos(\Psi) & 0 \\ 0 & 0 & 1 \end{bmatrix} Args: angle (float): angle of rotation in radians about the z axis Returns: np.array: 3x3 rotation matrix about the z axis """ c = np.cos(angle) s = np.sin(angle) mat = np.zeros((3, 3)) mat[0, :] = [c, -s, 0.] mat[1, :] = [s, c, 0.] mat[2, :] = [0., 0., 1.] return mat def rotate_crv(crv_in, axis, angle): C = crv2rotation(crv_in).T rot = rotation_matrix_around_axis(axis, angle) C = np.dot(C, rot) crv = rot2crv(C) return crv def euler2rot(euler): r""" Transforms Euler angles (roll, pitch and yaw :math:`\Phi, \Theta, \Psi`) into a 3x3 rotation matrix describing that rotates a vector in yaw pitch, and roll. The rotations are performed successively, first in yaw, then in pitch and finally in roll. .. math:: \mathbf{T}_{AG} = \mathbf{\tau}_x(\Phi) \mathbf{\tau}_y(\Theta) \mathbf{\tau}_z(\Psi) where :math:`\mathbf{\tau}` represents the rotation about the subscripted axis. Args: euler (np.array): 1x3 array with the Euler angles in the form ``[roll, pitch, yaw]`` in radians Returns: np.array: 3x3 transformation matrix describing the rotation by the input Euler angles. """ rot = rotation3d_z(euler[2]).dot(rotation3d_y(euler[1]).dot(rotation3d_x(euler[0]))) return rot def euler2quat(euler): """ Args: euler: Euler angles Returns: np.ndarray: Equivalent quaternion. """ euler_rot = euler2rot(euler) # this is Cag quat = rotation2quat(euler_rot) return quat def quat2euler(quat): r""" Quaternion to Euler angles transformation. Transforms a normalised quaternion :math:`\chi\longrightarrow[\phi, \theta, \psi]` to roll, pitch and yaw angles respectively. The transformation is valid away from the singularity present at: .. math:: \Delta = \frac{1}{2} where :math:`\Delta = q_0 q_2 - q_1 q_3`. The transformation is carried out as follows: .. math:: \psi &= \arctan{\left(2\frac{q_0q_3+q_1q_2}{1-2(q_2^2+q_3^2)}\right)} \\ \theta &= \arcsin(2\Delta) \\ \phi &= \arctan\left(2\frac{q_0q_1 + q_2q_3}{1-2(q_1^2+q_2^2)}\right) Args: quat (np.ndarray): Normalised quaternion. Returns: np.ndarray: Array containing the Euler angles :math:`[\phi, \theta, \psi]` for roll, pitch and yaw, respectively. References: Blanco, J.L. - A tutorial on SE(3) transformation parameterizations and on-manifold optimization. Technical Report 012010. ETS Ingenieria Informatica. Universidad de Malaga. 2013. """ assert np.abs(np.linalg.norm(quat) - 1.0) < 1.e6, 'Input quaternion is not normalised' q0 = quat[0] q1 = quat[1] q2 = quat[2] q3 = quat[3] delta = quat[0] * quat[2] - quat[1] * quat[3] if np.abs(delta) > 0.9 * 0.5: warn('Warning, approaching singularity. Delta {:.3f} for singularity at Delta=0.5'.format(np.abs(delta))) yaw = np.arctan2(2 * (q0 * q3 + q1 * q2), (1 - 2 * (q2 ** 2 + q3 ** 2))) pitch = np.arcsin(2 * delta) roll = np.arctan2(2 * (q0 * q1 + q2 * q3), (1 - 2 * (q1 ** 2 + q2 ** 2))) return np.array([roll, pitch, yaw]) def crv_dot2omega(crv, crv_dot): return np.dot(crv2tan(crv).T, crv_dot) def crv_dot2Omega(crv, crv_dot): return np.dot(crv2tan(crv), crv_dot) def quaternion_product(q, r): result = np.zeros((4,)) result[0] = q[0] * r[0] - q[1] * r[1] - q[2] * r[2] - q[3] * r[3] result[1] = q[0] * r[1] + q[1] * r[0] + q[2] * r[3] - q[3] * r[2] result[2] = q[0] * r[2] - q[1] * r[3] + q[2] * r[0] + q[3] * r[1] result[3] = q[0] * r[3] + q[1] * r[2] - q[2] * r[1] + q[3] * r[0] return result def omegadt2quat(omegadt): quat = np.zeros((4,)) omegadt_norm = np.linalg.norm(omegadt) quat[0] = np.cos(0.5 * omegadt_norm) quat[1:4] = unit_vector(omegadt) * np.sin(0.5 * omegadt_norm) return quat def rotate_quaternion(quat, omegadt): return quaternion_product(omegadt2quat(omegadt), quat) def get_triad(coordinates_def, frame_of_reference_delta, twist=None, n_nodes=3, ordering=np.array([0, 2, 1])): """ Generates two unit vectors in body FoR that define the local FoR for a beam element. These vectors are calculated using `frame_of_reference_delta` :return: """ # now, calculate tangent vector (and coefficients of the polynomial # fit just in case) tangent, polyfit = tangent_vector( coordinates_def, ordering) normal = np.zeros_like(tangent) binormal = np.zeros_like(tangent) # v_vector is the vector with origin the FoR node and delta # equals frame_of_reference_delta for inode in range(n_nodes): v_vector = frame_of_reference_delta[inode, :] normal[inode, :] = unit_vector(np.cross( tangent[inode, :], v_vector ) ) binormal[inode, :] = -unit_vector(np.cross( tangent[inode, :], normal[inode, :] ) ) if twist is not None: raise NotImplementedError( 'Structural twist is not yet supported in algebra.get_triad, but it is in beamstructures.py') return tangent, binormal, normal def der_Cquat_by_v(q, v): """ Being C=C(quat) the rotational matrix depending on the quaternion q and defined as C=quat2rotation(q), the function returns the derivative, w.r.t. the quanternion components, of the vector dot(C,v), where v is a constant vector. The elements of the resulting derivative matrix D are ordered such that: .. math:: d(C*v) = D*d(q) where :math:`d(.)` is a delta operator. """ vx, vy, vz = v q0, q1, q2, q3 = q return 2. * np.array([[q0 * vx + q2 * vz - q3 * vy, q1 * vx + q2 * vy + q3 * vz, q0 * vz + q1 * vy - q2 * vx, -q0 * vy + q1 * vz - q3 * vx], [q0 * vy - q1 * vz + q3 * vx, -q0 * vz - q1 * vy + q2 * vx, q1 * vx + q2 * vy + q3 * vz, q0 * vx + q2 * vz - q3 * vy], [q0 * vz + q1 * vy - q2 * vx, q0 * vy - q1 * vz + q3 * vx, -q0 * vx - q2 * vz + q3 * vy, q1 * vx + q2 * vy + q3 * vz]]) def der_CquatT_by_v(q, v): r""" Returns the derivative with respect to quaternion components of a projection matrix times a constant vector. Being :math:`\mathbf{C}=\mathbf{R}(\boldsymbol{\chi})^\top` the projection matrix depending on the quaternion :math:`\boldsymbol{\chi}` and obtained through the function defined as ``C=quat2rotation(q).T``, this function returns the derivative with respect to the quaternion components, of the vector :math:`(\mathbf{C\cdot v})`, where :math:`\mathbf{v}` is a constant vector. The derivative operation is defined as: .. math:: \delta(\mathbf{C}\cdot \mathbf{v}) = \frac{\partial}{\partial\boldsymbol{\chi}}\left(\mathbf{C\cdot v}\right)\delta\boldsymbol{\chi} where, for simplicity, we define .. math:: \mathbf{D} = \frac{\partial}{\partial\boldsymbol{\chi}}\left(\mathbf{C\cdot v}\right) \in \mathbb{R}^{3\times4} and :math:`\delta(\bullet)` is a delta operator. The members of :math:`\mathbf{D}` are the following: .. math:: \mathbf{D}_{11} &= 2 (q_0 v_x - q_2 v_z + q_3 v_y)\\ \mathbf{D}_{12} &= 2 (q_1 v_x - q_2 v_y + q_3 v_z)\\ \mathbf{D}_{13} &= 2 (-q_0 v_z + q_1 v_y - q_2 v_x)\\ \mathbf{D}_{14} &= 2 (q_0 v_y + q_1 v_z - q_3 v_x) .. math:: \mathbf{D}_{21} &= 2 (q_0 v_y + q_1 v_z - q_3 v_x)\\ \mathbf{D}_{22} &= 2 (q_0 v_z - q_1 v_y + q_2 v_x)\\ \mathbf{D}_{23} &= 2 (q_1 v_x + q_2 v_y + q_3 v_z)\\ \mathbf{D}_{24} &= 2 (-q_0 v_x + q_2 v_z - q_3 v_y) .. math:: \mathbf{D}_{31} &= 2 (q_0 v_z - q_1 v_y + q_2 v_x)\\ \mathbf{D}_{32} &= 2 (-q_0 v_y - q_1 v_z + q_3 v_x)\\ \mathbf{D}_{33} &= 2 (q_0 v_x - q_2 v_z + q_3 v_y)\\ \mathbf{D}_{34} &= 2 (q_1 v_x + q_2 v_y + q_3 v_z)\\ Returns: np.array: :math:`\mathbf{D}` matrix. """ vx, vy, vz = v q0, q1, q2, q3 = q return 2. * np.array([[q0 * vx - q2 * vz + q3 * vy, q1 * vx + q2 * vy + q3 * vz, - q0 * vz + q1 * vy - q2 * vx, q0 * vy + q1 * vz - q3 * vx], [q0 * vy + q1 * vz - q3 * vx, q0 * vz - q1 * vy + q2 * vx, q1 * vx + q2 * vy + q3 * vz, -q0 * vx + q2 * vz - q3 * vy], [q0 * vz - q1 * vy + q2 * vx, -q0 * vy - q1 * vz + q3 * vx, q0 * vx - q2 * vz + q3 * vy, q1 * vx + q2 * vy + q3 * vz]]) def der_Tan_by_xv(fv0, xv): """ Being fv0 a cartesian rotation vector and Tan the corresponding tangential operator (computed through crv2tan(fv)), the function returns the derivative of dot(Tan,xv), where xv is a constant vector. The elements of the resulting derivative matrix D are ordered such that: .. math:: d(Tan*xv) = D*d(fv) where :math:`d(.)` is a delta operator. Note: The derivative expression has been derived symbolically and verified by FDs. A more compact expression may be possible. """ f0 = np.linalg.norm(fv0) if f0 < (1e9 * np.finfo(float).eps): tensor1 = np.array([[0., 0., 0.], [0., 0., -0.5], [0., 0.5, 0.]]) * xv[0] tensor2 = np.array([[0., 0., 0.5], [0., 0., 0.], [-0.5, 0., 0.]]) * xv[1] tensor3 = np.array([[0., -0.5, 0.], [0.5, 0., 0.], [0., 0., 0.]]) * xv[2] return tensor1 + tensor2 + tensor3 sf0, cf0 = np.sin(f0), np.cos(f0) fv0_x, fv0_y, fv0_z = fv0 xv_x, xv_y, xv_z = xv f0p2 = f0 ** 2 f0p3 = f0 ** 3 f0p4 = f0 ** 4 rs01 = sf0 / f0 rs03 = sf0 / f0p3 rc02 = (cf0 - 1) / f0p2 rc04 = (cf0 - 1) / f0p4 Ts02 = (1 - rs01) / f0p2 Ts04 = (1 - rs01) / f0p4 return np.array( [[xv_x * ((-fv0_y ** 2 - fv0_z ** 2) * (-cf0 * fv0_x / f0p2 + fv0_x * rs03) / f0p2 - 2 * fv0_x * (1 - rs01) * (-fv0_y ** 2 - fv0_z ** 2) / f0p4) + xv_y * (fv0_x * fv0_y * ( -cf0 * fv0_x / f0p2 + fv0_x * rs03) / f0p2 + fv0_y * Ts02 + fv0_x * fv0_z * rs03 - 2 * fv0_x ** 2 * fv0_y * Ts04 + 2 * fv0_x * fv0_z * rc04) + xv_z * ( fv0_x * fv0_z * (-cf0 * fv0_x / f0p2 + fv0_x * rs03) / f0p2 + fv0_z * Ts02 - fv0_x * fv0_y * rs03 - 2 * fv0_x ** 2 * fv0_z * Ts04 - 2 * fv0_x * fv0_y * rc04), # xv_x * (-2 * fv0_y * Ts02 + (-fv0_y ** 2 - fv0_z ** 2) * (-cf0 * fv0_y / f0p2 + fv0_y * rs03) / f0p2 - 2 * fv0_y * (1 - rs01) * ( -fv0_y ** 2 - fv0_z ** 2) / f0p4) + xv_y * (fv0_x * fv0_y * (-cf0 * fv0_y / f0p2 + fv0_y * rs03) / f0p2 + fv0_x * Ts02 + fv0_y * fv0_z * rs03 - 2 * fv0_x * fv0_y ** 2 * Ts04 + 2 * fv0_y * fv0_z * rc04) + xv_z * (fv0_x * fv0_z * (-cf0 * fv0_y / f0p2 + fv0_y * rs03) / f0p2 + rc02 - fv0_y ** 2 * rs03 - 2 * fv0_x * fv0_y * fv0_z * Ts04 - 2 * fv0_y ** 2 * rc04), # xv_x * (-2 * fv0_z * Ts02 + (-fv0_y ** 2 - fv0_z ** 2) * (-cf0 * fv0_z / f0p2 + fv0_z * rs03) / f0p2 - 2 * fv0_z * (1 - rs01) * ( -fv0_y ** 2 - fv0_z ** 2) / f0p4) + xv_y * (fv0_x * fv0_y * (-cf0 * fv0_z / f0p2 + fv0_z * rs03) / f0p2 - rc02 + fv0_z ** 2 * rs03 - 2 * fv0_x * fv0_y * fv0_z * Ts04 + 2 * fv0_z ** 2 * rc04) + xv_z * (fv0_x * fv0_z * (-cf0 * fv0_z / f0p2 + fv0_z * rs03) / f0p2 + fv0_x * Ts02 - fv0_y * fv0_z * rs03 - 2 * fv0_x * fv0_z ** 2 * Ts04 - 2 * fv0_y * fv0_z * rc04)], [xv_x * (fv0_x * fv0_y * (-cf0 * fv0_x / f0p2 + fv0_x * rs03) / f0p2 + fv0_y * Ts02 - fv0_x * fv0_z * rs03 - 2 * fv0_x ** 2 * fv0_y * Ts04 - 2 * fv0_x * fv0_z * rc04) + xv_y * (-2 * fv0_x * Ts02 + (-fv0_x ** 2 - fv0_z ** 2) * ( -cf0 * fv0_x / f0p2 + fv0_x * rs03) / f0p2 - 2 * fv0_x * (1 - rs01) * (-fv0_x ** 2 - fv0_z ** 2) / f0p4) + xv_z * (fv0_y * fv0_z * (-cf0 * fv0_x / f0p2 + fv0_x * rs03) / f0p2 - rc02 + fv0_x ** 2 * rs03 + 2 * fv0_x ** 2 * rc04 - 2 * fv0_x * fv0_y * fv0_z * Ts04), xv_x * (fv0_x * fv0_y * (-cf0 * fv0_y / f0p2 + fv0_y * rs03) / f0p2 + fv0_x * Ts02 - fv0_y * fv0_z * rs03 - 2 * fv0_x * fv0_y ** 2 * Ts04 - 2 * fv0_y * fv0_z * rc04) + xv_y * ((-fv0_x ** 2 - fv0_z ** 2) * (-cf0 * fv0_y / f0p2 + fv0_y * rs03) / f0p2 - 2 * fv0_y * ( 1 - rs01) * (-fv0_x ** 2 - fv0_z ** 2) / f0p4) + xv_z * (fv0_y * fv0_z * (-cf0 * fv0_y / f0p2 + fv0_y * rs03) / f0p2 + fv0_z * Ts02 + fv0_x * fv0_y * rs03 + 2 * fv0_x * fv0_y * rc04 - 2 * fv0_y ** 2 * fv0_z * Ts04), xv_x * (fv0_x * fv0_y * (-cf0 * fv0_z / f0p2 + fv0_z * rs03) / f0p2 + rc02 - fv0_z ** 2 * rs03 - 2 * fv0_x * fv0_y * fv0_z * Ts04 - 2 * fv0_z ** 2 * rc04) + xv_y * (-2 * fv0_z * Ts02 + (-fv0_x ** 2 - fv0_z ** 2) * ( -cf0 * fv0_z / f0p2 + fv0_z * rs03) / f0p2 - 2 * fv0_z * (1 - rs01) * ( -fv0_x ** 2 - fv0_z ** 2) / f0p4) + xv_z * ( fv0_y * fv0_z * (-cf0 * fv0_z / f0p2 + fv0_z * rs03) / f0p2 + fv0_y * Ts02 + fv0_x * fv0_z * rs03 + 2 * fv0_x * fv0_z * rc04 - 2 * fv0_y * fv0_z ** 2 * Ts04)], [xv_x * (fv0_x * fv0_z * (-cf0 * fv0_x / f0p2 + fv0_x * rs03) / f0p2 + fv0_z * Ts02 + fv0_x * fv0_y * rs03 - 2 * fv0_x ** 2 * fv0_z * Ts04 + 2 * fv0_x * fv0_y * rc04) + xv_y * (fv0_y * fv0_z * (-cf0 * fv0_x / f0p2 + fv0_x * rs03) / f0p2 + rc02 - fv0_x ** 2 * rs03 - 2 * fv0_x ** 2 * rc04 - 2 * fv0_x * fv0_y * fv0_z * Ts04) + xv_z * (-2 * fv0_x * Ts02 + ( -fv0_x ** 2 - fv0_y ** 2) * ( -cf0 * fv0_x / f0p2 + fv0_x * rs03) / f0p2 - 2 * fv0_x * (1 - rs01) * ( -fv0_x ** 2 - fv0_y ** 2) / f0p4), xv_x * (fv0_x * fv0_z * (-cf0 * fv0_y / f0p2 + fv0_y * rs03) / f0p2 - rc02 + fv0_y ** 2 * rs03 - 2 * fv0_x * fv0_y * fv0_z * Ts04 + 2 * fv0_y ** 2 * rc04) + xv_y * ( fv0_y * fv0_z * (-cf0 * fv0_y / f0p2 + fv0_y * rs03) / f0p2 + fv0_z * Ts02 - fv0_x * fv0_y * rs03 - 2 * fv0_x * fv0_y * rc04 - 2 * fv0_y ** 2 * fv0_z * Ts04) + xv_z * (-2 * fv0_y * Ts02 + (-fv0_x ** 2 - fv0_y ** 2) * ( -cf0 * fv0_y / f0p2 + fv0_y * rs03) / f0p2 - 2 * fv0_y * ( 1 - rs01) * (-fv0_x ** 2 - fv0_y ** 2) / f0p4), xv_x * (fv0_x * fv0_z * (-cf0 * fv0_z / f0p2 + fv0_z * rs03) / f0p2 + fv0_x * Ts02 + fv0_y * fv0_z * rs03 - 2 * fv0_x * fv0_z ** 2 * Ts04 + 2 * fv0_y * fv0_z * rc04) + xv_y * (fv0_y * fv0_z * (-cf0 * fv0_z / f0p2 + fv0_z * rs03) / f0p2 + fv0_y * Ts02 - fv0_x * fv0_z * rs03 - 2 * fv0_x * fv0_z * rc04 - 2 * fv0_y * fv0_z ** 2 * Ts04) + xv_z * ((-fv0_x ** 2 - fv0_y ** 2) * (-cf0 * fv0_z / f0p2 + fv0_z * rs03) / f0p2 - 2 * fv0_z * (1 - rs01) * (-fv0_x ** 2 - fv0_y ** 2) / f0p4)]]) def der_TanT_by_xv(fv0, xv): """ Being fv0 a cartesian rotation vector and Tan the corresponding tangential operator (computed through crv2tan(fv)), the function returns the derivative of dot(Tan^T,xv), where xv is a constant vector. The elements of the resulting derivative matrix D are ordered such that: .. math:: d(Tan^T*xv) = D*d(fv) where :math:`d(.)` is a delta operator. Note: The derivative expression has been derived symbolically and verified by FDs. A more compact expression may be possible. """ # Renaming variabes for clarity px = fv0[0] py = fv0[1] pz = fv0[2] vx = xv[0] vy = xv[1] vz = xv[2] # Defining useful functions eps = 1e-15 f0 = np.linalg.norm(fv0) if f0 < eps: f1 = -1.0 / 2.0 f2 = 1.0 / 6.0 g1 = -1.0 / 12.0 g2 = 0.0 # TODO: check this else: f1 = (np.cos(f0) - 1.0) / f0 ** 2.0 f2 = (1.0 - np.sin(f0) / f0) / f0 ** 2.0 g1 = (f0 * np.sin(f0) + 2.0 * (np.cos(f0) - 1.0)) / f0 ** 4.0 g2 = (2.0 / f0 ** 4 + np.cos(f0) / f0 ** 4 - 3.0 * np.sin(f0) / f0 ** 5) # Computing the derivatives of the functions df1dpx = -1.0 * px * g1 df1dpy = -1.0 * py * g1 df1dpz = -1.0 * pz * g1 df2dpx = -1.0 * px * g2 df2dpy = -1.0 * py * g2 df2dpz = -1.0 * pz * g2 # Compute the output matrix der_TanT_by_xv = np.zeros((3, 3), ) # First column (derivatives with psi_x) der_TanT_by_xv[0, 0] = -1.0 * df2dpx * ( py ** 2 + pz ** 2) * vx + df1dpx * pz * vy + df2dpx * px * py * vy + f2 * py * vy - df1dpx * py * vz + df2dpx * px * pz * vz + f2 * pz * vz der_TanT_by_xv[ 1, 0] = -1.0 * df1dpx * pz * vx + df2dpx * px * py * vx + f2 * py * vx - df2dpx * px ** 2 * vy - 2.0 * f2 * px * vy - df2dpx * pz ** 2 * vy + df1dpx * px * vz + f1 * vz + df2dpx * py * pz * vz der_TanT_by_xv[ 2, 0] = df1dpx * py * vx + df2dpx * px * pz * vx + f2 * pz * vx - df1dpx * px * vy - f1 * vy + df2dpx * py * pz * vy - df2dpx * px ** 2 * vz - 2.0 * f2 * px * vz - df2dpx * py ** 2 * vz # Second column (derivatives with psi_y) der_TanT_by_xv[ 0, 1] = -df2dpy * py ** 2 * vx - f2 * 2 * py * vx - df2dpy * pz ** 2 * vx + df1dpy * pz * vy + df2dpy * px * py * vy + f2 * px * vy - df1dpy * py * vz - f1 * vz + df2dpy * px * pz * vz der_TanT_by_xv[ 1, 1] = -df1dpy * pz * vx + df2dpy * px * py * vx + f2 * px * vx - df2dpy * px ** 2 * vy - df2dpy * pz ** 2 * vy + df1dpy * px * vz + df2dpy * py * pz * vz + f2 * pz * vz der_TanT_by_xv[ 2, 1] = df1dpy * py * vx + f1 * vx + df2dpy * px * pz * vx - df1dpy * px * vy + df2dpy * py * pz * vy + f2 * pz * vy - df2dpy * px ** 2 * vz - df2dpy * py ** 2 * vz - 2.0 * f2 * py * vz # Second column (derivatives with psi_z) der_TanT_by_xv[ 0, 2] = -df2dpz * py ** 2 * vx - df2dpz * pz ** 2 * vx - 2.0 * f2 * pz * vx + df1dpz * pz * vy + f1 * vy + df2dpz * px * py * vy - df1dpz * py * vz + df2dpz * px * pz * vz + f2 * px * vz der_TanT_by_xv[ 1, 2] = -df1dpz * pz * vx - f1 * vx + df2dpz * px * py * vx - df2dpz * px ** 2 * vy - df2dpz * pz ** 2 * vy - 2.0 * f2 * pz * vy + df1dpz * px * vz + df2dpz * py * pz * vz + f2 * py * vz der_TanT_by_xv[ 2, 2] = df1dpz * py * vx + df2dpz * px * pz * vx + f2 * px * vx - df1dpz * px * vy + df2dpz * py * pz * vy + f2 * py * vy - df2dpz * px ** 2 * vz - df2dpz * py ** 2 * vz return der_TanT_by_xv def der_Ccrv_by_v(fv0, v): r""" Being C=C(fv0) the rotational matrix depending on the Cartesian rotation vector fv0 and defined as C=crv2rotation(fv0), the function returns the derivative, w.r.t. the CRV components, of the vector dot(C,v), where v is a constant vector. The elements of the resulting derivative matrix D are ordered such that: .. math:: d(C*v) = D*d(fv0) where :math:`d(.)` is a delta operator. """ Cab0 = crv2rotation(fv0) T0 = crv2tan(fv0) vskew = skew(v) return -np.dot(Cab0, np.dot(vskew, T0)) def der_CcrvT_by_v(fv0, v): """ Being C=C(fv0) the rotation matrix depending on the Cartesian rotation vector fv0 and defined as C=crv2rotation(fv0), the function returns the derivative, w.r.t. the CRV components, of the vector dot(C.T,v), where v is a constant vector. The elements of the resulting derivative matrix D are ordered such that: .. math:: d(C.T*v) = D*d(fv0) where :math:`d(.)` is a delta operator. """ Cba0 = crv2rotation(fv0).T T0 = crv2tan(fv0) return np.dot(skew(np.dot(Cba0, v)), T0) def der_quat_wrt_crv(quat0): """ Provides change of quaternion, dquat, due to elementary rotation, dcrv, expressed as a 3 components Cartesian rotation vector such that .. math:: C(quat + dquat) = C(quat0)C(dw) where C are rotation matrices. Examples: Assume 3 FoRs, G, A and B where: - G is the initial FoR - quat0 defines te rotation required to obtain A from G, namely: Cga=quat2rotation(quat0) - dcrv is an inifinitesimal Cartesian rotation vector, defined in A components, which describes an infinitesimal rotation A -> B, namely: ..math :: Cab=crv2rotation(dcrv) - The total rotation G -> B is: Cga = Cga * Cab - As dcrv -> 0, Cga is equal to: .. math:: algebra.quat2rotation(quat0 + dquat), where dquat is the output of this function. """ Der = np.zeros((4, 3)) Der[0, :] = -0.5 * quat0[1:] Der[1:, :] = -0.5 * (-quat0[0] * np.eye(3) - skew(quat0[1:])) return Der def der_Ceuler_by_v(euler, v): r""" Provides the derivative of the product between the rotation matrix :math:`C^{AG}(\mathbf{\Theta})` and a constant vector, :math:`\mathbf{v}`, with respect to the Euler angles, :math:`\mathbf{\Theta}=[\phi,\theta,\psi]^T`: .. math:: \frac{\partial}{\partial\Theta}(C^{AG}(\Theta)\mathbf{v}^G) = \frac{\partial \mathbf{f}}{\partial\mathbf{\Theta}} where :math:`\frac{\partial \mathbf{f}}{\partial\mathbf{\Theta}}` is the resulting 3 by 3 matrix. Being :math:`C^{AG}(\Theta)` the rotation matrix from the G frame to the A frame in terms of the Euler angles :math:`\Theta` as: .. math:: C^{AG}(\Theta) = \begin{bmatrix} \cos\theta\cos\psi & -\cos\theta\sin\psi & \sin\theta \\ \cos\phi\sin\psi + \sin\phi\sin\theta\cos\psi & \cos\phi\cos\psi - \sin\phi\sin\theta\sin\psi & -\sin\phi\cos\theta \\ \sin\phi\sin\psi - \cos\phi\sin\theta\cos\psi & \sin\phi\cos\psi + \cos\phi\sin\theta\sin\psi & \cos\phi\cos\theta \end{bmatrix} the components of the derivative at hand are the following, where :math:`f_{1\theta} = \frac{\partial \mathbf{f}_1}{\partial\theta}`. .. math:: f_{1\phi} =&0 \\ f_{1\theta} = &-v_1\sin\theta\cos\psi \\ &+v_2\sin\theta\sin\psi \\ &+v_3\cos\theta \\ f_{1\psi} = &-v_1\cos\theta\sin\psi \\ &- v_2\cos\theta\cos\psi .. math:: f_{2\phi} = &+v_1(-\sin\phi\sin\psi + \cos\phi\sin\theta\cos\psi) + \\ &+v_2(-\sin\phi\cos\psi - \cos\phi\sin\theta\sin\psi) + \\ &+v_3(-\cos\phi\cos\theta)\\ f_{2\theta} = &+v_1(\sin\phi\cos\theta\cos\psi) + \\ &+v_2(-\sin\phi\cos\theta\sin\psi) +\\ &+v_3(\sin\phi\sin\theta) \\ f_{2\psi} = &+v_1(\cos\phi\cos\psi - \sin\phi\sin\theta\sin\psi) + \\ &+v_2(-\cos\phi\sin\psi - \sin\phi\sin\theta\cos\psi) .. math:: f_{3\phi} = &+v_1(\cos\phi\sin\psi+\sin\phi\sin\theta\cos\psi) + \\ &+v_2(\cos\phi\cos\psi - \sin\phi\sin\theta\sin\psi) + \\ &+v_3(-\sin\phi\cos\theta)\\ f_{3\theta} = &+v_1(-\cos\phi\cos\theta\cos\psi)+\\ &+v_2(\cos\phi\cos\theta\sin\psi) + \\ &+v_3(-\cos\phi\sin\theta)\\ f_{3\psi} = &+v_1(\sin\phi\cos\psi+\cos\phi\sin\theta\sin\psi) + \\ &+v_2(-\sin\phi\sin\psi + \cos\phi\sin\theta\cos\psi) Args: euler (np.ndarray): Vector of Euler angles, :math:`\mathbf{\Theta} = [\phi, \theta, \psi]`, in radians. v (np.ndarray): 3 dimensional vector in G frame. Returns: np.ndarray: Resulting 3 by 3 matrix :math:`\frac{\partial \mathbf{f}}{\partial\mathbf{\Theta}}`. """ res = np.zeros((3, 3)) # Notation shorthand. sin and cos of psi (roll) sp = np.sin(euler[0]) cp = np.cos(euler[0]) # Notation shorthand. sin and cos of theta (pitch) st = np.sin(euler[1]) ct = np.cos(euler[1]) # Notation shorthand. sin and cos of psi (yaw) ss = np.sin(euler[2]) cs = np.cos(euler[2]) v1 = v[0] v2 = v[1] v3 = v[2] res[0, 0] = v2 * (sp * ss + cp * st * cp) + v3 * (cp * ss - sp * st * cs) res[0, 1] = v1 * (-st * cs) + v2 * (sp * ct * cs) + v3 * (cp * ct * cs) res[0, 2] = v1 * (ct * ss) + v2 * (-cp * cs - sp * st * ss) + v3 * (sp * cs - cp * st * ss) res[1, 0] = v2 * (-sp * cs + cp * st * ss) + v3 * (-cp * cs + sp * st * ss) res[1, 1] = v1 * (-st * ss) + v2 * (sp * ct * ss) + v3 * (-cp * ct * ss) res[1, 2] = v1 * (ct * cs) + v2 * (-cp * ss + sp * st * cs) + v3 * (sp * ss + cp * st * cs) res[2, 0] = v2 * (cp * ct) + v3 * (-sp * ct) res[2, 1] = v1 * (-ct) + v2 * (-sp * st) + v3 * (-cp * st) return res def der_Peuler_by_v(euler, v): r""" Provides the derivative of the product between the projection matrix :math:`P^{AG}(\mathbf{\Theta})` (that projects a vector in G frame onto A frame) and a constant vector expressed in G frame of reference, :math:`\mathbf{v}_G`, with respect to the Euler angles, :math:`\mathbf{\Theta}=[\phi,\theta,\psi]^T`: .. math:: \frac{\partial}{\partial\Theta}(P^{AG}(\Theta)\mathbf{v}^G) = \frac{\partial \mathbf{f}}{\partial\mathbf{\Theta}} where :math:`\frac{\partial \mathbf{f}}{\partial\mathbf{\Theta}}` is the resulting 3 by 3 matrix. Being :math:`P^{AG}(\Theta)` the projection matrix from the G frame to the A frame in terms of the Euler angles :math:`\Theta` as :math:`P^{AG}(\Theta) = \tau_x(-\Phi)\tau_y(-\Theta)\tau_z(-\Psi)`, where the rotation matrix is expressed as: .. math:: C^{AG}(\Theta) = \begin{bmatrix} \cos\theta\cos\psi & -\cos\theta\sin\psi & \sin\theta \\ \cos\phi\sin\psi + \sin\phi\sin\theta\cos\psi & \cos\phi\cos\psi - \sin\phi\sin\theta\sin\psi & -\sin\phi\cos\theta \\ \sin\phi\sin\psi - \cos\phi\sin\theta\cos\psi & \sin\phi\cos\psi + \cos\phi\sin\theta\sin\psi & \cos\phi\cos\theta \end{bmatrix} and the projection matrix as: .. math:: P^{AG}(\Theta) = \begin{bmatrix} \cos\theta\cos\psi & \cos\theta\sin\psi & -\sin\theta \\ -\cos\phi\sin\psi + \sin\phi\sin\theta\cos\psi & \cos\phi\cos\psi + \sin\phi\sin\theta\sin\psi & \sin\phi\cos\theta \\ \sin\phi\sin\psi + \cos\phi\sin\theta\cos\psi & -\sin\phi\cos\psi + \cos\phi\sin\theta\sin\psi & \cos\phi\cos\theta \end{bmatrix} the components of the derivative at hand are the following, where :math:`f_{1\theta} = \frac{\partial \mathbf{f}_1}{\partial\theta}`. .. math:: f_{1\phi} =&0 \\ f_{1\theta} = &-v_1\sin\theta\cos\psi \\ &+v_2\sin\theta\sin\psi \\ &+v_3\cos\theta \\ f_{1\psi} = &-v_1\cos\theta\sin\psi \\ &- v_2\cos\theta\cos\psi .. math:: f_{2\phi} = &+v_1(-\sin\phi\sin\psi + \cos\phi\sin\theta\cos\psi) + \\ &+v_2(-\sin\phi\cos\psi - \cos\phi\sin\theta\sin\psi) + \\ &+v_3(-\cos\phi\cos\theta)\\ f_{2\theta} = &+v_1(\sin\phi\cos\theta\cos\psi) + \\ &+v_2(-\sin\phi\cos\theta\sin\psi) +\\ &+v_3(\sin\phi\sin\theta)\\ f_{2\psi} = &+v_1(\cos\phi\cos\psi - \sin\phi\sin\theta\sin\psi) + \\ &+v_2(-\cos\phi\sin\psi - \sin\phi\sin\theta\cos\psi) .. math:: f_{3\phi} = &+v_1(\cos\phi\sin\psi+\sin\phi\sin\theta\cos\psi) + \\ &+v_2(\cos\phi\cos\psi - \sin\phi\sin\theta\sin\psi) + \\ &+v_3(-\sin\phi\cos\theta)\\ f_{3\theta} = &+v_1(-\cos\phi\cos\theta\cos\psi)+\\ &+v_2(\cos\phi\cos\theta\sin\psi) + \\ &+v_3(-\cos\phi\sin\theta)\\ f_{3\psi} = &+v_1(\sin\phi\cos\psi+\cos\phi\sin\theta\sin\psi) + \\ &+v_2(-\sin\phi\sin\psi + \cos\phi\sin\theta\cos\psi) Args: euler (np.ndarray): Vector of Euler angles, :math:`\mathbf{\Theta} = [\phi, \theta, \psi]`, in radians. v (np.ndarray): 3 dimensional vector in G frame. Returns: np.ndarray: Resulting 3 by 3 matrix :math:`\frac{\partial \mathbf{f}}{\partial\mathbf{\Theta}}`. """ res = np.zeros((3, 3)) # Notation shorthand. sin and cos of psi (roll) sp = np.sin(euler[0]) cp = np.cos(euler[0]) # Notation shorthand. sin and cos of theta (pitch) st = np.sin(euler[1]) ct = np.cos(euler[1]) # Notation shorthand. sin and cos of psi (yaw) ss = np.sin(euler[2]) cs = np.cos(euler[2]) v1 = v[0] v2 = v[1] v3 = v[2] res[0, 1] = v1 * (-st * cs) + v2 * (-st * ss) - v3 * ct res[0, 2] = -v1 * (ct * ss) + v2 * ct * cs res[1, 0] = v1 * (sp * ss + cp * st * cs) + v2 * (-sp * cs + cp * st * ss) + v3 * (cp * ct) res[1, 1] = v1 * (sp * ct * cs) + v2 * (sp * ct * ss) + v3 * (-sp * st) res[1, 2] = v1 * (-cp * cs - sp * st * ss) + v2 * (-cp * ss + sp * st * cs) res[2, 0] = v1 * (cp * ss - sp * st * cs) + v2 * (-cp * cs - sp * st * ss) + v3 * (-sp * ct) res[2, 1] = v1 * (cp * ct * cs) + v2 * (cp * ct * ss) + v3 * (-cp * st) res[2, 2] = v1 * (sp * ss + -cp * st * ss) + v2 * (sp * ss + cp * st * cs) return res def der_Ceuler_by_v_NED(euler, v): r""" Provides the derivative of the product between the rotation matrix :math:`C^{AG}(\mathbf{\Theta})` and a constant vector, :math:`\mathbf{v}`, with respect to the Euler angles, :math:`\mathbf{\Theta}=[\phi,\theta,\psi]^T`: .. math:: \frac{\partial}{\partial\Theta}(C^{AG}(\Theta)\mathbf{v}^G) = \frac{\partial \mathbf{f}}{\partial\mathbf{\Theta}} where :math:`\frac{\partial \mathbf{f}}{\partial\mathbf{\Theta}}` is the resulting 3 by 3 matrix. Being :math:`C^{AG}(\Theta)` the rotation matrix from the G frame to the A frame in terms of the Euler angles :math:`\Theta` as: .. math:: C^{AG}(\Theta) = \begin{bmatrix} \cos\theta\cos\psi & \cos\theta\sin\psi & -\sin\theta \\ -\cos\phi\sin\psi + \sin\phi\sin\theta\cos\psi & \cos\phi\cos\psi + \sin\phi\sin\theta\sin\psi & \sin\phi\cos\theta \\ \sin\phi\sin\psi + \cos\phi\sin\theta\cos\psi & -\sin\phi\cos\psi + \cos\psi\sin\theta\sin\psi & \cos\phi\cos\theta \end{bmatrix} the components of the derivative at hand are the following, where :math:`f_{1\theta} = \frac{\partial \mathbf{f}_1}{\partial\theta}`. .. math:: f_{1\phi} =&0 \\ f_{1\theta} = &-v_1\sin\theta\cos\psi \\ &-v_2\sin\theta\sin\psi \\ &-v_3\cos\theta \\ f_{1\psi} = &-v_1\cos\theta\sin\psi + v_2\cos\theta\cos\psi .. math:: f_{2\phi} = &+v_1(\sin\phi\sin\psi + \cos\phi\sin\theta\cos\psi) + \\ &+v_2(-\sin\phi\cos\psi + \cos\phi\sin\theta\sin\psi) + \\ &+v_3(\cos\phi\cos\theta) \\ f_{2\theta} = &+v_1(\sin\phi\cos\theta\cos\psi) + \\ &+v_2(\sin\phi\cos\theta\sin\psi) +\\ &-v_3(\sin\phi\sin\theta) \\ f_{2\psi} = &+v_1(-\cos\phi\cos\psi - \sin\phi\sin\theta\sin\psi) + \\ &+v_2(-\cos\phi\sin\psi + \sin\phi\sin\theta\cos\psi) .. math:: f_{3\phi} = &+v_1(\cos\phi\sin\psi-\sin\phi\sin\theta\cos\psi) + \\ &+v_2(-\cos\phi\cos\psi - \sin\phi\sin\theta\sin\psi) + \\ &+v_3(-\sin\phi\cos\theta) \\ f_{3\theta} = &+v_1(\cos\phi\cos\theta\cos\psi)+\\ &+v_2(\cos\phi\cos\theta\sin\psi) + \\ &+v_3(-\cos\phi\sin\theta) \\ f_{3\psi} = &+v_1(\sin\phi\cos\psi-\cos\phi\sin\theta\sin\psi) + \\ &+v_2(\sin\phi\sin\psi + \cos\phi\sin\theta\cos\psi) Note: This function is defined in a North East Down frame which is not the typically used one in SHARPy. Args: euler (np.ndarray): Vector of Euler angles, :math:`\mathbf{\Theta} = [\phi, \theta, \psi]`, in radians. v (np.ndarray): 3 dimensional vector in G frame. Returns: np.ndarray: Resulting 3 by 3 matrix :math:`\frac{\partial \mathbf{f}}{\partial\mathbf{\Theta}}`. """ # TODO: Verify with new euler rotation matrices res = np.zeros((3, 3)) # Notation shorthand. sin and cos of psi (roll) sp = np.sin(euler[0]) cp = np.cos(euler[0]) # Notation shorthand. sin and cos of theta (pitch) st = np.sin(euler[1]) ct = np.cos(euler[1]) # Notation shorthand. sin and cos of psi (yaw) ss = np.sin(euler[2]) cs = np.cos(euler[2]) v1 = v[0] v2 = v[1] v3 = v[2] res[0, 0] = 0 res[0, 1] = - v1 * st * cs - v2 * st * ss - v3 * ct res[0, 2] = -v1 * ct * ss + v2 * ct * cs res[1, 0] = v1 * (sp * ss + cp * st * cs) + v2 * (-sp * cs + cp * st * ss) + v3 * cp * ct res[1, 1] = v1 * (sp * ct * cs) + v2 * sp * ct * ss - v3 * sp * st res[1, 2] = v1 * (-cp * cs - sp * st * ss) + v2 * (-cp * ss + sp * st * cs) res[2, 0] = v1 * (cp * ss - sp * st * cs) + v2 * (-cp * cs - sp * st * ss) + v3 * (-sp * ct) res[2, 1] = v1 * cp * ct * cs + v2 * cp * ct * ss - v3 * cp * st res[2, 2] = v1 * (sp * cs - cp * st * ss) + v2 * (sp * ss + cp * st * cs) return res def cross3(v, w): """ Computes the cross product of two vectors (v and w) with size 3 """ res = np.zeros((3,), ) res[0] = v[1] * w[2] - v[2] * w[1] res[1] = -v[0] * w[2] + v[2] * w[0] res[2] = v[0] * w[1] - v[1] * w[0] return res def deuler_dt(euler): r""" Rate of change of the Euler angles in time for a given angular velocity in A frame :math:`\omega^A=[p, q, r]`. .. math:: \begin{bmatrix}\dot{\phi} \\ \dot{\theta} \\ \dot{\psi}\end{bmatrix} = \begin{bmatrix} 1 & \sin\phi\tan\theta & -\cos\phi\tan\theta \\ 0 & \cos\phi & \sin\phi \\ 0 & -\frac{\sin\phi}{\cos\theta} & \frac{\cos\phi}{\cos\theta} \end{bmatrix} \begin{bmatrix} p \\ q \\ r \end{bmatrix} Args: euler (np.ndarray): Euler angles :math:`[\phi, \theta, \psi]` for roll, pitch and yaw, respectively. Returns: np.ndarray: Propagation matrix relating the rotational velocities to the euler angles. """ phi = euler[0] # roll theta = euler[1] # pitch A = np.zeros((3, 3)) A[0, 0] = 1 A[0, 1] = np.tan(theta) * np.sin(phi) A[0, 2] = -np.tan(theta) * np.cos(phi) A[1, 1] = np.cos(phi) A[1, 2] = np.sin(phi) A[2, 1] = -np.sin(phi) / np.cos(theta) A[2, 2] = np.cos(phi) / np.cos(theta) return A def deuler_dt_NED(euler): r""" Warnings: Based on a NED frame Rate of change of the Euler angles in time for a given angular velocity in A frame :math:`\omega^A=[p, q, r]`. .. math:: \begin{bmatrix}\dot{\phi} \\ \dot{\theta} \\ \dot{\psi}\end{bmatrix} = \begin{bmatrix} 1 & \sin\phi\tan\theta & \cos\phi\tan\theta \\ 0 & \cos\phi & -\sin\phi \\ 0 & \frac{\sin\phi}{\cos\theta} & \frac{\cos\phi}{\cos\theta} \end{bmatrix} \begin{bmatrix} p \\ q \\ r \end{bmatrix} Note: This function is defined in a North East Down frame which is not the typically used one in SHARPy. Args: euler (np.ndarray): Euler angles :math:`[\phi, \theta, \psi]` for roll, pitch and yaw, respectively. Returns: np.ndarray: Propagation matrix relating the rotational velocities to the euler angles. """ # TODO: Verify with the new euler rotation matrices phi = euler[0] # roll theta = euler[1] # pitch A = np.zeros((3, 3)) A[0, 0] = 1 A[0, 1] = np.tan(theta) * np.sin(phi) A[0, 2] = np.tan(theta) * np.cos(phi) A[1, 1] = np.cos(phi) A[1, 2] = -np.sin(phi) A[2, 1] = np.sin(phi) / np.cos(theta) A[2, 2] = np.cos(phi) / np.cos(theta) return A def der_Teuler_by_w(euler, w): r""" Calculates the matrix .. math:: \frac{\partial}{\partial\Theta}\left.\left(T^{GA}(\mathbf{\Theta}) \mathbf{\omega}^A\right)\right|_{\Theta_0,\omega^A_0} from the linearised euler propagation equations .. math:: \delta\mathbf{\dot{\Theta}} = \frac{\partial}{\partial\Theta}\left.\left(T^{GA}(\mathbf{\Theta}) \mathbf{\omega}^A\right)\right|_{\Theta_0,\omega^A_0}\delta\mathbf{\Theta} + T^{GA}(\mathbf{\Theta_0}) \delta\mathbf{\omega}^A where :math:`T^{GA}` is the nonlinear relation between the euler angle rates and the rotational velocities and is provided by :func:`deuler_dt`. The concerned matrix is calculated as follows: .. math:: \frac{\partial}{\partial\Theta}\left.\left(T^{GA}(\mathbf{\Theta}) \mathbf{\omega}^A\right)\right|_{\Theta_0,\omega^A_0} = \\ \begin{bmatrix} q\cos\phi\tan\theta-r\sin\phi\tan\theta & q\sin\phi\sec^2\theta + r\cos\phi\sec^2\theta & 0 \\ -q\sin\phi - r\cos\phi & 0 & 0 \\ q\frac{\cos\phi}{\cos\theta}-r\frac{\sin\phi}{\cos\theta} & q\sin\phi\tan\theta\sec\theta + r\cos\phi\tan\theta\sec\theta & 0 \end{bmatrix}_{\Theta_0, \omega^A_0} Note: This function is defined in a North East Down frame which is not the typically used one in SHARPy. Args: euler (np.ndarray): Euler angles at the linearisation point :math:`\mathbf{\Theta}_0 = [\phi,\theta,\psi]` or roll, pitch and yaw angles, respectively. w (np.ndarray): Rotational velocities at the linearisation point in A frame :math:`\omega^A_0`. Returns: np.ndarray: Computed :math:`\frac{\partial}{\partial\Theta}\left.\left(T^{GA}(\mathbf{\Theta})\mathbf{\omega}^A\right)\right|_{\Theta_0,\omega^A_0}` """ q = w[1] r = w[2] cp = np.cos(euler[0]) sp = np.sin(euler[0]) st = np.sin(euler[1]) ct = np.cos(euler[1]) tt = np.tan(euler[1]) tsec = ct ** -1 derT = np.zeros((3, 3)) derT[0, 0] = q * cp * tt + r * sp * tt derT[0, 1] = q * sp * tsec ** 2 - r * cp * tsec ** 2 derT[1, 0] = -q * sp + r * cp derT[2, 0] = - q * cp / ct - r * sp / ct derT[2, 1] = - q * sp * tt * tsec + r * cp * tt * tsec return derT def der_Teuler_by_w_NED(euler, w): r""" Warnings: Based on a NED G frame Calculates the matrix .. math:: \frac{\partial}{\partial\Theta}\left.\left(T^{GA}(\mathbf{\Theta}) \mathbf{\omega}^A\right)\right|_{\Theta_0,\omega^A_0} from the linearised euler propagation equations .. math:: \delta\mathbf{\dot{\Theta}} = \frac{\partial}{\partial\Theta}\left.\left(T^{GA}(\mathbf{\Theta}) \mathbf{\omega}^A\right)\right|_{\Theta_0,\omega^A_0}\delta\mathbf{\Theta} + T^{GA}(\mathbf{\Theta_0}) \delta\mathbf{\omega}^A where :math:`T^{GA}` is the nonlinear relation between the euler angle rates and the rotational velocities and is provided by :func:`deuler_dt`. The concerned matrix is calculated as follows: .. math:: \frac{\partial}{\partial\Theta}\left.\left(T^{GA}(\mathbf{\Theta}) \mathbf{\omega}^A\right)\right|_{\Theta_0,\omega^A_0} = \\ \begin{bmatrix} q\cos\phi\tan\theta-r\sin\phi\tan\theta & q\sin\phi\sec^2\theta + r\cos\phi\sec^2\theta & 0 \\ -q\sin\phi - r\cos\phi & 0 & 0 \\ q\frac{\cos\phi}{\cos\theta}-r\frac{\sin\phi}{\cos\theta} & q\sin\phi\tan\theta\sec\theta + r\cos\phi\tan\theta\sec\theta & 0 \end{bmatrix}_{\Theta_0, \omega^A_0} Args: euler (np.ndarray): Euler angles at the linearisation point :math:`\mathbf{\Theta}_0 = [\phi,\theta,\psi]` or roll, pitch and yaw angles, respectively. w (np.ndarray): Rotational velocities at the linearisation point in A frame :math:`\omega^A_0`. Returns: np.ndarray: Computed :math:`\frac{\partial}{\partial\Theta}\left.\left(T^{GA}(\mathbf{\Theta})\mathbf{\omega}^A\right)\right|_{\Theta_0,\omega^A_0}` """ # TODO: Verify with new Euler rotation matrices p = w[0] q = w[1] r = w[2] cp = np.cos(euler[0]) sp = np.sin(euler[0]) st = np.sin(euler[1]) ct = np.cos(euler[1]) tt = np.tan(euler[1]) tsec = ct ** -1 derT = np.zeros((3, 3)) derT[0, 0] = q * cp * tt - r * sp * tt derT[0, 1] = q * sp * tsec ** 2 + r * cp * tsec ** 2 derT[1, 0] = -q * sp - r * cp derT[2, 0] = q * cp / ct - r * sp / ct derT[2, 1] = q * sp * tt * tsec + r * cp * tt * tsec return derT def norm3d(v): """ Norm of a 3D vector Notes: Faster than np.linalg.norm Args: v (np.ndarray): 3D vector Returns: np.ndarray: Norm of the vector """ return np.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) def normsq3d(v): """ Square of the norm of a 3D vector Args: v (np.ndarray): 3D vector Returns: np.ndarray: Square of the norm of the vector """ return v[0] * v[0] + v[1] * v[1] + v[2] * v[2] def get_transformation_matrix(transformation): r""" Returns a projection matrix function between the desired frames of reference. Examples: The projection matrix :math:`C^GA(\chi)` expresses a vector in the body-attached reference frame ``A`` in the inertial frame ``G``, which is a function of the quaternion. .. code-block:: cga_function = get_transformation_matrix('ga') cga = cga_function(quat) # The actual projection matrix between A and G for a known quaternion If the projection involves the ``G`` and ``B`` frames, the output function will take both the quaternion and the CRV as arguments. .. code-block:: cgb_function = get_transformation_matrix('gb') cgb = cgb_function(psi, quat) # The actual projection matrix between B and G for a known CRV and quaternion Args: transformation (str): Desired projection matrix function. Returns: function: Function to obtain the desired projection matrix. The function will either take the CRV, the quaternion, or both as arguments. Note: If a rotation is desired, it can be achieved by transposing the resulting projection matrix. """ if transformation == 'ab': cab = crv2rotation return cab elif transformation == 'ba': def cba(psi): return crv2rotation(psi).T return cba elif transformation == 'ga': cga = quat2rotation return cga elif transformation == 'ag': def cag(quat): return quat2rotation(quat).T return cag elif transformation == 'bg': def cbg(psi, quat): cag = get_transformation_matrix('ag') cba = get_transformation_matrix('ba') return cba(psi).dot(cag(quat)) return cbg elif transformation == 'gb': def cgb(psi, quat): cab = get_transformation_matrix('ba') cga = get_transformation_matrix('ga') return cga(quat).dot(cab(psi)) return cgb else: raise NameError('Unknown transformation.') def der_skewp_skewp_v(p, v): """ This function computes: .. math:: \frac{d}{d\boldsymbol{p}} (\tilde{\boldsymbol{p}} \tilde{\boldsymbol{p}} v) """ der = np.zeros((3, 3)) der[0, 0] = v[1] * p[1] + v[2] * p[2] der[0, 1] = -2 * v[0] * p[1] + v[1] * p[0] der[0, 2] = -2 * v[0] * p[2] + v[2] * p[0] der[1, 0] = v[0] * p[1] - 2 * v[1] * p[0] der[1, 1] = v[0] * p[0] + v[2] * p[2] der[1, 2] = -2 * v[1] * p[2] + v[2] * p[1] der[2, 0] = v[0] * p[2] - 2 * v[2] * p[0] der[2, 1] = v[1] * p[2] - 2 * v[2] * p[1] der[2, 2] = v[0] * p[0] + v[1] * p[1] return der def der_skewpT_v(p, v): """ This function computes: .. math:: \frac{d}{d\boldsymbol{p}} \tilde{\boldsymbol{p}}^T v) """ return skew(v) def der_skewp_v(p, v): """ This function computes: .. math:: \frac{d}{d\boldsymbol{p}} \tilde{\boldsymbol{p}} v) """ return -skew(v) ================================================ FILE: sharpy/utils/analytical.py ================================================ """Analytical Functions Analytical solutions for 2D aerofoil based on thin plates theory Author: Salvatore Maraniello Date: 23 May 2017 References: 1. Simpson, R.J.S., Palacios, R. & Murua, J., 2013. Induced-Drag Calculations in the Unsteady Vortex Lattice Method. AIAA Journal, 51(7), pp.1775–1779. 2. Gulcat, U., 2009. Propulsive Force of a Flexible Flapping Thin Airfoil. Journal of Aircraft, 46(2), pp.465–473. """ import numpy as np import scipy.special as scsp # imaginary variable j = 1.0j def theo_fun(k): r"""Returns the value of Theodorsen's function at a reduced frequency :math:`k`. .. math:: \mathcal{C}(jk) = \frac{H_1^{(2)}(k)}{H_1^{(2)}(k) + jH_0^{(2)}(k)} where :math:`H_0^{(2)}(k)` and :math:`H_1^{(2)}(k)` are Hankel functions of the second kind. Args: k (np.array): Reduced frequency/frequencies at which to evaluate the function. Returns: np.array: Value of Theodorsen's function evaluated at the desired reduced frequencies. """ H1 = scsp.hankel2(1, k) H0 = scsp.hankel2(0, k) C = H1 / (H1 + j * H0) return C def qs_derivs(x_ea_perc, x_fh_perc): """ Provides quasi-steady aerodynamic lift and moment coefficients derivatives Ref. Palacios and Cesnik, Chap 3. Args: x_ea_perc: position of axis of rotation in percentage of chord (measured from LE) x_fc_perc: position of flap axis of rotation in percentage of chord (measured from LE) """ # parameters nu_ea = 2.0 * x_ea_perc - 1.0 nu_fh = 2.0 * x_fh_perc - 1.0 th = np.arccos(-nu_fh) # pitch/pitch rate related quantities CLa = 2. * np.pi # ok CLda = np.pi * (1. - 2. * nu_ea) CMda = -0.25 * np.pi # flap related quantities CLb = 2. * (np.pi - th + np.sin(th)) CLdb = (0.5 - nu_fh) * 2. * (np.pi - th) + (2. - nu_fh) * np.sin(th) CMb = -0.5 * (1 + nu_fh) * np.sin(th) CMdb = -.25 * (np.pi - th + 2. / 3. * np.sin(th) * (0.5 - nu_fh) * (2. + nu_fh)) return CLa, CLda, CLb, CLdb, CMda, CMb, CMdb def nc_derivs(x_ea_perc, x_fh_perc): """ Provides non-circulatory aerodynamic lift and moment coefficients derivatives Ref. Palacios and Cesnik, Chap 3. Args: x_ea_perc: position of axis of rotation in percentage of chord (measured from LE) x_fc_perc: position of flap axis of rotation in percentage of chord (measured from LE) """ # parameters nu_ea = 2.0 * x_ea_perc - 1.0 nu_fh = 2.0 * x_fh_perc - 1.0 th = np.arccos(-nu_fh) # pitch/pitch rate related quantities CLda = np.pi # ok CLdda = -np.pi * nu_ea CMda = -.25 * np.pi CMdda = -0.25 * np.pi * (0.25 - nu_ea) # flap related quantities CLdb = np.pi - th - nu_fh * np.sin(th) CLddb = -nu_fh * (np.pi - th) + 1. / 3. * (2. + nu_fh ** 2) * np.sin(th) CMdb = -0.25 * (np.pi - th + (2. / 3. - nu_fh - 2. / 3. * nu_fh ** 2) * np.sin(th)) CMddb = -0.25 * ((0.25 - nu_fh) * (np.pi - th) + \ (2. / 3. - 5. / 12. * nu_fh + nu_fh ** 2 / 3. + nu_fh ** 3 / 6.) * np.sin(th)) return CLda, CLdda, CLdb, CLddb, CMda, CMdda, CMdb, CMddb def theo_CL_freq_resp(k, x_ea_perc, x_fh_perc): """ Frequency response of lift coefficient according Theodorsen's theory. The output is a 3 elements array containing the CL frequency response w.r.t. to pitch, plunge and flap motion, respectively. Sign conventions are as follows: * plunge: positive when moving upward * x_ea_perc: position of axis of rotation in percentage of chord (measured from LE) * x_fc_perc: position of flap axis of rotation in percentage of chord (measured from LE) Warning: this function uses different input/output w.r.t. theo_lift """ df, ddf = j * k, -k ** 2 # get quasi-steady derivatives CLa_qs, CLda_qs, CLb_qs, CLdb_qs, void, void, void = qs_derivs(x_ea_perc, x_fh_perc) # quasi-steady lift CLqs = np.array([ CLa_qs + CLda_qs * df, CLa_qs * df, CLb_qs + CLdb_qs * df, ]) # get non-circulatory derivatives CLda_nc, CLdda_nc, CLdb_nc, CLddb_nc, void, void, void, void \ = nc_derivs(x_ea_perc, x_fh_perc) # unsteady lift df, ddf = j * k, -k ** 2 CLun = np.array([ CLda_nc * df + CLdda_nc * ddf, CLda_nc * ddf, CLdb_nc * df + CLddb_nc * ddf ]) ### Total response Y = theo_fun(k) * CLqs + CLun # sign convention update Y[1] = -Y[1] # plunge dof positive upward return Y def theo_CM_freq_resp(k, x_ea_perc, x_fh_perc): """ Frequency response of moment coefficient according Theodorsen's theory. The output is a 3 elements array containing the CL frequency response w.r.t. to pitch, plunge and flap motion, respectively. """ df, ddf = j * k, -k ** 2 # get quasi-steady derivatives void, void, void, void, CMda_qs, CMb_qs, CMdb_qs = qs_derivs(x_ea_perc, x_fh_perc) # quasi-steady lift CMqs = np.array([ CMda_qs * df, 0.0 * k, CMb_qs + CMdb_qs * df, ]) # get non-circulatory coefficients void, void, void, void, CMda_nc, CMdda_nc, CMdb_nc, CMddb_nc = \ nc_derivs(x_ea_perc, x_fh_perc) # unsteady lift CMun = np.array([ CMda_nc * df + CMdda_nc * ddf, CMda_nc * ddf, CMdb_nc * df + CMddb_nc * ddf ]) ### Total response Y = CMqs + CMun # sign convention update Y[1] = -Y[1] return Y def theo_lift(w, A, H, c, rhoinf, uinf, x12): r""" Theodorsen's solution for lift of aerofoil undergoing sinusoidal motion. Time histories are built assuming: * ``a(t)=+/- A cos(w t) ??? not verified`` * :math:`h(t)=-H\cos(w t)` Args: w: frequency (rad/sec) of oscillation A: amplitude of angle of attack change H: amplitude of plunge motion c: aerofoil chord rhoinf: flow density uinf: flow speed x12: distance of elastic axis from mid-point of aerofoil (positive if the elastic axis is ahead) """ # reduced frequency k = 0.5 * w * c / uinf # compute theodorsen's function Ctheo = theo_fun(k) # Lift: circulatory Lcirc = np.pi * rhoinf * uinf * c * Ctheo * ((uinf + w * j * (0.25 * c + x12)) * A + w * H * j) Lmass = 0.25 * np.pi * rhoinf * c ** 2 * ((j * w * uinf - x12 * w ** 2) * A - H * w ** 2) Ltot = Lcirc + Lmass return Ltot, Lcirc, Lmass def garrick_drag_plunge(w, H, c, rhoinf, uinf, time): r""" Returns Garrick solution for drag coefficient at a specific time. Ref.[1], eq.(8) (see also eq.(1) and (2)) or Ref[2], eq.(2) The aerofoil vertical motion is assumed to be: .. math:: h(t)=-H\cos(wt) The :math:`C_d` is such that: * :math:`C_d>0`: drag * :math:`C_d<0`: suction """ b = 0.5 * c k = b * w / uinf Hast = H / b s = uinf * time / b # compute theodorsen's function Ctheo = theo_fun(k) Cd = -2. * np.pi * k ** 2 * Hast ** 2 * ( Ctheo.imag * np.cos(k * s) + Ctheo.real * np.sin(k * s)) ** 2 return Cd def garrick_drag_pitch(w, A, c, rhoinf, uinf, x12, time): r""" Returns Garrick solution for drag coefficient at a specific time. Ref.[1], eq.(9), (10) and (11) The aerofoil pitching motion is assumed to be: .. math:: a(t)=A\sin(\omegat)=A\sin(ks) The :math:`C_d` is such that: * :math:`C_d>0`: drag * :math:`C_d<0`: suction """ x12 = x12 / c b = 0.5 * c k = b * w / uinf s = uinf * time / b # compute theodorsen's function Ctheo = theo_fun(k) F, G = Ctheo.real, Ctheo.imag sks, cks = np.sin(k * s), np.cos(k * s) # angle of attack a = A * sks # lift term Cl = np.pi * A * (k * cks + x12 * k ** 2 * sks + 2. * F * (sks + (0.5 - x12) * k * cks) + 2. * G * (cks - (0.5 - x12) * k * sks)) # suction force Y1 = 2. * (F - k * G * (0.5 - x12)) Y2 = 2. * (G - k * F * (0.5 - x12)) - k Cs = 0.5 * np.pi * A ** 2 * (Y1 * sks + Y2 * cks) ** 2 Cd = a * Cl - Cs return Cd def sears_fun(kg): """ Produces Sears function """ S12 = 2. / np.pi / kg / (scsp.hankel1(0, kg) + 1.j * scsp.hankel1(1, kg)) S = np.exp(-1.j * kg) * S12.conj() return S def sears_lift_sin_gust(w0, L, Uinf, chord, tv): """ Returns the lift coefficient for a sinusoidal gust (see set_gust.sin) as the imaginary part of the CL complex function defined below. The input gust must be the imaginary part of .. math:: wgust = w0*\exp(1.0j*C*(Ux*S.time[tt] - xcoord) ) with: .. math:: C=2\pi/L and ``xcoord=0`` at the aerofoil half-chord. """ # reduced frequency kg = np.pi * chord / L # Theo's funciton Ctheo = theo_fun(kg) # Sear's function J0, J1 = scsp.j0(kg), scsp.j1(kg) S = (J0 - 1.0j * J1) * Ctheo + 1.0j * J1 phase = np.angle(S) CL = 2. * np.pi * w0 / Uinf * np.abs(S) * np.sin(2. * np.pi * Uinf / L * tv + phase) return CL def sears_CL_freq_resp(k): """ Frequency response of lift coefficient according Sear's solution. Ref. Palacios and Cesnik, Chap.3 """ # hanckel functions H1 = scsp.hankel1(1, k) H0 = scsp.hankel1(0, k) # Sear's function S12star = 2. / (np.pi * k * (H0 + 1.j * H1)) S0 = np.exp(-1.0j * k) * S12star.conj(S12star) # CL frequency response CL = 2. * np.pi * S0 return CL def wagner_imp_start(aeff, Uinf, chord, tv): """ Lift coefficient resulting from impulsive start solution. """ sv = 2.0 * Uinf / chord * tv fiv = 1.0 - 0.165 * np.exp(-0.0455 * sv) - 0.335 * np.exp(-0.3 * sv) CLv = 2. * np.pi * aeff * fiv return CLv def flat_plate_analytical(kv, x_ea_perc, x_fh_perc, input_seq, output_seq, output_scal=None, plunge_deriv=True): r""" Computes the analytical frequency response of a plat plate for the input output sequences in ``input_seq`` and ``output_seq`` over the frequency points ``kv``, if available. The output complex values array ``Yan`` has shape ``(Nout, Nin, Nk)``; if an analytical solution is not available, the response is assumed to be zero. If ``plunge_deriv`` is ``True``, the plunge response is expressed in terms of first derivative dh. Args: kv (np.array): Frequency range of length ``Nk``. x_ea_perc (float): Elastic axis location along the chord as chord length percentage. x_fh_perc (float): Flap hinge location along the chord as chord length percentage. input_seq (list(str)): List of ``Nin`` number of inputs. Supported inputs include: * ``gust_sears``: Response to a continuous sinusoidal gust. * ``pitch``: Response to an oscillatory pitching motion. * ``plunge``: Response to an oscillatory plunging motion. output_seq (list(str)): List of ``Nout`` number of outputs. Supported outputs include: * ``Fy``: Vertical force. * ``Mz``: Pitching moment. output_scal (np.array): Array of factors by which to divide the desired outputs. Dimensions of ``Nout``. plunge_deriv (bool): If ``True`` expresses the plunge response in terms of the first derivative, i.e. the rate of change of plunge :math:`d\dot{h}`. Returns: np.array: A ``(Nout, Nin, Nk)`` array containing the scaled frequency response for the inputs and outputs specified. See Also: The lift coefficient due to pitch and plunging motions is calculated using :func:`sharpy.utils.analytical.theo_CL_freq_resp`. In turn, the pitching moment is found using :func:`sharpy.utils.analytical.theo_CM_freq_resp`. The response to the continuous sinusoidal gust is calculated using :func:`sharpy.utils.analytical.sears_CL_freq_resp`. """ Nout = len(output_seq) Nin = len(input_seq) Nk = len(kv) Yfreq_an = np.zeros((Nout, Nin, Nk), dtype=np.complex) # Get Theodorsen solutions CLtheo = theo_CL_freq_resp(kv, x_ea_perc, x_fh_perc) CMtheo = theo_CM_freq_resp(kv, x_ea_perc, x_fh_perc) # scaling if output_scal is None: output_scal = np.ones((Nout,)) for oo in range(Nout): for ii in range(Nin): ### Sears if input_seq[ii] == 'gust_sears': # Fx,Mz null if output_seq[oo] == 'Fy': Yfreq_an[oo, ii, :] = sears_CL_freq_resp(kv) ### Theodorsen if input_seq[ii] == 'pitch': if output_seq[oo] == 'Fy': Yfreq_an[oo, ii, :] = CLtheo[0] if output_seq[oo] == 'Mz': Yfreq_an[oo, ii, :] = CMtheo[0] if input_seq[ii] == 'plunge': Fact = 1.0 if plunge_deriv: Fact = -1.j / kv if output_seq[oo] == 'Fy': Yfreq_an[oo, ii, :] = Fact * CLtheo[1] if output_seq[oo] == 'Mz': Yfreq_an[oo, ii, :] = Fact * CMtheo[1] # scale output Yfreq_an[oo, :, :] = Yfreq_an[oo, :, :] / output_scal[oo] return Yfreq_an # if __name__ == '__main__': # import matplotlib.pyplot as plt # # kv = np.linspace(0.001, 50, 1001) # # ### test Sear's function # sv = sears_fun(kv) # plt.plot(kv, sv.real, '-') # plt.plot(kv, sv.imag, '--') # plt.show() # # CL = theo_CL_freq_resp(kv, 0.25, 0.8) # # kv = np.linspace(0.001, 2.0, 101) # # data for dimensional analysis # b = 1.3 # U = 15.0 # H = 0.3 # A = 5.0 * np.pi / 180. # rho = 1.225 # tref = b / U # # ### plunge frequency response # Ltheo = -theo_lift(kv / tref, 0, H, 2. * b, rho, U, 0.0)[0] # Fref = b * rho * U ** 2 # CLfreq = Ltheo / Fref / (H / b) # # Yfreq = theo_CL_freq_resp(kv, x_ea_perc=1.0, x_fh_perc=0.9) # CLfreq02 = Yfreq[1] # Er = np.max(np.abs(CLfreq - CLfreq02)) # print('Max error for CL plunge freq response: %.2e' % Er) # # # moments # Yfreq = theo_CM_freq_resp(kv, x_ea_perc=1.0, x_fh_perc=0.9) # CMfreq = Yfreq[1] # plunge motion # CMfreq_vel = 1.j / kv * CMfreq # CMmag_vel, CMph_vel = np.abs(CMfreq_vel), np.angle(CMfreq_vel, deg=True) # # fig = plt.figure('Momentum coefficient frequency response') # ax = fig.add_subplot(121) # ax.plot(kv, CMmag_vel, 'k', label='magnitude') # ax.legend() # ax = fig.add_subplot(122) # ax.plot(kv, CMph_vel, 'r', label='phase') # ax.legend() # plt.show() # # ### pitching frequency response # Yfreq = theo_CL_freq_resp(kv, x_ea_perc=.5, x_fh_perc=0.9) # CLfreq02 = Yfreq[0] # # ### geometry # c = 3. # m # b = 0.5 * c # # ### motion # ktarget = 1. # H = 0.02 * b # m Ref.[1] # A = 1. * np.pi / 180. # rad - Ref.[1] # x12 = -0.5 * c # f0 = 5. # Hz # w0 = 2. * np.pi * f0 # rad/s # # uinf = b * w0 / ktarget # rhoinf = 1.225 # kg/m3 # qinf = 0.5 * c * rhoinf * uinf ** 2 # # C=theo_fun(k=ktarget) # # L=theo_lift(w0,A,H,c,rhoinf,uinf,x12) # # ##### Plunge Induced drag # Ncicles = 5 # tv = np.linspace(0., 2. * np.pi * Ncicles / w0, 200 * Ncicles + 1) # Cdv = garrick_drag_plunge(w0, H, c, rhoinf, uinf, tv) # hv = -H * np.cos(w0 * tv) # dhv = w0 * H * np.sin(w0 * tv) # aeffv = np.arctan(-dhv / uinf) # # fig = plt.figure('Induced drag - plunge motion',(10,6)) # # ax=fig.add_subplot(111) # # ax.plot(tv,hv/c,'r',label=r'h/c') # # ax.plot(tv,Cdv,'k',label=r'Induced Drag') # # ax.legend() # # plt.show() # fig = plt.figure('Plunge motion - Phase vs kinematics', (10, 6)) # ax = fig.add_subplot(111) # # ax.plot(aeffv,hv/c,'r',label=r'h/c') # ax.plot(180. / np.pi * aeffv, Cdv, 'k', label=r'Induced Drag') # ax.set_xlabel('deg') # ax.legend() # plt.close() # # ##### Pitching Induced drag # Ncicles = 5 # tv = np.linspace(0., 2. * np.pi * Ncicles / w0, 200 * Ncicles + 1) # Cdv = garrick_drag_pitch(w0, A, c, rhoinf, uinf, x12, tv) # aeffv = A * np.sin(w0 * tv) # fig = plt.figure('Pitch motion - Phase vs kinematics', (10, 6)) # ax = fig.add_subplot(111) # # ax.plot(aeffv,hv/c,'r',label=r'h/c') # ax.plot(180. / np.pi * aeffv, Cdv, 'k', label=r'Induced Drag') # ax.set_xlabel('deg') # ax.legend() # # ##### Sear's solution test # L = .5 * c # w0 = 0.3 # uinf = 6.0 # # # gust profile at LE # tv = np.linspace(0., 2., 300) # C = 2. * np.pi / L # wgustLE = w0 * np.sin(C * uinf * tv) # CLv = sears_lift_sin_gust(w0, L, uinf, c, tv) # # fig = plt.figure('Gust response', (10, 6)) # ax = fig.add_subplot(111) # ax.plot(tv, wgustLE, 'k', label=r'vertical gust velocity at LE [m/s]') # ax.plot(tv, CLv, 'r', label=r'CL') # ax.set_xlabel('time') # ax.legend() # # plt.show() # plt.close('all') # # ##### Wagner impulsive start # uinf = 20.0 # chord = 3.0 # aeff = 2.0 * np.pi / 180. # tv = np.linspace(0., 10., 300) # # CLv = wagner_imp_start(aeff, uinf, chord, tv) # CLv_inf = wagner_imp_start(aeff, uinf, chord, 1e3 * tv[-1]) # # fig = plt.figure('Impulsive start', (10, 6)) # ax = fig.add_subplot(111) # ax.plot(tv, CLv / CLv_inf, 'r', label=r'CL') # ax.set_xlabel('time') # ax.legend() # plt.show() ================================================ FILE: sharpy/utils/constants.py ================================================ import ctypes as ct import numpy as np NDIM = int(3) ct_NDIM = ct.c_uint(NDIM) deg2rad = np.pi/180. vortex_radius_def = 1e-6 cfact_biot = 0.25 / np.pi ================================================ FILE: sharpy/utils/control_utils.py ================================================ """Controller Utilities """ import numpy as np def second_order_fd(history, n_calls, dt): # history is ordered such that the last element is the most recent one (t), and thus # it goes [(t - 2), (t - 1), (t)] coefficients = np.zeros((3,)) if n_calls <= 1: # no derivative, return 0 pass elif n_calls == 2: # else: # first order derivative coefficients[1:3] = [-1.0, 1.0] else: # # second order derivative coefficients[:] = [1.0, -4.0, 3.0] coefficients *= 0.5 derivative = np.dot(coefficients, history)/dt return derivative class PID(object): """ Class implementing a classic PID controller Instance attributes: :param `gain_p`: Proportional gain. :param `gain_i`: Integral gain. :param `gain_d`: Derivative gain. :param `dt`: Simulation time step. The class should be used as: pid = PID(100, 10, 0.1, 0.1) pid.set_point(target_point) control = pid(current_point) """ # for i in range(n_steps): # state[i] = np.sum(feedback[:i]) # controller.set_point(set_point[i]) # feedback[i] = controller(state[i]) def __init__(self, gain_p, gain_i, gain_d, dt): self._kp = gain_p self._ki = gain_i self._kd = gain_d self._point = 0.0 self._dt = dt self._accumulated_integral = 0.0 self._integral_limits = np.array([-1., 1.])*10000 self._error_history = np.zeros((3,)) self._derivator = second_order_fd self._derivative_limits = np.array([-1, 1])*10000 self._n_calls = 0 self._anti_windup_lim = None def set_point(self, point): self._point = point def set_anti_windup_lim(self, lim): self._anti_windup_lim = lim def reset_integrator(self): self._accumulated_integral = 0.0 def __call__(self, state): self._n_calls += 1 actuation = 0.0 error = self._point - state # displace previous errors one position to the left self._error_history = np.roll(self._error_history, -1) self._error_history[-1] = error detailed = np.zeros((3,)) # Proportional gain actuation += error*self._kp detailed[0] = error*self._kp # Derivative gain derivative = self._derivator(self._error_history, self._n_calls, self._dt) derivative = max(derivative, self._derivative_limits[0]) derivative = min(derivative, self._derivative_limits[1]) actuation += derivative*self._kd detailed[2] = derivative*self._kd # Integral gain aux_acc_int = self._accumulated_integral + error*self._dt if aux_acc_int < self._integral_limits[0]: aux_acc_int = self._integral_limits[0] elif aux_acc_int > self._integral_limits[1]: aux_acc_int = self._integral_limits[1] if self._anti_windup_lim is not None: # Apply anti wind up aux_actuation = actuation + aux_acc_int*self._ki if ((aux_actuation > self._anti_windup_lim[0]) and (aux_actuation < self._anti_windup_lim[1])): # Within limits self._accumulated_integral = aux_acc_int # If the system exceeds the limits, this # will not be added to the self._accumulated_integral else: self._accumulated_integral = aux_acc_int actuation += self._accumulated_integral*self._ki detailed[1] = self._accumulated_integral*self._ki return actuation, detailed ================================================ FILE: sharpy/utils/controller_interface.py ================================================ from abc import ABCMeta, abstractmethod import sharpy.utils.cout_utils as cout import os dict_of_controllers = {} controllers = {} # for internal working # decorator def controller(arg): # global available_solvers global dict_of_controllers try: arg.controller_id except AttributeError: raise AttributeError('Class defined as controller has no controller_id attribute') dict_of_controllers[arg.controller_id] = arg return arg def print_available_controllers(): cout.cout_wrap('The available controllers in this session are:', 2) for name, i_controller in dict_of_controllers.items(): cout.cout_wrap('%s ' % i_controller.controller_id, 2) class BaseController(metaclass=ABCMeta): def teardown(self): pass def controller_from_string(string): return dict_of_controllers[string] def controller_list_from_path(cwd): onlyfiles = [f for f in os.listdir(cwd) if os.path.isfile(os.path.join(cwd, f))] for i_file in range(len(onlyfiles)): if onlyfiles[i_file].split('.')[-1] == 'py': # support autosaved files in the folder if onlyfiles[i_file] == "__init__.py": onlyfiles[i_file] = "" continue onlyfiles[i_file] = onlyfiles[i_file].replace('.py', '') else: onlyfiles[i_file] = "" files = [file for file in onlyfiles if not file == ""] return files def initialise_controller(controller_name, print_info=True): if print_info: cout.cout_wrap('Generating an instance of %s' % controller_name, 2) cls_type = controller_from_string(controller_name) controller = cls_type() return controller def dictionary_of_controllers(print_info=True): import sharpy.controllers dictionary = dict() for controller in dict_of_controllers: init_controller = initialise_controller(controller, print_info=print_info) dictionary[controller] = init_controller.settings_default return dictionary ================================================ FILE: sharpy/utils/cout_utils.py ================================================ import textwrap import colorama import os import numpy as np import subprocess import sharpy.utils.sharpydir as sharpydir cwd = os.getcwd() class Writer(object): fore_colours = ['', colorama.Fore.BLUE, colorama.Fore.CYAN, colorama.Fore.YELLOW, colorama.Fore.RED] reset = colorama.Style.RESET_ALL output_columns = 80 separator = '-'*output_columns sharpy_ascii = \ """-------------------------------------------------------------------------------- ###### ## ## ### ######## ######## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## #### ###### ######### ## ## ######## ######## ## ## ## ## ######### ## ## ## ## ## ## ## ## ## ## ## ## ## ## ###### ## ## ## ## ## ## ## ## --------------------------------------------------------------------------------""" sharpy_license = \ '''Aeroelastics Lab, Aeronautics Department. Copyright (c), Imperial College London. All rights reserved. License available at https://github.com/imperialcollegelondon/sharpy''' wrapper = textwrap.TextWrapper(width=output_columns, break_long_words=False) def __init__(self): self.print_screen = False self.print_file = False self.file = None self.file_route = '' self.file_name = '' def initialise(self, print_screen, print_file, file_route=None, file_name=None): # copy settings self.print_screen = print_screen self.print_file = print_file if self.print_file: self.file_route = file_route self.file_name = file_name # create folder if necessary if not os.path.exists(self.file_route): try: os.makedirs(self.file_route) except FileExistsError: pass self.file = open(self.file_route + '/' + self.file_name, 'w') self.print_welcome_message() def print_welcome_message(self): self.__call__(self.sharpy_ascii) self.__call__(self.sharpy_license) self.__call__('Running SHARPy from ' + cwd, 2) self.__call__('SHARPy being run is in ' + sharpydir.SharpyDir, 2) try: self.__call__(print_git_status(), 2) except subprocess.CalledProcessError: pass import sharpy.utils.solver_interface as solver_interface # solver_interface.print_available_solvers() def cout_quiet(self): self.print_screen = False def cout_talk(self): self.print_screen = True def print_separator(self, level=0): self.__call__(self.separator, level) def __call__(self, in_line, level=0): if self.print_screen: line = in_line lines = line.split("\n") if level > 4: raise AttributeError('Output level cannot be > 4') if len(lines) == 1: print(self.fore_colours[level] + line + self.reset) else: newline = '' for line in lines: if len(line) > self.output_columns: line = '\n'.join(self.wrapper.wrap(line)) print(self.fore_colours[level] + line + self.reset) # newline += line + "\n" # print(self.fore_colours[level] + newline + self.reset) if self.print_file: line = in_line lines = line.split("\n") if len(lines) == 1: self.file.write(line + '\n') else: newline = '' for line in lines: if len(line) > self.output_columns: line = '\n'.join(self.wrapper.wrap(line)) newline += line + "\n" self.file.write(newline) def close(self): if self.file is not None: if not self.file.closed: self.file.close() def __del__(self): self.close() cout_wrap = Writer() def start_writer(): global cout_wrap cout_wrap = Writer() # pass def finish_writer(): global cout_wrap if cout_wrap is not None: cout_wrap.close() # table output for residuals class TablePrinter(object): global cout_wrap divider_char = '|' line_char = '=' def __init__(self, n_fields=3, field_length=12, field_types=[['g']]*100, filename=None): self.n_fields = n_fields self.file = None self.divider_line = None try: field_length[0] except TypeError: self.field_length = np.full((self.n_fields, ), field_length, dtype=int) else: if len(field_length) == n_fields: self.field_length = field_length else: raise Exception('len(field_length /= n_fields') self.field_names = None self.field_types = field_types if cout_wrap is None: start_writer() # if filename is not None: # self.file = open(filename, 'w') self.file = filename def print_header(self, field_names): self.field_names = field_names if not len(self.field_names) == self.n_fields: raise Exception('len(field_names) /= n_fields') for i_name in range(self.n_fields): name = self.field_names[i_name] if len(name) >= self.field_length[i_name]: name = name[0:self.field_length[i_name]] string = '' divider_line = '' # for i_field in range(self.n_fields): # string += '|{0[' + str(i_field) + ']:^' + str(self.field_length[i_field]) + '}' # divider_line += '-'*(self.field_length[i_field]) + '|' for i_field in range(self.n_fields): field_length = self.field_length[i_field] string += self.divider_char + '{' + str(i_field) + ':^' + str(field_length + 2) + '}' # string += '|{0[' + str(i_field) + ']:^' + str(self.field_length[i_field]) + '}' divider_line += self.divider_char + (field_length + 2)*self.line_char string += self.divider_char divider_line += self.divider_char self.divider_line = divider_line cout_wrap('\n\n') cout_wrap(divider_line) cout_wrap(string.format(*(self.field_names))) cout_wrap(divider_line) if self.file is not None: with open(self.file, 'a+') as f: f.write(divider_line) f.write('\n' + string.format(*(self.field_names))) f.write('\n' + divider_line) def print_line(self, line_data): string = '' for i_field in range(self.n_fields): string += (self.divider_char + '{0[' + str(i_field) + ']:^' + str(self.field_length[i_field] + 2) + '.' + str(max(int(self.field_length[i_field]/2), 4)) + self.field_types[i_field] + '}') string += self.divider_char cout_wrap(string.format(line_data)) if self.file is not None: with open(self.file, 'a') as f: f.write('\n'+string.format(line_data)) def close_file(self): if self.file is not None: with open(self.file, 'a+') as f: f.write('\n' + self.divider_line) def print_divider_line(self): cout_wrap(self.divider_line) if self.file is not None: with open(self.file, 'a+') as f: f.write('\n' + self.divider_line) def character_return(self, n_lines=1): cout_wrap(n_lines * '\n') if self.file is not None: with open(self.file, 'a+') as f: f.write(n_lines * '\n') # version tracker and output def get_git_revision_hash(di=sharpydir.SharpyDir): return subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=di).strip().decode('utf-8') def get_git_revision_short_hash(di=sharpydir.SharpyDir): return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'], cwd=di).strip().decode('utf-8') def get_git_revision_branch(di=sharpydir.SharpyDir): return subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], cwd=di).strip().decode('utf-8') def get_git_tag(di=sharpydir.SharpyDir): return subprocess.check_output(['git', 'describe'], cwd=di).strip().decode('utf-8') def print_git_status(): return ('The branch being run is ' + get_git_revision_branch() + '\n'\ 'The version and commit hash are: ' + get_git_tag() + '-' + get_git_revision_short_hash()) def check_running_unittest(): import sys # Define if the script is being run in unittest running_unittest = False for arg in sys.argv: if "unittest" in arg: print("running unittest") running_unittest = True return running_unittest ================================================ FILE: sharpy/utils/ctypes_utils.py ================================================ import ctypes as ct import platform import os def import_ctypes_lib(route, libname): lib_path = os.path.join(route, libname) if platform.system() == 'Darwin': ext = '.dylib' elif platform.system() == 'Linux': ext = '.so' else: raise NotImplementedError('The platform ' + platform.system() + 'is not supported') lib_path += ext lib_path = os.path.abspath(lib_path) library = ct.CDLL(lib_path, mode=ct.RTLD_GLOBAL) return library ================================================ FILE: sharpy/utils/datastructures.py ================================================ """Data Management Structures These classes are responsible for storing the aerodynamic and structural time step information and relevant variables. """ import copy import ctypes as ct import numpy as np import sharpy.utils.algebra as algebra import sharpy.utils.multibody as mb class TimeStepInfo(object): """ Time step class. It is the parent class of the AeroTimeStepInfo and NonliftingBodyTimeStepInfo, which contain the relevant aerodynamic attributes for a single time step. All variables should be expressed in ``G`` FoR unless otherwise stated. Attributes: ct_dimensions: Pointer to ``dimensions`` to interface the C++ library `uvlmlib`` dimensions (np.ndarray): Matrix defining the dimensions of the vortex grid on solid surfaces ``[num_surf x chordwise panels x spanwise panels]`` n_surf (int): Number of aerodynamic surfaces on solid bodies. Each aerodynamic surface on solid bodies will have an associted wake. zeta (list(np.ndarray): Location of solid grid vertices ``[n_surf][3 x (chordwise nodes + 1) x (spanwise nodes + 1)]`` zeta_dot (list(np.ndarray)): Time derivative of ``zeta`` normals (list(np.ndarray)): Normal direction to panels at the panel center ``[n_surf][3 x chordwise nodes x spanwise nodes]`` forces (list(np.ndarray)): Forces not associated to time derivatives on grid vertices ``[n_surf][3 x (chordwise nodes + 1) x (spanwise nodes + 1)]`` dynamic_forces (list(np.ndarray)): Forces associated to time derivatives on grid vertices ``[n_surf][3 x (chordwise nodes + 1) x (spanwise nodes + 1)]`` u_ext (list(np.ndarray)): Background flow velocity on solid grid nodes ``[n_surf][3 x (chordwise nodes + 1) x (spanwise nodes + 1)]`` inertial_steady_forces (list(np.ndarray)): Total aerodynamic steady forces in ``G`` FoR ``[n_surf x 6]`` body_steady_forces (list(np.ndarray)): Total aerodynamic steady forces in ``A`` FoR ``[n_surf x 6]`` inertial_unsteady_forces (list(np.ndarray)): Total aerodynamic unsteady forces in ``G`` FoR ``[n_surf x 6]`` body_unsteady_forces (list(np.ndarray)): Total aerodynamic unsteady forces in ``A`` FoR ``[n_surf x 6]`` postproc_cell (dict): Variables associated to cells to be postprocessed postproc_node (dict): Variables associated to nodes to be postprocessed in_global_AFoR (bool): ``True`` if the variables are stored in the global A FoR. ``False`` if they are stored in the local A FoR of each body. Always ``True`` for single-body simulations. Currently not used. Args: dimensions (np.ndarray): Matrix defining the dimensions of the vortex grid on solid surfaces ``[num_surf x chordwise panels x spanwise panels]`` """ def __init__(self, dimensions): self.ct_dimensions = None self.dimensions = dimensions.copy() self.n_surf = self.dimensions.shape[0] # generate placeholder for aero grid zeta coordinates self.zeta = [] for i_surf in range(self.n_surf): self.zeta.append(np.zeros((3, dimensions[i_surf, 0] + 1, dimensions[i_surf, 1] + 1), dtype=ct.c_double)) self.zeta_dot = [] for i_surf in range(self.n_surf): self.zeta_dot.append(np.zeros((3, dimensions[i_surf, 0] + 1, dimensions[i_surf, 1] + 1), dtype=ct.c_double)) # panel normals self.normals = [] for i_surf in range(self.n_surf): self.normals.append(np.zeros((3, dimensions[i_surf, 0], dimensions[i_surf, 1]), dtype=ct.c_double)) # panel forces self.forces = [] for i_surf in range(self.n_surf): self.forces.append(np.zeros((6, dimensions[i_surf, 0] + 1, dimensions[i_surf, 1] + 1), dtype=ct.c_double)) # panel forces self.dynamic_forces = [] for i_surf in range(self.n_surf): self.dynamic_forces.append(np.zeros((6, dimensions[i_surf, 0] + 1, dimensions[i_surf, 1] + 1), dtype=ct.c_double)) # placeholder for external velocity self.u_ext = [] for i_surf in range(self.n_surf): self.u_ext.append(np.zeros((3, dimensions[i_surf, 0] + 1, dimensions[i_surf, 1] + 1), dtype=ct.c_double)) # total forces - written by AeroForcesCalculator self.inertial_steady_forces = np.zeros((self.n_surf, 6)) self.body_steady_forces = np.zeros((self.n_surf, 6)) self.inertial_unsteady_forces = np.zeros((self.n_surf, 6)) self.body_unsteady_forces = np.zeros((self.n_surf, 6)) self.postproc_cell = dict() self.postproc_node = dict() # Multibody variables self.in_global_AFoR = True def copy(self): """ Returns a copy of a deepcopy of a :class:`~sharpy.utils.datastructures.TimeStepInfo` """ return self.create_placeholder(TimeStepInfo(self.dimensions)) def create_placeholder(self, copied): # generate placeholder for aero grid zeta coordinates for i_surf in range(copied.n_surf): copied.zeta[i_surf] = self.zeta[i_surf].astype(dtype=ct.c_double, copy=True, order='C') for i_surf in range(copied.n_surf): copied.zeta_dot[i_surf] = self.zeta_dot[i_surf].astype(dtype=ct.c_double, copy=True, order='C') # panel normals for i_surf in range(copied.n_surf): copied.normals[i_surf] = self.normals[i_surf].astype(dtype=ct.c_double, copy=True, order='C') # panel forces for i_surf in range(copied.n_surf): copied.forces[i_surf] = self.forces[i_surf].astype(dtype=ct.c_double, copy=True, order='C') # panel forces for i_surf in range(copied.n_surf): copied.dynamic_forces[i_surf] = self.dynamic_forces[i_surf].astype(dtype=ct.c_double, copy=True, order='C') # placeholder for external velocity for i_surf in range(copied.n_surf): copied.u_ext[i_surf] = self.u_ext[i_surf].astype(dtype=ct.c_double, copy=True, order='C') # total forces copied.inertial_steady_forces = self.inertial_steady_forces.astype(dtype=ct.c_double, copy=True, order='C') copied.body_steady_forces = self.body_steady_forces.astype(dtype=ct.c_double, copy=True, order='C') copied.inertial_unsteady_forces = self.inertial_unsteady_forces.astype(dtype=ct.c_double, copy=True, order='C') copied.body_unsteady_forces = self.body_unsteady_forces.astype(dtype=ct.c_double, copy=True, order='C') copied.postproc_cell = copy.deepcopy(self.postproc_cell) copied.postproc_node = copy.deepcopy(self.postproc_node) return copied def generate_ctypes_pointers(self): """ Generates the pointers to aerodynamic variables used to interface the C++ library ``uvlmlib`` """ self.ct_dimensions = self.dimensions.astype(dtype=ct.c_uint, copy=True) n_surf = len(self.dimensions) from sharpy.utils.constants import NDIM self.ct_zeta_list = [] for i_surf in range(self.n_surf): for i_dim in range(NDIM): self.ct_zeta_list.append(self.zeta[i_surf][i_dim, :, :].reshape(-1)) self.ct_zeta_dot_list = [] for i_surf in range(self.n_surf): for i_dim in range(NDIM): self.ct_zeta_dot_list.append(self.zeta_dot[i_surf][i_dim, :, :].reshape(-1)) self.ct_u_ext_list = [] for i_surf in range(self.n_surf): for i_dim in range(NDIM): self.ct_u_ext_list.append(self.u_ext[i_surf][i_dim, :, :].reshape(-1)) self.ct_normals_list = [] for i_surf in range(self.n_surf): for i_dim in range(NDIM): self.ct_normals_list.append(self.normals[i_surf][i_dim, :, :].reshape(-1)) self.ct_forces_list = [] for i_surf in range(self.n_surf): for i_dim in range(NDIM*2): self.ct_forces_list.append(self.forces[i_surf][i_dim, :, :].reshape(-1)) self.ct_dynamic_forces_list = [] for i_surf in range(self.n_surf): for i_dim in range(NDIM*2): self.ct_dynamic_forces_list.append(self.dynamic_forces[i_surf][i_dim, :, :].reshape(-1)) try: self.postproc_cell['incidence_angle'] except KeyError: with_incidence_angle = False else: with_incidence_angle = True if with_incidence_angle: self.ct_incidence_list = [] for i_surf in range(self.n_surf): self.ct_incidence_list.append(self.postproc_cell['incidence_angle'][i_surf][:, :].reshape(-1)) self.ct_p_dimensions = ((ct.POINTER(ct.c_uint)*n_surf) (* np.ctypeslib.as_ctypes(self.ct_dimensions))) self.ct_p_zeta = ((ct.POINTER(ct.c_double)*len(self.ct_zeta_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_zeta_list])) self.ct_p_zeta_dot = ((ct.POINTER(ct.c_double)*len(self.ct_zeta_dot_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_zeta_dot_list])) self.ct_p_u_ext = ((ct.POINTER(ct.c_double)*len(self.ct_u_ext_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_u_ext_list])) self.ct_p_normals = ((ct.POINTER(ct.c_double)*len(self.ct_normals_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_normals_list])) self.ct_p_forces = ((ct.POINTER(ct.c_double)*len(self.ct_forces_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_forces_list])) self.ct_p_dynamic_forces = ((ct.POINTER(ct.c_double)*len(self.ct_dynamic_forces_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_dynamic_forces_list])) if with_incidence_angle: self.postproc_cell['incidence_angle_ct_pointer'] = ((ct.POINTER(ct.c_double)*len(self.ct_incidence_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_incidence_list])) def remove_ctypes_pointers(self): """ Removes the pointers to aerodynamic variables used to interface the C++ library ``uvlmlib`` """ list_class_attributes = list(self.__dict__.keys()).copy() for name_attribute in list_class_attributes: if "ct_p_" in name_attribute: self.__delattr__(name_attribute) for k in list(self.postproc_cell.keys()): if 'ct_list' in k: del self.postproc_cell[k] elif 'ct_pointer' in k: del self.postproc_cell[k] class NonliftingBodyTimeStepInfo(TimeStepInfo): """ Nonlifting Body Time step class. It is the inheritance from ``TimeStepInfo`` and contains the relevant aerodynamic attributes for a single time step of a nonlifting body. All variables should be expressed in ``G`` FoR unless otherwise stated. Attributes: ct_dimensions: Pointer to ``dimensions`` to interface the C++ library `uvlmlib`` dimensions (np.ndarray): Matrix defining the dimensions of the vortex grid on solid surfaces ``[num_surf x radial panels x spanwise panels]`` n_surf (int): Number of aerodynamic surfaces on nonlifting bodies. zeta (list(np.ndarray): Location of solid grid vertices ``[n_surf][3 x (radial panel) x (spanwise panel)]`` zeta_dot (list(np.ndarray)): Time derivative of ``zeta`` normals (list(np.ndarray)): Normal direction to panels at the panel center ``[n_surf][3 x radial panels x spanwise panels]`` forces (list(np.ndarray)): Forces not associated to time derivatives on grid vertices ``[n_surf][3 x (radial panels) x (spanwise panels)]`` dynamic_forces (list(np.ndarray)): Forces associated to time derivatives on grid vertices ``[n_surf][3 x (radial panels) x (spanwise panels)]`` u_ext (list(np.ndarray)): Background flow velocity on solid grid panel ``[n_surf][3 x (radial panels) x (spanwise panel + 1)]`` sigma (list(np.ndarray)): Source strength associated to solid panels ``[n_surf][3 x radial panel x spanwise panel]`` sigma_dot (list(np.ndarray)): Time derivative of ``sigma`` pressure_coefficients (list(np.ndarray)): Pressure coefficient associated to solid panels ``[n_surf][radial panel x spanwise panel]`` inertial_total_forces (list(np.ndarray)): Total aerodynamic forces in ``G`` FoR ``[n_surf x 6]`` body_total_forces (list(np.ndarray)): Total aerodynamic forces in ``A`` FoR ``[n_surf x 6]`` inertial_steady_forces (list(np.ndarray)): Total aerodynamic steady forces in ``G`` FoR ``[n_surf x 6]`` body_steady_forces (list(np.ndarray)): Total aerodynamic steady forces in ``A`` FoR ``[n_surf x 6]`` inertial_unsteady_forces (list(np.ndarray)): Total aerodynamic unsteady forces in ``G`` FoR ``[n_surf x 6]`` body_unsteady_forces (list(np.ndarray)): Total aerodynamic unsteady forces in ``A`` FoR ``[n_surf x 6]`` postproc_cell (dict): Variables associated to cells to be postprocessed postproc_node (dict): Variables associated to panel to be postprocessed in_global_AFoR (bool): ``True`` if the variables are stored in the global A FoR. ``False`` if they are stored in the local A FoR of each body. Always ``True`` for single-body simulations. Currently not used. Args: dimensions (np.ndarray): Matrix defining the dimensions of the vortex grid on solid surfaces ``[num_surf x radial panels x spanwise panels]`` """ def __init__(self, dimensions): #remove dimensions_star as input super().__init__(dimensions) # allocate sigma matrices self.sigma = [] for i_surf in range(self.n_surf): self.sigma.append(np.zeros((dimensions[i_surf, 0], dimensions[i_surf, 1]), dtype=ct.c_double)) self.sigma_dot = [] for i_surf in range(self.n_surf): self.sigma_dot.append(np.zeros((dimensions[i_surf, 0], dimensions[i_surf, 1]), dtype=ct.c_double)) self.pressure_coefficients = [] for i_surf in range(self.n_surf): self.pressure_coefficients.append(np.zeros((dimensions[i_surf, 0], dimensions[i_surf, 1]), dtype=ct.c_double)) def copy(self): """ Returns a copy of a deepcopy of a :class:`~sharpy.utils.datastructures.AeroTimeStepInfo` """ return self.create_placeholder(NonliftingBodyTimeStepInfo(self.dimensions)) def create_placeholder(self, copied): super().create_placeholder(copied) # allocate sigma matrices for i_surf in range(copied.n_surf): copied.sigma[i_surf] = self.sigma[i_surf].astype(dtype=ct.c_double, copy=True, order='C') for i_surf in range(copied.n_surf): copied.sigma_dot[i_surf] = self.sigma_dot[i_surf].astype(dtype=ct.c_double, copy=True, order='C') for i_surf in range(copied.n_surf): copied.pressure_coefficients[i_surf] = self.pressure_coefficients[i_surf].astype(dtype=ct.c_double, copy=True, order='C') return copied def generate_ctypes_pointers(self): """ Generates the pointers to aerodynamic variables used to interface the C++ library ``uvlmlib`` """ super().generate_ctypes_pointers() from sharpy.utils.constants import NDIM self.ct_sigma_list = [] for i_surf in range(self.n_surf): self.ct_sigma_list.append(self.sigma[i_surf][:, :].reshape(-1)) self.ct_sigma_dot_list = [] for i_surf in range(self.n_surf): self.ct_sigma_dot_list.append(self.sigma_dot[i_surf][:, :].reshape(-1)) self.ct_pressure_coefficients_list = [] for i_surf in range(self.n_surf): self.ct_pressure_coefficients_list.append(self.pressure_coefficients[i_surf][:, :].reshape(-1)) self.ct_p_sigma = ((ct.POINTER(ct.c_double)*len(self.ct_sigma_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_sigma_list])) self.ct_p_sigma_dot = ((ct.POINTER(ct.c_double)*len(self.ct_sigma_dot_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_sigma_list])) self.ct_p_pressure_coefficients = ((ct.POINTER(ct.c_double)*len(self.ct_pressure_coefficients_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_pressure_coefficients_list])) class AeroTimeStepInfo(TimeStepInfo): """ Aerodynamic Time step class. Contains the relevant aerodynamic attributes for a single time step. All variables should be expressed in ``G`` FoR unless otherwise stated. Attributes: ct_dimensions: Pointer to ``dimensions`` to interface the C++ library `uvlmlib`` ct_dimensions_star: Pointer to ``dimensions_star`` to interface the C++ library `uvlmlib`` dimensions (np.ndarray): Matrix defining the dimensions of the vortex grid on solid surfaces ``[num_surf x chordwise panels x spanwise panels]`` dimensions_star (np.ndarray): Matrix defining the dimensions of the vortex grid on wakes ``[num_surf x streamwise panels x spanwise panels]`` n_surf (int): Number of aerodynamic surfaces on solid bodies. Each aerodynamic surface on solid bodies will have an associted wake. zeta (list(np.ndarray): Location of solid grid vertices ``[n_surf][3 x (chordwise nodes + 1) x (spanwise nodes + 1)]`` zeta_dot (list(np.ndarray)): Time derivative of ``zeta`` normals (list(np.ndarray)): Normal direction to panels at the panel center ``[n_surf][3 x chordwise nodes x spanwise nodes]`` forces (list(np.ndarray)): Forces not associated to time derivatives on grid vertices ``[n_surf][3 x (chordwise nodes + 1) x (spanwise nodes + 1)]`` dynamic_forces (list(np.ndarray)): Forces associated to time derivatives on grid vertices ``[n_surf][3 x (chordwise nodes + 1) x (spanwise nodes + 1)]`` zeta_star (list(np.ndarray): Location of wake grid vertices ``[n_surf][3 x (streamwise nodes + 1) x (spanwise nodes + 1)]`` u_ext (list(np.ndarray)): Background flow velocity on solid grid nodes ``[n_surf][3 x (chordwise nodes + 1) x (spanwise nodes + 1)]`` u_ext_star (list(np.ndarray)): Background flow velocity on wake grid nodes ``[n_surf][3 x (streamwise nodes + 1) x (spanwise nodes + 1)]`` gamma (list(np.ndarray)): Circulation associated to solid panels ``[n_surf][3 x chordwise nodes x spanwise nodes]`` gamma_star (list(np.ndarray)): Circulation associated to wake panels ``[n_surf][3 x streamwise nodes x spanwise nodes]`` gamma_dot (list(np.ndarray)): Time derivative of ``gamma`` inertial_total_forces (list(np.ndarray)): Total aerodynamic forces in ``G`` FoR ``[n_surf x 6]`` body_total_forces (list(np.ndarray)): Total aerodynamic forces in ``A`` FoR ``[n_surf x 6]`` inertial_steady_forces (list(np.ndarray)): Total aerodynamic steady forces in ``G`` FoR ``[n_surf x 6]`` body_steady_forces (list(np.ndarray)): Total aerodynamic steady forces in ``A`` FoR ``[n_surf x 6]`` inertial_unsteady_forces (list(np.ndarray)): Total aerodynamic unsteady forces in ``G`` FoR ``[n_surf x 6]`` body_unsteady_forces (list(np.ndarray)): Total aerodynamic unsteady forces in ``A`` FoR ``[n_surf x 6]`` postproc_cell (dict): Variables associated to cells to be postprocessed postproc_node (dict): Variables associated to nodes to be postprocessed in_global_AFoR (bool): ``True`` if the variables are stored in the global A FoR. ``False`` if they are stored in the local A FoR of each body. Always ``True`` for single-body simulations. Currently not used. control_surface_deflection (np.ndarray): Deflection of the control surfaces, in `rad` and if fitted. Args: dimensions (np.ndarray): Matrix defining the dimensions of the vortex grid on solid surfaces ``[num_surf x chordwise panels x spanwise panels]`` dimensions_star (np.ndarray): Matrix defining the dimensions of the vortex grid on wakes ``[num_surf x streamwise panels x spanwise panels]`` Note, Ben Preston 27/09/24: forces and dynamic forces are stated to be in ``G`` however were actually determined to be in ``A``. This issue may apply to other parameters, however errors due to this were only apparent in the AeroForcesCalculator postprocessor. """ def __init__(self, dimensions, dimensions_star): super().__init__(dimensions) self.ct_dimensions_star = None self.dimensions_star = dimensions_star.copy() # generate placeholder for aero grid zeta_star coordinates self.zeta_star = [] for i_surf in range(self.n_surf): self.zeta_star.append(np.zeros((3, dimensions_star[i_surf, 0] + 1, dimensions_star[i_surf, 1] + 1), dtype=ct.c_double)) self.u_ext_star = [] for i_surf in range(self.n_surf): self.u_ext_star.append(np.zeros((3, dimensions_star[i_surf, 0] + 1, dimensions_star[i_surf, 1] + 1), dtype=ct.c_double)) # allocate gamma and gamma star matrices self.gamma = [] for i_surf in range(self.n_surf): self.gamma.append(np.zeros((dimensions[i_surf, 0], dimensions[i_surf, 1]), dtype=ct.c_double)) self.gamma_star = [] for i_surf in range(self.n_surf): self.gamma_star.append(np.zeros((dimensions_star[i_surf, 0], dimensions_star[i_surf, 1]), dtype=ct.c_double)) self.gamma_dot = [] for i_surf in range(self.n_surf): self.gamma_dot.append(np.zeros((dimensions[i_surf, 0], dimensions[i_surf, 1]), dtype=ct.c_double)) # Distance from the trailing edge of the wake vertices self.dist_to_orig = [] for i_surf in range(self.n_surf): self.dist_to_orig.append(np.zeros((dimensions_star[i_surf, 0] + 1, dimensions_star[i_surf, 1] + 1), dtype=ct.c_double)) self.wake_conv_vel = [] for i_surf in range(self.n_surf): self.wake_conv_vel.append(np.zeros((dimensions_star[i_surf, 0], dimensions_star[i_surf, 1]), dtype=ct.c_double)) # Junction handling self.flag_zeta_phantom = np.zeros((1, self.n_surf), dtype=ct.c_int) self.control_surface_deflection = np.array([]) def copy(self): return self.create_placeholder(AeroTimeStepInfo(self.dimensions, self.dimensions_star)) def create_placeholder(self, copied): super().create_placeholder(copied) # generate placeholder for aero grid zeta_star coordinates for i_surf in range(copied.n_surf): copied.zeta_star[i_surf] = self.zeta_star[i_surf].astype(dtype=ct.c_double, copy=True, order='C') # placeholder for external velocity for i_surf in range(copied.n_surf): copied.u_ext_star[i_surf] = self.u_ext_star[i_surf].astype(dtype=ct.c_double, copy=True, order='C') # allocate gamma and gamma star matrices for i_surf in range(copied.n_surf): copied.gamma[i_surf] = self.gamma[i_surf].astype(dtype=ct.c_double, copy=True, order='C') for i_surf in range(copied.n_surf): copied.gamma_dot[i_surf] = self.gamma_dot[i_surf].astype(dtype=ct.c_double, copy=True, order='C') for i_surf in range(copied.n_surf): copied.gamma_star[i_surf] = self.gamma_star[i_surf].astype(dtype=ct.c_double, copy=True, order='C') for i_surf in range(copied.n_surf): copied.dist_to_orig[i_surf] = self.dist_to_orig[i_surf].astype(dtype=ct.c_double, copy=True, order='C') for i_surf in range(copied.n_surf): copied.wake_conv_vel[i_surf] = self.wake_conv_vel[i_surf].astype(dtype=ct.c_double, copy=True, order='C') copied.control_surface_deflection = self.control_surface_deflection.astype(dtype=ct.c_double, copy=True) # phantom panel flags copied.flag_zeta_phantom = self.flag_zeta_phantom.astype(dtype=ct.c_int, copy=True, order='C') return copied def generate_ctypes_pointers(self): from sharpy.utils.constants import NDIM n_surf = len(self.dimensions) super().generate_ctypes_pointers() self.ct_dimensions_star = self.dimensions_star.astype(dtype=ct.c_uint, copy=True) self.ct_zeta_star_list = [] for i_surf in range(self.n_surf): for i_dim in range(NDIM): self.ct_zeta_star_list.append(self.zeta_star[i_surf][i_dim, :, :].reshape(-1)) self.ct_u_ext_star_list = [] for i_surf in range(self.n_surf): for i_dim in range(NDIM): self.ct_u_ext_star_list.append(self.u_ext_star[i_surf][i_dim, :, :].reshape(-1)) self.ct_gamma_list = [] for i_surf in range(self.n_surf): self.ct_gamma_list.append(self.gamma[i_surf][:, :].reshape(-1)) self.ct_gamma_dot_list = [] for i_surf in range(self.n_surf): self.ct_gamma_dot_list.append(self.gamma_dot[i_surf][:, :].reshape(-1)) self.ct_gamma_star_list = [] for i_surf in range(self.n_surf): self.ct_gamma_star_list.append(self.gamma_star[i_surf][:, :].reshape(-1)) self.ct_dist_to_orig_list = [] for i_surf in range(self.n_surf): self.ct_dist_to_orig_list.append(self.dist_to_orig[i_surf][:, :].reshape(-1)) self.ct_wake_conv_vel_list = [] for i_surf in range(self.n_surf): self.ct_wake_conv_vel_list.append(self.wake_conv_vel[i_surf][:, :].reshape(-1)) self.ct_flag_zeta_phantom_list = self.flag_zeta_phantom[:].reshape(-1) self.ct_p_dimensions_star = ((ct.POINTER(ct.c_uint)*n_surf) (* np.ctypeslib.as_ctypes(self.ct_dimensions_star))) self.ct_p_zeta_star = ((ct.POINTER(ct.c_double)*len(self.ct_zeta_star_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_zeta_star_list])) self.ct_p_u_ext_star = ((ct.POINTER(ct.c_double)*len(self.ct_u_ext_star_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_u_ext_star_list])) self.ct_p_gamma = ((ct.POINTER(ct.c_double)*len(self.ct_gamma_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_gamma_list])) self.ct_p_gamma_dot = ((ct.POINTER(ct.c_double)*len(self.ct_gamma_dot_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_gamma_dot_list])) self.ct_p_gamma_star = ((ct.POINTER(ct.c_double)*len(self.ct_gamma_star_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_gamma_star_list])) self.ct_p_dist_to_orig = ((ct.POINTER(ct.c_double)*len(self.ct_dist_to_orig_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_dist_to_orig_list])) self.ct_p_wake_conv_vel = ((ct.POINTER(ct.c_double)*len(self.ct_wake_conv_vel_list)) (* [np.ctypeslib.as_ctypes(array) for array in self.ct_wake_conv_vel_list])) self.ct_p_flag_zeta_phantom = np.ctypeslib.as_ctypes(self.ct_flag_zeta_phantom_list) def init_matrix_structure(dimensions, with_dim_dimension, added_size=0): matrix = [] for i_surf in range(len(dimensions)): if with_dim_dimension: matrix.append(np.zeros((3, dimensions[i_surf, 0] + added_size, dimensions[i_surf, 1] + added_size), dtype=ct.c_double)) else: matrix.append(np.zeros((dimensions[i_surf, 0] + added_size, dimensions[i_surf, 1] + added_size), dtype=ct.c_double)) return matrix def standalone_ctypes_pointer(matrix): ct_list = [] n_surf = len(matrix) if len(matrix[0].shape) == 2: # [i_surf][m, n], like gamma for i_surf in range(n_surf): ct_list.append(matrix[i_surf][:, :].reshape(-1)) elif len(matrix[0].shape) == 3: # [i_surf][i_dim, m, n], like zeta for i_surf in range(n_surf): n_dim = matrix[i_surf].shape[0] for i_dim in range(n_dim): ct_list.append(matrix[i_surf][i_dim, :, :].reshape(-1)) ct_pointer = ((ct.POINTER(ct.c_double)*len(ct_list)) (* [np.ctypeslib.as_ctypes(array) for array in ct_list])) return ct_list, ct_pointer class StructTimeStepInfo(object): """ Structural Time Step Class. Contains the relevant attributes for the structural description of a single time step. Attributes: in_global_AFoR (bool): ``True`` if the variables are stored in the global A FoR. ``False'' if they are stored in the local A FoR of each body. Always ``True`` for single-body simulations num_node (int): Number of nodes num_elem (int): Number of elements num_node_elem (int): Number of nodes per element pos (np.ndarray): Displacements. ``[num_node x 3]`` containing the vector of ``x``, ``y`` and ``z`` coordinates (in ``A`` frame) of the beam nodes. pos_dot (np.ndarray): Velocities. Time derivative of ``pos``. pos_ddot (np.ndarray): Accelerations. Time derivative of ``pos_dot`` psi (np.ndarray): Cartesian Rotation Vector. ``[num_elem x num_node_elem x 3]`` CRV for each node in each element. psi_dot (np.ndarray): Time derivative of ``psi``. psi_ddot (np.ndarray): Time derivative of ``psi_dot``. quat (np.ndarray): Quaternion expressing the transformation between the ``A`` and ``G`` frames. for_pos (np.ndarray): ``A`` frame of reference position (with respect to the `G`` frame of reference). for_vel (np.ndarray): ``A`` frame of reference velocity. Expressed in A FoR for_acc (np.ndarray): ``A`` frame of reference acceleration. Expressed in A FoR steady_applied_forces (np.ndarray): Forces applied to the structure not associated to time derivatives ``[num_nodes x 6]``. Expressed in B FoR unsteady_applied_forces (np.ndarray): Forces applied to the structure associated to time derivatives ``[num_node x 6]``. Expressed in B FoR runtime_steady_forces (np.ndarray): Steady forces generated at runtime through runtime generators ``[num_node x 6]``. Expressed in B FoR runtime_unsteady_forces (np.ndarray): Unsteady forces generated at runtime through runtime generators ``[num_node x 6]``. Expressed in B FoR gravity_forces (np.ndarray): Gravity forces at nodes ``[num_node x 6]``. Expressed in A FoR total_gravity_forces (np.ndarray): Total gravity forces on the structure ``[6]``. Expressed in A FoR total_forces (np.ndarray): Total forces applied to the structure ``[6]``. Expressed in A FoR q (np.ndarray): State vector associated to the structural system of equations ``[num_dof + 10]`` dqdt (np.ndarray): Time derivative of ``q`` dqddt (np.ndarray): Time derivative of ``dqdt`` postproc_cell (dict): Variables associated to cells to be postprocessed postproc_node (dict): Variables associated to nodes to be postprocessed psi_local (np.ndarray): Cartesian Rotation Vector for each node in each element in local FoR psi_dot_local (np.ndarray): Time derivative of ``psi`` in the local FoR mb_FoR_pos (np.ndarray): Position of the local A FoR of each body ``[num_bodies x 6]`` mb_FoR_vel (np.ndarray): Velocity of the local A FoR of each body ``[num_bodies x 6]`` mb_FoR_acc (np.ndarray): Acceleration of the local A FoR of each body ``[num_bodies x 6]`` mb_quat (np.ndarray): Quaternion of the local A FoR of each body ``[num_bodies x 4]`` mb_dquatdt (np.ndarray): Time derivative of ``mb_quat`` forces_constraints_nodes (np.ndarray): Forces associated to Lagrange Constraints on nodes ``[num_node x 6]`` forces_constraints_FoR (np.ndarray): Forces associated to Lagrange Contraints on frames of reference ``[num_bodies x 10]`` mb_dict (np.ndarray): Dictionary with the multibody information. It comes from the file ``case.mb.h5`` """ def __init__(self, num_node, num_elem, num_node_elem=3, num_dof=None, num_bodies=1): self.in_global_AFoR = True self.num_node = num_node self.num_elem = num_elem self.num_node_elem = num_node_elem # generate placeholder for node coordinates self.pos = np.zeros((self.num_node, 3), dtype=ct.c_double, order='F') self.pos_dot = np.zeros((self.num_node, 3), dtype=ct.c_double, order='F') self.pos_ddot = np.zeros((self.num_node, 3), dtype=ct.c_double, order='F') # placeholder for CRV self.psi = np.zeros((self.num_elem, num_node_elem, 3), dtype=ct.c_double, order='F') self.psi_dot = np.zeros((self.num_elem, num_node_elem, 3), dtype=ct.c_double, order='F') self.psi_ddot = np.zeros((self.num_elem, num_node_elem, 3), dtype=ct.c_double, order='F') # FoR data self.quat = np.array([1., 0, 0, 0], dtype=ct.c_double, order='F') self.for_pos = np.zeros((6,), dtype=ct.c_double, order='F') self.for_vel = np.zeros((6,), dtype=ct.c_double, order='F') self.for_acc = np.zeros((6,), dtype=ct.c_double, order='F') self.steady_applied_forces = np.zeros((self.num_node, 6), dtype=ct.c_double, order='F') self.unsteady_applied_forces = np.zeros((self.num_node, 6), dtype=ct.c_double, order='F') self.runtime_steady_forces = np.zeros((self.num_node, 6), dtype=ct.c_double, order='F') self.runtime_unsteady_forces = np.zeros((self.num_node, 6), dtype=ct.c_double, order='F') self.gravity_forces = np.zeros((self.num_node, 6), dtype=ct.c_double, order='F') self.total_gravity_forces = np.zeros((6,), dtype=ct.c_double, order='F') self.total_forces = np.zeros((6,), dtype=ct.c_double, order='F') if num_dof is None: # For backwards compatibility num_dof = (self.num_node.value - 1)*6 self.q = np.zeros((num_dof.value + 6 + 4,), dtype=ct.c_double, order='F') self.dqdt = np.zeros((num_dof.value + 6 + 4,), dtype=ct.c_double, order='F') self.dqddt = np.zeros((num_dof.value + 6 + 4,), dtype=ct.c_double, order='F') self.postproc_cell = dict() self.postproc_node = dict() # Multibody self.psi_local = np.zeros((self.num_elem, num_node_elem, 3), dtype=ct.c_double, order='F') self.psi_dot_local = np.zeros((self.num_elem, num_node_elem, 3), dtype=ct.c_double, order='F') self.mb_FoR_pos = np.zeros((num_bodies,6), dtype=ct.c_double, order='F') self.mb_FoR_vel = np.zeros((num_bodies,6), dtype=ct.c_double, order='F') self.mb_FoR_acc = np.zeros((num_bodies,6), dtype=ct.c_double, order='F') self.mb_quat = np.zeros((num_bodies,4), dtype=ct.c_double, order='F') self.mb_dquatdt = np.zeros((num_bodies, 4), dtype=ct.c_double, order='F') self.forces_constraints_nodes = np.zeros((self.num_node, 6), dtype=ct.c_double, order='F') self.forces_constraints_FoR = np.zeros((num_bodies, 10), dtype=ct.c_double, order='F') self.mb_dict = None self.mb_prescribed_dict = None def copy(self): """ Returns a copy of a deepcopy of a :class:`~sharpy.utils.datastructures.StructTimeStepInfo` """ copied = StructTimeStepInfo(self.num_node, self.num_elem, self.num_node_elem, ct.c_int(len(self.q)-10), self.mb_quat.shape[0]) copied.in_global_AFoR = self.in_global_AFoR copied.num_node = self.num_node copied.num_elem = self.num_elem copied.num_node_elem = self.num_node_elem # generate placeholder for node coordinates copied.pos = self.pos.astype(dtype=ct.c_double, order='F', copy=True) copied.pos_dot = self.pos_dot.astype(dtype=ct.c_double, order='F', copy=True) copied.pos_ddot = self.pos_ddot.astype(dtype=ct.c_double, order='F', copy=True) # placeholder for CRV copied.psi = self.psi.astype(dtype=ct.c_double, order='F', copy=True) copied.psi_dot = self.psi_dot.astype(dtype=ct.c_double, order='F', copy=True) copied.psi_ddot = self.psi_ddot.astype(dtype=ct.c_double, order='F', copy=True) # FoR data copied.quat = self.quat.astype(dtype=ct.c_double, order='F', copy=True) copied.for_pos = self.for_pos.astype(dtype=ct.c_double, order='F', copy=True) copied.for_vel = self.for_vel.astype(dtype=ct.c_double, order='F', copy=True) copied.for_acc = self.for_acc.astype(dtype=ct.c_double, order='F', copy=True) copied.steady_applied_forces = self.steady_applied_forces.astype(dtype=ct.c_double, order='F', copy=True) copied.unsteady_applied_forces = self.unsteady_applied_forces.astype(dtype=ct.c_double, order='F', copy=True) copied.runtime_steady_forces = self.runtime_steady_forces.astype(dtype=ct.c_double, order='F', copy=True) copied.runtime_unsteady_forces = self.runtime_unsteady_forces.astype(dtype=ct.c_double, order='F', copy=True) copied.gravity_forces = self.gravity_forces.astype(dtype=ct.c_double, order='F', copy=True) copied.total_gravity_forces = self.total_gravity_forces.astype(dtype=ct.c_double, order='F', copy=True) copied.total_forces = self.total_forces.astype(dtype=ct.c_double, order='F', copy=True) copied.q = self.q.astype(dtype=ct.c_double, order='F', copy=True) copied.dqdt = self.dqdt.astype(dtype=ct.c_double, order='F', copy=True) copied.dqddt = self.dqddt.astype(dtype=ct.c_double, order='F', copy=True) copied.postproc_cell = copy.deepcopy(self.postproc_cell) copied.postproc_node = copy.deepcopy(self.postproc_node) copied.psi_local = self.psi_local.astype(dtype=ct.c_double, order='F', copy=True) copied.psi_dot_local = self.psi_dot_local.astype(dtype=ct.c_double, order='F', copy=True) copied.mb_FoR_pos = self.mb_FoR_pos.astype(dtype=ct.c_double, order='F', copy=True) copied.mb_FoR_vel = self.mb_FoR_vel.astype(dtype=ct.c_double, order='F', copy=True) copied.mb_FoR_acc = self.mb_FoR_acc.astype(dtype=ct.c_double, order='F', copy=True) copied.mb_quat = self.mb_quat.astype(dtype=ct.c_double, order='F', copy=True) copied.mb_dquatdt = self.mb_dquatdt.astype(dtype=ct.c_double, order='F', copy=True) copied.forces_constraints_nodes = self.forces_constraints_nodes.astype(dtype=ct.c_double, order='F', copy=True) copied.forces_constraints_FoR = self.forces_constraints_FoR.astype(dtype=ct.c_double, order='F', copy=True) copied.mb_dict = copy.deepcopy(self.mb_dict) copied.mb_prescribed_dict = copy.deepcopy(self.mb_prescribed_dict) return copied def glob_pos(self, include_rbm=True): """ Returns the position of the nodes in ``G`` FoR """ coords = self.pos.copy() c = self.cga() for i_node in range(self.num_node): coords[i_node, :] = np.dot(c, coords[i_node, :]) if include_rbm: coords[i_node, :] += self.for_pos[0:3] return coords def cga(self): return algebra.quat2rotation(self.quat) def cag(self): return self.cga().T def euler_angles(self): """ Returns the 3 Euler angles (roll, pitch, yaw) for a given time step. :returns: `np.array` (roll, pitch, yaw) in radians. """ return algebra.quat2euler(self.quat) def get_body(self, beam, num_dof_ibody, ibody): """ get_body Extract the body number ``ibody`` from a multibody system This function returns a :class:`~sharpy.utils.datastructures.StructTimeStepInfo` class (``ibody_StructTimeStepInfo``) that only includes the body number ``ibody`` of the original multibody system ``self`` Args: beam(:class:`~sharpy.structure.models.beam.Beam`): beam information of the multibody system num_dof_ibody (int): Number of degrees of freedom associated to the ``ibody`` ibody(int): body number to be extracted Returns: StructTimeStepInfo: timestep information of the isolated body """ # Define the nodes and elements belonging to the body ibody_elems, ibody_nodes = mb.get_elems_nodes_list(beam, ibody) ibody_num_node = len(ibody_nodes) ibody_num_elem = len(ibody_elems) ibody_first_dof = 0 for index_body in range(ibody - 1): aux_elems, aux_nodes = mb.get_elems_nodes_list(beam, index_body) ibody_first_dof += np.sum(beam.vdof[aux_nodes] > -1)*6 # Initialize the new StructTimeStepInfo ibody_StructTimeStepInfo = StructTimeStepInfo(ibody_num_node, ibody_num_elem, self.num_node_elem, num_dof = num_dof_ibody, num_bodies = beam.num_bodies) # Assign all the variables ibody_StructTimeStepInfo.quat = self.mb_quat[ibody, :].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.for_pos = self.mb_FoR_pos[ibody, :].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.for_vel = self.mb_FoR_vel[ibody, :] ibody_StructTimeStepInfo.for_acc = self.mb_FoR_acc[ibody, :] ibody_StructTimeStepInfo.pos = self.pos[ibody_nodes,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.pos_dot = self.pos_dot[ibody_nodes,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.pos_ddot = self.pos_ddot[ibody_nodes,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.psi = self.psi[ibody_elems,:,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.psi_local = self.psi_local[ibody_elems,:,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.psi_dot = self.psi_dot[ibody_elems,:,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.psi_dot_local = self.psi_dot_local[ibody_elems,:,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.psi_ddot = self.psi_ddot[ibody_elems,:,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.steady_applied_forces = self.steady_applied_forces[ibody_nodes,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.unsteady_applied_forces = self.unsteady_applied_forces[ibody_nodes,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.runtime_steady_forces = self.runtime_steady_forces[ibody_nodes,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.runtime_unsteady_forces = self.runtime_unsteady_forces[ibody_nodes,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.gravity_forces = self.gravity_forces[ibody_nodes,:].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.total_gravity_forces = self.total_gravity_forces.astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.q[0:num_dof_ibody.value] = self.q[ibody_first_dof:ibody_first_dof+num_dof_ibody.value].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.dqdt[0:num_dof_ibody.value] = self.dqdt[ibody_first_dof:ibody_first_dof+num_dof_ibody.value].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.dqddt[0:num_dof_ibody.value] = self.dqddt[ibody_first_dof:ibody_first_dof+num_dof_ibody.value].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.dqdt[-10:-4] = ibody_StructTimeStepInfo.for_vel.astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.dqddt[-10:-4] = ibody_StructTimeStepInfo.for_acc.astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.dqdt[-4:] = self.quat.astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.dqddt[-4:] = self.mb_dquatdt[ibody, :].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.mb_dquatdt[ibody, :] = self.mb_dquatdt[ibody, :].astype(dtype=ct.c_double, order='F', copy=True) ibody_StructTimeStepInfo.mb_quat = None ibody_StructTimeStepInfo.mb_FoR_pos = None ibody_StructTimeStepInfo.mb_FoR_vel = None ibody_StructTimeStepInfo.mb_FoR_acc = None return ibody_StructTimeStepInfo def compute_psi_local_AFoR(self, for0_pos, for0_vel, quat0): """ compute_psi_local_AFoR Compute psi and psi_dot in the local A frame of reference Args: for0_pos (np.ndarray): Position of the global A FoR for0_vel (np.ndarray): Velocity of the global A FoR quat0 (np.ndarray): Quaternion of the global A FoR """ # Define the rotation matrices between the different FoR CAslaveG = algebra.quat2rotation(self.quat).T CGAmaster = algebra.quat2rotation(quat0) Csm = np.dot(CAslaveG, CGAmaster) for ielem in range(self.psi.shape[0]): for inode in range(3): self.psi_local[ielem, inode, :] = algebra.rotation2crv(np.dot(Csm,algebra.crv2rotation(self.psi[ielem,inode,:]))) self.psi_dot_local[ielem, inode, :] = np.zeros((3)) def change_to_local_AFoR(self, for0_pos, for0_vel, quat0): """ change_to_local_AFoR Reference a :class:`~sharpy.utils.datastructures.StructTimeStepInfo` to the local A frame of reference Args: for0_pos (np.ndarray): Position of the global A FoR for0_vel (np.ndarray): Velocity of the global A FoR quat0 (np.ndarray): Quaternion of the global A FoR """ # Define the rotation matrices between the different FoR CAslaveG = algebra.quat2rotation(self.quat).T CGAmaster = algebra.quat2rotation(quat0) Csm = np.dot(CAslaveG, CGAmaster) # Modify position for inode in range(self.pos.shape[0]): vel_master = (self.pos_dot[inode,:] + for0_vel[0:3] + algebra.cross3(for0_vel[3:6], self.pos[inode, :])) self.pos[inode, :] = np.dot(Csm,self.pos[inode,:]) + np.dot(CAslaveG, for0_pos[0:3] - self.for_pos[0:3]) self.pos_dot[inode, :] = (np.dot(Csm, vel_master) - self.for_vel[0:3] - algebra.cross3(self.for_vel[3:6], self.pos[inode,:])) self.gravity_forces[inode, 0:3] = np.dot(Csm, self.gravity_forces[inode, 0:3]) self.gravity_forces[inode, 3:6] = np.dot(Csm, self.gravity_forces[inode, 3:6]) # Modify local rotations for ielem in range(self.psi.shape[0]): for inode in range(3): self.psi[ielem, inode, :] = self.psi_local[ielem,inode,:].copy() self.psi_dot[ielem, inode, :] = self.psi_dot_local[ielem, inode, :].copy() def change_to_global_AFoR(self, for0_pos, for0_vel, quat0): """ Reference a :class:`~sharpy.utils.datastructures.StructTimeStepInfo` to the global A frame of reference Args: for0_pos (np.ndarray): Position of the global A FoR for0_vel (np.ndarray): Velocity of the global A FoR quat0 (np.ndarray): Quaternion of the global A FoR """ # Define the rotation matrices between the different FoR CGAslave = algebra.quat2rotation(self.quat) CAmasterG = algebra.quat2rotation(quat0).T Cms = np.dot(CAmasterG, CGAslave) for inode in range(self.pos.shape[0]): vel_slave = (self.pos_dot[inode, :] + self.for_vel[0:3] + algebra.cross3(self.for_vel[3:6], self.pos[inode, :])) self.pos[inode, :] = np.dot(Cms, self.pos[inode,:]) + np.dot(CAmasterG, self.for_pos[0:3] - for0_pos[0:3]) self.pos_dot[inode, :] = (np.dot(Cms, vel_slave) - for0_vel[0:3] - algebra.cross3(for0_vel[3:6], self.pos[inode, :])) self.gravity_forces[inode, 0:3] = np.dot(Cms, self.gravity_forces[inode, 0:3]) self.gravity_forces[inode, 3:6] = np.dot(Cms, self.gravity_forces[inode, 3:6]) for ielem in range(self.psi.shape[0]): for inode in range(3): self.psi_local[ielem, inode, :] = self.psi[ielem, inode, :].copy() # Copy here the result from the structural computation self.psi_dot_local[ielem, inode, :] = self.psi_dot[ielem, inode, :].copy() # Copy here the result from the structural computation # psi_slave = self.psi[ielem, inode, :] + np.zeros((3,),) self.psi[ielem, inode, :] = algebra.rotation2crv(np.dot(Cms,algebra.crv2rotation(self.psi[ielem,inode,:]))) # Convert psi_dot_local to psi_dot to be used by the rest of the code psi_master = self.psi[ielem,inode,:] + np.zeros((3,),) cbam = algebra.crv2rotation(psi_master).T cbas = algebra.crv2rotation(self.psi_local[ielem, inode, :]).T ts = algebra.crv2tan(self.psi_local[ielem, inode, :]) inv_tm = np.linalg.inv(algebra.crv2tan(psi_master)) self.psi_dot[ielem, inode, :] = np.dot(inv_tm, (np.dot(ts, self.psi_dot_local[ielem, inode, :]) + np.dot(cbas, self.for_vel[3:6]) - np.dot(cbam, for0_vel[3:6]))) def nodal_b_for_2_a_for(self, nodal, beam, filter=np.array([True]*6), ibody=None): """ Projects a nodal variable from the local, body-attached frame (B) to the reference A frame. Args: nodal (np.array): Nodal variable of size ``(num_node, 6)`` beam (sharpy.datastructures.StructTimeStepInfo): beam info. filter (np.array): optional argument that filters and does not convert a specific degree of freedom. Defaults to ``np.array([True, True, True, True, True, True])``. Returns: np.array: the ``nodal`` argument projected onto the reference ``A`` frame. """ nodal_a = np.zeros_like(nodal) for i_node in range(self.num_node): # get master elem and i_local_node i_master_elem, i_local_node = beam.node_master_elem[i_node, :] if ((ibody is None) or (beam.body_number[i_master_elem] == ibody)): crv = self.psi[i_master_elem, i_local_node, :] cab = algebra.crv2rotation(crv) nodal_a[i_node, 0:3] = np.dot(cab, nodal[i_node, 0:3]) nodal_a[i_node, 3:6] = np.dot(cab, nodal[i_node, 3:6]) nodal_a *= filter return nodal_a def nodal_type_b_for_2_a_for(self, beam, force_type=['steady', 'unsteady'], filter=np.array([True]*6), ibody=None): forces_output = [] for ft in force_type: if ft == 'steady': fb = self.steady_applied_forces elif ft == 'unsteady': fb = self.unsteady_applied_forces forces_output.append(self.nodal_b_for_2_a_for(fb, beam, filter=filter, ibody=ibody)) return forces_output def extract_resultants(self, beam, force_type=['steady', 'unsteady', 'grav'], ibody=None): forces_output = [] for ft in force_type: totals = np.zeros((6)) if ft == 'steady': fa = self.nodal_type_b_for_2_a_for(beam, force_type=['steady'], ibody=ibody)[0] elif ft == 'grav': fa = self.gravity_forces.copy() elif ft == 'unsteady': fa = self.nodal_type_b_for_2_a_for(beam, force_type=['unsteady'], ibody=ibody)[0] for i_node in range(beam.num_node): totals += fa[i_node, :] totals[3:6] += algebra.cross3(self.pos[i_node, :], fa[i_node, 0:3]) forces_output.append(totals) return forces_output class LinearTimeStepInfo(object): """ Linear timestep info containing the state, input and output variables for a given timestep """ def __init__(self): self.x = None self.y = None self.u = None self.t = None def copy(self): copied = LinearTimeStepInfo() copied.x = self.x.copy() copied.y = self.y.copy() copied.u = self.u.copy() copied.t = self.t.copy() class Linear(object): """ This is the class responsible for the transfer of information between linear systems and can be accessed as ``data.linear``. It stores as class attributes the following classes that describe the linearised problem. Attributes: ss (sharpy.linear.src.libss.StateSpace): State-space system linear_system (sharpy.linear.utils.ss_interface.BaseElement): Assemble system properties tsaero0 (sharpy.utils.datastructures.AeroTimeStepInfo): Linearisation aerodynamic timestep tsstruct0 (sharpy.utils.datastructures.StructTimeStepInfo): Linearisation structural timestep timestep_info (list): Linear time steps """ def __init__(self, tsaero0, tsstruct0): self.linear_system = None self.ss = None self.tsaero0 = tsaero0 self.tsstruct0 = tsstruct0 self.timestep_info = [] self.uvlm = None self.beam = None ================================================ FILE: sharpy/utils/docutils.py ================================================ """Documentation Generator Functions to automatically document the code. Comments and complaints: N. Goizueta """ import os import shutil import inspect import glob import warnings import importlib.util import yaml import sharpy.utils.sharpydir as sharpydir import sharpy.utils.exceptions as exceptions import sharpy.utils.solver_interface as solver_interface def generate_documentation(): """ Main routine that generates the documentation in ``./docs/source/includes`` """ print('Cleaning docs/source/includes') shutil.rmtree(sharpydir.SharpyDir + '/docs/source/includes/') solver_interface.output_documentation() # Solvers and generators have a slightly different generation method # generator_interface.output_documentation() # Main sharpy source code sharpy_folders = get_sharpy_folders() ignore_modules = yaml.load(open(sharpydir.SharpyDir + '/docs/docignore.yml', 'r'), Loader=yaml.Loader) for folder in sharpy_folders: folder_name = folder.replace(sharpydir.SharpyDir, '') folder_name = folder_name[1:] if check_folder_in_ignore(folder, ignore_modules['modules']): continue mtitle, mbody = write_folder(folder, ignore_modules['modules']) create_index_files(folder, mtitle, mbody) main_msg = 'The core SHARPy documentation is found herein.\n\n' \ '.. note::\n\n' \ '\tThe docs are still a work in progress and therefore, ' \ 'most functions/classes with which there is not much user interaction are not fully documented. ' \ 'We would appreciate any help by means of you contributing to our growing documentation!\n\n\n' \ 'If you feel that a function/class is not well documented and, hence, you cannot use it, feel free ' \ 'to raise an issue so that we can improve it.\n\n' create_index_files('./', 'SHARPy Source Code', main_msg) def write_folder(folder, ignore_list): """ Creates the documentation for the contents in a folder. It checks that the file folder is not in the ``ignore_list``. If there is a subfolder in the folder, this gets opened, written and an index file is created. Args: folder (str): Absolute path to folder ignore_list (list): List with filenames and folders to ignore and skip Returns: tuple: Tuple containing the title and body of the docstring found for it to be added to the index of the current folder. """ files, mtitle, mbody = open_folder(folder) for file in files: if os.path.isfile(file) and not check_folder_in_ignore(file, ignore_list): if file[-3:] != '.py': continue write_file(file) elif os.path.isdir(file) and not check_folder_in_ignore(file, ignore_list): mtitlesub, mbodysub = write_folder(file, ignore_list) create_index_files(file, mtitlesub, mbodysub) return mtitle, mbody def write_file(file): """ Writes the contents of a python file with one module per page. Warnings: If the function to be written does not have a docstring no output will be produced and a warning will be given. Args: file (str): Absolute path to file """ file_name = file.replace(sharpydir.SharpyDir, '') source = file_name.replace('.py', '') outfile = source.replace('sharpy/', '') try: output_documentation_module_page(source, outfile) except exceptions.DocumentationError: # Future feature- remove try except so it raises the error that no title has been given warnings.warn('Module %s not written - no title given' %source) def check_folder_in_ignore(folder, ignore_list): """ Checks whether a folder is in the ``ignore_list``. Args: folder (str): Absolute path to folder ignore_list (list): Ignore list Returns: bool: Bool whether file/folder is in ignore list. """ file_name_check = folder.replace(sharpydir.SharpyDir, '') file_name_check = file_name_check[1:] if file_name_check in ignore_list: return True else: return False def output_documentation_module_page(path_to_module, docs_folder_name): """ Generates the documentation for a package with a single page per module in the desired folder Returns: """ import_path = path_to_module import_path = import_path.replace(sharpydir.SharpyDir, "") if import_path[0] == "/": import_path = import_path[1:] import_path = import_path.replace("/", ".") module = importlib.import_module(import_path) print('Generating documentation files') path_to_folder = sharpydir.SharpyDir + '/docs/source/includes/' + docs_folder_name if os.path.exists(path_to_folder): print('Cleaning directory %s' %path_to_folder) shutil.rmtree(path_to_folder) module_content = inspect.getmembers(module) index_file_content = [] for item in module_content: if not inspect.isfunction(item[1]) and not inspect.isclass(item[1]): continue md_path = item[1].__module__.split('.') if md_path[0] != 'sharpy': continue isclass = False if inspect.isclass(item[1]): isclass = True if not item[1].__doc__: continue if not os.path.exists(path_to_folder): print('Creating directory %s' %path_to_folder) os.makedirs(path_to_folder, exist_ok=True) docs = '' filename = item[1].__name__ + '.rst' index_file_content.append(item[1].__name__) with open(path_to_folder + '/' + filename, 'w') as outfile: title = item[1].__name__ python_method_path = import_path + '.' + title docs += title + '\n' + len(title)*'-' + '\n\n' if isclass: docs += '.. autoclass:: ' + python_method_path docs += '\n\t:members:' else: docs += '.. automodule:: ' + python_method_path outfile.write(docs) print('\tCreated %s' % path_to_folder + '/' + filename) # create index file if index_file_content != []: with open(path_to_folder + '/index.rst', 'w') as outfile: index_title, body = get_module_title_and_body(module) if module.__doc__ is not None: outfile.write(index_title + '\n' + len(index_title)*'+' + '\n\n') outfile.write(body + '\n\n') outfile.write('.. toctree::\n\t:glob:\n\n') for item in index_file_content: outfile.write('\t./' + item + '\n') def output_documentation(package_path, docs_folder_name): docs_folder = sharpydir.SharpyDir + '/docs/source/includes/' + docs_folder_name if os.path.exists(docs_folder): print('Cleaning directory %s' % docs_folder) shutil.rmtree(docs_folder) print('Creating directory %s' % docs_folder) os.makedirs(docs_folder, exist_ok=True) files = solver_interface.solver_list_from_path(package_path) index_file_content = [] for file in files: module, module_path = module_from_path(package_path, file) content = inspect.getmembers(module) for member in content: if member[0].lower() == file: title = member[0] if not member[1].__doc__: continue index_file_content.append(title.lower()) docs = '' docs += title + '\n' + len(title)*'-' + '\n\n' docs += '.. autoclass:: ' + module_path + '.' + member[1].__name__ docs += '\n\t:members:' with open(docs_folder + '/' + file + '.rst', 'w') as outfile: outfile.write(docs) print('\tCreated %s' % docs_folder + '/' + file + '.rst') else: continue # create index file with open(docs_folder + '/index.rst', 'w') as outfile: index_title = docs_folder_name.capitalize() outfile.write(index_title + '\n' + len(index_title)*'+' + '\n\n') outfile.write('.. toctree::\n\t:glob:\n\n') for item in index_file_content: outfile.write('\t./' + item + '\n') def module_from_path(package_path, filename): if filename is None: name = inspect.getmodulename(package_path) else: name = inspect.getmodulename(package_path + '/' + filename + '.py') python_path = package_path.replace(sharpydir.SharpyDir, "") if python_path[0] == '/': python_path = python_path[1:] python_path = python_path.replace("/", ".") python_path = python_path.replace('.__init__.py', '') if name == '__init__': module_path = python_path # module_path = module_path.replace('..py', '') else: module_path = python_path + '.' + name module = importlib.import_module(module_path) return module, module_path def create_index_files(docs_folder, folder_title=None, folder_body=None): file_name = docs_folder.replace(sharpydir.SharpyDir, '') source = file_name.replace('.py', '') outfile = source.replace('sharpy/', '') docs_path = sharpydir.SharpyDir + '/docs/source/includes/' + outfile autodocindexfilename = docs_path + '/index.rst' rst_files = glob.glob('%s/*/*index.rst' % docs_path) # Sort files alphabetically rst_files.sort(key=lambda x: x.replace(docs_path, '')) if folder_title is None: folder_title = docs_path.split('/')[-1].capitalize() if rst_files: ordered_list = [] with open(autodocindexfilename, 'w') as outfile: outfile.write(folder_title + '\n' + len(folder_title)*'-' + '\n\n') if folder_body is not None: outfile.write(folder_body + '\n') outfile.write('.. toctree::\n\t:maxdepth: 1\n\n') for item in rst_files: # index_file = item.replace(sharpydir.SharpyDir, '') index_file = item.replace(docs_path, '') index_file = index_file.replace('.rst', '') if index_file[0] != '/': outfile.write('\t./' + index_file + '\n') else: outfile.write('\t.' + index_file + '\n') def get_module_title_and_body(module): docstring = module.__doc__ title = None body = None if docstring is not None: docstring = docstring.split('\n') title = docstring[0] if title == '': try: title = docstring[1] except IndexError: raise exceptions.DocumentationError('Module %s has been given no title in neither the 1st or 2nd ' 'lines of its docstring' % module.__name__) body = '\n'.join(docstring[1:]) else: # Had some issues with complete folders not being written if no docs present... need to verify # raise exceptions.DocumentationError('Module %s has been given no title in neither the 1st or 2nd lines of ' # 'its docstring' # % module.__name__) pass return title, body def get_sharpy_folders(): sharpy_directory = sharpydir.SharpyDir + '/sharpy/' files = glob.glob('%s/*' % sharpy_directory) for item in files: if not os.path.isdir(item): files.remove(item) elif item.replace(sharpy_directory, '')[0] == '_': files.remove(item) return files def open_folder(folder_path): files = glob.glob(folder_path + '/*') outfiles = [] mtitle = None mbody = None for file in files: if file.replace(folder_path, '')[1:] == '__init__.py': mtitle, mbody = module_title(file) elif file.replace(folder_path, '')[1] != '_': outfiles.append(file) return outfiles, mtitle, mbody def module_title(file): module, module_path = module_from_path(file, None) title, body = get_module_title_and_body(module) return title, body if __name__ == '__main__': generate_documentation() ================================================ FILE: sharpy/utils/exceptions.py ================================================ """SHARPy Exception Classes """ import sharpy.utils.cout_utils as cout class DefaultValueBaseException(Exception): def __init__(self, variable, value, message=''): super().__init__(message) def output_message(self, message, color_id=3): if cout.cout_wrap is None: print(message) else: cout.cout_wrap.print_separator(3) cout.cout_wrap(message, color_id) cout.cout_wrap.print_separator(3) class NoDefaultValueException(DefaultValueBaseException): def __init__(self, variable, value=None, message=''): super().__init__(message, value) self.output_message("The variable " + variable + " has no default value, please indicate one") class NotValidInputFile(Exception): def __init__(self, message): super().__init__(message) class NotImplementedSolver(Exception): def __init__(self, solver_name, message=''): super().__init__(message) if cout.cout_wrap is None: print( "The solver " + solver_name + " is not implemented. Check the list of available solvers when starting SHARPy") else: cout.cout_wrap( "The solver " + solver_name + " is not implemented. Check the list of available solvers when starting SHARPy", 3) class NotConvergedStructuralSolver(Exception): def __init__(self, solver_name, n_iter=None, message=''): super().__init__(message) cout.cout_wrap("The solver " + solver_name + " did not converge in " + str(n_iter) + " iterations.", 3) class DocumentationError(Exception): """ Error in documentation """ try: cout.cout_wrap('Documentation for module has been given no title') except ValueError: pass class NotConvergedSolver(Exception): """ To be raised when the solver does not converge. Before this, SHARPy would add a pdb trace, but this causes problems when using SHARPy as a black box. """ pass class NotValidSetting(DefaultValueBaseException): """ Raised when a user gives a setting an invalid value """ def __init__(self, setting, variable, options, value=None, message=''): message = 'The setting %s with entry %s is not one of the valid options: %s' % (setting, variable, options) super().__init__(variable, value, message=message) self.output_message(message, color_id=4) class NotValidSettingType(DefaultValueBaseException): """ Raised when a user gives a setting with an invalid type """ def __init__(self, setting, variable, data_types, value=None, message=''): message = 'The setting %s with entry %s is not one of the valid types: %s' % (setting, variable, data_types) super().__init__(variable, value, message=message) self.output_message(message, color_id=4) class SolverNotFound(Exception): def __init__(self, solver_name): message = 'The solver %s cannot be found in the list of solvers. Ensure you have spelt the solver name ' \ 'correctly.' % solver_name super().__init__(message) class NotRecognisedSetting(DefaultValueBaseException): """ Raised when a setting is not recognised """ def __init__(self, setting, value=None, message=''): message = 'Unrecognised setting {:s}. Please check input file and/or documentation'.format(setting) super().__init__(variable=None, value=None, message=message) self.output_message(message, color_id=4) ================================================ FILE: sharpy/utils/frequencyutils.py ================================================ """Frequency Space Tools """ import warnings import numpy as np import scipy.linalg as sclalg from sharpy.utils import cout_utils as cout import sharpy.linear.src.libss as libss def frequency_error(Y_fom, Y_rom, wv): n_in = Y_fom.shape[1] n_out = Y_fom.shape[0] cout.cout_wrap('Computing error in frequency response') max_error = np.zeros((n_out, n_in, 2)) for m in range(n_in): for p in range(n_out): cout.cout_wrap('m = %g, p = %g' % (m, p)) max_error[p, m, 0] = error_between_signals(Y_fom[p, m, :].real, Y_rom[p, m, :].real, wv, 'real') max_error[p, m, 1] = error_between_signals(Y_fom[p, m, :].imag, Y_rom[p, m, :].imag, wv, 'imag') if np.max(np.log10(max_error)) >= 0: warnings.warn('Significant mismatch in the frequency response of the ROM and FOM') return np.max(max_error) def error_between_signals(sig1, sig2, wv, sig_title=''): abs_error = np.abs(sig1 - sig2) max_error = np.max(abs_error) max_error_index = np.argmax(abs_error) pct_error = max_error/sig1[max_error_index] max_err_freq = wv[max_error_index] if 1e-1 > max_error > 1e-3: c = 3 elif max_error >= 1e-1: c = 4 else: c = 1 cout.cout_wrap('\tError Magnitude -%s-: log10(error) = %.2f (%.2f pct) at %.2f rad/s' % (sig_title, np.log10(max_error), pct_error, max_err_freq), c) return max_error def freqresp_relative_error(y1, y2, wv=None, **kwargs): r""" Relative error between a reference signal and a second signal. The error metric is defined as in [1] to be: .. math:: \varepsilon_{rel}[\mathbf{Y}_1, \mathbf{Y}_2] = \frac{\max_{i,j} (\sup_{w\in[0, \bar{w}]}[\mathbf{Y}_2 - \mathbf{Y}_1]_{i, j})}{\max_{i,j}(\sup_{w\in[0, \bar{w}]}[\mathbf{Y}_1]_{i,j})}. Args: y1 (np.ndarray): Reference signal frequency response. y2 (np.ndarray): Frequency response matrix. wv (Optional [np.ndarray]): Array of frequencies. Required when specifying a max and min value **kwargs: Key word arguments for max and min frequencies. See below. Keyword Args vmin (float): Lower bound value to find index in ``wv``. vmax (float): Upper bound value to find index in ``wv``. Returns: float: Maximum relative error between frequency responses. References: Maraniello, S. and Palacios, R. Parametric Reduced Order Modelling of the Unsteady Vortex Lattice Method. AIAA Journal. 2020 """ p, m, nwv = y1.shape assert (p, m, nwv) == y2.shape, "Frequency responses do not have the same number of inputs, " \ "outputs or evaluation points." # freq_upper_limit = kwargs.get('vmax', None) # freq_lower_limit = kwargs.get('vmin', 0) # # if wv is not None and freq_upper_limit is not None: # i_min, i_max = find_limits(wv, vmin=freq_lower_limit, vmax=freq_upper_limit) # else: # i_min = 0 # i_max = None i_min, i_max = find_limits(wv, **kwargs) err = np.zeros((p, m)) for pi in range(p): for mi in range(m): err[pi, mi] = np.max(y1[pi, mi, i_min:i_max] - y2[pi, mi, i_min:i_max]) / np.max(y1[pi, mi, i_min:i_max]) return np.max(err) def find_limits(wv, **kwargs): """ Returns the indices corresponding to the ``vmax`` and ``vmin`` key-word arguments parsed found in the ordered array ``wv``. Args: wv (np.ndarray): Ordered range. Keyword Args: vmin (float): Lower bound value to find index in ``wv``. vmax (float): Upper bound value to find index in ``wv``. Returns: tuple: Index of ``vmin`` and index of ``vmax``. """ freq_upper_limit = kwargs.get('vmax', None) freq_lower_limit = kwargs.get('vmin', 0) if wv is not None and freq_upper_limit is not None: # find indices in frequencies i_max = np.where(freq_upper_limit - wv >= 0)[-1][-1] try: i_min = np.where(freq_lower_limit - wv >= 0)[-1][-1] except IndexError: i_min = 0 else: i_min = 0 i_max = None return i_min, i_max def frobenius_norm(a): r""" Frobenius norm. Also known as Schatten 2-norm or Hilbert-Schmidt norm. .. math:: ||\mathbf{A}||_F = \sqrt{\mathrm{trace}(\mathbf{A^*A})} Args: a (np.ndarray): Complex matrix. Returns: float: Frobenius norm of the matrix ``a``. References: Antoulas, A. Approximation to Large Scale Dynamical Systems. SIAM 2005. Ch 3, Eq 3.5 """ dims = len(a.shape) if dims == 1: a.shape = (a.shape[0], 1) a_star = np.conj(a).T return np.sqrt(np.trace(a_star.dot(a))) def l2norm(y_freq, wv, **kwargs): r""" Computes the L-2 norm of a complex valued function. .. math:: \mathcal{L}_2 = \left(\int_{-\infty}^\infty ||\mathbf{F}(i\omega)||^2_{F2}\,d\omega\right)^{0.5} where :math:`||\mathbf{F}(i\omega)||_{F2}` refers to teh Frobenius norm calculated by :func:`sharpy.utils.frequencyutils.frobenius_norm`. Args: y_freq (np.ndarray): Complex valued function. wv (np.ndarray): Frequency array. **kwargs: Key word arguments for max and min frequencies. See below. Keyword Args vmin (float): Lower bound value to find index in ``wv``. vmax (float): Upper bound value to find index in ``wv``. Returns: float: L-2 norm of ``y_freq``. References: Antoulas, A. Approximation to Large Scale Dynamical Systems. SIAM 2005. Ch 5, Eq 5.10, pg 126 """ nwv = y_freq.shape[-1] assert nwv == len(wv), "Number of frequency evaluations different %g vs %g" % (nwv, len(wv)) i_min, i_max = find_limits(wv, **kwargs) freq_range = wv[i_min:i_max].copy() h2 = np.zeros(len(freq_range)) # frobenius norm at each frequency for i in range(len(freq_range)): h2[i] = frobenius_norm(y_freq[:, :, i]) ** 2 integral_h2 = np.sqrt(np.max(np.trapz(h2, freq_range))) return integral_h2 def hamiltonian(gamma, ss): """ Returns the Hamiltonian of a linear system as defined in [1]. References: [1] Bruinsma, N. A., & Steinbuch, M. (1990). A fast algorithm to compute the H∞-norm of a transfer function matrix. Systems and Control Letters, 14(4), 287–293. https://doi.org/10.1016/0167-6911(90)90049-Z Args: gamma (float): Evaluation point. ss (sharpy.linear.src.libss.StateSpace): Linear system. Returns: np.ndarray: Hamiltonian evaluated at ``gamma``. """ a, b, c, d = ss.get_mats() p, m = d.shape r = d.T.dot(d) - gamma ** 2 * np.eye(m) s = d.dot(d.T) - gamma ** 2 * np.eye(p) rinv = sclalg.inv(r) sinv = sclalg.inv(s) ham = np.block([[a - b.dot(rinv.dot(d.T.dot(c))), - gamma * b.dot(rinv.dot(b.T))], [gamma * c.T.dot(sinv.dot(c)), - a.T + c.T.dot(d.dot(rinv.dot(b.T)))]]) return ham def h_infinity_norm(ss, **kwargs): r""" Returns H-infinity norm of a linear system using iterative methods. The H-infinity norm of a MIMO system is traditionally calculated finding the largest SVD of the transfer function evaluated across the entire frequency spectrum. That can prove costly for a large number of evaluations, hence the iterative methods of [1] are employed. In the case of a SISO system the H-infinity norm corresponds to the maximum frequency gain. A scalar value is returned if the system is stable. If the system is unstable it returns ``np.Inf``. References: [1] Bruinsma, N. A., & Steinbuch, M. (1990). A fast algorithm to compute the H∞-norm of a transfer function matrix. Systems and Control Letters, 14(4), 287–293. https://doi.org/10.1016/0167-6911(90)90049-Z Args: ss (sharpy.linear.src.libss.StateSpace): Multi input multi output system. **kwargs: Key-word arguments. Keyword Args: tol (float (optional)): Tolerance. Defaults to ``1e-7``. tol_imag_eigs (float (optional)): Tolerance to find purely imaginary eigenvalues. Defaults to ``1e-7``. iter_max (int (optional)): Maximum number of iterations. print_info (bool (optional)): Print status and information. Defaults to ``False``. Returns: float: H-infinity norm of the system. """ tol = kwargs.get('tol', 1e-7) iter_max = kwargs.get('iter_max', 10) print_info = kwargs.get('print_info', False) # tolerance to find purely imaginary eigenvalues i.e those with Re(eig) < tol_imag_eigs tol_imag_eigs = kwargs.get('tol_imag_eigs', 1e-7) if ss.dt is not None: ss = libss.disc2cont(ss) # 1) Compute eigenvalues of original system eigs = sclalg.eigvals(ss.A) if any(eigs.real > tol_imag_eigs): if print_info: try: cout.cout_wrap('System is unstable - H-inf = np.inf') except ValueError: print('System is unstable - H-inf = np.inf') return np.inf # 2) Find eigenvalue that maximises equation. If all real pick largest eig if np.max(np.abs(eigs.imag) < tol_imag_eigs): eig_m = np.max(eigs.real) else: eig_m, _ = max_eigs(eigs) # 3) Choose best option for gamma_lb max_steady_state = np.max(sclalg.svd(ss.transfer_function_evaluation(0), compute_uv=False)) max_eig_m = np.max(sclalg.svd(ss.transfer_function_evaluation(1j*np.abs(eig_m)), compute_uv=False)) max_d = np.max(sclalg.svd(ss.D, compute_uv=False)) gamma_lb = max(max_steady_state, max_eig_m, max_d) iter_num = 0 if print_info: try: cout.cout_wrap('Calculating H-inf norm\n{0:>4s} ::::: {1:^8s}'.format('Iter', 'Hinf')) except ValueError: print('Calculating H_inf norm\n{0:>4s} ::::: {1:^8s}'.format('Iter', 'Hinf')) while iter_num < iter_max: if print_info: try: cout.cout_wrap('{0:>4g} ::::: {1:>8.2e}'.format(iter_num, gamma_lb)) except ValueError: print('{0:>4g} ::::: {1:>8.2e}'.format(iter_num, gamma_lb)) gamma = (1 + 2 * tol) * gamma_lb # 4) compute hamiltonian and eigenvalues ham = hamiltonian(gamma, ss) eigs = sclalg.eigvals(ham) # If eigenvalues all eigenvalues are purely imaginary if any(np.abs(eigs.real) < tol_imag_eigs): # Select imaginary eigenvalues and those with positive values condition_imag = (np.abs(eigs.real) < tol_imag_eigs) * eigs.imag > 0 imag_eigs = eigs[condition_imag].imag # Sort them in decreasing order order = np.argsort(imag_eigs)[::-1] imag_eigs = imag_eigs[order] if len(imag_eigs) == 1: m = imag_eigs[0] svdmax = np.max(sclalg.svd(ss.transfer_function_evaluation(1j*m), compute_uv=False)) gamma_lb = svdmax else: m_list = [0.5 * (imag_eigs[i] + imag_eigs[i+1]) for i in range(len(imag_eigs) - 1)] svdmax = [np.max(sclalg.svd(ss.transfer_function_evaluation(1j*m), compute_uv=False)) for m in m_list] gamma_lb = max(svdmax) else: gamma_ub = gamma break iter_num += 1 if iter_num == iter_max: raise np.linalg.LinAlgError('Unconverged H-inf solution after %g iterations' % iter_num) hinf = 0.5 * (gamma_lb + gamma_ub) return hinf def max_eigs(eigs): r""" Returns the maximum of .. math:: \left|\frac{Im(\lambda_i)}{Re(\lambda_i)}\frac{1}{\lambda_i}\right| for a given array of eigenvalues ``eigs``. Used as part of the computation of the H infinity norm References: [1] Bruinsma, N. A., & Steinbuch, M. (1990). A fast algorithm to compute the H∞-norm of a transfer function matrix. Systems and Control Letters, 14(4), 287–293. https://doi.org/10.1016/0167-6911(90)90049-Z Args: eigs (np.ndarray): Array of eigenvalues. Returns: complex: Maximum value of function. """ func = np.abs(eigs.imag / eigs.real / eigs) i_max = np.argmax(func) return func[i_max], i_max def find_target_system(data, target_system): """ Finds target system ``aeroelastic``, ``aerodynamic`` or ``structural``. Args: data (sharpy.PreSharpy): Object containing problem data target_system (str): Desired target system. Returns: sharpy.linear.src.libss.StateSpace: State-space object of target system """ if target_system == 'aeroelastic': ss = data.linear.ss elif target_system == 'structural': ss = data.linear.linear_system.beam.ss elif target_system == 'aerodynamic': ss = data.linear.linear_system.uvlm.ss # this could be a ROM else: raise NameError('Unrecognised system') return ss ================================================ FILE: sharpy/utils/generate_cases.py ================================================ """ Generate cases This library provides functions and classes to help in the definition of SHARPy cases Examples: tests in: tests/utils/generate_cases examples: test/coupled/multibody/fix_node_velocity_wrtG/test_fix_node_velocity_wrtG test/coupled/multibody/fix_node_velocity_wrtA/test_fix_node_velocity_wrtA test/coupled/multibody/double_pendulum/test_double_pendulum_geradin test/coupled/prescribed/WindTurbine/test_rotor Notes: To use this library: import sharpy.utils.generate_cases as generate_cases """ import numpy as np import h5py as h5 import os import sys import pandas as pd import scipy.integrate from copy import deepcopy import sharpy.utils.algebra as algebra import sharpy.utils.solver_interface as solver_interface import sharpy.utils.generator_interface as generator_interface import sharpy.utils.controller_interface as controller_interface import sharpy.structure.utils.lagrangeconstraints as lagrangeconstraints import sharpy.utils.cout_utils as cout from sharpy.structure.utils.lagrangeconstraintsjax import DICT_OF_LC if not cout.check_running_unittest(): cout.cout_wrap.print_screen = True cout.cout_wrap.print_file = False ###################################################################### ######################### AUX FUNCTIONS ############################ ###################################################################### def get_airfoil_camber(x, y, n_points_camber): """ get_airfoil_camber Define the camber of an airfoil based on its coordinates Args: x (np.array): x coordinates of the airfoil surface y (np.array): y coordinates of the airfoil surface n_points_camber (int): number of points to define the camber line Returns: camber_x (np.array): x coordinates of the camber line camber_y (np.array): y coordinates of the camber line Notes: The x and y vectors are expected in XFOIL format: TE - suction side - LE - pressure side - TE """ # Returns the airfoil camber for a given set of coordinates (XFOIL format expected) x = np.array(x, dtype=float) y = np.array(y, dtype=float) n = len(x) imin_x = 0 # Look for the minimum x (it will be assumed as the LE position for i in range(n): if(x[i] < x[imin_x]): imin_x = i x_suction = np.zeros((imin_x+1, )) y_suction = np.zeros((imin_x+1, )) x_pressure = np.zeros((n-imin_x, )) y_pressure = np.zeros((n-imin_x, )) for i in range(0, imin_x+1): x_suction[i] = x[imin_x-i] y_suction[i] = y[imin_x-i] for i in range(imin_x, n): x_pressure[i-imin_x] = x[i] y_pressure[i-imin_x] = y[i] # Compute the camber coordinates camber_y = np.zeros((n_points_camber, )) camber_x = np.linspace(0.0, 1.0, n_points_camber) # camber_y=0.5*(np.interp(camber_x,x[imin_x::-1],y[imin_x::-1])+np.interp(camber_x,x[imin_x:],y[imin_x:])) camber_y = 0.5*(np.interp(camber_x, x_suction, y_suction) + np.interp(camber_x, x_pressure, y_pressure)) # The function should be called as: camber_x, camber_y = get_airfoil_camber(x,y) return camber_x, camber_y def from_node_list_to_elem_matrix(node_list, connectivities): """ from_node_list_to_elem_matrix Convert list of properties associated to nodes to matrix of properties associated to elements based on the connectivities The 'ith' value of the 'node_list' array stores the property of the 'ith' node. The 'jth' 'kth' value of the 'elem_matrix' array stores the property of the 'kth' node within the 'jth' element Args: node_list (np.array): Properties of the nodes connectivities (np.array): Connectivities between the nodes to form elements Returns: elem_matrix (np.array): Properties of the elements """ num_elem = len(connectivities) # TODO: change the "3" for self.num_node_elem elem_matrix = np.zeros((num_elem, 3), dtype=node_list.dtype) for ielem in range(num_elem): elem_matrix[ielem, :] = node_list[connectivities[ielem, :]] return elem_matrix def from_node_array_to_elem_matrix(node_array, connectivities): """ from_node_array_to_elem_matrix Same as the previous function but with an array as input """ num_elem = len(connectivities) prop_size = node_array.shape[1:] # TODO: change the "3" for self.num_node_elem elem_matrix = np.zeros(prop_size + (num_elem, 3), dtype=node_array.dtype) for ielem in range(num_elem): for inode_in_elem in range(3): elem_matrix[:, ielem, inode_in_elem] = node_array[connectivities[ielem, inode_in_elem], :] return elem_matrix def read_column_sheet_type01(excel_file_name, excel_sheet, column_name): """ read_column_sheet_type01 This function reads a column from an excel file with the following format: - First row: column_name - Second row: units (not read, not checked) - Third row: type of data (see below) Args: excel_file_name (string): File name excel_sheet (string): Name of the sheet inside the excel file column_name (string): Name of the column Returns: var: Data in the excel file according to the type of data defined in the third row """ xls = pd.ExcelFile(excel_file_name) excel_db = pd.read_excel(xls, sheet_name=excel_sheet) num_elem = excel_db.index.stop - 2 try: excel_db[column_name] except KeyError: return None if excel_db[column_name][1] == 'one_int': var = excel_db[column_name][2] elif excel_db[column_name][1] == 'one_float': var = excel_db[column_name][2] elif excel_db[column_name][1] == 'one_str': var = excel_db[column_name][2] elif excel_db[column_name][1] == 'vec_int': var = np.zeros((num_elem,), dtype=int) elif excel_db[column_name][1] == 'vec_float': var = np.zeros((num_elem,), dtype=float) elif excel_db[column_name][1] == 'vec_str': var = np.zeros((num_elem,), dtype=object) else: raise RuntimeError("ERROR: not recognized number type") if 'vec' in excel_db[column_name][1]: for i in range(2, excel_db.index.stop): var[i - 2] = excel_db[column_name][i] return var def get_factor_geometric_progression(a0, Sn_target, n): r""" This function provides the factor in a geometric series which first element is 'a0', has 'n' points and the sum of the spacings is 'Sn_target' approximately. .. math:: \sum_{k=1}^n a_0 r^{k-1} = \frac{a_0 (1 - r^n)}{1 - r} """ # Given an initial value, tol = 1e-12 max_it = 1000 # Case of uniform distribution if abs(a0*n - Sn_target) < tol: return 1. # First estimation for r if a0*n < Sn_target: r = 1.1 else: r = 0.9 # Iterative computation error = 2.*tol Sn_temp = a0*(1-r**n)/(1-r) it = 0 while ((error > tol) and (it < max_it)): derivative = ((1-n*r**(n-1))*(1-r) + (1-r**n))/(1-r)**2 r += (Sn_target - Sn_temp)/a0/derivative Sn_temp = a0*(1-r**n)/(1-r) error = abs(Sn_temp - Sn_target)/Sn_target it += 1 if it == max_it: message = ("Maximum iterations reached. Sn target:%f . Sn obtained:%f . Relative error: %f" % (Sn_target, Sn_temp, error)) cout.cout_wrap(message, 3) return r def get_ielem_inode(connectivities, inode): num_elem, num_node_in_elem = connectivities.shape for ielem in range(num_elem): for inode_in_elem in range(num_node_in_elem): if connectivities[ielem, inode_in_elem] == inode: return ielem, inode_in_elem raise RuntimeError("ERROR: cannot find ielem and inode_in_elem") def get_aoacl0_from_camber(x, y): """ This section provies the angle of attack of zero lift for a thin airfoil which camber line is defined by 'x' and 'y' coordinates Check Theory of wing sections. Abbott. pg 69 """ # Scale c = x[-1] - x[0] xc = (x - x[0])/c yc = (y - y[0])/c # Remove the first and last points that may give rise to problems xc = xc[1:-1] yc = yc[1:-1] f1 = 1./(np.pi*(1-xc)*np.sqrt(xc*(1-xc))) int = yc*f1 return -scipy.integrate.trapezoid(int, xc) def get_mu0_from_camber(x, y): """ This funrcion provides the constant :math:`\mu_0` for a thin airfoil which camber line is defined by 'x' and 'y' coordinates Check Theory of wing sections. Abbott. pg 69 """ # Scale c = x[-1] - x[0] xc = (x - x[0])/c yc = (y - y[0])/c # Remove the first and last points that may give rise to problems xc = xc[1:-1] yc = yc[1:-1] f2 = (1. - 2*xc)/np.sqrt(xc*(1. - xc)) int = yc*f2 return scipy.integrate.trapz(int, xc) def list_methods(class_instance, print_info=True, clean=True): list = [] for str in dir(class_instance): func = getattr(class_instance, str) if callable(func): if clean: if not str.startswith("__"): list.append(str) else: list.append(str) if print_info: for str in list: print(str) return list def set_variable_dict(dictionary, variable, set_value): if variable in dictionary: dictionary[variable] = set_value for key, value in dictionary.items(): if isinstance(value, dict): set_variable_dict(value, variable, set_value) def define_or_concatenate(variable, value, axis=0): """ if variable is None, value is assigned If variable is an array, value is concatenated along axis """ if variable is None: variable = value else: variable = np.concatenate((variable, value), axis=axis) return variable ###################################################################### ############### STRUCTURAL INFORMATION ############################# ###################################################################### class StructuralInformation(): """ StructuralInformation Structural information needed to build a case """ def __init__(self): """ __init__ Initialization """ # Variables to write in the h5 file self.num_node_elem = None self.num_node = None self.num_elem = None self.coordinates = None self.connectivities = None self.elem_stiffness = None self.stiffness_db = None self.elem_mass = None self.mass_db = None self.frame_of_reference_delta = None self.structural_twist = None self.boundary_conditions = None self.beam_number = None self.body_number = None self.app_forces = None self.lumped_mass_nodes = None self.lumped_mass = None self.lumped_mass_inertia = None self.lumped_mass_position = None self.lumped_mass_mat = None self.lumped_mass_mat_nodes = None def copy(self): """ copy Returns a copy of the object Returns: copied(StructuralInformation): new object with the same properties """ copied = StructuralInformation() # Variables to write in the h5 file copied.num_node_elem = self.num_node_elem copied.num_node = self.num_node copied.num_elem = self.num_elem copied.coordinates = self.coordinates.astype(dtype=float, copy=True) copied.connectivities = self.connectivities.astype(dtype=int, copy=True) copied.elem_stiffness = self.elem_stiffness.astype(dtype=int, copy=True) copied.stiffness_db = self.stiffness_db.astype(dtype=float, copy=True) copied.elem_mass = self.elem_mass.astype(dtype=int, copy=True) copied.mass_db = self.mass_db.astype(dtype=float, copy=True) copied.frame_of_reference_delta = self.frame_of_reference_delta.astype(dtype=float, copy=True) copied.structural_twist = self.structural_twist.astype(dtype=float, copy=True) copied.boundary_conditions = self.boundary_conditions.astype(dtype=int, copy=True) copied.beam_number = self.beam_number.astype(dtype=int, copy=True) copied.body_number = self.body_number.astype(dtype=int, copy=True) copied.app_forces = self.app_forces.astype(dtype=float, copy=True) if isinstance(self.lumped_mass_nodes, np.ndarray): copied.lumped_mass_nodes = self.lumped_mass_nodes.astype(dtype=int, copy=True) copied.lumped_mass = self.lumped_mass.astype(dtype=float, copy=True) copied.lumped_mass_inertia = self.lumped_mass_inertia.astype(dtype=float, copy=True) copied.lumped_mass_position = self.lumped_mass_position.astype(dtype=float, copy=True) if isinstance(self.lumped_mass_mat_nodes, np.ndarray): copied.lumped_mass_mat_nodes = self.lumped_mass_mat_nodes.astype(dtype=int, copy=True) copied.lumped_mass_mat = self.lumped_mass_mat.astype(dtype=float, copy=True) return copied def set_to_zero(self, num_node_elem, num_node, num_elem, num_mass_db=None, num_stiffness_db=None, num_lumped_mass=0, num_lumped_mass_mat=0): """ set_to_zero Sets to zero all the variables Args: num_node_elem (int): number of nodes per element num_node (int): number of nodes num_elem (int): number of elements num_mass_db (int): number of different mass matrices in the case num_stiffness_db (int): number of different stiffness matrices in the case num_lumped_mass (int): number of lumped masses in the case num_lumped_mass_mat (int): number of lumped masses given as matrices """ if num_mass_db is None: num_mass_db = self.num_elem if num_stiffness_db is None: num_stiffness_db = self.num_elem self.num_node_elem = num_node_elem self.num_node = num_node self.num_elem = num_elem self.coordinates = np.zeros((num_node, 3), dtype=float) self.connectivities = np.zeros((num_elem, num_node_elem), dtype=int) self.elem_stiffness = np.zeros((num_elem,), dtype=int) self.stiffness_db = np.zeros((num_stiffness_db, 6, 6), dtype=float) self.elem_mass = np.zeros((num_elem,), dtype=int) self.mass_db = np.zeros((num_mass_db, 6, 6), dtype=float) self.frame_of_reference_delta = np.zeros((num_elem, num_node_elem, 3), dtype=float) self.structural_twist = np.zeros((num_elem, num_node_elem), dtype=float) self.boundary_conditions = np.zeros((num_node,), dtype=int) self.beam_number = np.zeros((num_elem,), dtype=int) self.body_number = np.zeros((num_elem,), dtype=int) self.app_forces = np.zeros((num_node, 6), dtype=int) if not num_lumped_mass == 0: self.lumped_mass_nodes = np.zeros((num_lumped_mass,), dtype=int) self.lumped_mass = np.zeros((num_lumped_mass,), dtype=float) self.lumped_mass_inertia = np.zeros((num_lumped_mass, 3, 3), dtype=float) self.lumped_mass_position = np.zeros((num_lumped_mass, 3), dtype=float) if not num_lumped_mass_mat == 0: self.lumped_mass_mat_nodes = np.zeros((num_lumped_mass_mat,), dtype=int) self.lumped_mass_mat = np.zeros((num_lumped_mass_mat, 6, 6), dtype=float) def generate_full_structure(self, num_node_elem, num_node, num_elem, coordinates, connectivities, elem_stiffness, stiffness_db, elem_mass, mass_db, frame_of_reference_delta, structural_twist, boundary_conditions, beam_number, app_forces, lumped_mass_nodes=None, lumped_mass=None, lumped_mass_inertia=None, lumped_mass_position=None, lumped_mass_mat_nodes=None, lumped_mass_mat=None): """ generate_full_structure Defines the whole case from the appropiated variables Args: num_node_elem (int): number of nodes per element num_node (int): number of nodes num_elem (int): number of elements coordinates (np.array): nodes coordinates connectivities (np.array): element connectivities elem_stiffness (np.array): element stiffness index stiffness_db (np.array): Stiffness matrices elem_mass (np.array): element mass index mass_db (np.array): Mass matrices frame_of_reference_delta (np.array): element direction of the y axis in the BFoR wrt the AFoR structural_twist (np.array): element based twist boundary_conditions (np.array): node boundary condition beam_number (np.array): node beam number app_forces (np.array): steady applied follower forces at the nodes lumped_mass_nodes (np.array): nodes with lumped masses lumped_mass (np.array): value of the lumped masses lumped_mass_inertia (np.array): inertia of the lumped masses lumped_mass_position (np.array): position of the lumped masses lumped_mass_mat_nodes (np.array): nodes with lumped masses given by matrices lumped_mass_mat (np.array): value of the lumped masses given by matrices """ self.num_node_elem = num_node_elem self.num_node = num_node self.num_elem = num_elem self.coordinates = coordinates self.connectivities = connectivities self.elem_stiffness = elem_stiffness self.stiffness_db = stiffness_db self.elem_mass = elem_mass self.mass_db = mass_db self.frame_of_reference_delta = frame_of_reference_delta self.structural_twist = structural_twist self.boundary_conditions = boundary_conditions self.beam_number = beam_number self.body_number = np.zeros((num_elem,), dtype=int) self.app_forces = app_forces if isinstance(lumped_mass_nodes, np.ndarray): self.lumped_mass_nodes = lumped_mass_nodes self.lumped_mass = lumped_mass self.lumped_mass_inertia = lumped_mass_inertia self.lumped_mass_position = lumped_mass_position if isinstance(lumped_mass_mat_nodes, np.ndarray): self.lumped_mass_mat_nodes = lumped_mass_mat_nodes self.lumped_mass_mat = lumped_mass_mat def generate_1to1_from_vectors(self, num_node_elem, num_node, num_elem, coordinates, stiffness_db, mass_db, frame_of_reference_delta, vec_node_structural_twist, num_lumped_mass=0, num_lumped_mass_mat=0): self.num_node_elem = num_node_elem self.num_node = num_node self.num_elem = num_elem self.coordinates = coordinates self.create_simple_connectivities() self.elem_stiffness = np.linspace(0,self.num_elem - 1, self.num_elem, dtype=int) self.stiffness_db = stiffness_db self.elem_mass = np.linspace(0,self.num_elem - 1, self.num_elem, dtype=int) self.mass_db = mass_db self.create_frame_of_reference_delta(y_BFoR=frame_of_reference_delta) self.structural_twist = from_node_list_to_elem_matrix(vec_node_structural_twist, self.connectivities) self.boundary_conditions = np.zeros((self.num_node,), dtype=int) self.beam_number = np.zeros((self.num_elem,), dtype=int) self.body_number = np.zeros((self.num_elem,), dtype=int) self.app_forces = np.zeros((self.num_node, 6), dtype=float) if not num_lumped_mass == 0: self.lumped_mass_nodes = np.zeros((num_lumped_mass,), dtype=int) self.lumped_mass = np.zeros((num_lumped_mass,), dtype=float) self.lumped_mass_inertia = np.zeros((num_lumped_mass, 3, 3), dtype=float) self.lumped_mass_position = np.zeros((num_lumped_mass, 3), dtype=float) if not num_lumped_mass_mat == 0: self.lumped_mass_mat_nodes = np.zeros((num_lumped_mass_mat,), dtype=int) self.lumped_mass_mat = np.zeros((num_lumped_mass_mat, 6, 6), dtype=float) def create_frame_of_reference_delta(self, y_BFoR='y_AFoR'): """ create_frame_of_reference_delta Define the coordinates of the yB axis in the AFoR Args: y_BFoR (string): Direction of the yB axis """ if isinstance(y_BFoR,(list,np.ndarray)): yB = np.asarray(y_BFoR) cout.cout_wrap(("WARNING: custom FoR delta defined, using the given value: y_BFoR = {y_BFoR}" % (y_BFoR)), 3) elif y_BFoR == 'x_AFoR': yB = np.array([1.0, 0.0, 0.0]) elif y_BFoR == 'y_AFoR': yB = np.array([0.0, 1.0, 0.0]) elif y_BFoR == 'z_AFoR': yB = np.array([0.0, 0.0, 1.0]) else: cout.cout_wrap("WARNING: y_BFoR not recognized, using the default value: y_BFoR = y_AFoR", 3) # y vector of the B frame of reference self.frame_of_reference_delta = np.zeros((self.num_elem, self.num_node_elem, 3), dtype=float) for ielem in range(self.num_elem): for inode in range(self.num_node_elem): # TODO: do i need to use the connectivities? self.frame_of_reference_delta[ielem, inode, :] = yB def create_mass_db_from_vector(self, vec_mass_per_unit_length, vec_mass_iner_x, vec_mass_iner_y, vec_mass_iner_z, vec_pos_cg_B, vec_mass_iner_yz=None): """ create_mass_db_from_vector Create the mass matrices from the vectors of properties Args: vec_mass_per_unit_length (np.array): masses per unit length vec_mass_iner_x (np.array): inertias around the x axis vec_mass_iner_y (np.array): inertias around the y axis vec_mass_iner_z (np.array): inertias around the z axis vec_pos_cg_B (np.array): position of the masses vec_mass_iner_yz (np.array): inertias around the yz axis """ if vec_mass_iner_yz is None: vec_mass_iner_yz = np.zeros_like(vec_mass_per_unit_length) self.mass_db = np.zeros((len(vec_mass_per_unit_length), 6, 6), dtype=float) mass = np.zeros((6, 6),) for i in range(len(vec_mass_per_unit_length)): mass[0:3, 0:3] = np.eye(3)*vec_mass_per_unit_length[i] mass[0:3, 3:6] = -1.0*vec_mass_per_unit_length[i]*algebra.skew(vec_pos_cg_B[i]) mass[3:6, 0:3] = -1.0*mass[0:3, 3:6] mass[3:6, 3:6] = np.diag([vec_mass_iner_x[i], vec_mass_iner_y[i], vec_mass_iner_z[i]]) mass[4, 5] = vec_mass_iner_yz[i] mass[5, 4] = vec_mass_iner_yz[i] self.mass_db[i] = mass def create_stiff_db_from_vector(self, vec_EA, vec_GAy, vec_GAz, vec_GJ, vec_EIy, vec_EIz, vec_EIyz=None): """ create_stiff_db_from_vector Create the stiffness matrices from the vectors of properties Args: vec_EA (np.array): Axial stiffness vec_GAy (np.array): Shear stiffness in the y direction vec_GAz (np.array): Shear stiffness in the z direction vec_GJ (np.array): Torsional stiffness vec_EIy (np.array): Bending stiffness in the y direction vec_EIz (np.array): Bending stiffness in the z direction vec_EIyz (np.array): Bending stiffness in the yz direction """ if vec_EIyz is None: vec_EIyz = np.zeros_like(vec_EA) self.stiffness_db = np.zeros((len(vec_EA), 6, 6),) for i in range(len(vec_EA)): self.stiffness_db[i] = np.diag([vec_EA[i], vec_GAy[i], vec_GAz[i], vec_GJ[i], vec_EIy[i], vec_EIz[i]]) self.stiffness_db[i][4, 5] = vec_EIyz[i] self.stiffness_db[i][5, 4] = vec_EIyz[i] def create_simple_connectivities(self): """ create_simple_connectivities Create the matrix of connectivities for one single beam with the nodes ordered in increasing xB direction """ self.connectivities = np.zeros((self.num_elem, self.num_node_elem), dtype=int) for ielem in range(self.num_elem): self.connectivities[ielem, :] = (np.array([0, 2, 1], dtype=int) + ielem*(self.num_node_elem - 1)) def rotate_around_origin(self, axis, angle): """ rotate_around_origin Rotates a structure Args: axis (np.array): axis of rotation angle (float): angle of rotation in radians """ rot = algebra.rotation_matrix_around_axis(axis, angle) for inode in range(len(self.coordinates)): self.coordinates[inode, :] = np.dot(rot, self.coordinates[inode, :]) for ielem in range(self.num_elem): for inode in range(self.num_node_elem): self.frame_of_reference_delta[ielem, inode, :] = np.dot(rot, self.frame_of_reference_delta[ielem, inode, :]) def compute_basic_num_elem(self): """ compute_basic_num_elem It computes the number of elements when no nodes are shared between beams """ if ((self.num_node-1) % (self.num_node_elem-1)) == 0: self.num_elem = int((self.num_node-1)/(self.num_node_elem-1)) else: raise RuntimeError("The number of nodes cannot be converted into " + self.num_node_elem + "-noded elements") def compute_basic_num_node(self): """ compute_basic_num_node It computes the number of nodes when no nodes are shared between beams """ self.num_node = self.num_elem*(self.num_node_elem-1) + 1 def generate_uniform_sym_beam(self, node_pos, mass_per_unit_length, mass_iner, EA, GA, GJ, EI, num_node_elem=3, y_BFoR='y_AFoR', num_lumped_mass=0, num_lumped_mass_mat=0): """ generate_uniform_sym_beam Generates the input data for SHARPy of a uniform symmetric beam Args: node_pos (np.array): coordinates of the nodes mass_per_unit_length (float): mass per unit length mass_iner (float): Inertia of the mass EA (float): Axial stiffness GA (float): Shear stiffness GJ (float): Torsional stiffness EI (float): Bending stiffness num_node_elem (int): number of nodes per element y_BFoR (str): orientation of the yB axis num_lumped_mass (int): number of lumped masses """ self.generate_uniform_beam(node_pos, mass_per_unit_length, mass_iner, mass_iner, mass_iner, np.zeros((3,),), EA, GA, GA, GJ, EI, EI, num_node_elem, y_BFoR, num_lumped_mass, num_lumped_mass_mat) def generate_uniform_beam(self, node_pos, mass_per_unit_length, mass_iner_x, mass_iner_y, mass_iner_z, pos_cg_B, EA, GAy, GAz, GJ, EIy, EIz, num_node_elem=3, y_BFoR='y_AFoR', num_lumped_mass=0, num_lumped_mass_mat=0): """ generate_uniform_beam Generates the input data for SHARPy of a uniform beam Args: node_pos (np.array): coordinates of the nodes mass_per_unit_length (float): mass per unit length mass_iner_x (float): Inertia of the mass in the x direction mass_iner_y (float): Inertia of the mass in the y direction mass_iner_z (float): Inertia of the mass in the z direction pos_cg_B (np.array): position of the masses EA (np.array): Axial stiffness GAy (np.array): Shear stiffness in the y direction GAz (np.array): Shear stiffness in the z direction GJ (np.array): Torsional stiffness EIy (np.array): Bending stiffness in the y direction EIz (np.array): Bending stiffness in the z direction num_node_elem (int): number of nodes per element y_BFoR (str): orientation of the yB axis num_lumped_mass (int): number of lumped masses num_lumped_mass_mat (int): number of lumped masses given as matrices """ self.num_node = len(node_pos) self.num_node_elem = num_node_elem self.compute_basic_num_elem() self.set_to_zero(self.num_node_elem, self.num_node, self.num_elem, 1, 1, num_lumped_mass, num_lumped_mass_mat) self.coordinates = node_pos self.create_simple_connectivities() # self.create_mass_db_from_vector(np.ones((num_elem,),)*mass_per_unit_length, # np.ones((num_elem,),)*mass_iner_x, # np.ones((num_elem,),)*mass_iner_y, # np.ones((num_elem,),)*mass_iner_z, # np.ones((num_elem,),)*pos_cg_B]) # self.create_stiff_db_from_vector(np.ones((num_elem,),)*EA, # np.ones((num_elem,),)*GAy, # np.ones((num_elem,),)*GAz, # np.ones((num_elem,),)*GJ, # np.ones((num_elem,),)*EIy, # np.ones((num_elem,),)*EIz) self.create_mass_db_from_vector(np.array([mass_per_unit_length]), np.array([mass_iner_x]), np.array([mass_iner_y]), np.array([mass_iner_z]), np.array([pos_cg_B])) self.create_stiff_db_from_vector(np.array([EA]), np.array([GAy]), np.array([GAz]), np.array([GJ]), np.array([EIy]), np.array([EIz])) self.create_frame_of_reference_delta(y_BFoR) self.boundary_conditions = np.zeros((self.num_node), dtype=int) def add_lumped_mass(self, node, mass=None, inertia=None, pos=None, mat=None): """ Add lumped mass to structure node(int): Node where the lumped mass is to be placed For lumped masses: mass(float): Mass inertia(np.array): 3x3 inertia matrix pos(np.array): 3 coordinates of the mass position For lumped masses described as a 6x6 matrix: mat(np.array): 6x6 mass and inertia matrix """ if (mass is not None) and (inertia is not None): if pos is None: pos = np.zeros((3)) if mat is not None: raise ValueError("mass, inertia and mat cannot be defined at the same time") self.lumped_mass_nodes = define_or_concatenate(self.lumped_mass_nodes, np.array([node]), axis=0) self.lumped_mass = define_or_concatenate(self.lumped_mass, np.array([mass]), axis=0) self.lumped_mass_inertia = define_or_concatenate(self.lumped_mass_inertia, np.array([inertia]), axis=0) self.lumped_mass_position = define_or_concatenate(self.lumped_mass_position, np.array([pos]), axis=0) elif (mat is not None): self.lumped_mass_mat_nodes = define_or_concatenate(self.lumped_mass_mat_nodes, np.array([node]), axis=0) self.lumped_mass_mat = define_or_concatenate(self.lumped_mass_mat, np.array([mat]), axis=0) def assembly_structures(self, *args): """ assembly_structures This function concatenates structures to be writen in the same h5 File Args: *args: list of StructuralInformation() to be meged into 'self' Notes: The structures does NOT merge any node (even if nodes are defined at the same coordinates) """ total_num_beam = max(self.beam_number)+1 total_num_body = max(self.body_number)+1 total_num_node = self.num_node total_num_elem = self.num_elem total_num_stiff = self.stiffness_db.shape[0] total_num_mass = self.mass_db.shape[0] for structure_to_add in args: self.coordinates = np.concatenate((self.coordinates, structure_to_add.coordinates), axis=0) self.connectivities = np.concatenate((self.connectivities, structure_to_add.connectivities + total_num_node), axis=0) assert self.num_node_elem == structure_to_add.num_node_elem, "num_node_elem does NOT match" self.stiffness_db = np.concatenate((self.stiffness_db, structure_to_add.stiffness_db), axis=0) self.elem_stiffness = np.concatenate((self.elem_stiffness, structure_to_add.elem_stiffness + total_num_stiff), axis=0) self.mass_db = np.concatenate((self.mass_db, structure_to_add.mass_db), axis=0) self.elem_mass = np.concatenate((self.elem_mass, structure_to_add.elem_mass + total_num_mass), axis=0) self.frame_of_reference_delta = np.concatenate((self.frame_of_reference_delta, structure_to_add.frame_of_reference_delta), axis=0) self.structural_twist = np.concatenate((self.structural_twist, structure_to_add.structural_twist), axis=0) self.boundary_conditions = np.concatenate((self.boundary_conditions, structure_to_add.boundary_conditions), axis=0) self.beam_number = np.concatenate((self.beam_number, structure_to_add.beam_number + total_num_beam), axis=0) self.body_number = np.concatenate((self.body_number, structure_to_add.body_number + total_num_body), axis=0) self.app_forces = np.concatenate((self.app_forces, structure_to_add.app_forces), axis=0) # self.body_number = np.concatenate((self.body_number, structure_to_add.body_number), axis=0) if isinstance(self.lumped_mass_nodes, np.ndarray) and isinstance(structure_to_add.lumped_mass_nodes, np.ndarray): self.lumped_mass_nodes = np.concatenate((self.lumped_mass_nodes, structure_to_add.lumped_mass_nodes + total_num_node), axis=0) self.lumped_mass = np.concatenate((self.lumped_mass, structure_to_add.lumped_mass), axis=0) self.lumped_mass_inertia = np.concatenate((self.lumped_mass_inertia, structure_to_add.lumped_mass_inertia), axis=0) self.lumped_mass_position = np.concatenate((self.lumped_mass_position, structure_to_add.lumped_mass_position), axis=0) elif isinstance(structure_to_add.lumped_mass_nodes, np.ndarray): self.lumped_mass_nodes = structure_to_add.lumped_mass_nodes + total_num_node self.lumped_mass = structure_to_add.lumped_mass self.lumped_mass_inertia = structure_to_add.lumped_mass_inertia self.lumped_mass_position = structure_to_add.lumped_mass_position if isinstance(self.lumped_mass_mat_nodes, np.ndarray) and isinstance(structure_to_add.lumped_mass_mat_nodes, np.ndarray): self.lumped_mass_mat_nodes = np.concatenate((self.lumped_mass_mat_nodes, structure_to_add.lumped_mass_mat_nodes + total_num_node), axis=0) self.lumped_mass_mat = np.concatenate((self.lumped_mass_mat, structure_to_add.lumped_mass_mat), axis=0) elif isinstance(structure_to_add.lumped_mass_mat_nodes, np.ndarray): self.lumped_mass_mat_nodes = structure_to_add.lumped_mass_mat_nodes + total_num_node self.lumped_mass_mat = structure_to_add.lumped_mass_mat total_num_stiff += structure_to_add.stiffness_db.shape[0] total_num_mass += structure_to_add.mass_db.shape[0] total_num_beam += max(structure_to_add.beam_number) + 1 total_num_body += max(structure_to_add.body_number) + 1 total_num_node += structure_to_add.num_node total_num_elem += structure_to_add.num_elem self.num_node = total_num_node self.num_elem = total_num_elem def check_StructuralInformation(self): """ check_StructuralInformation Check some properties of the StructuralInformation() Notes: These conditions have to be to correctly define a case but they are not the only ones """ # CHECKING if(self.elem_stiffness.shape[0] != self.num_elem): raise RuntimeError("ERROR: Element stiffness must be defined for each element") if(self.elem_mass.shape[0] != self.num_elem): raise RuntimeError("ERROR: Element mass must be defined for each element") if(self.frame_of_reference_delta.shape[0] != self.num_elem): raise RuntimeError("ERROR: The first dimension of FoR does not match the number of elements") if(self.frame_of_reference_delta.shape[1] != self.num_node_elem): raise RuntimeError("ERROR: The second dimension of FoR does not match the number of nodes element") if(self.frame_of_reference_delta.shape[2] != 3): raise RuntimeError("ERROR: The third dimension of FoR must be 3") if(self.structural_twist.shape[0] != self.num_elem): raise RuntimeError("ERROR: The structural twist must be defined for each element") if(self.boundary_conditions.shape[0] != self.num_node): raise RuntimeError("ERROR: The boundary conditions must be defined for each node") if(self.beam_number.shape[0] != self.num_elem): raise RuntimeError("ERROR: The beam number must be defined for each element") if(self.app_forces.shape[0] != self.num_node): raise RuntimeError("ERROR: The first dimension of the applied forces matrix does not match the number of nodes") if(self.app_forces.shape[1] != 6): raise RuntimeError("ERROR: The second dimension of the applied forces matrix must be 6") default = StructuralInformation() for attr, value in self.__dict__.items(): if not hasattr(default, attr): raise RuntimeError(("StructuralInformation has no attribute named '%s'" % attr)) def generate_fem_file(self, route, case_name): """ generate_fem_file Writes the h5 file with the structural information Args: route (string): path of the case case_name (string): name of the case """ # TODO: check variables that are not defined self.check_StructuralInformation() # Writting the file with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: # TODO: include something to write only exsisting variables h5file.create_dataset('coordinates', data=self.coordinates) h5file.create_dataset('connectivities', data=self.connectivities) h5file.create_dataset('num_node_elem', data=self.num_node_elem) h5file.create_dataset('num_node', data=self.num_node) h5file.create_dataset('num_elem', data=self.num_elem) h5file.create_dataset('stiffness_db', data=self.stiffness_db) h5file.create_dataset('elem_stiffness', data=self.elem_stiffness) h5file.create_dataset('mass_db', data=self.mass_db) h5file.create_dataset('elem_mass', data=self.elem_mass) h5file.create_dataset('frame_of_reference_delta', data=self.frame_of_reference_delta) h5file.create_dataset('structural_twist', data=self.structural_twist) h5file.create_dataset('boundary_conditions', data=self.boundary_conditions) h5file.create_dataset('beam_number', data=self.beam_number) h5file.create_dataset('body_number', data=self.body_number) h5file.create_dataset('app_forces', data=self.app_forces) # h5file.create_dataset('body_number', data=self.body_number) if isinstance(self.lumped_mass_nodes, np.ndarray): h5file.create_dataset('lumped_mass_nodes', data=self.lumped_mass_nodes) h5file.create_dataset('lumped_mass', data=self.lumped_mass) h5file.create_dataset('lumped_mass_inertia', data=self.lumped_mass_inertia) h5file.create_dataset('lumped_mass_position', data=self.lumped_mass_position) if isinstance(self.lumped_mass_mat_nodes, np.ndarray): h5file.create_dataset('lumped_mass_mat_nodes', data=self.lumped_mass_mat_nodes) h5file.create_dataset('lumped_mass_mat', data=self.lumped_mass_mat) ###################################################################### ############### BLADE AERODYNAMIC INFORMATION ###################### ###################################################################### class AerodynamicInformation(): """ AerodynamicInformation Aerodynamic information needed to build a case Note: It should be defined after the StructuralInformation of the case """ def __init__(self): """ __init__ Initialization """ self.aero_node = None self.chord = None self.twist = None self.sweep = None self.surface_m = None self.surface_distribution = None self.m_distribution = None self.elastic_axis = None self.airfoil_distribution = None # TODO: allow airfoils to be of different length (like user_defined_m_distribution) self.airfoils = None self.user_defined_m_distribution = [None] # TODO: Define the following variables at some point # self.control_surface = None # self.control_surface_type = None # self.control_surface_deflection = None # self.control_surface_chord = None # self.control_surface_hinge_coords = None self.polars = None self.first_twist = [None] def copy(self): """ copy Returns a copy of the object Returns: copied(AerodynamicInformation): new object with the same properties """ copied = AerodynamicInformation() copied.aero_node = self.aero_node.astype(dtype=bool, copy=True) copied.chord = self.chord.astype(dtype=float, copy=True) copied.twist = self.twist.astype(dtype=float, copy=True) copied.sweep = self.sweep.astype(dtype=float, copy=True) copied.surface_m = self.surface_m.astype(dtype=int, copy=True) copied.surface_distribution = self.surface_distribution.astype(dtype=int, copy=True) copied.m_distribution = self.m_distribution copied.elastic_axis = self.elastic_axis.astype(dtype=float, copy=True) copied.airfoil_distribution = self.airfoil_distribution.astype(dtype=int, copy=True) copied.airfoils = self.airfoils.astype(dtype=float, copy=True) copied.user_defined_m_distribution = self.user_defined_m_distribution.copy() if self.polars is not None: copied.polars = self.polars.copy() copied.first_twist = self.first_twist.copy() return copied def set_to_zero(self, num_node_elem, num_node, num_elem, num_airfoils=1, num_surfaces=0, num_points_camber=100): """ set_to_zero Sets to zero all the variables Args: num_node_elem (int): number of nodes per element num_node (int): number of nodes num_elem (int): number of elements num_airfoils (int): number of different airfoils num_surfaces (int): number of aerodynamic surfaces num_points_camber (int): number of points to define the camber line of the airfoil """ self.aero_node = np.zeros((num_node,), dtype=bool) self.chord = np.ones((num_elem, num_node_elem), dtype=float) self.twist = np.zeros((num_elem, num_node_elem), dtype=float) self.sweep = np.zeros((num_elem, num_node_elem), dtype=float) # TODO: SHARPy does not ignore the surface_m when the surface is not aerodynamic self.surface_m = np.array([], dtype=int) # self.surface_m = np.array([], dtype=int) self.surface_distribution = np.zeros((num_elem,), dtype=int) - 1 self.m_distribution = 'uniform' self.elastic_axis = np.zeros((num_elem, num_node_elem), dtype=float) self.airfoil_distribution = np.zeros((num_elem, num_node_elem), dtype=int) self.airfoils = np.zeros((num_airfoils, num_points_camber, 2), dtype=float) for iairfoil in range(num_airfoils): self.airfoils[iairfoil, :, 0] = np.linspace(0.0, 1.0, num_points_camber) self.first_twist = [True] def generate_full_aerodynamics(self, aero_node, chord, twist, sweep, surface_m, surface_distribution, m_distribution, elastic_axis, airfoil_distribution, airfoils, first_twist): """ generate_full_aerodynamics Defines the whole case from the appropiated variables Args: aero_node (np.array): defines if a node has aerodynamic properties or not chord (np.array): chord of the elements twist (np.array): twist of the elements sweep (np.array): sweep of the elements surface_m (np.array): Number of panels in the chord direction surface_distribution (np.array): Surface at which each element belongs m_distribution (str): distribution of the panels along the chord elastic_axis (np.array): position of the elastic axis in the chord airfoil_distribution (np.array): airfoil at each element node airfoils (np.array): coordinates of the camber lines of the airfoils first_twist (list(bool)): Apply the twist rotation before the sweep """ self.aero_node = aero_node self.chord = chord self.twist = twist self.sweep = sweep self.surface_m = surface_m self.surface_distribution = surface_distribution self.m_distribution = m_distribution self.elastic_axis = elastic_axis self.airfoil_distribution = airfoil_distribution self.airfoils = airfoils self.first_twist = first_twist def create_aerodynamics_from_vec(self, StructuralInformation, vec_aero_node, vec_chord, vec_twist, vec_sweep, vec_surface_m, vec_surface_distribution, vec_m_distribution, vec_elastic_axis, vec_airfoil_distribution, airfoils, user_defined_m_distribution=None, first_twist=True): """ create_aerodynamics_from_vec Defines the whole case from the appropiated variables in vector form (associated to nodes) Args: StructuralInformation (StructuralInformation): Structural infromation of the case vec_aero_node (np.array): defines if a node has aerodynamic properties or not vec_chord (np.array): chord of the nodes vec_twist (np.array): twist of the nodes vec_sweep (np.array): sweep of the nodes vec_surface_m (np.array): Number of panels in the chord direction vec_surface_distribution (np.array): Surface at which each element belongs vec_m_distribution (np.array): distribution of the panels along the chord vec_elastic_axis (np.array): position of the elastic axis in the chord vec_airfoil_distribution (np.array): airfoil at each element node airfoils (np.array): coordinates of the camber lines of the airfoils first_twist (bool): Apply the twist rotation before the sweep """ self.aero_node = vec_aero_node self.chord = from_node_list_to_elem_matrix(vec_chord, StructuralInformation.connectivities) self.twist = from_node_list_to_elem_matrix(vec_twist, StructuralInformation.connectivities) self.sweep = from_node_list_to_elem_matrix(vec_sweep, StructuralInformation.connectivities) self.elastic_axis = from_node_list_to_elem_matrix(vec_elastic_axis, StructuralInformation.connectivities) self.airfoil_distribution = from_node_list_to_elem_matrix(vec_airfoil_distribution, StructuralInformation.connectivities) self.surface_m = vec_surface_m self.surface_distribution = vec_surface_distribution self.m_distribution = vec_m_distribution self.airfoils = airfoils # TODO: this may not work for different surfaces if vec_m_distribution == 'user_defined': udmd_by_elements = from_node_array_to_elem_matrix(user_defined_m_distribution, StructuralInformation.connectivities) self.user_defined_m_distribution = [udmd_by_elements] self.first_twist = [first_twist] def create_one_uniform_aerodynamics(self, StructuralInformation, chord, twist, sweep, num_chord_panels, m_distribution, elastic_axis, num_points_camber, airfoil, first_twist=True): """ create_one_uniform_aerodynamics Defines the whole case from the appropiated variables constant at every point Args: StructuralInformation (StructuralInformation): Structural infromation of the case chord (float): chord twist (float): twist sweep (float): sweep num_chord_panels (int): Number of panels in the chord direction m_distribution (str): distribution of the panels along the chord elastic_axis (float): position of the elastic axis in the chord num_points_camber (int): Number of points to define the camber line airfoils (np.array): coordinates of the camber lines of the airfoils first_twist (bool): Apply the twist rotation before the sweep """ num_node = StructuralInformation.num_node num_node_elem = StructuralInformation.num_node_elem num_elem = StructuralInformation.num_elem self.aero_node = np.ones((num_node,), dtype = bool) self.chord = chord*np.ones((num_elem, num_node_elem), dtype=float) self.twist = twist*np.ones((num_elem, num_node_elem), dtype=float) self.sweep = sweep*np.ones((num_elem, num_node_elem), dtype=float) # TODO: SHARPy does not ignore the surface_m when the surface is not aerodynamic #self.surface_m = np.array([0], dtype = int) self.surface_m = np.array([num_chord_panels], dtype=int) self.surface_distribution = np.zeros((num_elem,), dtype=int) self.m_distribution = m_distribution self.elastic_axis = elastic_axis*np.ones((num_elem, num_node_elem), dtype=float) self.airfoil_distribution = np.zeros((num_elem, num_node_elem), dtype=int) self.airfoils = np.zeros((1, num_points_camber, 2), dtype=float) self.airfoils = airfoil if m_distribution == 'user_defined': self.user_defined_m_distribution = [] self.user_defined_m_distribution.append(np.zeros((num_chord_panels + 1, num_elem, num_node_elem))) self.first_twist = [first_twist] def change_airfoils_discretezation(self, airfoils, new_num_nodes): """ change_airfoils_discretezation Changes the discretization of the matrix of airfoil coordinates Args: airfoils (np.array): Matrix with the x-y coordinates of all the airfoils to be modified new_num_nodes (int): Number of points that the output coordinates will have Return: new_airfoils (np.array): Matrix with the x-y coordinates of all the airfoils with the new discretization """ nairfoils = airfoils.shape[0] new_airfoils = np.zeros((nairfoils, new_num_nodes, 2), dtype=float) for iairfoil in range(nairfoils): new_airfoils[iairfoil, :, 0] = np.linspace(airfoils[iairfoil, 0, 0], airfoils[iairfoil, -1, 0], new_num_nodes) new_airfoils[iairfoil, :, 1] = np.interp(new_airfoils[iairfoil, :, 0], airfoils[iairfoil, :, 0], airfoils[iairfoil, :, 1]) return new_airfoils def assembly_aerodynamics(self, *args): """ assembly_aerodynamics This function concatenates aerodynamic properties to be writen in the same h5 File Args: *args: list of AerodynamicInformation() to be meged into 'self' """ total_num_airfoils = len(self.airfoils[:, 0, 0]) # total_num_surfaces = len(self.surface_m) total_num_surfaces = np.sum(self.surface_m != -1) # TODO: check why I only need one definition of m and not one per surface for aerodynamics_to_add in args: self.chord = np.concatenate((self.chord, aerodynamics_to_add.chord), axis=0) self.twist = np.concatenate((self.twist, aerodynamics_to_add.twist), axis=0) self.sweep = np.concatenate((self.sweep, aerodynamics_to_add.sweep), axis=0) # self.m_distribution = np.concatenate((self.m_distribution, aerodynamics_to_add.m_distribution), axis=0) assert self.m_distribution == aerodynamics_to_add.m_distribution, "m_distribution does not match" for isurf in range(len(aerodynamics_to_add.surface_distribution)): if aerodynamics_to_add.surface_distribution[isurf] != -1: # print(self.surface_distribution) # print(aerodynamics_to_add.surface_distribution[isurf]) self.surface_distribution = np.concatenate((self.surface_distribution, np.array([aerodynamics_to_add.surface_distribution[isurf]], dtype=int) + total_num_surfaces), axis=0) else: self.surface_distribution = np.concatenate((self.surface_distribution, np.array([aerodynamics_to_add.surface_distribution[isurf]], dtype=int)), axis=0) self.surface_m = np.concatenate((self.surface_m, aerodynamics_to_add.surface_m), axis=0) self.aero_node = np.concatenate((self.aero_node, aerodynamics_to_add.aero_node), axis=0) self.elastic_axis = np.concatenate((self.elastic_axis, aerodynamics_to_add.elastic_axis), axis=0) # np.concatenate((self.airfoil_distribution, aerodynamics_to_add.airfoil_distribution), axis=0) self.airfoil_distribution = np.concatenate((self.airfoil_distribution, aerodynamics_to_add.airfoil_distribution + total_num_airfoils), axis=0) # TODO: this should NOT be needed according to SHARPy input files. Modify at some point if (self.airfoils.shape[1] == aerodynamics_to_add.airfoils.shape[1]): self.airfoils = np.concatenate((self.airfoils, aerodynamics_to_add.airfoils), axis=0) elif (self.airfoils.shape[1] > aerodynamics_to_add.airfoils.shape[1]): cout.cout_wrap("WARNING: redefining the discretization of airfoil camber line", 3) new_airfoils = self.change_airfoils_discretezation(aerodynamics_to_add.airfoils, self.airfoils.shape[1]) self.airfoils = np.concatenate((self.airfoils, new_airfoils), axis=0) elif (self.airfoils.shape[1] < aerodynamics_to_add.airfoils.shape[1]): cout.cout_wrap("WARNING: redefining the discretization of airfoil camber line", 3) new_airfoils = self.change_airfoils_discretezation(self.airfoils, aerodynamics_to_add.airfoils.shape[1]) self.airfoils = np.concatenate((new_airfoils, aerodynamics_to_add.airfoils), axis=0) if self.m_distribution.lower() == 'user_defined': self.user_defined_m_distribution = self.user_defined_m_distribution + aerodynamics_to_add.user_defined_m_distribution if self.polars is not None: self.polars = np.array([self.polars, aerodynamics_to_add.polars]) total_num_airfoils += len(aerodynamics_to_add.airfoils[:, 0, 0]) # total_num_surfaces += len(aerodynamics_to_add.surface_m) total_num_surfaces += np.sum(aerodynamics_to_add.surface_m != -1) self.first_twist.extend(aerodynamics_to_add.first_twist) # self.num_airfoils = total_num_airfoils # self.num_surfaces = total_num_surfaces def interpolate_airfoils_camber(self, pure_airfoils_camber, r_pure_airfoils, r, n_points_camber): """ interpolate_airfoils_camber Create the camber of the airfoil at each node position from the camber of the pure airfoils present in the blade Args: pure_airfoils_camber (np.array): xy coordinates of the camber lines of the pure airfoils r_pure_airfoils (np.array): radial position of the pure airfoils r (np.array): radial positions to compute the camber lines through linear interpolation Returns: airfoils_camber (np.array): camber lines at the new radial positions """ num_node = len(r) airfoils_camber = np.zeros((num_node, n_points_camber, 2),) for inode in range(num_node): # camber_x, camber_y = get_airfoil_camber(x,y) iairfoil = 0 while(r[inode] > r_pure_airfoils[iairfoil]): iairfoil += 1 if(iairfoil == len(r_pure_airfoils)): iairfoil -= 1 break # print("interpolating between: ", iairfoil-1, "and", iairfoil) beta = min((r[inode]-r_pure_airfoils[iairfoil-1])/(r_pure_airfoils[iairfoil]-r_pure_airfoils[iairfoil-1]), 1.0) beta = max(0.0, beta) airfoils_camber[inode, :, 0] = (1 - beta)*pure_airfoils_camber[iairfoil-1, :, 0] + beta*pure_airfoils_camber[iairfoil, :, 0] airfoils_camber[inode, :, 1] = (1 - beta)*pure_airfoils_camber[iairfoil-1, :, 1] + beta*pure_airfoils_camber[iairfoil, :, 1] return airfoils_camber def interpolate_airfoils_camber_thickness(self, pure_airfoils_camber, thickness_pure_airfoils, blade_thickness, n_points_camber): """ interpolate_airfoils_camber_thickness Create the camber of the airfoil at each node position from the camber of the pure airfoils present in the blade based on the thickness Args: pure_airfoils_camber (np.array): xy coordinates of the camber lines of the pure airfoils thicknesss_pure_airfoils (np.array): thickness of the pure airfoils blade_thickness (np.array): thickness of the blade positions Returns: airfoils_camber (np.array): camber lines at the new radial positions """ num_node = len(blade_thickness) airfoils_camber = np.zeros((num_node, n_points_camber, 2),) for inode in range(num_node): # camber_x, camber_y = get_airfoil_camber(x,y) iairfoil = 0 while(blade_thickness[inode] < thickness_pure_airfoils[iairfoil]): iairfoil += 1 if(iairfoil == len(thickness_pure_airfoils)): iairfoil -= 1 break beta = min((blade_thickness[inode] - thickness_pure_airfoils[iairfoil - 1])/(thickness_pure_airfoils[iairfoil] - thickness_pure_airfoils[iairfoil - 1]), 1.0) beta = max(0.0, beta) airfoils_camber[inode, :, 0] = (1 - beta)*pure_airfoils_camber[iairfoil-1, :, 0] + beta*pure_airfoils_camber[iairfoil, :, 0] airfoils_camber[inode, :, 1] = (1 - beta)*pure_airfoils_camber[iairfoil-1, :, 1] + beta*pure_airfoils_camber[iairfoil, :, 1] return airfoils_camber def check_AerodynamicInformation(self, StructuralInformation): """ check_AerodynamicInformation Check some properties of the AerodynamicInformation() Notes: These conditions have to be to correctly define a case but they are not the only ones """ # CHECKING if(self.aero_node.shape[0] != StructuralInformation.num_node): raise RuntimeError("ERROR: Aero node must be defined for each node") if(self.airfoil_distribution.shape[0] != StructuralInformation.num_elem or self.airfoil_distribution.shape[1] != StructuralInformation.num_node_elem): raise RuntimeError("ERROR: Airfoil distribution must be defined for each element/local node") if(self.chord.shape[0] != StructuralInformation.num_elem): raise RuntimeError("ERROR: The first dimension of the chord matrix does not match the number of elements") if(self.chord.shape[1] != StructuralInformation.num_node_elem): raise RuntimeError("ERROR: The second dimension of the chord matrix does not match the number of nodes per element") if(self.elastic_axis.shape[0] != StructuralInformation.num_elem): raise RuntimeError("ERROR: The first dimension of the elastic axis matrix does not match the number of elements") if(self.elastic_axis.shape[1] != StructuralInformation.num_node_elem): raise RuntimeError("ERROR: The second dimension of the elastic axis matrix does not match the number of nodes per element") if(self.surface_distribution.shape[0] != StructuralInformation.num_elem): raise RuntimeError("ERROR: The surface distribution must be defined for each element") if(self.twist.shape[0] != StructuralInformation.num_elem): raise RuntimeError("ERROR: The first dimension of the aerodynamic twist does not match the number of elements") if(self.twist.shape[1] != StructuralInformation.num_node_elem): raise RuntimeError("ERROR: The second dimension of the aerodynamic twist does not match the number nodes per element") default = AerodynamicInformation() for attr, value in self.__dict__.items(): if not hasattr(default, attr): raise RuntimeError(("AerodynamicInformation has no attribute named '%s'" % attr)) def generate_aero_file(self, route, case_name, StructuralInformation): """ generate_aero_file Writes the h5 file with the aerodynamic information Args: route (string): path of the case case_name (string): name of the case """ self.check_AerodynamicInformation(StructuralInformation) with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: h5file.create_dataset('aero_node', data=self.aero_node) chord_input = h5file.create_dataset('chord', data=self.chord) chord_input .attrs['units'] = 'm' twist_input = h5file.create_dataset('twist', data=self.twist) twist_input.attrs['units'] = 'rad' h5file.create_dataset('surface_m', data=self.surface_m) h5file.create_dataset('surface_distribution', data=self.surface_distribution) h5file.create_dataset('m_distribution', data=self.m_distribution.encode('ascii', 'ignore')) h5file.create_dataset('elastic_axis', data=self.elastic_axis) h5file.create_dataset('airfoil_distribution', data=self.airfoil_distribution) h5file.create_dataset('sweep', data=self.sweep) h5file.create_dataset('first_twist', data=np.array(self.first_twist)) airfoils_group = h5file.create_group('airfoils') for iairfoil in range(len(self.airfoils)): airfoils_group.create_dataset("%d" % iairfoil, data=self.airfoils[iairfoil, :, :]) if self.polars is not None: polars_group = h5file.create_group('polars') for iairfoil in range(len(self.airfoils)): polars_group.create_dataset("%d" % iairfoil, data=self.polars[iairfoil]) if self.m_distribution.lower() == 'user_defined': udmd_group = h5file.create_group('user_defined_m_distribution') for isurf in range(len(self.user_defined_m_distribution)): udmd_group.create_dataset("%d" % isurf, data=self.user_defined_m_distribution[isurf]) # control_surface_input = h5file.create_dataset('control_surface', data=control_surface) # control_surface_deflection_input = h5file.create_dataset('control_surface_deflection', data=control_surface_deflection) # control_surface_chord_input = h5file.create_dataset('control_surface_chord', data=control_surface_chord) # control_surface_types_input = h5file.create_dataset('control_surface_type', data=control_surface_type) ###################################################################### ############### BLADE AEROELASTIC INFORMATION ###################### ###################################################################### class AeroelasticInformation(): """ AeroelasticInformation Structural and aerodynamic information needed to build a case """ def __init__(self): """ __init__ Initialization """ self.StructuralInformation = StructuralInformation() self.AerodynamicInformation = AerodynamicInformation() def generate(self, StructuralInformation, AerodynamicInformation): """ generate Generates an object from the structural and the aerodynamic information Args: StructuralInformation (StructuralInformation): structural information AerodynamicInformation (AerodynamicInformation): aerodynamic information """ self.StructuralInformation = StructuralInformation.copy() self.AerodynamicInformation = AerodynamicInformation.copy() def assembly(self, *args): """ assembly This function concatenates structures and aerodynamic properties to be writen in the same h5 File Args: *args: list of AeroelasticInformation() to be meged into 'self' Notes: """ list_of_SI = [] list_of_AI = [] for AEI in args: list_of_SI.append(AEI.StructuralInformation) list_of_AI.append(AEI.AerodynamicInformation) self.StructuralInformation.assembly_structures(*list_of_SI) self.AerodynamicInformation.assembly_aerodynamics(*list_of_AI) def remove_duplicated_points(self, tol, skip=[]): """ remove_duplicated_points Removes the points that are closer than 'tol' and modifies the aeroelastic information accordingly Args: tol (float): tolerance. Maximum distance between nodes to be merged skip (list): nodes to keep (do not remove) Notes: This function will not work if an element or an aerdoynamic surface is completely eliminated This function only checks geometrical proximity, not aeroelastic properties as a merging criteria """ def find_connectivities_index(connectivities, iprev_node): icon = 0 jcon = 0 found = False while ((icon < connectivities.shape[0]) and (not found)): jcon = 0 while ((jcon < connectivities.shape[1]) and (not found)): if connectivities[icon, jcon] == iprev_node: found = True jcon += 1 icon += 1 return icon-1, jcon-1 replace_matrix = ( np.zeros((self.StructuralInformation.num_node, 4), dtype=int) - np.array([1, 1, 1, 0])) # Fill the first three columns for inode in range(self.StructuralInformation.num_node): for iprev_node in range(inode): if ((np.linalg.norm( self.StructuralInformation.coordinates[inode, :] - self.StructuralInformation.coordinates[iprev_node, :]) < tol) and ( inode not in skip)): cout.cout_wrap(("WARNING: Replacing node %d by node %d" % (inode, iprev_node)), 3) replace_matrix[inode, 0] = iprev_node replace_matrix[inode, 1], replace_matrix[inode, 2] = ( find_connectivities_index( self.StructuralInformation.connectivities, iprev_node)) break # Fill the last column for inode in range(self.StructuralInformation.num_node): if not (replace_matrix[inode, 0] == -1): # 'inode' is a node to be replaced replace_matrix[inode, 3] = -1 for inode2 in range(inode + 1, self.StructuralInformation.num_node): if not (replace_matrix[inode2, 0] == -1): # 'inode2' is also a node to be replaced replace_matrix[inode2, 3] = -1 else: replace_matrix[inode2, 3] += 1 nodes_to_keep = replace_matrix[:, 0] == -1 # Delete the structural variables associated to the node self.StructuralInformation.coordinates = ( self.StructuralInformation.coordinates[nodes_to_keep, :]) # self.StructuralInformation.elem_stiffness = ( # self.StructuralInformation.elem_stiffness[nodes_to_keep]) # self.StructuralInformation.elem_mass = ( # self.StructuralInformation.elem_mass[nodes_to_keep]) # self.StructuralInformation.structural_twist = ( # self.StructuralInformation.structural_twist[nodes_to_keep]) self.StructuralInformation.boundary_conditions = ( self.StructuralInformation.boundary_conditions[nodes_to_keep]) # self.StructuralInformation.beam_number = ( # self.StructuralInformation.beam_number[nodes_to_keep]) self.StructuralInformation.app_forces = ( self.StructuralInformation.app_forces[nodes_to_keep, :]) # self.StructuralInformation.frame_of_reference_delta = ( # self.StructuralInformation.frame_of_reference_delta[nodes_to_keep,:,:]) self.AerodynamicInformation.aero_node = ( self.AerodynamicInformation.aero_node[nodes_to_keep]) # Delete lumped masses if isinstance(self.StructuralInformation.lumped_mass_nodes, np.ndarray): lumped_to_keep = nodes_to_keep[self.StructuralInformation.lumped_mass_nodes] self.StructuralInformation.lumped_mass_nodes = ( self.StructuralInformation.lumped_mass_nodes[lumped_to_keep]) self.StructuralInformation.lumped_mass = ( self.StructuralInformation.lumped_mass[lumped_to_keep]) self.StructuralInformation.lumped_mass_inertia = ( self.StructuralInformation.lumped_mass_inertia[lumped_to_keep]) self.StructuralInformation.lumped_mass_position = ( self.StructuralInformation.lumped_mass_position[lumped_to_keep, :]) # Modify connectivities and matrices in ielem,inode_in_elem shape for icon in range(self.StructuralInformation.num_elem): for jcon in range(self.StructuralInformation.num_node_elem): inode = self.StructuralInformation.connectivities[icon, jcon] if not replace_matrix[inode, 0] == -1: # inode to be replaced self.StructuralInformation.connectivities[icon, jcon] = self.StructuralInformation.connectivities[replace_matrix[inode, 1], replace_matrix[inode, 2]] self.StructuralInformation.structural_twist[icon, jcon] = ( self.StructuralInformation.structural_twist[replace_matrix[inode, 1], replace_matrix[inode, 2]]) self.AerodynamicInformation.chord[icon, jcon] = ( self.AerodynamicInformation.chord[replace_matrix[inode, 1], replace_matrix[inode, 2]]) self.AerodynamicInformation.twist[icon, jcon] = ( self.AerodynamicInformation.twist[replace_matrix[inode, 1], replace_matrix[inode, 2]]) self.AerodynamicInformation.sweep[icon, jcon] = ( self.AerodynamicInformation.sweep[replace_matrix[inode, 1], replace_matrix[inode, 2]]) self.AerodynamicInformation.elastic_axis[icon, jcon] = ( self.AerodynamicInformation.elastic_axis[replace_matrix[inode, 1], replace_matrix[inode, 2]]) self.AerodynamicInformation.airfoil_distribution[icon, jcon] = ( self.AerodynamicInformation.airfoil_distribution[replace_matrix[inode, 1], replace_matrix[inode, 2]]) else: # inode NOT to be replace self.StructuralInformation.connectivities[icon, jcon] -= replace_matrix[inode, 3] if isinstance(self.StructuralInformation.lumped_mass_nodes, np.ndarray): for ilumped in range(len(self.StructuralInformation.lumped_mass_nodes)): inode = self.StructuralInformation.lumped_mass_nodes[ilumped] if not replace_matrix[inode, 0] == -1: self.StructuralInformation.lumped_mass_nodes[ilumped] = self.StructuralInformation.connectivities[replace_matrix[inode, 1], replace_matrix[inode, 2]] else: self.StructuralInformation.lumped_mass_nodes[ilumped] -= replace_matrix[inode, 3] self.StructuralInformation.num_node = nodes_to_keep.sum() # TODO: this function will not work if elements or aerodynamic surfaces are completely eliminated def copy(self): """ copy Returns a copy of the object Returns: copied(AeroelasticInformation): new object with the same properties """ copied = AeroelasticInformation() copied.StructuralInformation = self.StructuralInformation.copy() copied.AerodynamicInformation = self.AerodynamicInformation.copy() return copied def check(self): default = AeroelasticInformation() for attr, value in self.__dict__.items(): if not hasattr(default, attr): raise RuntimeError(("AeroelasticInformation has no attribute named '%s'" % attr)) def generate_h5_files(self, route, case_name): """ write_h5_files Writes the structural and aerodynamic h5 files """ self.check() self.StructuralInformation.generate_fem_file(route, case_name) self.AerodynamicInformation.generate_aero_file(route, case_name, self.StructuralInformation) ###################################################################### ###################### SOLVERS INFORMATION ######################### ###################################################################### class SimulationInformation(): """ SimulationInformation Simulation information needed to build a case """ def __init__(self): """ __init__ Initialization """ self.solvers = dict() # self.preprocessors = dict() self.postprocessors = dict() self.with_dynamic_forces = False self.dynamic_forces = None self.with_forced_vel = False self.for_vel = None self.for_acc = None def set_default_values(self): """ set_default_values Set the default values for all the solvers """ self.solvers = dict() aux_solvers = solver_interface.dictionary_of_solvers(print_info=False) aux_solvers.update(generator_interface.dictionary_of_generators(print_info=False)) aux_solvers.update(controller_interface.dictionary_of_controllers(print_info=False)) for solver in aux_solvers: if solver == 'PreSharpy': solver_name = 'SHARPy' else: solver_name = solver self.solvers[solver_name] = deepcopy(aux_solvers[solver]) if solver in ['GridLoader', 'NonliftingbodygridLoader']: # Skip this solver as no default values for GridLoader exist. continue def check(self): default = SimulationInformation() default.set_default_values() for solver in self.solvers['SHARPy']['flow']: for key in self.solvers[solver]: if key not in default.solvers[solver]: raise RuntimeError(("solver '%s' has no key named '%s'" % (solver, key))) def define_num_steps(self, num_steps): """ define_num_steps Set the number of steps in the simulation for all the solvers Args: num_steps (int): number of steps """ for solver in self.solvers: if 'n_time_steps' in self.solvers[solver]: self.solvers[solver]['n_time_steps'] = num_steps if 'num_steps' in self.solvers[solver]: self.solvers[solver]['num_steps'] = num_steps def define_uinf(self, unit_vector, norm): """ define_uinf Set the inflow velocity in the simulation for all the solvers Args: unit_vector (np.array): direction of the inflow velocity norm (float): Norm of the inflow velocity """ # Make sure uinf = unit_vector*norm norm = np.linalg.norm(uinf) unit_vector = uinf / norm self.solvers['AerogridLoader']['freestream_dir'] = unit_vector self.solvers['AerogridPlot']['u_inf'] = norm self.solvers['SteadyVelocityField']['u_inf'] = norm self.solvers['SteadyVelocityField']['u_inf_direction'] = unit_vector # self.solvers['StaticUvlm']['velocity_field_input'] = {'u_inf': norm, # 'u_inf_direction': unit_vector} # self.solvers['StepUvlm']['velocity_field_input'] = {'u_inf': norm, # 'u_inf_direction': unit_vector} def set_variable_all_dicts(self, variable, set_value): """ set_variable_all_dicts Defines the value of a variable in all the available solvers Args: variable (str): variable name set_value ( ): value """ set_variable_dict(self.solvers, variable, set_value) def generate_solver_file(self): """ generate_solver_file Generates the solver file Args: route (string): path of the case case_name (string): name of the case """ import configobj self.check() config = configobj.ConfigObj() config.filename = self.solvers['SHARPy']['route'] + '/' + self.solvers['SHARPy']['case'] + '.sharpy' config['SHARPy'] = self.solvers['SHARPy'] # for k, v in self.solvers['SHARPy'].items(): # config[k] = v # Loop through the solvers defined in "flow" and write them for solver in self.solvers['SHARPy']['flow']: config[solver] = self.solvers[solver] config.write() def generate_dyn_file(self, num_steps): """ generate_dyn_file Generates the dynamic file Args: route (string): path of the case case_name (string): name of the case num_steps (int): number of steps """ with h5.File(self.solvers['SHARPy']['route'] + '/' + self.solvers['SHARPy']['case'] + '.dyn.h5', 'a') as h5file: if self.with_dynamic_forces: h5file.create_dataset( 'dynamic_forces', data=self.dynamic_forces) if self.with_forced_vel: # TODO: check coherence velocity-acceleration h5file.create_dataset( 'for_vel', data=self.for_vel) h5file.create_dataset( 'for_acc', data=self.for_acc) h5file.create_dataset( 'num_steps', data=num_steps) ###################################################################### ######################### CLEAN FILES ############################## ###################################################################### def clean_test_files(route, case_name): """ clean_test_files Removes the previous h5 files Args: route (string): path of the case case_name (string): name of the case """ fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) dyn_file_name = route + '/' + case_name + '.dyn.h5' if os.path.isfile(dyn_file_name): os.remove(dyn_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) lagrange_file_name = route + '/' + case_name + '.mb.h5' if os.path.isfile(lagrange_file_name): os.remove(lagrange_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) ###################################################################### ##################### MULTIBODY INFORMATION ######################## ###################################################################### class BodyInformation(): def __init__(self): self.body_number = None self.FoR_position = None self.FoR_velocity = None self.FoR_acceleration = None self.FoR_movement = None self.quat = None def copy(self): copied = BodyInformation() copied.body_number = self.body_number copied.FoR_position = self.FoR_position.astype(dtype=float, copy=True) copied.FoR_velocity = self.FoR_velocity.astype(dtype=float, copy=True) copied.FoR_acceleration = self.FoR_acceleration.astype(dtype=float, copy=True) copied.FoR_movement = self.FoR_movement copied.quat = self.quat.astype(dtype=float, copy=True) def check(self): default = BodyInformation() for attr, value in self.__dict__.items(): if not hasattr(default, attr): raise RuntimeError(("BodyInformation has no attribute named '%s'" % attr)) class LagrangeConstraint(): def __init__(self): # Shared by all boundary conditions self.behaviour = None # Each BC will have its own variables def check(self): default = lagrangeconstraints.lc_from_string(self.behaviour) default.__init__(default) required_parameters = default.required_parameters for param in required_parameters: try: getattr(self, param) except: raise RuntimeError(("'%s' parameter required in '%s' lagrange constraint" % (param, self.behaviour))) has_behaviour = False for param, value in self.__dict__.items(): if not param in ['behaviour', 'scalingFactor', 'penaltyFactor', 'rot_axisA2']: if param not in required_parameters: raise RuntimeError(("'%s' parameter is not required in '%s' lagrange constraint" % (param, self.behaviour))) if param == 'behaviour': has_behaviour = True if not has_behaviour: raise RuntimeError(("'behaviour' parameter is required in '%s' lagrange constraint" % self.behaviour)) def generate_multibody_file(list_LagrangeConstraints, list_Bodies, route, case_name, use_jax=False): # Check for body in list_Bodies: body.check() if not use_jax: for lc in list_LagrangeConstraints: lc.check() with h5.File(route + '/' + case_name + '.mb.h5', 'a') as h5file: # Write the constraints h5file.create_dataset('num_constraints', data=len(list_LagrangeConstraints)) iconstraint = 0 for constraint in list_LagrangeConstraints: # Create the group associated to the constraint constraint_id = h5file.create_group('constraint_%02d' % iconstraint) # Write general parameters constraint_id.create_dataset("behaviour", data=constraint.behaviour.encode('ascii', 'ignore')) # I branch depending on if you use the JAX solver or not # this is beneficial where a constraint is implemented in one solver but not the other if use_jax: required_parameters = DICT_OF_LC[constraint.behaviour].required_params else: # Write parameters associated to the specific type of boundary condition default = lagrangeconstraints.lc_from_string(constraint.behaviour) default.__init__(default) required_parameters = default.required_parameters for param in required_parameters: constraint_id.create_dataset(param, data=getattr(constraint, param)) try: constraint_id.create_dataset("scalingFactor", data=constraint.scalingFactor) except AttributeError: pass try: constraint_id.create_dataset("penaltyFactor", data=constraint.penaltyFactor) except AttributeError: pass try: constraint_id.create_dataset("aerogrid_warp_factor", data=constraint.aerogrid_warp_factor) except AttributeError: pass try: constraint_id.create_dataset("rot_axisA2", data=constraint.rot_axisA2) except AttributeError: pass iconstraint += 1 # Write the body information h5file.create_dataset('num_bodies', data=len(list_Bodies)) ibody = 0 for body in list_Bodies: # Create the group associated to the body body_id = h5file.create_group('body_%02d' % ibody) # Write general parameters body_id.create_dataset("body_number", data=body.body_number) body_id.create_dataset("FoR_position", data=body.FoR_position) body_id.create_dataset("FoR_velocity", data=body.FoR_velocity) body_id.create_dataset("FoR_acceleration", data=body.FoR_acceleration) body_id.create_dataset("FoR_movement", data=body.FoR_movement) body_id.create_dataset("quat", data=body.quat) ibody += 1 ================================================ FILE: sharpy/utils/generator_interface.py ================================================ """Generator Interface """ from abc import ABCMeta, abstractmethod import sharpy.utils.cout_utils as cout import os import shutil dict_of_generators = {} generators = {} # for internal working # decorator def generator(arg): # global available_solvers global dict_of_generators try: arg.generator_id except AttributeError: raise AttributeError('Class defined as generator has no generator_id attribute') dict_of_generators[arg.generator_id] = arg return arg def print_available_generators(): cout.cout_wrap('The available generators on this session are:', 2) for name, i_generator in dict_of_generators.items(): cout.cout_wrap('%s ' % i_generator.generator_id, 2) class BaseGenerator(metaclass=ABCMeta): pass def generator_from_string(string): return dict_of_generators[string] def generator_list_from_path(cwd): onlyfiles = [f for f in os.listdir(cwd) if os.path.isfile(os.path.join(cwd, f))] for i_file in range(len(onlyfiles)): if onlyfiles[i_file].split('.')[-1] == 'py': # support autosaved files in the folder if onlyfiles[i_file] == "__init__.py": onlyfiles[i_file] = "" continue onlyfiles[i_file] = onlyfiles[i_file].replace('.py', '') else: onlyfiles[i_file] = "" files = [file for file in onlyfiles if not file == ""] return files def initialise_generator(generator_name, print_info=True): if print_info: cout.cout_wrap('Generating an instance of %s' % generator_name, 2) cls_type = generator_from_string(generator_name) gen = cls_type() return gen def dictionary_of_generators(print_info=True): import sharpy.generators dictionary = dict() for gen in dict_of_generators: init_gen = initialise_generator(gen, print_info) dictionary[gen] = init_gen.settings_default return dictionary def output_documentation(route=None): """ Creates the ``.rst`` files for the generators that have a docstring such that they can be parsed to Sphinx Args: route (str): Path to folder where generator files are to be created. """ import sharpy.utils.sharpydir as sharpydir if route is None: route = sharpydir.SharpyDir + '/docs/source/includes/generators/' if os.path.exists(route): print('Cleaning %s' %route) shutil.rmtree(route) print('Creating documentation files for generators in %s' % route) created_generators = dict() for k, v in dict_of_generators.items(): if k[0] == '_': continue generator_class = v() created_generators[k] = generator_class filename = k + '.rst' # try: # solver_folder = solver.solver_classification.lower() # except AttributeError: # solver_folder = 'other' # # if solver_folder not in solver_types: # solver_types.append(solver_folder) os.makedirs(route + '/', exist_ok=True) title = k + '\n' title += len(k)*'-' + 2*'\n' if generator_class.__doc__ is not None: print('\tCreating %s' % (route + '/' + filename)) autodoc_string = '\n\n.. autoclass:: sharpy.generators.' + k.lower() + '.'+ k + '\n\t:members:' with open(route + '/' + filename, "w") as out_file: out_file.write(title + autodoc_string) # # Creates index files depending on the type of solver # filename = 'generators.rst' # title = 'Velocity Field Generators' # title += '\n' + len(title)*'+' + 2*'\n' # with open(route + '/' + filename, "w") as out_file: # out_file.write(title) # out_file.write('.. toctree::' + '\n') # for k in dict_of_generators.keys(): # if k[0] == '_': # continue # out_file.write(' ./' + k + '\n') ================================================ FILE: sharpy/utils/geo_utils.py ================================================ """Airfoil Geometry Utils """ import numpy as np def generate_naca_camber(M=0, P=0): """ Defines the x and y coordinates of a 4-digit NACA profile's camber line (i.e no thickness). The NACA 4-series airfoils follow the nomenclature: NACA MPTT where: * M indicates the maximum camber :math:`M = 100m` * P indicates the position of the maximum camber :math:`P=10p` * TT indicates the thickness to chord ratio :math:`TT=(t/c)*100` Args: M (float): maximum camber times 100 (i.e. the first of the 4 digits) P (float): position of the maximum camber times 10 (i.e. the second of the 4 digits) Returns: (x_vec,y_vec): ``x`` and ``y`` coordinates of the chosen airfoil Example: The NACA2400 airfoil would have 2% camber with the maximum at 40% of the chord and 0 thickness. To plot the camber line one would use this function as: ``x_vec, y_vec = generate_naca_camber(M = 2, P = 4)`` """ m = M * 1e-2 p = P * 1e-1 def naca(x, m, p): if x < 1e-6: return 0.0 elif x < p: return m / (p * p) * (2 * p * x - x * x) elif x > p and x < 1 + 1e-6: return m / ((1 - p) * (1 - p)) * (1 - 2 * p + 2 * p * x - x * x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, m, p) for x in x_vec]) return x_vec, y_vec def interpolate_naca_camber(eta, M00, P00, M01, P01): """ Interpolate aerofoil camber at non-dimensional coordinate eta in (0,1), where (M00,P00) and (M01,P01) define the camber properties at eta=0 and eta=1 respectively. Notes: For two surfaces, eta can be in (-1,1). In this case, the root is eta=0 and the tips are at eta=+-1. """ # define domain eta = np.abs(eta) assert np.max(eta) < 1. + 1e-16, 'eta exceeding +/- 1!' # define reference x00, y00 = generate_naca_camber(M00, P00) x01, y01 = generate_naca_camber(M01, P01) # interpolate x_vec = x00 * (1. - eta) + x01 * eta y_vec = y00 * (1. - eta) + y01 * eta return x_vec, y_vec if __name__ == '__main__': a = 1 ================================================ FILE: sharpy/utils/h5utils.py ================================================ # Alfonso del Carre # alfonso.del-carre14@imperial.ac.uk # Imperial College London # LoCA lab # 28 Sept 2016 """H5 File Management Utilities Set of utilities for opening/reading files """ import h5py as h5 import os import errno import numpy as np import warnings from numpy import ndarray, float64, float32, array, int32, int64 import ctypes as ct BasicNumTypes = (float, float32, float64, int, int32, int64, complex) def check_file_exists(file_name): """ Checks if the file exists and throws a FileNotFoundError exception that includes the route to the non-existing file. Args: file_name (str): path to the HDF5 file Returns: FileNotFoundError : if the file does not exist, an error is raised with path to the non-existent file """ if not os.path.isfile(file_name): raise FileNotFoundError( errno.ENOENT, os.strerror(errno.ENOENT), file_name) def load_h5_in_dict(handle, path='/'): dictionary = {} for k, i in handle[path].items(): if isinstance(i, h5._hl.dataset.Dataset): dictionary[k] = i[()] elif isinstance(i, h5._hl.group.Group): dictionary[k] = load_h5_in_dict(handle, path + k + '/') if len(i.attrs.items()): dictionary['Attributes'] = load_attributes(handle, path + k + '/') return dictionary def load_attributes(handle, path): attributes = [] for k, i in handle[path].attrs.items(): attributes.append((k, i)) return attributes def check_fem_dict(fem_dict): print('\tRunning tests for the FEM input file...', end='') (num_elem_dict, num_node_elem_dict) = np.shape(fem_dict['connectivities']) num_elem = fem_dict['num_elem'] num_node_elem = fem_dict['num_node_elem'] if not ((num_elem == num_elem_dict) or (num_node_elem == num_node_elem_dict)): raise Exception('ERROR: FEM input file is not consistent') else: print(' PASSED') def check_data_dict(data_dict): pass # --------------------------------------------------------------- Reading tools def readh5(filename, GroupName=None): """ Read the HDF5 file 'filename' into a class. Groups within the hdf5 file are by default loaded as sub classes, unless they include a _read_as attribute (see sharpy.postproc.savedata). In this case, group can be loaded as classes, dictionaries, lists or tuples. filename: string to file location GroupName = string or list of strings. Default is None: if given, allows reading a specific group h5 file. Warning: Groups that need to be read as lists and tuples are assumed to conform to the format used in sharpy.postproc.savedata """ Hinst = ReadInto() ### read and scan file hdfile = h5.File(filename, 'r') NamesList = [] # dataset names hdfile.visit(NamesList.append) ### Identify higher level groups / attributes if GroupName is None: MainLev = [] for name in NamesList: if '/' not in name: MainLev.append(name) else: if type(GroupName) is list: MainLev = GroupName else: MainLev = [GroupName] ### Loop through higher level for name in MainLev: # sub-group if type(hdfile[name]) is h5._hl.group.Group: Ginst = read_group(hdfile[name]) try: Ginst.name = name except: pass setattr(Hinst, name, Ginst) else: setattr(Hinst, name, hdfile[name][()]) # close and return hdfile.close() return Hinst def read_group(Grp): """ Read an hdf5 group """ NamesList = [] Grp.visit(NamesList.append) ### identify higher level MainLev = [] for name in NamesList: if '/' not in name: MainLev.append(name) ### determine output format read_as = 'class' if '_read_as' in MainLev: read_as = Grp['_read_as'][()] ### initialise output if read_as == 'class': Hinst = ReadInto() elif read_as == 'dict': Hinst = {} elif read_as == 'list' or read_as == 'tuple': Hinst = [] ### Loop through higher level if read_as == 'list' or read_as == 'tuple': if '_as_array' in MainLev: Hinst = list(Grp['_as_array'][()]) else: N = len(MainLev) - 1 list_ts = MainLev.copy() list_ts.remove('_read_as') list_ts = np.sort(np.unique(np.array(list_ts, dtype=np.int))) if len(list_ts > 0): for nn in range(list_ts[0] - 1): Hinst.append('NoneType') for nn in list_ts: name = '%.5d' % nn ### extract value if type(Grp[name]) is h5._hl.group.Group: value = read_group(Grp[name]) else: value = Grp[name][()] Hinst.append(value) if read_as == 'tuple': tuple(Hinst) else: for name in MainLev: if name == '_read_as': continue ### extract value if type(Grp[name]) is h5._hl.group.Group: value = read_group(Grp[name]) else: value = Grp[name][()] ### allocate if read_as == 'class': setattr(Hinst, name, value) else: Hinst[name] = value return Hinst class ReadInto: def __init__(self, name='ReadInto'): self._name = name pass # ---------------------------------------------------------------- Saving tools def saveh5(savedir, h5filename, *class_inst, permission='a', ClassesToSave=()): """ Creates h5filename and saves all the classes specified in class_inst Args savedir: target directory h5filename: file name class_inst: a number of classes to save permission=['a','w']: append or overwrite, according to h5py.File ClassesToSave: if the classes in class_inst contain sub-classes, these will be saved only if instances of the classes in this list """ h5filename = os.path.join(savedir, h5filename) hdfile = h5.File(h5filename, permission) for cc in class_inst: add_as_grp(cc, hdfile, ClassesToSave=ClassesToSave) hdfile.close() return None def add_as_grp(obj, grpParent, grpname=None, ClassesToSave=(), SkipAttr=[], compress_float=False, overwrite=False): """ Given a class, dictionary, list or tuples instance 'obj', the routine adds it as a sub-group of name grpname to the parent group grpParent. An attribute _read_as, specifying the type of obj, is added to the group so as to allow reading correctly the h5 file. Usage and Remarks: - if obj contains dictionaries, listes or tuples, these are automatically saved - if list only contains scalars or arrays of the same dimension, this will be saved as a numpy array - if obj contains classes, only those that are instances of the classes specified in ClassesToSave will be saved - If grpParent already contains a sub-group with name grpname, this will not be overwritten. However, pre-existing attributes of the sub-group will be overwritten if obj contains attrributes with the same names. - attributes belonging to SkipAttr will not be saved - This functionality needs improving - if compress_float is True, numpy arrays will be saved in single precisions. """ ### determine if dict, list, tuple or class if isinstance(obj, list): ObjType = 'list' elif isinstance(obj, tuple): ObjType = 'tuple' elif isinstance(obj, dict): ObjType = 'dict' elif hasattr(obj, '__class__'): ObjType = 'class' else: raise NameError('object type not supported') ### determine sub-group name (only classes) if grpname is None: if ObjType == 'class': if hasattr(obj, '_name'): grpname = obj._name else: grpname = obj.__class__.__name__ else: raise NameError('grpname must be specified for dict,list and tuples') ### Create group (if necessary) if not (grpname in grpParent): grp = grpParent.create_group(grpname) grp['_read_as'] = ObjType else: if overwrite: del grpParent[grpname] grp = grpParent.create_group(grpname) grp['_read_as'] = ObjType else: grp = grpParent[grpname] assert grp['_read_as'][()] == ObjType, \ 'Can not overwrite group of different type' ### lists/tuples only: try to save as arrays if ObjType in ('list', 'tuple'): Success = save_list_as_array( list_obj=obj, grp_target=grp, compress_float=compress_float) if Success: return grpParent ### create/retrieve dictionary of attributes/elements to be saved if ObjType == 'dict': dictname = obj elif ObjType == 'class': dictname = obj.__dict__ else: N = len(obj) dictname = {} for nn in range(N): dictname['%.5d' % nn] = obj[nn] ### loop attributes and save SaveAsGroups = ClassesToSave + (list, dict, tuple,) for attr in dictname: if attr in SkipAttr: continue # ----- extract value & type value = dictname[attr] vtype = type(value) # ----- classes/dict/lists # ps: no need to delete if overwrite is True if isinstance(value, SaveAsGroups): add_as_grp(value, grp, attr, ClassesToSave, SkipAttr, compress_float, overwrite) continue # ----- if attr already in grp always overwrite if attr in grp: del grp[attr] # ----- Basic types if isinstance(value, BasicNumTypes + (str, bytes)): grp[attr] = value continue # c_types if isinstance(value, (ct.c_bool, ct.c_double, ct.c_int)): value = value.value grp[attr] = value continue # ndarrays if isinstance(value, ndarray): add_array_to_grp(value, attr, grp, compress_float) continue # ----- Special if value == None: grp[attr] = 'NoneType' continue grp[attr] = 'not saved' return grpParent def add_array_to_grp(data, name, grp, compress_float=False): """ Add numpy array (data) as dataset 'name' to the group grp. If compress is True, 64-bit float arrays are converted to 32-bit """ if compress_float and data.dtype == float64: # embed() grp.create_dataset(name, data=data, dtype='f4') else: grp[name] = data return grp def save_list_as_array(list_obj, grp_target, compress_float=False): """ Works for both lists and tuples. Returns True if the saving was successful. """ N = len(list_obj) if N > 0: type0 = type(list_obj[0]) if type0 in (BasicNumTypes + (str, ndarray)): SaveAsArray = True for nn in range(N): if type(list_obj[nn]) != type0: SaveAsArray = False break if type(list_obj[nn]) == ndarray: if list_obj[0].shape != list_obj[nn].shape: SaveAsArray = False break if SaveAsArray: if '_as_array' in grp_target: del grp_target['_as_array'] if type0 in BasicNumTypes: # list of scalars if type0 == float and compress_float: grp_target['_as_array'] = float32(list_obj) else: grp_target['_as_array'] = list_obj elif type0 == str: # list of strings string_dt = h5.special_dtype(vlen=str) grp_target.create_dataset('_as_array', data=array(list_obj, dtype=object), dtype=string_dt) elif type0 == ndarray: # list of arrays if list_obj[0].dtype in BasicNumTypes: if list_obj[0].dtype == float64 and compress_float: grp_target['_as_array'] = float32(list_obj) else: grp_target['_as_array'] = list_obj else: string_dt = h5.special_dtype(vlen=str) grp_target.create_dataset('_as_array', data=array(list_obj, dtype=object), dtype=string_dt) else: warnings.warn( '%s could not be saved as an array' % (grp_target.name,)) return False return True return False ================================================ FILE: sharpy/utils/input_arg.py ================================================ import os import sys import argparse import sharpy.utils.exceptions as exceptions import sharpy.utils.cout_utils as cout def read_settings(args): case_settings = args.input_filename cout.cout_wrap('Running SHARPy using the settings file: %s' % case_settings) settings = parse_settings(case_settings) return settings def parse_settings(file): from sharpy.utils.settings import load_config_file settings = load_config_file(os.path.realpath(file)) try: settings['SHARPy']['flow'] except KeyError: raise exceptions.NotValidInputFile('The solver file does not contain a SHARPy header.') from sharpy.utils.solver_interface import dict_of_solvers for solver in settings['SHARPy']['flow']: # Check that the solvers in the flow exist and that they have a valid set of settings try: dict_of_solvers[solver] except KeyError: raise exceptions.SolverNotFound(solver) try: settings[solver] except KeyError: raise exceptions.NotValidInputFile('The settings for the solver %s have not been given.' % solver) return settings ================================================ FILE: sharpy/utils/linearutils.py ================================================ """Linear state-space vector manipulation utilities""" import numpy as np import sharpy.utils.algebra as algebra def structural_vector_to_timestep(vector, tstruct, structure, phi=None, num_rig_dof=0, copy_tstep=True): """Transform a state-space structural vector into a time step object This adds to a reference time step the following variables extracted from the vector: * ``pos`` and ``pos_dot`` * ``psi`` and ``psi_dot`` * ``quat`` * ``for_pos`` and ``for_vel`` The rest of the structural time step variables are left unchanged Args: vector(np.ndarray): Vector to plot tstruct (sharpy.utils.datastructures.StructTimeStepInfo): Reference time step structure (sharpy.structure.models.beam.Beam): Structural information class phi (np.ndarray, optional): Eigenvector matrix to transform vector back to nodal coordinates num_rig_dof (int): Number of rigid degrees of freedom copy_tstep (bool): Return a copy of the reference time step. Else, modify the input one. Returns: sharpy.utils.datastructures.StructTimeStepInfo: Time step with the aforementioned variables populated from state-space vector. """ n_dof = vector.shape[0] v = vector[:n_dof//2] v_dot = vector[n_dof//2:] if phi is not None: # modal coordinates eta = phi.dot(v) eta_dot = phi.dot(v_dot) else: eta = v eta_dot = v_dot if num_rig_dof != 0: eta = eta[:-num_rig_dof] eta_dot = eta_dot[:-num_rig_dof] beta = eta_dot[-num_rig_dof:] beta_bar = np.zeros_like(beta) else: beta = np.array([]) beta_bar = np.array([]) if copy_tstep: tstep = tstruct.copy() else: tstep = tstruct vdof = structure.vdof num_dof = 6*sum(vdof >= 0) q = np.zeros((num_dof + num_rig_dof)) dqdt = np.zeros_like(q) dqddt = np.zeros_like(q) pos = np.zeros_like(tstep.pos) pos_dot = np.zeros_like(tstep.pos_dot) psi = np.zeros_like(tstep.psi) psi_dot = np.zeros_like(tstep.psi_dot) for_pos = np.zeros_like(tstep.for_pos) for_vel = np.zeros_like(tstep.for_vel) for_acc = np.zeros_like(tstep.for_acc) quat = np.zeros_like(tstep.quat) q[:num_dof + num_rig_dof] = np.concatenate((eta, beta_bar)) dqdt[:num_dof + num_rig_dof] = np.concatenate((eta_dot, beta)) for i_node in vdof[vdof >= 0]: pos[i_node + 1, :] = q[6*i_node: 6*i_node + 3] pos_dot[i_node + 1, :] = dqdt[6*i_node + 0: 6*i_node + 3] for i_elem in range(tstep.num_elem): for i_node in range(tstep.num_node_elem): psi[i_elem, i_node, :] = np.linalg.inv(algebra.crv2tan(tstep.psi[i_elem, i_node]).T).dot(q[i_node + 3: i_node + 6]) psi_dot[i_elem, i_node, :] = dqdt[i_node + 3: i_node + 6] if num_rig_dof != 0: # beam is clamped for_vel = beta[:6] if num_rig_dof == 9: quat = algebra.euler2quat(beta[-3:]) elif num_rig_dof == 10: quat = beta[-4:] else: raise NotImplementedError('Structural vector to timestep for cases without 9 or 10 rigid' 'degrees of freedom not yet supported.') tstep.q[:len(q)] += q tstep.dqdt[:len(q)] += dqdt tstep.pos += pos tstep.pos_dot += pos_dot tstep.psi += psi tstep.psi_dot += psi_dot tstep.for_pos += for_pos tstep.for_vel += for_vel tstep.quat += quat tstep.quat /= np.linalg.norm(tstep.quat) # normalise quaternion return tstep ================================================ FILE: sharpy/utils/model_utils.py ================================================ """ Modelling Utilities """ import numpy as np import sharpy.utils.algebra as algebra def mass_matrix_generator(m, xcg, inertia): """ This function takes the mass, position of the center of gravity wrt the elastic axis and the inertia matrix J (3x3) and returns the complete 6x6 mass matrix. """ mass = np.zeros((6, 6)) m_chi_cg = algebra.skew(m*xcg) mass[np.diag_indices(3)] = m mass[3:, 3:] = inertia mass[0:3, 3:6] = -m_chi_cg mass[3:6, 0:3] = m_chi_cg return mass ================================================ FILE: sharpy/utils/multibody.py ================================================ """ Multibody library Library used to manipulate multibody systems To use this library: import sharpy.utils.multibody as mb """ import numpy as np import sharpy.structure.utils.xbeamlib as xbeamlib import sharpy.utils.algebra as algebra import ctypes as ct import traceback def split_multibody(beam, tstep, mb_data_dict, ts): """ split_multibody This functions splits a structure at a certain time step in its different bodies Args: beam (:class:`~sharpy.structure.models.beam.Beam`): structural information of the multibody system tstep (:class:`~sharpy.utils.datastructures.StructTimeStepInfo`): timestep information of the multibody system mb_data_dict (dict): Dictionary including the multibody information ts (int): time step number Returns: MB_beam (list(:class:`~sharpy.structure.models.beam.Beam`)): each entry represents a body MB_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body """ MB_beam = [] MB_tstep = [] quat0 = tstep.quat.astype(dtype=ct.c_double, order='F', copy=True) for0_pos = tstep.for_pos.astype(dtype=ct.c_double, order='F', copy=True) for0_vel = tstep.for_vel.astype(dtype=ct.c_double, order='F', copy=True) ini_quat0 = beam.ini_info.quat.astype(dtype=ct.c_double, order='F', copy=True) ini_for0_pos = beam.ini_info.for_pos.astype(dtype=ct.c_double, order='F', copy=True) ini_for0_vel = beam.ini_info.for_vel.astype(dtype=ct.c_double, order='F', copy=True) for ibody in range(beam.num_bodies): ibody_beam = None ibody_tstep = None ibody_beam = beam.get_body(ibody = ibody) ibody_tstep = tstep.get_body(beam, ibody_beam.num_dof, ibody = ibody) ibody_beam.FoR_movement = mb_data_dict['body_%02d' % ibody]['FoR_movement'] ibody_beam.ini_info.compute_psi_local_AFoR(ini_for0_pos, ini_for0_vel, ini_quat0) ibody_beam.ini_info.change_to_local_AFoR(ini_for0_pos, ini_for0_vel, ini_quat0) if ts == 1: ibody_tstep.compute_psi_local_AFoR(for0_pos, for0_vel, quat0) ibody_tstep.change_to_local_AFoR(for0_pos, for0_vel, quat0) MB_beam.append(ibody_beam) MB_tstep.append(ibody_tstep) return MB_beam, MB_tstep def merge_multibody(MB_tstep, MB_beam, beam, tstep, mb_data_dict, dt): """ merge_multibody This functions merges a series of bodies into a multibody system at a certain time step Longer description Args: MB_beam (list(:class:`~sharpy.structure.models.beam.Beam`)): each entry represents a body MB_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body beam (:class:`~sharpy.structure.models.beam.Beam`): structural information of the multibody system tstep (:class:`~sharpy.utils.datastructures.StructTimeStepInfo`): timestep information of the multibody system mb_data_dict (dict): Dictionary including the multibody information dt(int): time step Returns: beam (:class:`~sharpy.structure.models.beam.Beam`): structural information of the multibody system tstep (:class:`~sharpy.utils.datastructures.StructTimeStepInfo`): timestep information of the multibody system """ update_mb_dB_before_merge(tstep, MB_tstep) quat0 = MB_tstep[0].quat.astype(dtype=ct.c_double, order='F', copy=True) for0_pos = MB_tstep[0].for_pos.astype(dtype=ct.c_double, order='F', copy=True) for0_vel = MB_tstep[0].for_vel.astype(dtype=ct.c_double, order='F', copy=True) for ibody in range(beam.num_bodies): MB_tstep[ibody].change_to_global_AFoR(for0_pos, for0_vel, quat0) first_dof = 0 for ibody in range(beam.num_bodies): # Renaming for clarity ibody_elems = MB_beam[ibody].global_elems_num ibody_nodes = MB_beam[ibody].global_nodes_num # Merge tstep tstep.pos[ibody_nodes,:] = MB_tstep[ibody].pos.astype(dtype=ct.c_double, order='F', copy=True) tstep.pos_dot[ibody_nodes,:] = MB_tstep[ibody].pos_dot.astype(dtype=ct.c_double, order='F', copy=True) tstep.pos_ddot[ibody_nodes,:] = MB_tstep[ibody].pos_ddot.astype(dtype=ct.c_double, order='F', copy=True) tstep.psi[ibody_elems,:,:] = MB_tstep[ibody].psi.astype(dtype=ct.c_double, order='F', copy=True) tstep.psi_local[ibody_elems,:,:] = MB_tstep[ibody].psi_local.astype(dtype=ct.c_double, order='F', copy=True) tstep.psi_dot[ibody_elems,:,:] = MB_tstep[ibody].psi_dot.astype(dtype=ct.c_double, order='F', copy=True) tstep.psi_dot_local[ibody_elems,:,:] = MB_tstep[ibody].psi_dot_local.astype(dtype=ct.c_double, order='F', copy=True) tstep.psi_ddot[ibody_elems,:,:] = MB_tstep[ibody].psi_ddot.astype(dtype=ct.c_double, order='F', copy=True) tstep.gravity_forces[ibody_nodes,:] = MB_tstep[ibody].gravity_forces.astype(dtype=ct.c_double, order='F', copy=True) tstep.steady_applied_forces[ibody_nodes,:] = MB_tstep[ibody].steady_applied_forces.astype(dtype=ct.c_double, order='F', copy=True) tstep.unsteady_applied_forces[ibody_nodes,:] = MB_tstep[ibody].unsteady_applied_forces.astype(dtype=ct.c_double, order='F', copy=True) tstep.runtime_steady_forces[ibody_nodes,:] = MB_tstep[ibody].runtime_steady_forces.astype(dtype=ct.c_double, order='F', copy=True) tstep.runtime_unsteady_forces[ibody_nodes,:] = MB_tstep[ibody].runtime_unsteady_forces.astype(dtype=ct.c_double, order='F', copy=True) # TODO: Do I need a change in FoR for the following variables? Maybe for the FoR ones. tstep.forces_constraints_nodes[ibody_nodes,:] = MB_tstep[ibody].forces_constraints_nodes.astype(dtype=ct.c_double, order='F', copy=True) tstep.forces_constraints_FoR[ibody, :] = MB_tstep[ibody].forces_constraints_FoR[ibody, :].astype(dtype=ct.c_double, order='F', copy=True) # Merge states ibody_num_dof = MB_beam[ibody].num_dof.value tstep.q[first_dof:first_dof+ibody_num_dof] = MB_tstep[ibody].q[:-10].astype(dtype=ct.c_double, order='F', copy=True) tstep.dqdt[first_dof:first_dof+ibody_num_dof] = MB_tstep[ibody].dqdt[:-10].astype(dtype=ct.c_double, order='F', copy=True) tstep.dqddt[first_dof:first_dof+ibody_num_dof] = MB_tstep[ibody].dqddt[:-10].astype(dtype=ct.c_double, order='F', copy=True) tstep.mb_dquatdt[ibody, :] = MB_tstep[ibody].dqddt[-4:].astype(dtype=ct.c_double, order='F', copy=True) first_dof += ibody_num_dof tstep.q[-10:] = MB_tstep[0].q[-10:].astype(dtype=ct.c_double, order='F', copy=True) tstep.dqdt[-10:] = MB_tstep[0].dqdt[-10:].astype(dtype=ct.c_double, order='F', copy=True) tstep.dqddt[-10:] = MB_tstep[0].dqddt[-10:].astype(dtype=ct.c_double, order='F', copy=True) # Define the new FoR information tstep.for_pos = MB_tstep[0].for_pos.astype(dtype=ct.c_double, order='F', copy=True) tstep.for_vel = MB_tstep[0].for_vel.astype(dtype=ct.c_double, order='F', copy=True) tstep.for_acc = MB_tstep[0].for_acc.astype(dtype=ct.c_double, order='F', copy=True) tstep.quat = MB_tstep[0].quat.astype(dtype=ct.c_double, order='F', copy=True) def update_mb_dB_before_merge(tstep, MB_tstep): """ update_mb_db_before_merge Updates the FoR information database before merging bodies Args: tstep (:class:`~sharpy.utils.datastructures.StructTimeStepInfo`): timestep information of the multibody system MB_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body """ for ibody in range(len(MB_tstep)): tstep.mb_FoR_pos[ibody,:] = MB_tstep[ibody].for_pos.astype(dtype=ct.c_double, order='F', copy=True) tstep.mb_FoR_vel[ibody,:] = MB_tstep[ibody].for_vel.astype(dtype=ct.c_double, order='F', copy=True) tstep.mb_FoR_acc[ibody,:] = MB_tstep[ibody].for_acc.astype(dtype=ct.c_double, order='F', copy=True) tstep.mb_quat[ibody,:] = MB_tstep[ibody].quat.astype(dtype=ct.c_double, order='F', copy=True) assert (MB_tstep[ibody].mb_dquatdt[ibody, :] == MB_tstep[ibody].dqddt[-4:]).all(), "Error in multibody storage" tstep.mb_dquatdt[ibody, :] = MB_tstep[ibody].dqddt[-4:].astype(dtype=ct.c_double, order='F', copy=True) def disp_and_accel2state(MB_beam, MB_tstep, Lambda, Lambda_dot, sys_size, num_LM_eq): """ disp2state Fills the vector of states according to the displacements information Args: MB_beam (list(:class:`~sharpy.structure.models.beam.Beam`)): each entry represents a body MB_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body Lambda(np.ndarray): Lagrange multipliers of holonomic constraints Lambda_dot(np.ndarray): Lagrange multipliers of non-holonomic constraints sys_size(int): number of degrees of freedom of the system of equations not accounting for lagrange multipliers num_LM_eq(int): Number of equations associated to the Lagrange Multipliers q(np.ndarray): Vector of states dqdt(np.ndarray): Time derivatives of states dqddt(np.ndarray): Second time derivatives of states """ q = np.zeros((sys_size + num_LM_eq, ), dtype=ct.c_double, order='F') dqdt = np.zeros((sys_size + num_LM_eq, ), dtype=ct.c_double, order='F') dqddt = np.zeros((sys_size + num_LM_eq, ), dtype=ct.c_double, order='F') first_dof = 0 for ibody in range(len(MB_beam)): ibody_num_dof = MB_beam[ibody].num_dof.value if (MB_beam[ibody].FoR_movement == 'prescribed'): xbeamlib.cbeam3_solv_disp2state(MB_beam[ibody], MB_tstep[ibody]) xbeamlib.cbeam3_solv_accel2state(MB_beam[ibody], MB_tstep[ibody]) q[first_dof:first_dof+ibody_num_dof]=MB_tstep[ibody].q[:-10].astype(dtype=ct.c_double, order='F', copy=True) dqdt[first_dof:first_dof+ibody_num_dof]=MB_tstep[ibody].dqdt[:-10].astype(dtype=ct.c_double, order='F', copy=True) dqddt[first_dof:first_dof+ibody_num_dof]=MB_tstep[ibody].dqddt[:-10].astype(dtype=ct.c_double, order='F', copy=True) first_dof += ibody_num_dof elif (MB_beam[ibody].FoR_movement == 'free'): dquatdt = MB_tstep[ibody].mb_dquatdt[ibody, :].astype(dtype=ct.c_double, order='F', copy=True) xbeamlib.xbeam_solv_disp2state(MB_beam[ibody], MB_tstep[ibody]) xbeamlib.xbeam_solv_accel2state(MB_beam[ibody], MB_tstep[ibody]) q[first_dof:first_dof+ibody_num_dof+10]=MB_tstep[ibody].q.astype(dtype=ct.c_double, order='F', copy=True) dqdt[first_dof:first_dof+ibody_num_dof+10]=MB_tstep[ibody].dqdt.astype(dtype=ct.c_double, order='F', copy=True) dqddt[first_dof:first_dof+ibody_num_dof+10]=MB_tstep[ibody].dqddt.astype(dtype=ct.c_double, order='F', copy=True) dqddt[first_dof+ibody_num_dof+6:first_dof+ibody_num_dof+10]=dquatdt.astype(dtype=ct.c_double, order='F', copy=True) first_dof += ibody_num_dof + 10 if num_LM_eq > 0: q[first_dof:] = Lambda.astype(dtype=ct.c_double, order='F', copy=True) dqdt[first_dof:] = Lambda_dot.astype(dtype=ct.c_double, order='F', copy=True) return q, dqdt, dqddt def state2disp_and_accel(q, dqdt, dqddt, MB_beam, MB_tstep, num_LM_eq): """ state2disp Recovers the displacements from the states Longer description Args: MB_beam (list(:class:`~sharpy.structure.models.beam.Beam`)): each entry represents a body MB_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body q(np.ndarray): Vector of states dqdt(np.ndarray): Time derivatives of states dqddt(np.ndarray): Second time derivatives of states num_LM_eq(int): Number of equations associated to the Lagrange Multipliers Lambda(np.ndarray): Lagrange multipliers of holonomic constraints Lambda_dot(np.ndarray): Lagrange multipliers of non-holonomic constraints """ Lambda = np.zeros((num_LM_eq, ), dtype=ct.c_double, order='F') Lambda_dot = np.zeros((num_LM_eq, ), dtype=ct.c_double, order='F') first_dof = 0 for ibody in range(len(MB_beam)): ibody_num_dof = MB_beam[ibody].num_dof.value if (MB_beam[ibody].FoR_movement == 'prescribed'): MB_tstep[ibody].q[:-10] = q[first_dof:first_dof+ibody_num_dof].astype(dtype=ct.c_double, order='F', copy=True) MB_tstep[ibody].dqdt[:-10] = dqdt[first_dof:first_dof+ibody_num_dof].astype(dtype=ct.c_double, order='F', copy=True) MB_tstep[ibody].dqddt[:-10] = dqddt[first_dof:first_dof+ibody_num_dof].astype(dtype=ct.c_double, order='F', copy=True) xbeamlib.cbeam3_solv_state2disp(MB_beam[ibody], MB_tstep[ibody]) xbeamlib.cbeam3_solv_state2accel(MB_beam[ibody], MB_tstep[ibody]) first_dof += ibody_num_dof elif (MB_beam[ibody].FoR_movement == 'free'): MB_tstep[ibody].q = q[first_dof:first_dof+ibody_num_dof+10].astype(dtype=ct.c_double, order='F', copy=True) MB_tstep[ibody].dqdt = dqdt[first_dof:first_dof+ibody_num_dof+10].astype(dtype=ct.c_double, order='F', copy=True) MB_tstep[ibody].dqddt = dqddt[first_dof:first_dof+ibody_num_dof+10].astype(dtype=ct.c_double, order='F', copy=True) MB_tstep[ibody].mb_dquatdt[ibody, :] = MB_tstep[ibody].dqddt[-4:] xbeamlib.xbeam_solv_state2disp(MB_beam[ibody], MB_tstep[ibody]) xbeamlib.xbeam_solv_state2accel(MB_beam[ibody], MB_tstep[ibody]) first_dof += ibody_num_dof + 10 Lambda = q[first_dof:].astype(dtype=ct.c_double, order='F', copy=True) Lambda_dot = dqdt[first_dof:].astype(dtype=ct.c_double, order='F', copy=True) return Lambda, Lambda_dot def get_elems_nodes_list(beam, ibody): """ get_elems_nodes_list This function returns the elements (``ibody_elements``) and the nodes (``ibody_nodes``) that belong to the body number ``ibody`` Args: beam (:class:`~sharpy.structure.models.beam.Beam`): structural information of the multibody system ibody (int): Body number about which the information is required Returns: ibody_elements (list): List of elements that belong the ``ibody`` ibody_nodes (list): List of nodes that belong the ``ibody`` """ int_list = np.arange(beam.num_elem) ibody_elements = int_list[beam.body_number == ibody] ibody_nodes = np.sort(np.unique(beam.connectivities[ibody_elements, :].reshape(-1))) return ibody_elements, ibody_nodes ================================================ FILE: sharpy/utils/multibodyjax.py ================================================ """ Multibody library for the NonlinearDynamicMultibodyJAX solver Library used to manipulate multibody systems To use this library: import sharpy.utils.multibodyjax as mb """ import numpy as np import sharpy.structure.utils.xbeamlib as xbeamlib import ctypes as ct def split_multibody(beam, tstep, mb_data_dict, ts): """ split_multibody This functions splits a structure at a certain time step in its different bodies Args: beam (:class:`~sharpy.structure.models.beam.Beam`): structural information of the multibody system tstep (:class:`~sharpy.utils.datastructures.StructTimeStepInfo`): timestep information of the multibody system mb_data_dict (dict): Dictionary including the multibody information ts (int): time step number Returns: MB_beam (list(:class:`~sharpy.structure.models.beam.Beam`)): each entry represents a body MB_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body """ MB_beam = [] MB_tstep = [] quat0 = tstep.quat.astype(dtype=ct.c_double, order='F', copy=True) for0_pos = tstep.for_pos.astype(dtype=ct.c_double, order='F', copy=True) for0_vel = tstep.for_vel.astype(dtype=ct.c_double, order='F', copy=True) ini_quat0 = beam.ini_info.quat.astype(dtype=ct.c_double, order='F', copy=True) ini_for0_pos = beam.ini_info.for_pos.astype(dtype=ct.c_double, order='F', copy=True) ini_for0_vel = beam.ini_info.for_vel.astype(dtype=ct.c_double, order='F', copy=True) for ibody in range(beam.num_bodies): ibody_beam = beam.get_body(ibody=ibody) ibody_tstep = tstep.get_body(beam, ibody_beam.num_dof, ibody=ibody) ibody_beam.FoR_movement = mb_data_dict['body_%02d' % ibody]['FoR_movement'] ibody_beam.ini_info.compute_psi_local_AFoR(ini_for0_pos, ini_for0_vel, ini_quat0) ibody_beam.ini_info.change_to_local_AFoR(ini_for0_pos, ini_for0_vel, ini_quat0) if ts == 1: ibody_tstep.compute_psi_local_AFoR(for0_pos, for0_vel, quat0) ibody_tstep.change_to_local_AFoR(for0_pos, for0_vel, quat0) MB_beam.append(ibody_beam) MB_tstep.append(ibody_tstep) return MB_beam, MB_tstep def merge_multibody(MB_tstep, MB_beam, beam, tstep, mb_data_dict, dt): """ merge_multibody This functions merges a series of bodies into a multibody system at a certain time step Longer description Args: MB_beam (list(:class:`~sharpy.structure.models.beam.Beam`)): each entry represents a body MB_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body beam (:class:`~sharpy.structure.models.beam.Beam`): structural information of the multibody system tstep (:class:`~sharpy.utils.datastructures.StructTimeStepInfo`): timestep information of the multibody system mb_data_dict (dict): Dictionary including the multibody information dt(int): time step Returns: beam (:class:`~sharpy.structure.models.beam.Beam`): structural information of the multibody system tstep (:class:`~sharpy.utils.datastructures.StructTimeStepInfo`): timestep information of the multibody system """ update_mb_dB_before_merge(tstep, MB_tstep) quat0 = MB_tstep[0].quat.astype(dtype=ct.c_double, order='F', copy=True) for0_pos = MB_tstep[0].for_pos.astype(dtype=ct.c_double, order='F', copy=True) for0_vel = MB_tstep[0].for_vel.astype(dtype=ct.c_double, order='F', copy=True) for ibody in range(beam.num_bodies): MB_tstep[ibody].change_to_global_AFoR(for0_pos, for0_vel, quat0) first_dof = 0 for ibody in range(beam.num_bodies): # Renaming for clarity ibody_elems = MB_beam[ibody].global_elems_num ibody_nodes = MB_beam[ibody].global_nodes_num # Merge tstep tstep.pos[ibody_nodes, :] = MB_tstep[ibody].pos.astype(dtype=ct.c_double, order='F', copy=True) tstep.pos_dot[ibody_nodes, :] = MB_tstep[ibody].pos_dot.astype(dtype=ct.c_double, order='F', copy=True) tstep.pos_ddot[ibody_nodes, :] = MB_tstep[ibody].pos_ddot.astype(dtype=ct.c_double, order='F', copy=True) tstep.psi[ibody_elems, :, :] = MB_tstep[ibody].psi.astype(dtype=ct.c_double, order='F', copy=True) tstep.psi_local[ibody_elems, :, :] = MB_tstep[ibody].psi_local.astype(dtype=ct.c_double, order='F', copy=True) tstep.psi_dot[ibody_elems, :, :] = MB_tstep[ibody].psi_dot.astype(dtype=ct.c_double, order='F', copy=True) tstep.psi_dot_local[ibody_elems, :, :] = MB_tstep[ibody].psi_dot_local.astype(dtype=ct.c_double, order='F', copy=True) tstep.psi_ddot[ibody_elems, :, :] = MB_tstep[ibody].psi_ddot.astype(dtype=ct.c_double, order='F', copy=True) tstep.gravity_forces[ibody_nodes, :] = MB_tstep[ibody].gravity_forces.astype(dtype=ct.c_double, order='F', copy=True) tstep.steady_applied_forces[ibody_nodes, :] = MB_tstep[ibody].steady_applied_forces.astype(dtype=ct.c_double, order='F', copy=True) tstep.unsteady_applied_forces[ibody_nodes, :] = MB_tstep[ibody].unsteady_applied_forces.astype( dtype=ct.c_double, order='F', copy=True) tstep.runtime_steady_forces[ibody_nodes, :] = MB_tstep[ibody].runtime_steady_forces.astype(dtype=ct.c_double, order='F', copy=True) tstep.runtime_unsteady_forces[ibody_nodes, :] = MB_tstep[ibody].runtime_unsteady_forces.astype( dtype=ct.c_double, order='F', copy=True) # TODO: Do I need a change in FoR for the following variables? Maybe for the FoR ones. tstep.forces_constraints_nodes[ibody_nodes, :] = MB_tstep[ibody].forces_constraints_nodes.astype( dtype=ct.c_double, order='F', copy=True) tstep.forces_constraints_FoR[ibody, :] = MB_tstep[ibody].forces_constraints_FoR[ibody, :].astype( dtype=ct.c_double, order='F', copy=True) # Merge states ibody_num_dof = MB_beam[ibody].num_dof.value tstep.q[first_dof:first_dof + ibody_num_dof] = MB_tstep[ibody].q[:-10].astype(dtype=ct.c_double, order='F', copy=True) tstep.dqdt[first_dof:first_dof + ibody_num_dof] = MB_tstep[ibody].dqdt[:-10].astype(dtype=ct.c_double, order='F', copy=True) tstep.dqddt[first_dof:first_dof + ibody_num_dof] = MB_tstep[ibody].dqddt[:-10].astype(dtype=ct.c_double, order='F', copy=True) tstep.mb_dquatdt[ibody, :] = MB_tstep[ibody].dqddt[-4:].astype(dtype=ct.c_double, order='F', copy=True) first_dof += ibody_num_dof tstep.q[-10:] = MB_tstep[0].q[-10:].astype(dtype=ct.c_double, order='F', copy=True) tstep.dqdt[-10:] = MB_tstep[0].dqdt[-10:].astype(dtype=ct.c_double, order='F', copy=True) tstep.dqddt[-10:] = MB_tstep[0].dqddt[-10:].astype(dtype=ct.c_double, order='F', copy=True) # Define the new FoR information tstep.for_pos = MB_tstep[0].for_pos.astype(dtype=ct.c_double, order='F', copy=True) tstep.for_vel = MB_tstep[0].for_vel.astype(dtype=ct.c_double, order='F', copy=True) tstep.for_acc = MB_tstep[0].for_acc.astype(dtype=ct.c_double, order='F', copy=True) tstep.quat = MB_tstep[0].quat.astype(dtype=ct.c_double, order='F', copy=True) def update_mb_dB_before_merge(tstep, MB_tstep): """ update_mb_db_before_merge Updates the FoR information database before merging bodies Args: tstep (:class:`~sharpy.utils.datastructures.StructTimeStepInfo`): timestep information of the multibody system MB_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body """ for ibody in range(len(MB_tstep)): tstep.mb_FoR_pos[ibody, :] = MB_tstep[ibody].for_pos.astype(dtype=ct.c_double, order='F', copy=True) tstep.mb_FoR_vel[ibody, :] = MB_tstep[ibody].for_vel.astype(dtype=ct.c_double, order='F', copy=True) tstep.mb_FoR_acc[ibody, :] = MB_tstep[ibody].for_acc.astype(dtype=ct.c_double, order='F', copy=True) tstep.mb_quat[ibody, :] = MB_tstep[ibody].quat.astype(dtype=ct.c_double, order='F', copy=True) assert (MB_tstep[ibody].mb_dquatdt[ibody, :] == MB_tstep[ibody].dqddt[-4:]).all(), "Error in multibody storage" tstep.mb_dquatdt[ibody, :] = MB_tstep[ibody].dqddt[-4:].astype(dtype=ct.c_double, order='F', copy=True) def disp_and_accel2state(MB_beam, MB_tstep, Lambda, Lambda_dot, sys_size, num_LM_eq): """ disp2state Fills the vector of states according to the displacements information Args: MB_beam (list(:class:`~sharpy.structure.models.beam.Beam`)): each entry represents a body MB_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body Lambda(np.ndarray): Lagrange multipliers of holonomic constraints Lambda_dot(np.ndarray): Lagrange multipliers of non-holonomic constraints sys_size(int): number of degrees of freedom of the system of equations not accounting for lagrange multipliers num_LM_eq(int): Number of equations associated to the Lagrange Multipliers q(np.ndarray): Vector of states dqdt(np.ndarray): Time derivatives of states dqddt(np.ndarray): Second time derivatives of states """ q = np.zeros((sys_size + num_LM_eq,), dtype=ct.c_double, order='F') dqdt = np.zeros((sys_size + num_LM_eq,), dtype=ct.c_double, order='F') dqddt = np.zeros((sys_size + num_LM_eq,), dtype=ct.c_double, order='F') first_dof = 0 for ibody in range(len(MB_beam)): ibody_num_dof = MB_beam[ibody].num_dof.value if (MB_beam[ibody].FoR_movement == 'prescribed'): MB_tstep[ibody].for_vel = np.zeros(6) MB_tstep[ibody].for_acc = np.zeros(6) MB_tstep[ibody].quat = MB_beam[ibody].ini_info.quat xbeamlib.cbeam3_solv_disp2state(MB_beam[ibody], MB_tstep[ibody]) xbeamlib.cbeam3_solv_accel2state(MB_beam[ibody], MB_tstep[ibody]) q[first_dof:first_dof + ibody_num_dof] = MB_tstep[ibody].q[:-10].astype(dtype=ct.c_double, order='F', copy=True) dqdt[first_dof:first_dof + ibody_num_dof] = MB_tstep[ibody].dqdt[:-10].astype(dtype=ct.c_double, order='F', copy=True) dqddt[first_dof:first_dof + ibody_num_dof] = MB_tstep[ibody].dqddt[:-10].astype(dtype=ct.c_double, order='F', copy=True) first_dof += ibody_num_dof elif (MB_beam[ibody].FoR_movement == 'prescribed_trim'): MB_tstep[ibody].for_vel = np.zeros(6) MB_tstep[ibody].for_acc = np.zeros(6) xbeamlib.cbeam3_solv_disp2state(MB_beam[ibody], MB_tstep[ibody]) xbeamlib.cbeam3_solv_accel2state(MB_beam[ibody], MB_tstep[ibody]) q[first_dof:first_dof + ibody_num_dof] = MB_tstep[ibody].q[:-10].astype(dtype=ct.c_double, order='F', copy=True) dqdt[first_dof:first_dof + ibody_num_dof] = MB_tstep[ibody].dqdt[:-10].astype(dtype=ct.c_double, order='F', copy=True) dqddt[first_dof:first_dof + ibody_num_dof] = MB_tstep[ibody].dqddt[:-10].astype(dtype=ct.c_double, order='F', copy=True) first_dof += ibody_num_dof elif (MB_beam[ibody].FoR_movement == 'free'): dquatdt = MB_tstep[ibody].mb_dquatdt[ibody, :].astype(dtype=ct.c_double, order='F', copy=True) xbeamlib.xbeam_solv_disp2state(MB_beam[ibody], MB_tstep[ibody]) xbeamlib.xbeam_solv_accel2state(MB_beam[ibody], MB_tstep[ibody]) q[first_dof:first_dof + ibody_num_dof + 10] = MB_tstep[ibody].q.astype(dtype=ct.c_double, order='F', copy=True) dqdt[first_dof:first_dof + ibody_num_dof + 10] = MB_tstep[ibody].dqdt.astype(dtype=ct.c_double, order='F', copy=True) dqddt[first_dof:first_dof + ibody_num_dof + 10] = MB_tstep[ibody].dqddt.astype(dtype=ct.c_double, order='F', copy=True) dqddt[first_dof + ibody_num_dof + 6:first_dof + ibody_num_dof + 10] = dquatdt.astype(dtype=ct.c_double, order='F', copy=True) first_dof += ibody_num_dof + 10 if num_LM_eq > 0: q[first_dof:] = Lambda.astype(dtype=ct.c_double, order='F', copy=True) dqdt[first_dof:] = Lambda_dot.astype(dtype=ct.c_double, order='F', copy=True) return q, dqdt, dqddt def state2disp_and_accel(q, dqdt, dqddt, MB_beam, MB_tstep, num_LM_eq): """ state2disp Recovers the displacements from the states Longer description Args: MB_beam (list(:class:`~sharpy.structure.models.beam.Beam`)): each entry represents a body MB_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body q(np.ndarray): Vector of states dqdt(np.ndarray): Time derivatives of states dqddt(np.ndarray): Second time derivatives of states num_LM_eq(int): Number of equations associated to the Lagrange Multipliers Lambda(np.ndarray): Lagrange multipliers of holonomic constraints Lambda_dot(np.ndarray): Lagrange multipliers of non-holonomic constraints """ first_dof = 0 for ibody in range(len(MB_beam)): ibody_num_dof = MB_beam[ibody].num_dof.value if (MB_beam[ibody].FoR_movement == 'prescribed'): MB_tstep[ibody].q[:-10] = q[first_dof:first_dof + ibody_num_dof].astype(dtype=ct.c_double, order='F', copy=True) MB_tstep[ibody].dqdt[:-10] = dqdt[first_dof:first_dof + ibody_num_dof].astype(dtype=ct.c_double, order='F', copy=True) MB_tstep[ibody].dqddt[:-10] = dqddt[first_dof:first_dof + ibody_num_dof].astype(dtype=ct.c_double, order='F', copy=True) xbeamlib.cbeam3_solv_state2disp(MB_beam[ibody], MB_tstep[ibody]) xbeamlib.cbeam3_solv_state2accel(MB_beam[ibody], MB_tstep[ibody]) first_dof += ibody_num_dof elif (MB_beam[ibody].FoR_movement == 'prescribed_trim'): MB_tstep[ibody].q[:-10] = q[first_dof:first_dof + ibody_num_dof].astype(dtype=ct.c_double, order='F', copy=True) MB_tstep[ibody].dqdt[:-10] = dqdt[first_dof:first_dof + ibody_num_dof].astype(dtype=ct.c_double, order='F', copy=True) MB_tstep[ibody].dqddt[:-10] = dqddt[first_dof:first_dof + ibody_num_dof].astype(dtype=ct.c_double, order='F', copy=True) xbeamlib.cbeam3_solv_state2disp(MB_beam[ibody], MB_tstep[ibody]) xbeamlib.cbeam3_solv_state2accel(MB_beam[ibody], MB_tstep[ibody]) first_dof += ibody_num_dof elif (MB_beam[ibody].FoR_movement == 'free'): MB_tstep[ibody].q = q[first_dof:first_dof + ibody_num_dof + 10].astype(dtype=ct.c_double, order='F', copy=True) MB_tstep[ibody].dqdt = dqdt[first_dof:first_dof + ibody_num_dof + 10].astype(dtype=ct.c_double, order='F', copy=True) MB_tstep[ibody].dqddt = dqddt[first_dof:first_dof + ibody_num_dof + 10].astype(dtype=ct.c_double, order='F', copy=True) MB_tstep[ibody].mb_dquatdt[ibody, :] = MB_tstep[ibody].dqddt[-4:] xbeamlib.xbeam_solv_state2disp(MB_beam[ibody], MB_tstep[ibody]) xbeamlib.xbeam_solv_state2accel(MB_beam[ibody], MB_tstep[ibody]) first_dof += ibody_num_dof + 10 lm_h = q[first_dof:].astype(dtype=ct.c_double, order='F', copy=True) lm_n = dqdt[first_dof:].astype(dtype=ct.c_double, order='F', copy=True) return lm_h, lm_n def get_elems_nodes_list(beam, ibody): """ get_elems_nodes_list This function returns the elements (``ibody_elements``) and the nodes (``ibody_nodes``) that belong to the body number ``ibody`` Args: beam (:class:`~sharpy.structure.models.beam.Beam`): structural information of the multibody system ibody (int): Body number about which the information is required Returns: ibody_elements (list): List of elements that belong the ``ibody`` ibody_nodes (list): List of nodes that belong the ``ibody`` """ int_list = np.arange(beam.num_elem) ibody_elements = int_list[beam.body_number == ibody] ibody_nodes = np.sort(np.unique(beam.connectivities[ibody_elements, :].reshape(-1))) return ibody_elements, ibody_nodes ================================================ FILE: sharpy/utils/num_utils.py ================================================ import numpy as np import ctypes as ct import scipy as sc def check_symmetric(mat): return np.allclose(mat.transpose(), mat, atol=1e-3) if __name__ == '__main__': a = np.array([[1, 2], [3, 4]]) print(check_symmetric(a)) a = np.array([[1, 2], [2, 1]]) print(check_symmetric(a)) ================================================ FILE: sharpy/utils/plotutils.py ================================================ """Plotting utilities """ import numpy as np from numpy import ndarray from typing import Optional from os import PathLike import vtk from vtk.numpy_interface import algorithms as algs from vtk.numpy_interface import dataset_adapter as dsa def plot_frame_to_vtk( grid_arr: ndarray, filename: str | PathLike, node_scalar_data: Optional[dict[str, ndarray]] = None, node_vector_data: Optional[dict[str, ndarray]] = None, cell_scalar_data: Optional[dict[str, ndarray]] = None, cell_vector_data: Optional[dict[str, ndarray]] = None, ) -> None: r""" Plot a single timestep of grid data :param grid_arr: Structured grid array with shape (n_x, n_y, n_z, 3) :param filename: Base filename, including directory. Information on the frame number will be appended to this. :param node_scalar_data: Dictionary of node scalar data :param node_vector_data: Dictionary of node vector data :param cell_scalar_data: Dictionary of cell scalar data :param cell_vector_data: Dictionary of cell vector data """ if grid_arr.shape[-1] != 3: raise ValueError("grid_arr must have trailing dimension of size 3") # planar grid should have 3 dimensions, while volume grid should have 4 dimensions match grid_arr.ndim: case 3: is_planar = True case 4: is_planar = False case _: raise ValueError( f"grid_arr must have 3 or 4 dimensions, got {grid_arr.ndim}-D array" ) sg = vtk.vtkStructuredGrid() if is_planar: sg.SetDimensions(*grid_arr.shape[:-1], 1) else: sg.SetDimensions(*grid_arr.shape[:-1]) i_swap = 2 - int(is_planar) # we swap axes as VTK likes z, y, x order # add point coordinate data points = vtk.vtkPoints() points_vec = algs.make_vector( *[np.swapaxes(grid_arr[..., i], 0, i_swap).ravel() for i in range(3)] ) points.SetData(dsa.numpyTovtkDataArray(points_vec, "Points")) sg.SetPoints(points) # cell scalar data if cell_scalar_data is not None: for name, arr in cell_scalar_data.items(): sg.GetCellData().AddArray( dsa.numpyTovtkDataArray(np.swapaxes(arr, 0, i_swap).ravel(), name) ) # cell vector data if cell_vector_data is not None: for name, arr in cell_vector_data.items(): vectors = algs.make_vector( np.swapaxes(arr[..., 0], 0, i_swap).ravel(), np.swapaxes(arr[..., 1], 0, i_swap).ravel(), np.swapaxes(arr[..., 2], 0, i_swap).ravel(), ) sg.GetCellData().AddArray(dsa.numpyTovtkDataArray(vectors, name)) # point scalar data if node_scalar_data is not None: for name, arr in node_scalar_data.items(): sg.GetPointData().AddArray( dsa.numpyTovtkDataArray(np.swapaxes(arr, 0, i_swap).ravel(), name) ) # point vector data if node_vector_data is not None: for name, arr in node_vector_data.items(): vectors = algs.make_vector( np.swapaxes(arr[..., 0], 0, i_swap).ravel(), np.swapaxes(arr[..., 1], 0, i_swap).ravel(), np.swapaxes(arr[..., 2], 0, i_swap).ravel(), ) sg.GetPointData().AddArray(dsa.numpyTovtkDataArray(vectors, name)) # write to file writer = vtk.vtkXMLStructuredGridWriter() writer.SetFileName(filename) writer.SetInputData(sg) writer.Write() def set_axes_equal(ax): '''Make axes of 3D plot have equal scale so that spheres appear as spheres, cubes as cubes, etc.. This is one possible solution to Matplotlib's ax.set_aspect('equal') and ax.axis('equal') not working for 3D. Input ax: a matplotlib axis, e.g., as output from plt.gca(). ''' x_limits = ax.get_xlim3d() y_limits = ax.get_ylim3d() z_limits = ax.get_zlim3d() x_range = abs(x_limits[1] - x_limits[0]) x_middle = np.mean(x_limits) y_range = abs(y_limits[1] - y_limits[0]) y_middle = np.mean(y_limits) z_range = abs(z_limits[1] - z_limits[0]) z_middle = np.mean(z_limits) # The plot bounding box is a sphere in the sense of the infinity # norm, hence I call half the max range the plot radius. plot_radius = 0.5*max([x_range, y_range, z_range]) ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius]) ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius]) ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius]) def plot_timestep(data, tstep=-1, minus_mstar=0, plotly=False, custom_scaling=False, z_compression=0.5): ''' This function creates a simple plot with matplotlib of a timestep in SHARPy. Notice that this function is not efficient at all for large surfaces, it just aims to provide a simple way of generating simple quick plots. Input: data (``sharpy.presharpy.presharpy.PreSharpy``): Main data strucuture in SHARPy tstep (int): Time step to plot minus_mstar (int): number of wake panels to remove from the visualisation (for efficiency) plotly(bool): calls in the plotly library, graph will not plot if set to false. custom_scaling(bool): aspect ratio of the wing will be modelled realistically if set to true. z_compression(int): if custom scaling is enabled, this decides how much the z axis is compressed. Returns: Plot object: Can be matplotlib.pyplot.plt (plotly=False) or plotly.graph_objects.Figure() (plotly=True) ''' if len(data.structure.timestep_info) == 0: struct_tstep = data.structure.ini_info aero_tstep = data.aero.ini_info else: struct_tstep = data.structure.timestep_info[tstep] aero_tstep = data.aero.timestep_info[tstep] if not plotly: try: from mpl_toolkits.mplot3d import axes3d import matplotlib.pyplot as plt except ModuleNotFoundError: print("Matplotlib package not found") return fig = plt.figure() ax = fig.add_subplot(111, projection='3d') # Plot structure # Split into different beams for ielem in range(data.structure.num_elem): nodes = data.structure.connectivities[ielem, :][[0, 2, 1]] ax.plot(struct_tstep.pos[nodes, 0], struct_tstep.pos[nodes, 1], struct_tstep.pos[nodes, 2], '-ob') # Plot aerodynamic grid if aero_tstep is not None: for isurf in range(aero_tstep.n_surf): # Solid grid ax.plot_wireframe(aero_tstep.zeta[isurf][0, :, :], aero_tstep.zeta[isurf][1, :, :], aero_tstep.zeta[isurf][2, :, :]) Mstar, Nstar = aero_tstep.dimensions_star[isurf] # Wake grid ax.plot_wireframe(aero_tstep.zeta_star[isurf][0, :(Mstar - minus_mstar), :], aero_tstep.zeta_star[isurf][1, :(Mstar - minus_mstar), :], aero_tstep.zeta_star[isurf][2, :(Mstar - minus_mstar), :]) else: try: import plotly.graph_objects as go except ModuleNotFoundError: print("Plotly package not found") return fig = go.Figure() # Plot aerodynamic grid if aero_tstep is not None: for isurf in range(aero_tstep.n_surf): M, N = aero_tstep.dimensions[isurf] # Plot surfaces for i_m in range(M): for i_n in range(N): vert_m = [i_m, i_m + 1, i_m +1, i_m, i_m] vert_n = [i_n, i_n, i_n +1, i_n + 1, i_n] xsurf = aero_tstep.zeta[isurf][0, vert_m, vert_n] ysurf = aero_tstep.zeta[isurf][1, vert_m, vert_n] zsurf = aero_tstep.zeta[isurf][2, vert_m, vert_n] if i_m == 0 and i_n == 0: fig.add_trace(go.Scatter3d(x=xsurf, y=ysurf, z=zsurf, mode='lines', line={'color':'grey'}, surfaceaxis=2, name='Aero surface')) else: fig.add_trace(go.Scatter3d(x=xsurf, y=ysurf, z=zsurf, mode='lines', line={'color':'grey'}, surfaceaxis=2, showlegend=False)) # Plot wireframe for i_m in range(M + 1): fig.add_trace(go.Scatter3d(x=aero_tstep.zeta[isurf][0, i_m, :], y=aero_tstep.zeta[isurf][1, i_m, :], z=aero_tstep.zeta[isurf][2, i_m, :], mode='lines', line={'color':'black'}, showlegend=False)) for i_n in range(N + 1): fig.add_trace(go.Scatter3d(x=aero_tstep.zeta[isurf][0, :, i_n], y=aero_tstep.zeta[isurf][1, :, i_n], z=aero_tstep.zeta[isurf][2, :, i_n], mode='lines', line={'color':'black'}, showlegend=False)) Mstar, Nstar = aero_tstep.dimensions_star[isurf] # Wake grid for i_m in range(Mstar - minus_mstar): for i_n in range(Nstar): vert_m = [i_m, i_m + 1, i_m +1, i_m, i_m] vert_n = [i_n, i_n, i_n +1, i_n + 1, i_n] xsurf = aero_tstep.zeta_star[isurf][0, vert_m, vert_n] ysurf = aero_tstep.zeta_star[isurf][1, vert_m, vert_n] zsurf = aero_tstep.zeta_star[isurf][2, vert_m, vert_n] if i_m == 0 and i_n == 0: fig.add_trace(go.Scatter3d(x=xsurf, y=ysurf, z=zsurf, mode='lines', line={'color':'lightskyblue'}, surfaceaxis=2, name='Aero wake')) else: fig.add_trace(go.Scatter3d(x=xsurf, y=ysurf, z=zsurf, mode='lines', line={'color':'lightskyblue'}, surfaceaxis=2, showlegend=False)) for i_m in range(Mstar + 1 - minus_mstar): fig.add_trace(go.Scatter3d(x=aero_tstep.zeta_star[isurf][0, i_m, :], y=aero_tstep.zeta_star[isurf][1, i_m, :], z=aero_tstep.zeta_star[isurf][2, i_m, :], mode='lines', line={'color':'grey'}, showlegend=False)) for i_n in range(Nstar + 1): fig.add_trace(go.Scatter3d(x=aero_tstep.zeta_star[isurf][0, :(Mstar + 1 - minus_mstar), i_n], y=aero_tstep.zeta_star[isurf][1, :(Mstar + 1 - minus_mstar), i_n], z=aero_tstep.zeta_star[isurf][2, :(Mstar + 1 - minus_mstar), i_n], mode='lines', line={'color':'grey'}, showlegend=False)) # Plot structure # Split into different beams nodes = data.structure.connectivities[0, :][[0, 2, 1]] fig = fig.add_trace(go.Scatter3d(x=struct_tstep.pos[nodes, 0], y=struct_tstep.pos[nodes, 1], z=struct_tstep.pos[nodes, 2], marker= {'size':2, 'color':'blue'}, line = {'color':'blue', 'width':4}, name='Beam nodes')) for ielem in range(1, data.structure.num_elem): nodes = data.structure.connectivities[ielem, :][[0, 2, 1]] fig.add_trace(go.Scatter3d(x=struct_tstep.pos[nodes, 0], y=struct_tstep.pos[nodes, 1], z=struct_tstep.pos[nodes, 2], marker= {'size':2, 'color':'blue'}, line = {'color':'blue', 'width':4}, showlegend=False)) #I LOVE SEMICOLONS RAAAAH ## Custom Scaling:: # This changes how the plotly graph is scaled so the aspect ratio of your wing stays realistic. # It takes the range of the x and y axes, and scales the graph based on them. # z is set to 0.5 by default so the wake 'plane' is not as large as it originally is. # This has the effect of making the wing flex less visible. # Increase the z_compression value if you want to make it more visible. # prints were used to check the min and max values of x and y in the graph while implementing this; if something goes wrong you might want to check those out. if custom_scaling==True: #print(np.max(aero_tstep.zeta_star[isurf][0,:(Mstar - minus_mstar),:])) #print(np.min(aero_tstep.zeta[isurf][0,:,:])) #print(np.max(aero_tstep.zeta[isurf][1,:,:])) #print(np.min(aero_tstep.zeta[isurf][1,:,:])) rangex=np.max(aero_tstep.zeta_star[isurf][0,:(Mstar - minus_mstar),:])-np.min(aero_tstep.zeta[isurf][0,:,:]);rangey=np.max(aero_tstep.zeta[isurf][1,:,:])-np.min(aero_tstep.zeta[isurf][1,:,:]);fig.update_layout(scene=dict(aspectmode='manual', aspectratio=dict(x=rangex,y=rangey,z=z_compression))) return fig ================================================ FILE: sharpy/utils/rom_interface.py ================================================ from abc import ABCMeta, abstractmethod import sharpy.utils.cout_utils as cout import os import sharpy.utils.frequencyutils as frequencyutils dict_of_roms = {} roms = {} # for internal working # decorator def rom(arg): # global available_solvers global dict_of_roms try: arg.rom_id except AttributeError: raise AttributeError('Class defined as ROM has no rom_id attribute') dict_of_roms[arg.rom_id] = arg return arg def print_available_solvers(): cout.cout_wrap('The available ROMs on this session are:', 2) for name, i_solver in dict_of_roms.items(): cout.cout_wrap('%s ' % i_solver.solver_id, 2) class BaseRom(metaclass=ABCMeta): # Solver id for populating available_roms[] @property def rom_id(self): raise NotImplementedError # The input is a ProblemData class structure @abstractmethod def initialise(self): pass # This executes the solver @abstractmethod def run(self, ss): pass @staticmethod def compare_fom_rom(y1, y2, wv=None, **kwargs): return frequencyutils.freqresp_relative_error(y1, y2, wv, **kwargs) # Save the ROM matrices to the given filename def save(self, filename): raise NotImplementedError('Save method for the currently chosen ROM is not yet supported') def rom_from_string(string): return dict_of_roms[string] def initialise_rom(rom_name): cout.cout_wrap('Generating an instance of %s' % rom_name, 2) cls_type = rom_from_string(rom_name) solver = cls_type() return solver def dictionary_of_solvers(): # import sharpy.rom dictionary = dict() for solver in dict_of_roms: if not solver.lower() == 'SaveData'.lower(): # TODO: why it does not work for savedata? init_solver = initialise_rom(solver) dictionary[solver] = init_solver.settings_default return dictionary ================================================ FILE: sharpy/utils/settings.py ================================================ """ Settings Generator Utilities """ import configparser import ctypes as ct import numpy as np import sharpy.utils.exceptions as exceptions import sharpy.utils.cout_utils as cout import ast class DictConfigParser(configparser.ConfigParser): def as_dict(self): d = dict(self._sections) for k in d: d[k] = dict(self._defaults, **d[k]) d[k].pop('__name__', None) return d def cast(k, v, pytype, ctype, default): try: # if default is None: # raise TypeError val = ctype(pytype(v)) except KeyError: val = ctype(default) cout.cout_wrap("--- The variable " + k + " has no given value, using the default " + default, 2) except TypeError: raise exceptions.NoDefaultValueException(k) except ValueError: val = ctype(v.value) return val def to_custom_types(dictionary, types, default, options=dict(), no_ctype=True): for k, v in types.items(): if type(v) != list: data_type = v else: if k in dictionary: data_type = get_data_type_for_several_options(dictionary[k], v, k) else: # Choose first data type in list for default value data_type = v[0] dictionary[k] = get_custom_type(dictionary, data_type, k, default, no_ctype) check_settings_in_options(dictionary, types, options) unrecognised_settings = [] for k in dictionary.keys(): if k not in list(types.keys()): unrecognised_settings.append(exceptions.NotRecognisedSetting(k)) for setting in unrecognised_settings: cout.cout_wrap(repr(setting), 4) if unrecognised_settings: raise Exception(unrecognised_settings) def get_data_type_for_several_options(dict_value, list_settings_types, setting_name): """ Checks the data type of the setting input in case of several data type options. Only a scalar or list can be the case for these cases. Args: dict_values: Dictionary value of processed settings list_settings_types (list): Possible setting type options for this setting Raises: exception.NotValidSetting: if the setting is not allowed. """ for data_type in list_settings_types: if 'list' in data_type and (type(dict_value) == list or not np.isscalar(dict_value)): return data_type elif 'list' not in data_type and np.isscalar(dict_value): return data_type exceptions.NotValidSettingType(setting_name, dict_value, list_settings_types) def get_default_value(default_value, k, v, data_type = None, py_type = None): if default_value is None: raise exceptions.NoDefaultValueException(k) if v in ['float', 'int', 'bool']: converted_value = cast(k, default_value, py_type, data_type, default_value) elif v == 'str': converted_value = cast(k, default_value, eval(v), eval(v), default_value) else: converted_value = default_value.copy() notify_default_value(k, converted_value) return converted_value def get_custom_type(dictionary, v, k, default, no_ctype): if v == 'int': if no_ctype: data_type = int else: data_type = ct.c_int try: dictionary[k] = cast(k, dictionary[k], int, data_type, default[k]) except KeyError: dictionary[k] = get_default_value(default[k], k, v, data_type=data_type, py_type=int) elif v == 'float': if no_ctype: data_type = float else: data_type = ct.c_double try: dictionary[k] = cast(k, dictionary[k], float, data_type, default[k]) except KeyError: dictionary[k] = get_default_value(default[k], k, v, data_type=data_type, py_type=float) elif v == 'str': try: dictionary[k] = cast(k, dictionary[k], str, str, default[k]) except KeyError: dictionary[k] = get_default_value(default[k], k, v) elif v == 'bool': if no_ctype: data_type = bool else: data_type = ct.c_bool try: dictionary[k] = cast(k, dictionary[k], str2bool, data_type, default[k]) except KeyError: dictionary[k] = get_default_value(default[k], k, v, data_type=data_type, py_type=str2bool) elif v == 'list(str)': try: # if isinstance(dictionary[k], list): # continue # dictionary[k] = dictionary[k].split(',') # getting rid of leading and trailing spaces dictionary[k] = list(map(lambda x: x.strip(), dictionary[k])) except KeyError: dictionary[k] = get_default_value(default[k], k, v) elif v == 'list(dict)': try: # if isinstance(dictionary[k], list): # continue # dictionary[k] = dictionary[k].split(',') # getting rid of leading and trailing spaces for i in range(len(dictionary[k])): dictionary[k][i] = ast.literal_eval(dictionary[k][i]) except KeyError: dictionary[k] = get_default_value(default[k], k, v) elif v == 'list(float)': try: dictionary[k] except KeyError: dictionary[k] = get_default_value(default[k], k, v) if isinstance(dictionary[k], np.ndarray): return dictionary[k] if isinstance(dictionary[k], list): for i in range(len(dictionary[k])): dictionary[k][i] = float(dictionary[k][i]) dictionary[k] = np.array(dictionary[k]) return dictionary[k] # dictionary[k] = dictionary[k].split(',') # # getting rid of leading and trailing spaces # dictionary[k] = list(map(lambda x: x.strip(), dictionary[k])) if dictionary[k].find(',') < 0: dictionary[k] = np.fromstring(dictionary[k].strip('[]'), sep=' ', dtype=ct.c_double) else: dictionary[k] = np.fromstring(dictionary[k].strip('[]'), sep=',', dtype=ct.c_double) elif v == 'list(int)': try: dictionary[k] except KeyError: dictionary[k] = get_default_value(default[k], k, v) if isinstance(dictionary[k], np.ndarray): return dictionary[k] if isinstance(dictionary[k], list): for i in range(len(dictionary[k])): dictionary[k][i] = int(dictionary[k][i]) dictionary[k] = np.array(dictionary[k]) return dictionary[k] # dictionary[k] = dictionary[k].split(',') # # getting rid of leading and trailing spaces # dictionary[k] = list(map(lambda x: x.strip(), dictionary[k])) if dictionary[k].find(',') < 0: dictionary[k] = np.fromstring(dictionary[k].strip('[]'), sep=' ').astype(ct.c_int) else: dictionary[k] = np.fromstring(dictionary[k].strip('[]'), sep=',').astype(ct.c_int) elif v == 'list(complex)': try: dictionary[k] except KeyError: dictionary[k] = get_default_value(default[k], k, v) if isinstance(dictionary[k], np.ndarray): return dictionary[k] if isinstance(dictionary[k], list): for i in range(len(dictionary[k])): dictionary[k][i] = complex(dictionary[k][i]) dictionary[k] = np.array(dictionary[k]) return dictionary[k] # dictionary[k] = dictionary[k].split(',') # # getting rid of leading and trailing spaces # dictionary[k] = list(map(lambda x: x.strip(), dictionary[k])) if dictionary[k].find(',') < 0: dictionary[k] = np.fromstring(dictionary[k].strip('[]'), sep=' ').astype(complex) else: dictionary[k] = np.fromstring(dictionary[k].strip('[]'), sep=',').astype(complex) elif v == 'dict': try: if not isinstance(dictionary[k], dict): raise TypeError('Setting for {:s} is not a dictionary'.format(k)) except KeyError: dictionary[k] = get_default_value(default[k], k, v) else: raise TypeError('Variable %s has an unknown type (%s) that cannot be casted' % (k, v)) return dictionary[k] def check_settings_in_options(settings, settings_types, settings_options): """ Checks that settings given a type ``str`` or ``int`` and allowable options are indeed valid. Args: settings (dict): Dictionary of processed settings settings_types (dict): Dictionary of settings types settings_options (dict): Dictionary of options (may be empty) Raises: exception.NotValidSetting: if the setting is not allowed. """ for k in settings_options: if settings_types[k] == 'int': try: value = settings[k].value except AttributeError: value = settings[k] if value not in settings_options[k]: raise exceptions.NotValidSetting(k, value, settings_options[k]) elif settings_types[k] == 'str': value = settings[k] if value not in settings_options[k] and value: # checks that the value is within the options and that it is not an empty string. raise exceptions.NotValidSetting(k, value, settings_options[k]) elif settings_types[k] == 'list(str)': for item in settings[k]: if item not in settings_options[k] and item: raise exceptions.NotValidSetting(k, item, settings_options[k]) else: pass # no other checks implemented / required def load_config_file(file_name: str) -> dict: """This function reads the flight condition and solver input files. Args: file_name (str): contains the path and file name of the file to be read by the ``configparser`` reader. Returns: config (dict): a ``ConfigParser`` object that behaves like a dictionary """ # config = DictConfigParser() # config.read(file_name) # dict_config = config.as_dict() import configobj dict_config = configobj.ConfigObj(file_name) return dict_config def str2bool(string): false_list = ['false', 'off', '0', 'no'] if isinstance(string, bool): return string if isinstance(string, ct.c_bool): return string.value if not string: return False elif string.lower() in false_list: return False else: return True def notify_default_value(k, v): cout.cout_wrap('Variable ' + k + ' has no assigned value in the settings file.') cout.cout_wrap(' will default to the value: ' + str(v), 1) class SettingsTable: """ Generates the documentation's setting table at runtime. Sphinx is our chosen documentation manager and takes docstrings in reStructuredText format. Given that the SHARPy solvers contain several settings, this class produces a table in reStructuredText format with the solver's settings and adds it to the solver's docstring. This table will then be printed alongside the remaining docstrings. To generate the table, parse the setting's description to a solver dictionary named ``settings_description``, in a similar fashion to what is done with ``settings_types`` and ``settings_default``. If no description is given it will be left blank. Then, add at the end of the solver's class declaration method an instance of the ``SettingsTable`` class and a call to the ``SettingsTable.generate()`` method. Examples: The end of the solver's class declaration should contain .. code-block:: python # Generate documentation table settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) to generate the settings table. """ def __init__(self): self.n_fields = 4 self.n_settings = 0 self.field_length = [0] * self.n_fields self.titles = ['Name', 'Type', 'Description', 'Default'] self.settings_types = dict() self.settings_description = dict() self.settings_default = dict() self.settings_options = dict() self.settings_options_strings = dict() self.line_format = '' self.table_string = '' def generate(self, settings_types, settings_default, settings_description, settings_options=dict(), header_line=None): """ Returns a rst-format table with the settings' names, types, description and default values Args: settings_types (dict): Setting types. settings_default (dict): Settings default value. settings_description (dict): Setting description. header_line (str): Header line description (optional) Returns: str: .rst formatted string with a table containing the settings' information. """ self.settings_types = settings_types self.settings_default = settings_default self.n_settings = len(self.settings_types) # if header_line is None: header_line = 'The settings that this solver accepts are given by a dictionary, ' \ 'with the following key-value pairs:' else: assert type(header_line) == str, 'header_line not a string, verify order of arguments' if type(settings_options) != dict: raise TypeError('settings_options is not a dictionary') if settings_options: # if settings_options are provided self.settings_options = settings_options self.n_fields += 1 self.field_length.append(0) self.titles.append('Options') self.process_options() try: self.settings_description = settings_description except AttributeError: pass self.set_field_length() self.line_format = self.setting_line_format() table_string = '\n ' + header_line + '\n' table_string += '\n ' + self.print_divider_line() table_string += ' ' + self.print_header() table_string += ' ' + self.print_divider_line() for setting in self.settings_types: table_string += ' ' + self.print_setting(setting) table_string += ' ' + self.print_divider_line() self.table_string = table_string return table_string def process_options(self): self.settings_options_strings = self.settings_options.copy() for k, v in self.settings_options.items(): opts = '' for option in v: opts += ' ``%s``,' %str(option) self.settings_options_strings[k] = opts[1:-1] # removes the initial whitespace and final comma def set_field_length(self): field_lengths = [[] for i in range(self.n_fields)] for setting in self.settings_types: stype = str(self.settings_types.get(setting, '')) description = self.settings_description.get(setting, '') default = str(self.settings_default.get(setting, '')) option = str(self.settings_options_strings.get(setting, '')) field_lengths[0].append(len(setting) + 4) # length of name field_lengths[1].append(len(stype) + 4) # length of type + 4 for the rst ``X`` field_lengths[2].append(len(description)) # length of type field_lengths[3].append(len(default) + 4) # length of type + 4 for the rst ``X`` if self.settings_options: field_lengths[4].append(len(option)) for i_field in range(self.n_fields): field_lengths[i_field].append(len(self.titles[i_field])) self.field_length[i_field] = max(field_lengths[i_field]) + 2 # add the two spaces as column dividers def print_divider_line(self): divider = '' for i_field in range(self.n_fields): divider += '='*(self.field_length[i_field]-2) + ' ' divider += '\n' return divider def print_setting(self, setting): type = '``' + str(self.settings_types.get(setting, '')) + '``' description = self.settings_description.get(setting, '') default = '``' + str(self.settings_default.get(setting, '')) + '``' if self.settings_options: option = self.settings_options_strings.get(setting, '') line = self.line_format.format(['``' + str(setting) + '``', type, description, default, option]) + '\n' else: line = self.line_format.format(['``' + str(setting) + '``', type, description, default]) + '\n' return line def print_header(self): header = self.line_format.format(self.titles) + '\n' return header def setting_line_format(self): string = '' for i_field in range(self.n_fields): string += '{0[' + str(i_field) + ']:<' + str(self.field_length[i_field]) + '}' return string def set_value_or_default(dictionary, key, default_val): try: value = dictionary[key] except KeyError: value = default_val return value ================================================ FILE: sharpy/utils/sharpydir.py ================================================ import os SharpyDir = os.path.realpath(os.path.dirname(__file__)+'/../..') ================================================ FILE: sharpy/utils/solver_interface.py ================================================ from abc import ABCMeta, abstractmethod import sharpy.utils.cout_utils as cout import os import sharpy.utils.settings as settings import inspect import shutil import sharpy.utils.exceptions as exceptions dict_of_solvers = {} solvers = {} # for internal working # decorator def solver(arg): # global available_solvers global dict_of_solvers try: arg.solver_id except AttributeError: raise AttributeError('Class defined as solver has no solver_id attribute') dict_of_solvers[arg.solver_id] = arg # a = arg() # settings.SettingsTable().print(a) return arg def print_available_solvers(): cout.cout_wrap('The available solvers on this session are:', 2) for name, i_solver in dict_of_solvers.items(): cout.cout_wrap('%s ' % i_solver.solver_id, 2) class BaseSolver(metaclass=ABCMeta): # solver_classification = 'other' settings_types = dict() settings_description = dict() settings_default = dict() # Solver id for populating available_solvers[] @property def solver_id(self): raise NotImplementedError # The input is a ProblemData class structure @abstractmethod def initialise(self, data, restart=False): pass # This executes the solver @abstractmethod def run(self, **kwargs): pass # @property def __doc__(self): # Generate documentation table settings_table = settings.SettingsTable() _doc = inspect.getdoc(self) _doc += settings_table.generate(settings_types, settings_default, settings_description) return _doc def teardown(self): pass def solver_from_string(string): try: solver = dict_of_solvers[string] except KeyError: raise exceptions.SolverNotFound(string) return solver def solver_list_from_path(cwd): onlyfiles = [f for f in os.listdir(cwd) if os.path.isfile(os.path.join(cwd, f))] for i_file in range(len(onlyfiles)): if onlyfiles[i_file].split('.')[-1] == 'py': # support autosaved files in the folder if onlyfiles[i_file] == "__init__.py": onlyfiles[i_file] = "" continue onlyfiles[i_file] = onlyfiles[i_file].replace('.py', '') else: onlyfiles[i_file] = "" files = [file for file in onlyfiles if not file == ""] return files def initialise_solver(solver_name, print_info=True): if print_info: cout.cout_wrap('Generating an instance of %s' % solver_name, 2) cls_type = solver_from_string(solver_name) solver = cls_type() return solver def dictionary_of_solvers(print_info=True): import sharpy.solvers import sharpy.postproc dictionary = dict() for solver in dict_of_solvers: if solver not in ['GridLoader', 'NonliftingBodyGridLoader']: init_solver = initialise_solver(solver, print_info) dictionary[solver] = init_solver.settings_default else: dictionary[solver] = {} return dictionary def output_documentation(route=None): """ Creates the ``.rst`` files for the solvers that have a docstring such that they can be parsed to Sphinx Args: route (str): Path to folder where solver files are to be created. """ import sharpy.utils.sharpydir as sharpydir solver_types = [] if route is None: base_route = sharpydir.SharpyDir + '/docs/source/includes/' route_solvers = base_route + 'solvers/' route_postprocs = base_route + 'postprocs/' if os.path.exists(route_solvers): print('Cleaning %s' % route_solvers) shutil.rmtree(route_solvers) if os.path.exists(route_postprocs): print('Cleaning %s', route_postprocs) shutil.rmtree(route_postprocs) print('Creating documentation files for solvers in %s' %route_solvers) print('Creating documentation files for post processors in %s' %route_postprocs) created_solvers = dict() for k, v in dict_of_solvers.items(): if k[0] == '_': continue solver = v() created_solvers[k] = solver filename = k + '.rst' try: solver_folder = solver.solver_classification.lower() except AttributeError: print('The solver {} does not have a classification. Dumping it into "Other"'.format(k)) solver_folder = 'other' if solver.__doc__ is None: continue if solver_folder == 'post-processor': solver_type = 'postprocessor' route_to_solver_python = 'sharpy.postproc.' folder = 'postprocs' solver_folder = '' # post-procs do not have sub classification unlike solvers else: solver_type = 'solver' route_to_solver_python = 'sharpy.solvers.' folder = 'solvers' if solver_folder not in solver_types: solver_types.append(solver_folder) if solver.solver_id == 'PreSharpy': route_to_solver_python = 'sharpy.presharpy.' os.makedirs(base_route + '/' + folder + '/' + solver_folder, exist_ok=True) title = k + '\n' title += len(k)*'-' + 2*'\n' if solver.__doc__ is not None: print('\tCreating %s' %(base_route + '/' + folder + '/' + solver_folder + '/' + filename)) autodoc_string = '' autodoc_string = '\n\n.. autoclass:: ' + route_to_solver_python + k.lower() + '.' + k + '\n\t:members:' with open(base_route + '/' + folder + '/' + solver_folder + '/' + filename, "w") as out_file: out_file.write(title + autodoc_string) # Creates index files depending on the type of solver for solver_type in solver_types: if solver_type == 'post-processor': continue filename = solver_type + '_solvers.rst' title = solver_type.capitalize() + ' Solvers' title += '\n' + len(title)*'+' + 2*'\n' with open(route_solvers + '/' + filename, "w") as out_file: out_file.write(title) out_file.write('.. toctree::' + '\n') for k in dict_of_solvers.keys(): if k[0] == '_': continue try: if created_solvers[k].solver_classification.lower() == solver_type and created_solvers[k].__doc__ is not None: out_file.write(' ./' + solver_type + '/' + k + '\n') except AttributeError: pass ================================================ FILE: sharpy/version.py ================================================ # version stored here to don't load dependencies by storing it in __init__.py __version__ = '2.4' ================================================ FILE: tests/__init__.py ================================================ ================================================ FILE: tests/coupled/__init__.py ================================================ ================================================ FILE: tests/coupled/dynamic/__init__.py ================================================ ================================================ FILE: tests/coupled/dynamic/hale/generate_hale.py ================================================ #! /usr/bin/env python3 import h5py as h5 import numpy as np import os import sharpy.utils.algebra as algebra case_name = 'hale' route = os.path.dirname(os.path.realpath(__file__)) + '/' # EXECUTION flow = ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'DynamicCoupled', 'BeamLoads' ] # if free_flight is False, the motion of the centre of the wing is prescribed. free_flight = True # FLIGHT CONDITIONS # the simulation is set such that the aircraft flies at a u_inf velocity while # the air is calm. u_inf = 10 rho = 1.225 # trim sigma = 1.5 alpha = 4.31 * np.pi / 180 beta = 0 roll = 0 gravity = 'on' cs_deflection = -2.08 * np.pi / 180 rudder_static_deflection = 0.0 rudder_step = 0.0 * np.pi / 180 thrust = 6.16 sigma = 1.5 lambda_dihedral = 20 * np.pi / 180 # gust settings gust_intensity = 0.20 gust_length = 1 * u_inf gust_offset = 0.0 * u_inf # numerics n_step = 5 structural_relaxation_factor = 0.6 relaxation_factor = 0.35 tolerance = 1e-6 fsi_tolerance = 1e-4 num_cores = 2 # MODEL GEOMETRY # beam span_main = 16.0 lambda_main = 0.25 ea_main = 0.3 ea = 1e7 ga = 1e5 gj = 1e4 eiy = 2e4 eiz = 4e6 m_bar_main = 0.75 j_bar_main = 0.075 length_fuselage = 10 offset_fuselage = 0 sigma_fuselage = 10 m_bar_fuselage = 0.2 j_bar_fuselage = 0.08 span_tail = 2.5 ea_tail = 0.5 fin_height = 2.5 ea_fin = 0.5 sigma_tail = 100 m_bar_tail = 0.3 j_bar_tail = 0.08 # lumped masses n_lumped_mass = 1 lumped_mass_nodes = np.zeros((n_lumped_mass,), dtype=int) lumped_mass = np.zeros((n_lumped_mass,)) lumped_mass[0] = 50 lumped_mass_inertia = np.zeros((n_lumped_mass, 3, 3)) lumped_mass_position = np.zeros((n_lumped_mass, 3)) # aero chord_main = 1.0 chord_tail = 0.5 chord_fin = 0.5 # DISCRETISATION # spatial discretisation # chordiwse panels m = 4 # spanwise elements n_elem_multiplier = 1 n_elem_main = int(4 * n_elem_multiplier) n_elem_tail = int(2 * n_elem_multiplier) n_elem_fin = int(2 * n_elem_multiplier) n_elem_fuselage = int(2 * n_elem_multiplier) n_surfaces = 5 # temporal discretisation physical_time = 30 tstep_factor = 1. dt = 1.0 / m / u_inf * tstep_factor n_tstep = 5 # END OF INPUT----------------------------------------------------------------- # beam processing n_node_elem = 3 span_main1 = (1.0 - lambda_main) * span_main span_main2 = lambda_main * span_main n_elem_main1 = round(n_elem_main * (1 - lambda_main)) n_elem_main2 = n_elem_main - n_elem_main1 # total number of elements n_elem = 0 n_elem += n_elem_main1 + n_elem_main1 n_elem += n_elem_main2 + n_elem_main2 n_elem += n_elem_fuselage n_elem += n_elem_fin n_elem += n_elem_tail + n_elem_tail # number of nodes per part n_node_main1 = n_elem_main1 * (n_node_elem - 1) + 1 n_node_main2 = n_elem_main2 * (n_node_elem - 1) + 1 n_node_main = n_node_main1 + n_node_main2 - 1 n_node_fuselage = n_elem_fuselage * (n_node_elem - 1) + 1 n_node_fin = n_elem_fin * (n_node_elem - 1) + 1 n_node_tail = n_elem_tail * (n_node_elem - 1) + 1 # total number of nodes n_node = 0 n_node += n_node_main1 + n_node_main1 - 1 n_node += n_node_main2 - 1 + n_node_main2 - 1 n_node += n_node_fuselage - 1 n_node += n_node_fin - 1 n_node += n_node_tail - 1 n_node += n_node_tail - 1 # stiffness and mass matrices n_stiffness = 3 base_stiffness_main = sigma * np.diag([ea, ga, ga, gj, eiy, eiz]) base_stiffness_fuselage = base_stiffness_main.copy() * sigma_fuselage base_stiffness_fuselage[4, 4] = base_stiffness_fuselage[5, 5] base_stiffness_tail = base_stiffness_main.copy() * sigma_tail base_stiffness_tail[4, 4] = base_stiffness_tail[5, 5] n_mass = 3 base_mass_main = np.diag([m_bar_main, m_bar_main, m_bar_main, j_bar_main, 0.5 * j_bar_main, 0.5 * j_bar_main]) base_mass_fuselage = np.diag([m_bar_fuselage, m_bar_fuselage, m_bar_fuselage, j_bar_fuselage, j_bar_fuselage * 0.5, j_bar_fuselage * 0.5]) base_mass_tail = np.diag([m_bar_tail, m_bar_tail, m_bar_tail, j_bar_tail, j_bar_tail * 0.5, j_bar_tail * 0.5]) # PLACEHOLDERS # beam x = np.zeros((n_node,)) y = np.zeros((n_node,)) z = np.zeros((n_node,)) beam_number = np.zeros((n_elem,), dtype=int) frame_of_reference_delta = np.zeros((n_elem, n_node_elem, 3)) structural_twist = np.zeros((n_elem, 3)) conn = np.zeros((n_elem, n_node_elem), dtype=int) stiffness = np.zeros((n_stiffness, 6, 6)) elem_stiffness = np.zeros((n_elem,), dtype=int) mass = np.zeros((n_mass, 6, 6)) elem_mass = np.zeros((n_elem,), dtype=int) boundary_conditions = np.zeros((n_node,), dtype=int) app_forces = np.zeros((n_node, 6)) # aero airfoil_distribution = np.zeros((n_elem, n_node_elem), dtype=int) surface_distribution = np.zeros((n_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces,), dtype=int) m_distribution = 'uniform' aero_node = np.zeros((n_node,), dtype=bool) twist = np.zeros((n_elem, n_node_elem)) sweep = np.zeros((n_elem, n_node_elem)) chord = np.zeros((n_elem, n_node_elem,)) elastic_axis = np.zeros((n_elem, n_node_elem,)) # FUNCTIONS------------------------------------------------------------- def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) dyn_file_name = route + '/' + case_name + '.dyn.h5' if os.path.isfile(dyn_file_name): os.remove(dyn_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def generate_fem(): stiffness[0, ...] = base_stiffness_main stiffness[1, ...] = base_stiffness_fuselage stiffness[2, ...] = base_stiffness_tail mass[0, ...] = base_mass_main mass[1, ...] = base_mass_fuselage mass[2, ...] = base_mass_tail we = 0 wn = 0 # inner right wing beam_number[we:we + n_elem_main1] = 0 y[wn:wn + n_node_main1] = np.linspace(0.0, span_main1, n_node_main1) for ielem in range(n_elem_main1): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main1] = 0 elem_mass[we:we + n_elem_main1] = 0 boundary_conditions[0] = 1 # remember this is in B FoR app_forces[0] = [0, thrust, 0, 0, 0, 0] we += n_elem_main1 wn += n_node_main1 # outer right wing beam_number[we:we + n_elem_main1] = 0 y[wn:wn + n_node_main2 - 1] = y[wn - 1] + np.linspace(0.0, np.cos(lambda_dihedral) * span_main2, n_node_main2)[1:] z[wn:wn + n_node_main2 - 1] = z[wn - 1] + np.linspace(0.0, np.sin(lambda_dihedral) * span_main2, n_node_main2)[1:] for ielem in range(n_elem_main2): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main2] = 0 elem_mass[we:we + n_elem_main2] = 0 boundary_conditions[wn + n_node_main2 - 2] = -1 we += n_elem_main2 wn += n_node_main2 - 1 # inner left wing beam_number[we:we + n_elem_main1 - 1] = 1 y[wn:wn + n_node_main1 - 1] = np.linspace(0.0, -span_main1, n_node_main1)[1:] for ielem in range(n_elem_main1): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = 0 elem_stiffness[we:we + n_elem_main1] = 0 elem_mass[we:we + n_elem_main1] = 0 we += n_elem_main1 wn += n_node_main1 - 1 # outer left wing beam_number[we:we + n_elem_main2] = 1 y[wn:wn + n_node_main2 - 1] = y[wn - 1] + np.linspace(0.0, -np.cos(lambda_dihedral) * span_main2, n_node_main2)[1:] z[wn:wn + n_node_main2 - 1] = z[wn - 1] + np.linspace(0.0, np.sin(lambda_dihedral) * span_main2, n_node_main2)[1:] for ielem in range(n_elem_main2): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main2] = 0 elem_mass[we:we + n_elem_main2] = 0 boundary_conditions[wn + n_node_main2 - 2] = -1 we += n_elem_main2 wn += n_node_main2 - 1 # fuselage beam_number[we:we + n_elem_fuselage] = 2 x[wn:wn + n_node_fuselage - 1] = np.linspace(0.0, length_fuselage, n_node_fuselage)[1:] z[wn:wn + n_node_fuselage - 1] = np.linspace(0.0, offset_fuselage, n_node_fuselage)[1:] for ielem in range(n_elem_fuselage): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [0.0, 1.0, 0.0] conn[we, 0] = 0 elem_stiffness[we:we + n_elem_fuselage] = 1 elem_mass[we:we + n_elem_fuselage] = 1 we += n_elem_fuselage wn += n_node_fuselage - 1 global end_of_fuselage_node end_of_fuselage_node = wn - 1 # fin beam_number[we:we + n_elem_fin] = 3 x[wn:wn + n_node_fin - 1] = x[end_of_fuselage_node] z[wn:wn + n_node_fin - 1] = z[end_of_fuselage_node] + np.linspace(0.0, fin_height, n_node_fin)[1:] for ielem in range(n_elem_fin): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_of_fuselage_node elem_stiffness[we:we + n_elem_fin] = 2 elem_mass[we:we + n_elem_fin] = 2 we += n_elem_fin wn += n_node_fin - 1 end_of_fin_node = wn - 1 # right tail beam_number[we:we + n_elem_tail] = 4 x[wn:wn + n_node_tail - 1] = x[end_of_fin_node] y[wn:wn + n_node_tail - 1] = np.linspace(0.0, span_tail, n_node_tail)[1:] z[wn:wn + n_node_tail - 1] = z[end_of_fin_node] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_of_fin_node elem_stiffness[we:we + n_elem_tail] = 2 elem_mass[we:we + n_elem_tail] = 2 boundary_conditions[wn + n_node_tail - 2] = -1 we += n_elem_tail wn += n_node_tail - 1 # left tail beam_number[we:we + n_elem_tail] = 5 x[wn:wn + n_node_tail - 1] = x[end_of_fin_node] y[wn:wn + n_node_tail - 1] = np.linspace(0.0, -span_tail, n_node_tail)[1:] z[wn:wn + n_node_tail - 1] = z[end_of_fin_node] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = end_of_fin_node elem_stiffness[we:we + n_elem_tail] = 2 elem_mass[we:we + n_elem_tail] = 2 boundary_conditions[wn + n_node_tail - 2] = -1 we += n_elem_tail wn += n_node_tail - 1 with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=n_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=n_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=n_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) lumped_mass_nodes_handle = h5file.create_dataset( 'lumped_mass_nodes', data=lumped_mass_nodes) lumped_mass_handle = h5file.create_dataset( 'lumped_mass', data=lumped_mass) lumped_mass_inertia_handle = h5file.create_dataset( 'lumped_mass_inertia', data=lumped_mass_inertia) lumped_mass_position_handle = h5file.create_dataset( 'lumped_mass_position', data=lumped_mass_position) def generate_aero_file(): global x, y, z # control surfaces n_control_surfaces = 2 control_surface = np.zeros((n_elem, n_node_elem), dtype=int) - 1 control_surface_type = np.zeros((n_control_surfaces,), dtype=int) control_surface_deflection = np.zeros((n_control_surfaces,)) control_surface_chord = np.zeros((n_control_surfaces,), dtype=int) control_surface_hinge_coord = np.zeros((n_control_surfaces,), dtype=float) # control surface type 0 = static # control surface type 1 = dynamic control_surface_type[0] = 0 control_surface_deflection[0] = cs_deflection control_surface_chord[0] = m control_surface_hinge_coord[0] = -0.25 # nondimensional wrt elastic axis (+ towards the trailing edge) control_surface_type[1] = 0 control_surface_deflection[1] = rudder_static_deflection control_surface_chord[1] = 1 control_surface_hinge_coord[1] = -0. # nondimensional wrt elastic axis (+ towards the trailing edge) we = 0 wn = 0 # right wing (surface 0, beam 0) i_surf = 0 airfoil_distribution[we:we + n_elem_main, :] = 0 surface_distribution[we:we + n_elem_main] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_main] = True temp_chord = np.linspace(chord_main, chord_main, n_node_main) temp_sweep = np.linspace(0.0, 0 * np.pi / 180, n_node_main) node_counter = 0 for i_elem in range(we, we + n_elem_main): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = temp_chord[node_counter] elastic_axis[i_elem, i_local_node] = ea_main sweep[i_elem, i_local_node] = temp_sweep[node_counter] we += n_elem_main wn += n_node_main # left wing (surface 1, beam 1) i_surf = 1 airfoil_distribution[we:we + n_elem_main, :] = 0 # airfoil_distribution[wn:wn + n_node_main - 1] = 0 surface_distribution[we:we + n_elem_main] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_main - 1] = True # chord[wn:wn + num_node_main - 1] = np.linspace(main_chord, main_tip_chord, num_node_main)[1:] # chord[wn:wn + num_node_main - 1] = main_chord # elastic_axis[wn:wn + num_node_main - 1] = main_ea temp_chord = np.linspace(chord_main, chord_main, n_node_main) node_counter = 0 for i_elem in range(we, we + n_elem_main): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = temp_chord[node_counter] elastic_axis[i_elem, i_local_node] = ea_main sweep[i_elem, i_local_node] = -temp_sweep[node_counter] we += n_elem_main wn += n_node_main - 1 we += n_elem_fuselage wn += n_node_fuselage - 1 - 1 # # # fin (surface 2, beam 3) i_surf = 2 airfoil_distribution[we:we + n_elem_fin, :] = 1 # airfoil_distribution[wn:wn + n_node_fin] = 0 surface_distribution[we:we + n_elem_fin] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_fin] = True # chord[wn:wn + num_node_fin] = fin_chord for i_elem in range(we, we + n_elem_fin): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_fin elastic_axis[i_elem, i_local_node] = ea_fin control_surface[i_elem, i_local_node] = 1 # twist[end_of_fuselage_node] = 0 # twist[wn:] = 0 # elastic_axis[wn:wn + num_node_main] = fin_ea we += n_elem_fin wn += n_node_fin - 1 # # # # right tail (surface 3, beam 4) i_surf = 3 airfoil_distribution[we:we + n_elem_tail, :] = 2 # airfoil_distribution[wn:wn + n_node_tail] = 0 surface_distribution[we:we + n_elem_tail] = i_surf surface_m[i_surf] = m # XXX not very elegant aero_node[wn:] = True # chord[wn:wn + num_node_tail] = tail_chord # elastic_axis[wn:wn + num_node_main] = tail_ea for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): twist[i_elem, i_local_node] = -0 for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_tail elastic_axis[i_elem, i_local_node] = ea_tail control_surface[i_elem, i_local_node] = 0 we += n_elem_tail wn += n_node_tail # # # left tail (surface 4, beam 5) i_surf = 4 airfoil_distribution[we:we + n_elem_tail, :] = 2 # airfoil_distribution[wn:wn + n_node_tail - 1] = 0 surface_distribution[we:we + n_elem_tail] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_tail - 1] = True # chord[wn:wn + num_node_tail] = tail_chord # elastic_axis[wn:wn + num_node_main] = tail_ea # twist[we:we + num_elem_tail] = -tail_twist for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): twist[i_elem, i_local_node] = -0 for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_tail elastic_axis[i_elem, i_local_node] = ea_tail control_surface[i_elem, i_local_node] = 0 we += n_elem_tail wn += n_node_tail with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( generate_naca_camber(P=0, M=0))) naca_airfoil_tail = airfoils_group.create_dataset('1', data=np.column_stack( generate_naca_camber(P=0, M=0))) naca_airfoil_fin = airfoils_group.create_dataset('2', data=np.column_stack( generate_naca_camber(P=0, M=0))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input.attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # sweep sweep_input = h5file.create_dataset('sweep', data=sweep) dim_attr = sweep_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) control_surface_input = h5file.create_dataset('control_surface', data=control_surface) control_surface_deflection_input = h5file.create_dataset('control_surface_deflection', data=control_surface_deflection) control_surface_chord_input = h5file.create_dataset('control_surface_chord', data=control_surface_chord) control_surface_hinge_coord_input = h5file.create_dataset('control_surface_hinge_coord', data=control_surface_hinge_coord) control_surface_types_input = h5file.create_dataset('control_surface_type', data=control_surface_type) def generate_naca_camber(M=0, P=0): mm = M * 1e-2 p = P * 1e-1 def naca(x, mm, p): if x < 1e-6: return 0.0 elif x < p: return mm / (p * p) * (2 * p * x - x * x) elif x > p and x < 1 + 1e-6: return mm / ((1 - p) * (1 - p)) * (1 - 2 * p + 2 * p * x - x * x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, mm, p) for x in x_vec]) return x_vec, y_vec def generate_solver_file(): file_name = route + '/' + case_name + '.sharpy' settings = dict() settings['SHARPy'] = {'case': case_name, 'route': route, 'flow': flow, 'write_screen': 'off', 'write_log': 'off', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} settings['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-1, 'min_delta': tolerance, 'gravity_on': gravity, 'gravity': 9.81} settings['StaticUvlm'] = {'print_info': 'on', 'horseshoe': 'off', 'num_cores': num_cores, 'n_rollup': 0, 'rollup_dt': dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho} settings['StaticCoupled'] = {'print_info': 'off', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': settings['NonLinearStatic'], 'aero_solver': 'StaticUvlm', 'aero_solver_settings': settings['StaticUvlm'], 'max_iter': 100, 'n_load_steps': n_step, 'tolerance': fsi_tolerance, 'relaxation_factor': structural_relaxation_factor} settings['StaticTrim'] = {'solver': 'StaticCoupled', 'solver_settings': settings['StaticCoupled'], 'initial_alpha': alpha, 'initial_deflection': cs_deflection, 'initial_thrust': thrust} settings['NonLinearDynamicCoupledStep'] = {'print_info': 'off', 'max_iterations': 950, 'delta_curved': 1e-1, 'min_delta': tolerance, 'newmark_damp': 5e-3, 'gravity_on': gravity, 'gravity': 9.81, 'num_steps': n_tstep, 'dt': dt, 'initial_velocity': u_inf} relative_motion = 'off' settings['StepUvlm'] = {'print_info': 'off', 'num_cores': num_cores, 'convection_scheme': 2, 'gamma_dot_filtering': 7, 'velocity_field_generator': 'GustVelocityField', 'velocity_field_input': {'u_inf': int(not free_flight) * u_inf, 'u_inf_direction': [1., 0, 0], 'gust_shape': '1-cos', 'gust_parameters': {'gust_length': gust_length, 'gust_intensity': gust_intensity * u_inf}, 'offset': gust_offset, 'relative_motion': relative_motion}, 'rho': rho, 'n_time_steps': n_tstep, 'dt': dt} settings['BeamLoads'] = {'csv_output': True} solver = 'NonLinearDynamicCoupledStep' settings['DynamicCoupled'] = {'structural_solver': solver, 'structural_solver_settings': settings[solver], 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'fsi_substeps': 3, 'fsi_tolerance': 1e-3, 'relaxation_factor': 0, 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': 0.5, 'n_time_steps': n_tstep, 'dt': dt, 'include_unsteady_force_contribution': 'on', } settings['BeamLoader'] = {'unsteady': 'on', 'orientation': algebra.euler2quat(np.array([roll, alpha, beta]))} settings['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'mstar': int(20 / tstep_factor), 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': ['1', '0', '0'], 'dt': dt}} import configobj config = configobj.ConfigObj() config.filename = file_name for k, v in settings.items(): config[k] = v config.write() clean_test_files() generate_fem() generate_aero_file() generate_solver_file() ================================================ FILE: tests/coupled/dynamic/test_dynamic.py ================================================ import numpy as np import unittest import os class TestCoupledDynamic(unittest.TestCase): """ Tests for dynamic coupled problems to identify errors in the unsteady solvers. Implemented tests: - Gust response of the hale aircraft """ route_file_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) def test_hale_dynamic(self): """ Case and results from: tests/coupled/dynamic/hale reference results produced with SHARPy version 2.0 :return: """ import sharpy.sharpy_main try: import hale.generate_hale except: import tests.coupled.dynamic.hale.generate_hale case_name = 'hale' cases_folder = os.path.join(self.route_file_dir, case_name) output_folder = cases_folder + '/output/' sharpy.sharpy_main.main(['', cases_folder + '/hale.sharpy']) n_tstep = 5 # compare results with reference values ref_Fz = -531.023900359779 ref_My = -1530.0477841197576 file = os.path.join(output_folder, case_name, 'beam/beam_loads_%i.csv' % (n_tstep)) beam_loads_ts = np.loadtxt(file, delimiter=',') np.testing.assert_almost_equal(float(beam_loads_ts[0, 6]), ref_Fz, decimal=3, err_msg='Vertical load on wing root not within 3 decimal points of reference.', verbose=True) np.testing.assert_almost_equal(float(beam_loads_ts[0, 8]), ref_My, decimal=3, err_msg='Pitching moment on wing root not within 3 decimal points of reference.', verbose=True) @classmethod def tearDown(self): """ Removes all created files within this test. """ import shutil folders = ['hale/output'] for folder in folders: shutil.rmtree(self.route_file_dir + '/' + folder) files = ['hale/hale.aero.h5', 'hale/hale.fem.h5', 'hale/hale.sharpy'] for file in files: file_dir = self.route_file_dir + '/' + file if os.path.isfile(file_dir): os.remove(file_dir) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/coupled/multibody/__init__.py ================================================ import tests.coupled.multibody.double_pendulum import tests.coupled.multibody.fix_node_velocity_wrtA import tests.coupled.multibody.fix_node_velocity_wrtG ================================================ FILE: tests/coupled/multibody/double_pendulum/__init__.py ================================================ ================================================ FILE: tests/coupled/multibody/double_pendulum/test_double_pendulum_geradin.py ================================================ import numpy as np import unittest import os import shutil # Data from Geradin # time[s] theta[rad] geradin_FoR0 = np.array([[-0.0117973, 1.56808], [0.0816564, 1.5394], [0.171988, 1.41698], [0.235203, 1.31521], [0.307327, 1.09265], [0.427399, 0.601124], [0.526338, 0.0899229], [0.646417, -0.394903], [0.748531, -0.765465], [0.868959, -0.94209], [0.905131, -0.956217], [0.965504, -0.903829], [1.06828, -0.69149], [1.16508, -0.425429], [1.29798, -0.240495], [1.42483, -0.0688408], [1.56382, 0.169571], [1.78751, 0.634087], [1.89627, 0.806105], [1.98075, 0.844608], [2.10125, 0.734983], [2.19143, 0.478564], [2.26934, 0.0347871], [2.37751, -0.315796], [2.52803, -0.546627], [2.60034, -0.601682], [2.76314, -0.645155], [2.88987, -0.580701], [3.05893, -0.423295], [3.24611, -0.239453], [3.43335, -0.00201041], [3.51194, 0.157214], [3.59668, 0.423517], [3.6815, 0.756821], [3.73591, 0.87633], [3.81435, 0.901554], [3.93481, 0.751729], [4.04305, 0.468146], [4.17525, 0.0366783], [4.34949, -0.556439], [4.44551, -0.987179], [4.57784, -1.29805], [4.65016, -1.3531], [4.70444, -1.35419], [4.78294, -1.27537], [4.86762, -1.06267], [4.99464, -0.743611]]) geradin_FoR1 = np.array([[0.00756934, 0.0266485], [0.134225, 0.0241027], [0.309222, 0.100987], [0.418117, 0.393606], [0.490855, 0.713752], [0.533195, 0.820103], [0.635787, 0.871642], [0.762124, 0.587696], [0.85826, 0.264156], [0.918194, -0.0720575], [0.996205, -0.422034], [1.05008, -0.784926], [1.09792, -1.1477], [1.1639, -1.47063], [1.27207, -1.82121], [1.38636, -2.09152], [1.47067, -2.20042], [1.53694, -2.26875], [1.67582, -2.12414], [1.8028, -1.84528], [1.89365, -1.5121], [1.97843, -1.2056], [2.00271, -1.07208], [2.08146, -0.765457], [2.14818, -0.431789], [2.19686, -0.0575583], [2.24552, 0.303273], [2.29421, 0.690904], [2.37299, 1.02433], [2.44573, 1.34447], [2.55464, 1.65049], [2.65749, 1.92983], [2.7904, 2.12817], [2.94135, 2.27254], [3.03182, 2.27072], [3.18853, 2.17376], [3.30891, 1.95694], [3.42312, 1.61964], [3.50121, 1.33666], [3.56714, 0.973525], [3.61495, 0.583954], [3.66883, 0.221062], [3.71673, -0.0881084], [3.80076, -0.451606], [3.87271, -0.828262], [3.95678, -1.15156], [3.98681, -1.25937], [4.08307, -1.47571], [4.13729, -1.5304], [4.27618, -1.38579], [4.36701, -1.066], [4.4217, -0.705294], [4.50652, -0.37199], [4.59132, -0.0520868], [4.68815, 0.240774], [4.79703, 0.519993], [4.91188, 0.74549], [4.98432, 0.797635]]) class TestDoublePendulum(unittest.TestCase): """ Validation of a double pendulum with a mass at each tip position Reference case: M. Geradin and A. Cardona, "Flexible multibody dynamics : a finite element approach" """ def setUp(self): import sharpy.utils.generate_cases as gc from sharpy.utils.constants import deg2rad # Structural properties mass_per_unit_length = 1. mass_iner = 1e-4 EA = 1e9 GA = 1e9 GJ = 1e9 EI = 1e9 # Beam1 global nnodes1 nnodes1 = 11 l1 = 1.0 m1 = 1.0 theta_ini1 = 90.*deg2rad # Beam2 nnodes2 = nnodes1 l2 = l1 m2 = m1 theta_ini2 = 00.*deg2rad # airfoils airfoil = np.zeros((1,20,2),) airfoil[0,:,0] = np.linspace(0.,1.,20) # Simulation numtimesteps = 10 dt = 0.01 # Create the structure beam1 = gc.AeroelasticInformation() r1 = np.linspace(0.0, l1, nnodes1) node_pos1 = np.zeros((nnodes1,3),) node_pos1[:, 0] = r1*np.sin(theta_ini1) node_pos1[:, 2] = -r1*np.cos(theta_ini1) beam1.StructuralInformation.generate_uniform_sym_beam(node_pos1, mass_per_unit_length, mass_iner, EA, GA, GJ, EI, num_node_elem = 3, y_BFoR = 'y_AFoR', num_lumped_mass=1) beam1.StructuralInformation.body_number = np.zeros((beam1.StructuralInformation.num_elem,), dtype = int) beam1.StructuralInformation.boundary_conditions[0] = 1 beam1.StructuralInformation.boundary_conditions[-1] = -1 beam1.StructuralInformation.lumped_mass_nodes = np.array([nnodes1-1], dtype = int) beam1.StructuralInformation.lumped_mass = np.ones((1,))*m1 beam1.StructuralInformation.lumped_mass_inertia = np.zeros((1,3,3)) beam1.StructuralInformation.lumped_mass_position = np.zeros((1,3)) beam1.AerodynamicInformation.create_one_uniform_aerodynamics( beam1.StructuralInformation, chord = 1., twist = 0., sweep = 0., num_chord_panels = 4, m_distribution = 'uniform', elastic_axis = 0.25, num_points_camber = 20, airfoil = airfoil) beam2 = gc.AeroelasticInformation() r2 = np.linspace(0.0, l2, nnodes2) node_pos2 = np.zeros((nnodes2,3),) node_pos2[:, 0] = r2*np.sin(theta_ini2) + node_pos1[-1, 0] node_pos2[:, 2] = -r2*np.cos(theta_ini2) + node_pos1[-1, 2] beam2.StructuralInformation.generate_uniform_sym_beam(node_pos2, mass_per_unit_length, mass_iner, EA, GA, GJ, EI, num_node_elem = 3, y_BFoR = 'y_AFoR', num_lumped_mass=1) beam2.StructuralInformation.body_number = np.zeros((beam1.StructuralInformation.num_elem,), dtype = int) beam2.StructuralInformation.boundary_conditions[0] = 1 beam2.StructuralInformation.boundary_conditions[-1] = -1 beam2.StructuralInformation.lumped_mass_nodes = np.array([nnodes2-1], dtype = int) beam2.StructuralInformation.lumped_mass = np.ones((1,))*m2 beam2.StructuralInformation.lumped_mass_inertia = np.zeros((1,3,3)) beam2.StructuralInformation.lumped_mass_position = np.zeros((1,3)) beam2.AerodynamicInformation.create_one_uniform_aerodynamics( beam2.StructuralInformation, chord = 1., twist = 0., sweep = 0., num_chord_panels = 4, m_distribution = 'uniform', elastic_axis = 0.25, num_points_camber = 20, airfoil = airfoil) beam1.assembly(beam2) # Simulation details SimInfo = gc.SimulationInformation() SimInfo.set_default_values() SimInfo.define_uinf(np.array([0.0,1.0,0.0]), 1.) SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader', 'AerogridLoader', 'DynamicCoupled'] global name_hinge name_hinge = 'dpg_hinge' SimInfo.solvers['SHARPy']['case'] = name_hinge SimInfo.solvers['SHARPy']['write_screen'] = 'off' SimInfo.solvers['SHARPy']['route'] = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/' SimInfo.solvers['SHARPy']['log_folder'] = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/output/' SimInfo.set_variable_all_dicts('dt', dt) SimInfo.define_num_steps(numtimesteps) SimInfo.set_variable_all_dicts('rho', 0.0) SimInfo.set_variable_all_dicts('velocity_field_input', SimInfo.solvers['SteadyVelocityField']) SimInfo.set_variable_all_dicts('output', os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/output/') SimInfo.solvers['BeamLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['mstar'] = 2 SimInfo.solvers['AerogridLoader']['wake_shape_generator'] = 'StraightWake' SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] = {'u_inf':1., 'u_inf_direction': np.array([0., 1., 0.]), 'dt': dt} SimInfo.solvers['WriteVariablesTime']['FoR_number'] = np.array([0, 1], dtype = int) SimInfo.solvers['WriteVariablesTime']['FoR_variables'] = ['mb_quat'] SimInfo.solvers['WriteVariablesTime']['structure_nodes'] = np.array([nnodes1-1, nnodes1+nnodes2-1], dtype = int) SimInfo.solvers['WriteVariablesTime']['structure_variables'] = ['pos'] SimInfo.solvers['NonLinearDynamicMultibody']['gravity_on'] = True SimInfo.solvers['NonLinearDynamicMultibody']['time_integrator'] = 'NewmarkBeta' SimInfo.solvers['NonLinearDynamicMultibody']['time_integrator_settings'] = {'newmark_damp': 0.15, 'dt': dt} SimInfo.solvers['NonLinearDynamicMultibody']['write_lm'] = True SimInfo.solvers['BeamPlot']['include_FoR'] = True SimInfo.solvers['DynamicCoupled']['structural_solver'] = 'NonLinearDynamicMultibody' SimInfo.solvers['DynamicCoupled']['structural_solver_settings'] = SimInfo.solvers['NonLinearDynamicMultibody'] SimInfo.solvers['DynamicCoupled']['aero_solver'] = 'StepUvlm' SimInfo.solvers['DynamicCoupled']['aero_solver_settings'] = SimInfo.solvers['StepUvlm'] SimInfo.solvers['DynamicCoupled']['postprocessors'] = ['WriteVariablesTime', 'BeamPlot', 'AerogridPlot'] SimInfo.solvers['DynamicCoupled']['postprocessors_settings'] = {'WriteVariablesTime': SimInfo.solvers['WriteVariablesTime'], 'BeamPlot': SimInfo.solvers['BeamPlot'], 'AerogridPlot': SimInfo.solvers['AerogridPlot']} SimInfo.with_forced_vel = False SimInfo.with_dynamic_forces = False # Create the MB and BC files LC1 = gc.LagrangeConstraint() LC1.behaviour = 'hinge_FoR' LC1.body_FoR = 0 LC1.rot_axis_AFoR = np.array([0.0,1.0,0.0]) LC1.scalingFactor = 1e6 LC1.penaltyFactor = 0. LC2 = gc.LagrangeConstraint() LC2.behaviour = 'hinge_node_FoR' LC2.node_in_body = nnodes1-1 LC2.body = 0 LC2.body_FoR = 1 LC2.rot_axisB = np.array([0.0,1.0,0.0]) LC2.scalingFactor = 1e6 LC2.penaltyFactor = 0. LC = [] LC.append(LC1) LC.append(LC2) MB1 = gc.BodyInformation() MB1.body_number = 0 MB1.FoR_position = np.zeros((6,),) MB1.FoR_velocity = np.zeros((6,),) MB1.FoR_acceleration = np.zeros((6,),) MB1.FoR_movement = 'free' MB1.quat = np.array([1.0,0.0,0.0,0.0]) MB2 = gc.BodyInformation() MB2.body_number = 1 MB2.FoR_position = np.array([node_pos2[0, 0], node_pos2[0, 1], node_pos2[0, 2], 0.0, 0.0, 0.0]) MB2.FoR_velocity = np.zeros((6,),) MB2.FoR_acceleration = np.zeros((6,),) MB2.FoR_movement = 'free' MB2.quat = np.array([1.0,0.0,0.0,0.0]) MB = [] MB.append(MB1) MB.append(MB2) # Write files gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(numtimesteps) beam1.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) gc.generate_multibody_file(LC, MB,SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) # Same case without dissipation global name_nb_zero_dis name_nb_zero_dis = 'dpg_nb_zero_dis' SimInfo.solvers['SHARPy']['case'] = name_nb_zero_dis SimInfo.solvers['NonLinearDynamicMultibody']['time_integrator_settings'] = {'newmark_damp': 0., 'dt': dt} gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(numtimesteps) beam1.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) gc.generate_multibody_file(LC, MB,SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) # Same case with generalised alpha global name_ga name_ga = 'dpg_ga' SimInfo.solvers['SHARPy']['case'] = name_ga SimInfo.solvers['NonLinearDynamicMultibody']['time_integrator'] = 'GeneralisedAlpha' SimInfo.solvers['NonLinearDynamicMultibody']['time_integrator_settings'] = {'am': 0.5, 'af': 0.5, 'dt': dt} gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(numtimesteps) beam1.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) gc.generate_multibody_file(LC, MB,SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) # Same case with spherical joints global name_spherical name_spherical = 'dpg_spherical' SimInfo.solvers['SHARPy']['case'] = name_spherical SimInfo.solvers['NonLinearDynamicMultibody']['time_integrator'] = 'NewmarkBeta' SimInfo.solvers['NonLinearDynamicMultibody']['time_integrator_settings'] = {'newmark_damp': 0.15, 'dt': dt} LC1 = gc.LagrangeConstraint() LC1.behaviour = 'spherical_FoR' LC1.body_FoR = 0 LC1.scalingFactor = 1e6 LC2 = gc.LagrangeConstraint() LC2.behaviour = 'spherical_node_FoR' LC2.node_in_body = nnodes1-1 LC2.body = 0 LC2.body_FoR = 1 LC2.scalingFactor = 1e6 gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(numtimesteps) beam1.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) gc.generate_multibody_file(LC, MB,SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) def run_and_assert(self, name): import sharpy.sharpy_main solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/' + name + '.sharpy') sharpy.sharpy_main.main(['', solver_path]) # read output and compare output_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/output/' + name + '/WriteVariablesTime/' pos_tip_data = np.loadtxt(("%sstruct_pos_node%d.dat" % (output_path, nnodes1*2-1)), ) self.assertAlmostEqual(pos_tip_data[-1, 1], 1.051004, 4) self.assertAlmostEqual(pos_tip_data[-1, 2], 0.000000, 4) self.assertAlmostEqual(pos_tip_data[-1, 3], -0.9986984, 4) def test_doublependulum_hinge(self): self.run_and_assert(name_hinge) def test_doublependulum_spherical(self): self.run_and_assert(name_spherical) def test_doublependulum_ga(self): import sharpy.sharpy_main nb_solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/' + name_nb_zero_dis + '.sharpy') sharpy.sharpy_main.main(['', nb_solver_path]) ga_solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/' + name_ga + '.sharpy') sharpy.sharpy_main.main(['', ga_solver_path]) # read output and compare nb_output_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/output/' + name_nb_zero_dis + '/WriteVariablesTime/' nb_pos_tip_data = np.loadtxt(("%sstruct_pos_node%d.dat" % (nb_output_path, nnodes1*2-1)), ) ga_output_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/output/' + name_ga + '/WriteVariablesTime/' ga_pos_tip_data = np.loadtxt(("%sstruct_pos_node%d.dat" % (ga_output_path, nnodes1*2-1)), ) self.assertAlmostEqual(nb_pos_tip_data[-1, 1], ga_pos_tip_data[-1, 1], 4) self.assertAlmostEqual(nb_pos_tip_data[-1, 2], ga_pos_tip_data[-1, 2], 4) self.assertAlmostEqual(nb_pos_tip_data[-1, 3], ga_pos_tip_data[-1, 3], 4) def tearDown(self): solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) solver_path += '/' for name in [name_hinge, name_spherical, name_ga, name_nb_zero_dis]: files_to_delete = [name + '.aero.h5', name + '.dyn.h5', name + '.fem.h5', name + '.mb.h5', name + '.sharpy'] for f in files_to_delete: os.remove(solver_path + f) shutil.rmtree(solver_path + 'output/') if __name__ == '__main__': unittest.main() ================================================ FILE: tests/coupled/multibody/double_prescribed_pendulum/test_double_prescribed_pendulum.py ================================================ import numpy as np import os import shutil from matplotlib import pyplot as plt import unittest import sharpy.sharpy_main import sharpy.utils.algebra as ag import sharpy.utils.generate_cases as gc class TestDoublePrescribedPendulum(unittest.TestCase): # this case is not convenient for using a setup method, as the second case definition depends upon the first # we'll just shove it all in the run method @staticmethod def run_and_assert(): # Simulation inputs case_name_free = 'free_double_pendulum_jax' case_name_prescribed = 'prescribed_double_pendulum_jax' try: shutil.rmtree('' + str(os.path.dirname(os.path.realpath(__file__))) + '/cases/') except: pass try: shutil.rmtree('' + str(os.path.dirname(os.path.realpath(__file__))) + '/output/') except: pass os.makedirs('' + str(os.path.dirname(os.path.realpath(__file__))) + '/cases/') os.makedirs('' + str(os.path.dirname(os.path.realpath(__file__))) + '/output/') case_route = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/cases/' case_out_folder = case_route + '/output/' total_time = 0.05 # physical time (s) dt = 0.002 # time step length (s) n_tstep = int(total_time / dt) # number of time steps hinge_ang = np.deg2rad(30.) # Beam structural properties beam_l = 0.5 # beam length (m) beam_yz = 0.02 # cross-section width/height (m) beam_A = beam_yz ** 2 # square cross-section area` beam_rho = 2700.0 # beam material density (kg/m^3) beam_m = beam_rho * beam_A # beam mass per unit length (kg/m) beam_iner = beam_m * beam_yz ** 2 / 6. # beam inertia per unit length (kg m) EA = 2.800e7 # axial stiffness GA = 1.037e7 # shear stiffness GJ = 6.914e2 # torsional stiffness EI = 9.333e2 # bending stiffness # Toggle on to make spaghetti # GJ *= 5e-2 # EI *= 5e-2 # Airfoils (required as the NoAero solver still requires an aero grid) airfoil = np.zeros((1, 20, 2)) airfoil[0, :, 0] = np.linspace(0., 1., 20) # Beam 0 beam0_n_nodes = 11 # number of nodes beam0_l = beam_l # beam length beam0_ml = 0.0 # lumped mass beam0_theta_ini = np.deg2rad(90.) # initial beam angle # Beam 1 beam1_n_nodes = 11 # number of nodes beam1_l = beam_l # beam length beam1_ml = 0.0 # lumped mass beam1_theta_ini = np.deg2rad(90.) # initial beam angle for hinge_dir in ('y',): # Create beam 0 structure beam0 = gc.AeroelasticInformation() # use aeroelastic as controllers implemented here beam0_r = np.linspace(0., beam0_l, beam0_n_nodes) # local node placement beam0_pos_ini = np.zeros((beam0_n_nodes, 3)) # initial global node placement match hinge_dir: case 'x': beam0_pos_ini[:, 1] = beam0_r * np.sin(beam0_theta_ini) beam0_pos_ini[:, 2] = -beam0_r * np.cos(beam0_theta_ini) y_BFoR = 'x_AFoR' case 'y': beam0_pos_ini[:, 0] = beam0_r * np.sin(beam0_theta_ini) beam0_pos_ini[:, 2] = -beam0_r * np.cos(beam0_theta_ini) y_BFoR = 'y_AFoR' case _: raise KeyError beam0.StructuralInformation.generate_uniform_beam(beam0_pos_ini, beam_m, beam_iner, beam_iner / 2.0, beam_iner / 2.0, np.zeros(3), EA, GA, GA, GJ, EI, EI, num_node_elem=3, y_BFoR=y_BFoR, num_lumped_mass=1) beam0.StructuralInformation.body_number = np.zeros(beam0.StructuralInformation.num_elem, dtype=int) beam0.StructuralInformation.boundary_conditions[0] = 1 beam0.StructuralInformation.boundary_conditions[-1] = -1 beam0.StructuralInformation.lumped_mass_nodes = np.array([beam0_n_nodes - 1], dtype=int) beam0.StructuralInformation.lumped_mass = np.ones(1) * beam0_ml beam0.StructuralInformation.lumped_mass_inertia = np.zeros((1, 3, 3)) beam0.StructuralInformation.lumped_mass_position = np.zeros((1, 3)) beam0.AerodynamicInformation.create_one_uniform_aerodynamics( beam0.StructuralInformation, chord=1., twist=0., sweep=0., num_chord_panels=4, m_distribution='uniform', elastic_axis=0.25, num_points_camber=20, airfoil=airfoil) # Create beam 1 structure beam1 = gc.AeroelasticInformation() beam1_r = np.linspace(0., beam1_l, beam0_n_nodes) # local node placement beam1_pos_ini = np.zeros((beam0_n_nodes, 3)) # initial global node placement match hinge_dir: case 'x': beam1_pos_ini[:, 1] = beam1_r * np.sin(beam1_theta_ini) + beam0_pos_ini[-1, 1] beam1_pos_ini[:, 2] = -beam0_r * np.cos(beam1_theta_ini) + beam0_pos_ini[-1, 2] case 'y': beam1_pos_ini[:, 0] = beam1_r * np.sin(beam1_theta_ini) + beam0_pos_ini[-1, 0] beam1_pos_ini[:, 2] = -beam1_r * np.cos(beam1_theta_ini) + beam0_pos_ini[-1, 2] case _: raise KeyError beam1.StructuralInformation.generate_uniform_beam(beam1_pos_ini, beam_m, beam_iner, beam_iner / 2.0, beam_iner / 2.0, np.zeros(3), EA, GA, GA, GJ, EI, EI, num_node_elem=3, y_BFoR=y_BFoR, num_lumped_mass=1) beam1.StructuralInformation.body_number = np.zeros(beam1.StructuralInformation.num_elem, dtype=int) beam1.StructuralInformation.boundary_conditions[0] = 1 beam1.StructuralInformation.boundary_conditions[-1] = -1 beam1.StructuralInformation.lumped_mass_nodes = np.array([beam1_n_nodes - 1], dtype=int) beam1.StructuralInformation.lumped_mass = np.ones(1) * beam1_ml beam1.StructuralInformation.lumped_mass_inertia = np.zeros((1, 3, 3)) beam1.StructuralInformation.lumped_mass_position = np.zeros((1, 3)) beam1.AerodynamicInformation.create_one_uniform_aerodynamics( beam1.StructuralInformation, chord=1., twist=0., sweep=0., num_chord_panels=4, m_distribution='uniform', elastic_axis=0.25, num_points_camber=20, airfoil=airfoil) # Combine beam1 into beam0 beam0.assembly(beam1) # Create the MB and BC parameters # Free hinge LC0_free = gc.LagrangeConstraint() LC0_free.behaviour = 'hinge_FoR' LC0_free.body_FoR = 0 match hinge_dir: case 'x': LC0_free.rot_axis_AFoR = np.array((1., 0., 0.)) case 'y': LC0_free.rot_axis_AFoR = np.array((0., 1., 0.)) LC0_free.scalingFactor = dt ** -2 # Free hinge between beams LC1_free = gc.LagrangeConstraint() LC1_free.behaviour = 'hinge_node_FoR' LC1_free.node_in_body = beam0_n_nodes - 1 LC1_free.body = 0 LC1_free.body_FoR = 1 match hinge_dir: case 'x': LC1_free.rot_axisB = np.array((np.sin(hinge_ang), np.cos(hinge_ang), 0.)) LC1_free.rot_axisA2 = np.array((np.cos(hinge_ang), np.sin(hinge_ang), 0.)) case 'y': LC1_free.rot_axisB = np.array((np.sin(hinge_ang), np.cos(hinge_ang), 0.)) LC1_free.rot_axisA2 = np.array((np.sin(hinge_ang), np.cos(hinge_ang), 0.)) LC1_free.scalingFactor = dt ** -2 LC_free = [LC0_free, LC1_free] # List of LCs MB0 = gc.BodyInformation() MB0.body_number = 0 MB0.FoR_position = np.zeros(6) MB0.FoR_velocity = np.zeros(6) MB0.FoR_acceleration = np.zeros(6) MB0.FoR_movement = 'free' MB0.quat = np.array([1., 0., 0., 0.]) MB1 = gc.BodyInformation() MB1.body_number = 1 MB1.FoR_position = np.array([*beam1_pos_ini[0, :], 0., 0., 0.]) MB1.FoR_velocity = np.zeros(6) MB1.FoR_acceleration = np.zeros(6) MB1.FoR_movement = 'free' MB1.quat = np.array((1., 0., 0., 0.)) MB = [MB0, MB1] # List of MBs # Simulation details SimInfo = gc.SimulationInformation() SimInfo.set_default_values() SimInfo.with_forced_vel = False SimInfo.with_dynamic_forces = False SimInfo.solvers['SHARPy']['flow'] = [ 'BeamLoader', 'AerogridLoader', 'DynamicCoupled' ] SimInfo.solvers['SHARPy']['case'] = case_name_free SimInfo.solvers['SHARPy']['write_screen'] = False SimInfo.solvers['SHARPy']['route'] = case_route SimInfo.solvers['SHARPy']['log_folder'] = case_out_folder SimInfo.set_variable_all_dicts('dt', dt) SimInfo.define_num_steps(n_tstep) SimInfo.set_variable_all_dicts('output', case_out_folder) SimInfo.solvers['BeamLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['mstar'] = 2 SimInfo.solvers['AerogridLoader']['wake_shape_generator'] = 'StraightWake' SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] = {'u_inf': 1., 'u_inf_direction': np.array( (0., 1., 0.)), 'dt': dt} SimInfo.solvers['NonLinearDynamicMultibodyJAX']['write_lm'] = False SimInfo.solvers['NonLinearDynamicMultibodyJAX']['gravity_on'] = True SimInfo.solvers['NonLinearDynamicMultibodyJAX']['gravity'] = 9.81 SimInfo.solvers['NonLinearDynamicMultibodyJAX']['time_integrator'] = 'NewmarkBetaJAX' SimInfo.solvers['NonLinearDynamicMultibodyJAX']['time_integrator_settings'] = {'newmark_damp': 0.0, 'dt': dt} SimInfo.solvers['BeamPlot']['include_FoR'] = False SimInfo.solvers['DynamicCoupled']['structural_solver'] = 'NonLinearDynamicMultibodyJAX' SimInfo.solvers['DynamicCoupled']['structural_solver_settings'] = SimInfo.solvers[ 'NonLinearDynamicMultibodyJAX'] SimInfo.solvers['DynamicCoupled']['aero_solver'] = 'NoAero' SimInfo.solvers['DynamicCoupled']['aero_solver_settings'] = SimInfo.solvers['NoAero'] SimInfo.solvers['DynamicCoupled']['postprocessors'] = ['BeamPlot'] SimInfo.solvers['DynamicCoupled']['postprocessors_settings'] = {'BeamPlot': {}} # Write files gc.clean_test_files(case_route, case_name_free) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(n_tstep) beam0.generate_h5_files(case_route, case_name_free) gc.generate_multibody_file(LC_free, MB, case_route, case_name_free, use_jax=True) # Run SHARPy for free case case_data_free = sharpy.sharpy_main.main(['', case_route + case_name_free + '.sharpy']) # Calculate constraint angles quat0 = [case_data_free.structure.timestep_info[i].mb_quat[0, :] for i in range(n_tstep)] quat1 = [case_data_free.structure.timestep_info[i].mb_quat[1, :] for i in range(n_tstep)] psi_ga = [case_data_free.structure.timestep_info[i].psi[int((beam0_n_nodes - 3) / 2), 1, :] for i in range(n_tstep)] psi_a = [ag.quat2crv(quat0[i]) for i in range(n_tstep)] psi_hg = [ag.rotation2crv(ag.quat2rotation(quat0[i]) @ ag.crv2rotation(psi_ga[i]) @ ag.quat2rotation(quat1[i]).T) for i in range(n_tstep)] # Calculate constraint velocities omega_a = [case_data_free.structure.timestep_info[i].mb_FoR_vel[0, 3:] for i in range(n_tstep)] omega_h = [case_data_free.structure.timestep_info[i].mb_FoR_vel[1, 3:] for i in range(n_tstep)] psi_ga_dot = [case_data_free.structure.timestep_info[i].psi_dot[int((beam0_n_nodes - 3) / 2), 1, :] for i in range(n_tstep)] psi_a_dot = [np.linalg.solve(ag.crv2tan(psi_a[i]), omega_a[i]) for i in range(n_tstep)] term1 = [-ag.crv2tan(psi_ga[i]) @ psi_ga_dot[i] - ag.crv2rotation(psi_ga[i]).T @ omega_a[i] + ag.crv2rotation(psi_ga[i]).T @ ag.quat2rotation(quat0[i]).T @ ag.quat2rotation(quat1[i]) @ omega_h[i] for i in range(n_tstep)] psi_hg_dot = [np.linalg.inv(ag.crv2tan(psi_hg[i])) @ ag.crv2rotation(psi_hg[i]) @ term1[i] for i in range(n_tstep)] # Controller inputs angle_input_file0 = case_out_folder + f'angle_input0_{hinge_dir}.npy' angle_input_file1 = case_out_folder + f'angle_input1_{hinge_dir}.npy' vel_input_file0 = case_out_folder + f'vel_input0_{hinge_dir}.npy' vel_input_file1 = case_out_folder + f'vel_input1_{hinge_dir}.npy' angle_input0 = np.array(psi_a) angle_input1 = np.array(psi_hg) vel_input0 = np.array(psi_a_dot) vel_input1 = np.array(psi_hg_dot) # Uncomment to stop prescribed pendulum tracking after a certain timestep (prove both cases aren't actually free!) # angle_input1[int(n_tstep/2):, :] = angle_input1[int(n_tstep/2) - 1, :] # vel_input1[int(n_tstep/2):, :] = 0. np.save(angle_input_file0, angle_input0) np.save(angle_input_file1, angle_input1) np.save(vel_input_file0, vel_input0) np.save(vel_input_file1, vel_input1) SimInfo.define_num_steps(n_tstep - 1) # Prescribe top joint angular velocity LC0_prescribed = gc.LagrangeConstraint() LC0_prescribed.behaviour = 'control_rot_vel_FoR' LC0_prescribed.controller_id = 'controller0' LC0_prescribed.body_FoR = 0 LC0_prescribed.scalingFactor = dt ** -2 # Actuated joint between beams LC1_prescribed = gc.LagrangeConstraint() LC1_prescribed.behaviour = 'control_node_FoR_rot_vel' LC1_prescribed.controller_id = 'controller1' LC1_prescribed.node_in_body = beam1_n_nodes - 1 LC1_prescribed.body = 0 LC1_prescribed.body_FoR = 1 LC1_prescribed.scalingFactor = dt ** -2 LC1_prescribed.rel_posB = np.zeros(3) LC_prescribed = [LC0_prescribed, LC1_prescribed] # List of LCs SimInfo.solvers['SHARPy']['case'] = case_name_prescribed SimInfo.solvers['DynamicCoupled']['controller_id'] = { 'controller0': 'MultibodyController', 'controller1': 'MultibodyController', } SimInfo.solvers['DynamicCoupled']['controller_settings']['controller0'] = { 'ang_history_input_file': angle_input_file0, 'ang_vel_history_input_file': vel_input_file0, 'dt': dt, } SimInfo.solvers['DynamicCoupled']['controller_settings']['controller1'] = { 'ang_history_input_file': angle_input_file1, 'ang_vel_history_input_file': vel_input_file1, 'dt': dt, } # Write files gc.clean_test_files(case_route, case_name_prescribed) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(n_tstep) beam0.generate_h5_files(case_route, case_name_prescribed) gc.generate_multibody_file(LC_prescribed, MB, case_route, case_name_prescribed, use_jax=True) # Run SHARPy for prescribed case case_data_prescribed = sharpy.sharpy_main.main(['', case_route + case_name_prescribed + '.sharpy']) # compare (controller framework operates a timestep behind) # comparing y tip displacement pos_free = case_data_free.structure.timestep_info[-3].pos[-1, 1] pos_prescribed = case_data_prescribed.structure.timestep_info[-1].pos[-1, 1] diff = np.abs((pos_free / pos_prescribed) - 1.) if diff > 1e-3: raise ValueError(f"Free and prescribed pendulum results no not closely match: diff={diff}") def test_double_prescribed_pendulum(self): self.run_and_assert() def tearDown(self): solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/' shutil.rmtree(solver_path + 'cases/') shutil.rmtree(solver_path + 'output/') def teardown_method(self): pass if __name__ == '__main__': unittest.main() ================================================ FILE: tests/coupled/multibody/double_slanted_pendulum/__init__.py ================================================ ================================================ FILE: tests/coupled/multibody/double_slanted_pendulum/test_double_pendulum_slanted.py ================================================ import numpy as np import unittest import os import shutil from sharpy.utils.constants import deg2rad class TestDoublePendulumSlanted(unittest.TestCase): """ Validation of a double pendulum with distributed mass and flared hinge axis at the connection As given in https://dx.doi.org/10.2514/6.2024-1441 """ def _setUp(self, lateral): import sharpy.utils.generate_cases as gc import sharpy.utils.algebra as ag # Structural properties length_beam = 0.5 #meters cx_length = 0.02 #meters A = cx_length * cx_length #assume rectangular cross section a= d^2 material_density = 2700.0 #kg/m^3 mass_per_unit_length = material_density * A #kg/m mass_iner = (mass_per_unit_length) * (cx_length * cx_length + cx_length * cx_length) / (12.0) EA = 2.800e7 GA = 1.037e7 GJ = 6.914e2 EI = 9.333e2 lateral_ini = lateral # Beam1 global nnodes1 nnodes1 = 11 l1 = length_beam m1 = 0.0 theta_ini1 = 90. * deg2rad # Beam2 nnodes2 = nnodes1 l2 = l1 m2 = m1 theta_ini2 = 90. * deg2rad # airfoils airfoil = np.zeros((1, 20, 2), ) airfoil[0, :, 0] = np.linspace(0., 1., 20) # Simulation numtimesteps = 30 dt = 0.01 # Create the structure beam1 = gc.AeroelasticInformation() r1 = np.linspace(0.0, l1, nnodes1) node_pos1 = np.zeros((nnodes1, 3), ) node_pos1[:, 0] = r1 * np.sin(theta_ini1) * np.cos(lateral_ini) node_pos1[:, 1] = r1 * np.sin(theta_ini1) * np.sin(lateral_ini) node_pos1[:, 2] = -r1 * np.cos(theta_ini1) beam1.StructuralInformation.generate_uniform_beam(node_pos1, mass_per_unit_length, mass_iner, mass_iner / 2.0, mass_iner / 2.0, np.zeros((3,), ), EA, GA, GA, GJ, EI, EI, num_node_elem=3, y_BFoR='y_AFoR', num_lumped_mass=1) beam1.StructuralInformation.body_number = np.zeros((beam1.StructuralInformation.num_elem,), dtype=int) beam1.StructuralInformation.boundary_conditions[0] = 1 beam1.StructuralInformation.boundary_conditions[-1] = -1 beam1.StructuralInformation.lumped_mass_nodes = np.array([nnodes1 - 1], dtype=int) beam1.StructuralInformation.lumped_mass = np.ones((1,)) * m1 beam1.StructuralInformation.lumped_mass_inertia = np.zeros((1, 3, 3)) beam1.StructuralInformation.lumped_mass_position = np.zeros((1, 3)) beam1.AerodynamicInformation.create_one_uniform_aerodynamics( beam1.StructuralInformation, chord=1., twist=0., sweep=0., num_chord_panels=4, m_distribution='uniform', elastic_axis=0.25, num_points_camber=20, airfoil=airfoil) beam2 = gc.AeroelasticInformation() r2 = np.linspace(0.0, l2, nnodes2) node_pos2 = np.zeros((nnodes2, 3), ) node_pos2[:, 0] = r2 * np.sin(theta_ini2) * np.cos(lateral_ini) + node_pos1[-1, 0] node_pos2[:, 1] = r2 * np.sin(theta_ini2) * np.sin(lateral_ini) + node_pos1[-1, 1] node_pos2[:, 2] = -r2 * np.cos(theta_ini2) + node_pos1[-1, 2] + 0.00000001 beam2.StructuralInformation.generate_uniform_beam(node_pos2, mass_per_unit_length, mass_iner, mass_iner / 2.0, mass_iner / 2.0, np.zeros((3,), ), EA, GA, GA, GJ, EI, EI, num_node_elem=3, y_BFoR='y_AFoR', num_lumped_mass=1) beam2.StructuralInformation.body_number = np.zeros((beam1.StructuralInformation.num_elem,), dtype=int) beam2.StructuralInformation.boundary_conditions[0] = 1 beam2.StructuralInformation.boundary_conditions[-1] = -1 beam2.StructuralInformation.lumped_mass_nodes = np.array([nnodes2 - 1], dtype=int) beam2.StructuralInformation.lumped_mass = np.ones((1,)) * m2 beam2.StructuralInformation.lumped_mass_inertia = np.zeros((1, 3, 3)) beam2.StructuralInformation.lumped_mass_position = np.zeros((1, 3)) beam2.AerodynamicInformation.create_one_uniform_aerodynamics( beam2.StructuralInformation, chord=1., twist=0., sweep=0., num_chord_panels=4, m_distribution='uniform', elastic_axis=0.25, num_points_camber=20, airfoil=airfoil) beam1.assembly(beam2) # Simulation details SimInfo = gc.SimulationInformation() SimInfo.set_default_values() SimInfo.define_uinf(np.array([0.0, 1.0, 0.0]), 1.) SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader', 'AerogridLoader', 'DynamicCoupled'] global name_hinge_slanted name_hinge_slanted = 'name_hinge_slanted' SimInfo.solvers['SHARPy']['case'] = name_hinge_slanted SimInfo.solvers['SHARPy']['write_screen'] = 'off' SimInfo.solvers['SHARPy']['route'] = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/' SimInfo.solvers['SHARPy']['log_folder'] = os.path.abspath( os.path.dirname(os.path.realpath(__file__))) + '/output/' SimInfo.set_variable_all_dicts('dt', dt) SimInfo.define_num_steps(numtimesteps) SimInfo.set_variable_all_dicts('rho', 0.0) SimInfo.set_variable_all_dicts('velocity_field_input', SimInfo.solvers['SteadyVelocityField']) SimInfo.set_variable_all_dicts('output', os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/output/') SimInfo.solvers['BeamLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['initial_align'] = 'off' SimInfo.solvers['AerogridLoader']['aligned_grid'] = 'off' SimInfo.solvers['AerogridLoader']['mstar'] = 2 SimInfo.solvers['AerogridLoader']['wake_shape_generator'] = 'StraightWake' SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] = {'u_inf': 1., 'u_inf_direction': np.array([0., 1., 0.]), 'dt': dt} SimInfo.solvers['WriteVariablesTime']['FoR_number'] = np.array([0, 1], dtype=int) SimInfo.solvers['WriteVariablesTime']['FoR_variables'] = ['for_pos', 'mb_quat'] SimInfo.solvers['WriteVariablesTime']['structure_nodes'] = np.array([nnodes1 - 1, nnodes1 + nnodes2 - 1], dtype=int) SimInfo.solvers['WriteVariablesTime']['structure_variables'] = ['pos'] SimInfo.solvers['NonLinearDynamicMultibody']['gravity_on'] = True SimInfo.solvers['NonLinearDynamicMultibody']['gravity'] = 9.81 SimInfo.solvers['NonLinearDynamicMultibody']['time_integrator'] = 'NewmarkBeta' SimInfo.solvers['NonLinearDynamicMultibody']['time_integrator_settings'] = {'newmark_damp': 0.0, 'dt': dt} SimInfo.solvers['NonLinearDynamicMultibody']['write_lm'] = True SimInfo.solvers['BeamPlot']['include_FoR'] = True SimInfo.solvers['DynamicCoupled']['structural_solver'] = 'NonLinearDynamicMultibody' SimInfo.solvers['DynamicCoupled']['structural_solver_settings'] = SimInfo.solvers['NonLinearDynamicMultibody'] SimInfo.solvers['DynamicCoupled']['aero_solver'] = 'NoAero' SimInfo.solvers['DynamicCoupled']['aero_solver_settings'] = SimInfo.solvers['NoAero'] SimInfo.solvers['DynamicCoupled']['postprocessors'] = ['WriteVariablesTime', 'BeamPlot', 'AerogridPlot'] SimInfo.solvers['DynamicCoupled']['postprocessors_settings'] = { 'WriteVariablesTime': SimInfo.solvers['WriteVariablesTime'], 'BeamPlot': SimInfo.solvers['BeamPlot'], 'AerogridPlot': SimInfo.solvers['AerogridPlot'] } SimInfo.with_forced_vel = False SimInfo.with_dynamic_forces = False # Create the MB and BC files LC1 = gc.LagrangeConstraint() LC1.behaviour = 'hinge_FoR' LC1.body_FoR = 0 LC1.rot_axis_AFoR = np.array([0.0, 1.0, 0.0]) LC1.scalingFactor = 1e6 LC1.penaltyFactor = 0. LC2 = gc.LagrangeConstraint() LC2.behaviour = 'hinge_node_FoR' LC2.node_in_body = nnodes1 - 1 LC2.body = 0 LC2.body_FoR = 1 LC2.rot_axisB = np.array([np.sin(45.0 * deg2rad), np.cos(45.0 * deg2rad), 0.0]) LC2.rot_axisA2 = np.array([np.sin(45.0 * deg2rad), np.cos(45.0 * deg2rad), 0.0]) LC2.scalingFactor = 1e6 LC2.penaltyFactor = 0. LC = [] LC.append(LC1) LC.append(LC2) MB1 = gc.BodyInformation() MB1.body_number = 0 MB1.FoR_position = np.zeros(6) MB1.FoR_velocity = np.zeros(6) MB1.FoR_acceleration = np.zeros(6) MB1.FoR_movement = 'free' MB1.quat = ag.rotation2quat(ag.rotation3d_z(lateral_ini) @ ag.quat2rotation(np.array([1., 0., 0., 0.]))) MB2 = gc.BodyInformation() MB2.body_number = 1 MB2.FoR_position = np.array([node_pos2[0, 0], node_pos2[0, 1], node_pos2[0, 2], 0., 0., 0.]) MB2.FoR_velocity = np.zeros(6) MB2.FoR_acceleration = np.zeros(6) MB2.FoR_movement = 'free' MB2.quat = ag.rotation2quat(ag.rotation3d_z(lateral_ini) @ ag.quat2rotation(np.array([1., 0., 0., 0.]))) MB = [] MB.append(MB1) MB.append(MB2) # Write files gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(numtimesteps) beam1.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) gc.generate_multibody_file(LC, MB, SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) # Same case with penalty weights global name_hinge_slanted_pen name_hinge_slanted_pen = 'name_hinge_slanted_pen' SimInfo.solvers['SHARPy']['case'] = name_hinge_slanted_pen LC1.scalingFactor = 1e-24 LC1.penaltyFactor = 1e0 LC2.scalingFactor = 1e-24 LC2.penaltyFactor = 1e0 gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(numtimesteps) beam1.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) gc.generate_multibody_file(LC, MB, SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) # Same case with rotated global reference global name_hinge_slanted_lateralrot name_hinge_slanted_lateralrot = 'name_hinge_slanted_lateralrot' SimInfo.solvers['SHARPy']['case'] = name_hinge_slanted_lateralrot LC2.rot_axisB = np.array([np.sin(45.0 * deg2rad), np.cos(45.0 * deg2rad), 0.0]) LC2.rot_axisA2 = np.array([np.sin(45.0 * deg2rad), np.cos(45.0 * deg2rad), 0.0]) gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(numtimesteps) beam1.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) gc.generate_multibody_file(LC, MB, SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(numtimesteps) beam1.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) gc.generate_multibody_file(LC, MB, SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) def run_and_assert(self, name, lateral): import sharpy.sharpy_main import sharpy.utils.algebra as ag solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/' + name + '.sharpy') sharpy.sharpy_main.main(['', solver_path]) # read output and compare output_path = os.path.abspath( os.path.dirname(os.path.realpath(__file__))) + '/output/' + name + '/WriteVariablesTime/' pos_tip_data = np.loadtxt(("%sstruct_pos_node%d.dat" % (output_path, nnodes1 * 2 - 1)), ) for_pos_tip_data = np.loadtxt(("%sFoR_%02d_for_pos.dat" % (output_path, 0)), ) quat_tip_data = np.loadtxt(("%sFoR_%02d_mb_quat.dat" % (output_path, 0)), ) calc_pos_tip_data = ag.rotation3d_z(-lateral) @ (for_pos_tip_data[-1, 1:4] + ag.quat2rotation(quat_tip_data[-1, 1:]) @ pos_tip_data[-1, 1:]) self.assertAlmostEqual(calc_pos_tip_data[0], 0.80954978, 4) self.assertAlmostEqual(calc_pos_tip_data[1], 0.1024842, 4) self.assertAlmostEqual(calc_pos_tip_data[2], -0.48183994, 4) def test_doublependulum_hinge_slanted(self): lateral_hinge = 0. * deg2rad self._setUp(lateral_hinge) self.run_and_assert(name_hinge_slanted, lateral_hinge) def test_doublependulum_hinge_slanted_pen(self): lateral_hinge = 0. * deg2rad self._setUp(lateral_hinge) self.run_and_assert(name_hinge_slanted_pen, lateral_hinge) def test_doublependulum_hinge_slanted_lateralrot(self): lateral_hinge = 30. * deg2rad self._setUp(lateral_hinge) self.run_and_assert(name_hinge_slanted_lateralrot, lateral_hinge) def tearDown(self): solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) solver_path += '/' for name in ['name_hinge_slanted', 'name_hinge_slanted_pen', 'name_hinge_slanted_lateralrot']: files_to_delete = [name + '.aero.h5', name + '.dyn.h5', name + '.fem.h5', name + '.mb.h5', name + '.sharpy'] for f in files_to_delete: os.remove(solver_path + f) shutil.rmtree(solver_path + 'output/') if __name__ == '__main__': unittest.main() ================================================ FILE: tests/coupled/multibody/fix_node_velocity_wrtA/__init__.py ================================================ ================================================ FILE: tests/coupled/multibody/fix_node_velocity_wrtA/test_fix_node_velocity_wrtA.py ================================================ import numpy as np import unittest import os import shutil folder = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) class TestFixNodeVelocitywrtA(unittest.TestCase): def setUp(self): import sharpy.utils.generate_cases as gc nodes_per_elem = 3 # beam1: uniform and symmetric with aerodynamic properties equal to zero nnodes1 = 11 length1 = 10. mass_per_unit_length = 1. mass_iner = 1e-4 EA = 1e7 GA = 1e7 GJ = 1e3 EI = 1e4 # Create beam1 beam1 = gc.AeroelasticInformation() # Structural information beam1.StructuralInformation.num_node = nnodes1 beam1.StructuralInformation.num_node_elem = nodes_per_elem beam1.StructuralInformation.compute_basic_num_elem() beam1.StructuralInformation.set_to_zero(beam1.StructuralInformation.num_node_elem, beam1.StructuralInformation.num_node, beam1.StructuralInformation.num_elem) node_pos = np.zeros((nnodes1, 3), ) node_pos[:, 0] = np.linspace(0.0, length1, nnodes1) beam1.StructuralInformation.generate_uniform_sym_beam(node_pos, mass_per_unit_length, mass_iner, EA, GA, GJ, EI, num_node_elem = 3, y_BFoR = 'y_AFoR', num_lumped_mass=1) beam1.StructuralInformation.boundary_conditions[0] = 1 beam1.StructuralInformation.boundary_conditions[-1] = -1 beam1.StructuralInformation.lumped_mass_nodes = np.array([nnodes1-1], dtype=int) beam1.StructuralInformation.lumped_mass = np.array([1.]) beam1.StructuralInformation.lumped_mass_inertia = np.zeros((1, 3, 3),) beam1.StructuralInformation.lumped_mass_position = np.zeros((1, 3),) # beam1.StructuralInformation.structural_twist += 2.*deg2rad beam1.StructuralInformation.app_forces[-1, 2] = -10. # Aerodynamic information airfoil = np.zeros((1,20,2),) airfoil[0,:,0] = np.linspace(0.,1.,20) beam1.AerodynamicInformation.create_one_uniform_aerodynamics( beam1.StructuralInformation, chord = 1., twist = 0., sweep = 0., num_chord_panels = 4, m_distribution = 'uniform', elastic_axis = 0.5, num_points_camber = 20, airfoil = airfoil) # SOLVER CONFIGURATION SimInfo = gc.SimulationInformation() SimInfo.set_default_values() SimInfo.define_uinf(np.array([0.0,1.0,0.0]), 10.) SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'DynamicCoupled', 'BeamPlot'] global name name = 'fix_node_velocity_wrtA' SimInfo.solvers['SHARPy']['case'] = name SimInfo.solvers['SHARPy']['write_screen'] = 'off' SimInfo.solvers['SHARPy']['route'] = folder + '/' SimInfo.solvers['SHARPy']['log_folder'] = folder + '/output/' SimInfo.set_variable_all_dicts('dt', 0.05) SimInfo.set_variable_all_dicts('rho', 0.0) SimInfo.set_variable_all_dicts('velocity_field_input', SimInfo.solvers['SteadyVelocityField']) SimInfo.solvers['BeamLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['mstar'] = 2 SimInfo.solvers['AerogridLoader']['wake_shape_generator'] = 'StraightWake' SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] = {'u_inf':10., 'u_inf_direction': np.array([0., 1., 0.]), 'dt': 0.05} SimInfo.solvers['NonLinearStatic']['print_info'] = False SimInfo.solvers['StaticCoupled']['structural_solver'] = 'NonLinearStatic' SimInfo.solvers['StaticCoupled']['structural_solver_settings'] = SimInfo.solvers['NonLinearStatic'] SimInfo.solvers['StaticCoupled']['aero_solver'] = 'StaticUvlm' SimInfo.solvers['StaticCoupled']['aero_solver_settings'] = SimInfo.solvers['StaticUvlm'] SimInfo.solvers['StaticCoupled']['relaxation_factor'] = 0.0 SimInfo.solvers['NonLinearDynamicMultibody']['gravity_on'] = True SimInfo.solvers['WriteVariablesTime']['structure_nodes'] = np.array([0, int((nnodes1-1)/2), -1], dtype = int) SimInfo.solvers['WriteVariablesTime']['structure_variables'] = ['pos'] SimInfo.solvers['BeamPlot']['include_FoR'] = True SimInfo.solvers['NonLinearDynamicMultibody']['relaxation_factor'] = 0.2 SimInfo.solvers['NonLinearDynamicMultibody']['min_delta'] = 1e-6 SimInfo.solvers['NonLinearDynamicMultibody']['max_iterations'] = 200 SimInfo.solvers['NonLinearDynamicMultibody']['write_lm'] = False SimInfo.solvers['NonLinearDynamicMultibody']['time_integrator'] = 'NewmarkBeta' SimInfo.solvers['NonLinearDynamicMultibody']['time_integrator_settings'] = {'newmark_damp': 1e-3, 'dt': 0.05} SimInfo.solvers['WriteVariablesTime']['cleanup_old_solution'] = 'on' SimInfo.solvers['DynamicCoupled']['structural_solver'] = 'NonLinearDynamicMultibody' SimInfo.solvers['DynamicCoupled']['structural_solver_settings'] = SimInfo.solvers['NonLinearDynamicMultibody'] SimInfo.solvers['DynamicCoupled']['aero_solver'] = 'StepUvlm' SimInfo.solvers['DynamicCoupled']['aero_solver_settings'] = SimInfo.solvers['StepUvlm'] SimInfo.solvers['DynamicCoupled']['postprocessors'] = ['WriteVariablesTime', 'BeamPlot', 'AerogridPlot'] SimInfo.solvers['DynamicCoupled']['postprocessors_settings'] = {'WriteVariablesTime': SimInfo.solvers['WriteVariablesTime'], 'BeamPlot': SimInfo.solvers['BeamPlot'], 'AerogridPlot': SimInfo.solvers['AerogridPlot']} ntimesteps = 10 SimInfo.define_num_steps(ntimesteps) # Define dynamic simulation SimInfo.with_forced_vel = False SimInfo.with_dynamic_forces = False LC2 = gc.LagrangeConstraint() LC2.behaviour = 'lin_vel_node_wrtA' LC2.velocity = np.array([0.,0.,0.]) LC2.body_number = 0 LC2.node_number = int((nnodes1-1)/2) LC = [] # LC.append(LC1) LC.append(LC2) # Define the multibody infromation for the tower and the rotor MB1 = gc.BodyInformation() MB1.body_number = 0 MB1.FoR_position = np.zeros((6,),) MB1.FoR_velocity = np.zeros((6,),) MB1.FoR_acceleration = np.zeros((6,),) MB1.FoR_movement = 'prescribed' MB1.quat = np.array([1.0,0.0,0.0,0.0]) MB = [] MB.append(MB1) gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(ntimesteps) beam1.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) gc.generate_multibody_file(LC, MB,SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) # def tearDown(): # pass def test_testfixnodevelocitywrta(self): import sharpy.sharpy_main solver_path = folder + '/fix_node_velocity_wrtA.sharpy' sharpy.sharpy_main.main(['', solver_path]) # read output and compare output_path = folder + '/output/fix_node_velocity_wrtA/WriteVariablesTime/' # quat_data = np.matrix(np.genfromtxt(output_path + 'FoR_00_mb_quat.dat', delimiter=' ')) pos_tip_data = np.loadtxt(("%sstruct_pos_node-1.dat" % output_path), ) self.assertAlmostEqual(pos_tip_data[0, 1], 9.993007e+00, 3) self.assertAlmostEqual(pos_tip_data[0, 2], 0., 3) self.assertAlmostEqual(pos_tip_data[0, 3], -3.402154e-01, 3) self.assertAlmostEqual(pos_tip_data[-1, 1], 9.962520e+00, 3) self.assertAlmostEqual(pos_tip_data[-1, 2], 0., 3) self.assertAlmostEqual(pos_tip_data[-1, 3], -6.943423e-01, 3) pos_root_data = np.loadtxt(("%sstruct_pos_node0.dat" % output_path), ) self.assertAlmostEqual(pos_root_data[0, 1], 0.0, 2) self.assertAlmostEqual(pos_root_data[0, 2], 0.0, 2) self.assertAlmostEqual(pos_root_data[0, 3], 0.0, 2) self.assertAlmostEqual(pos_root_data[-1, 1], 0.0, 2) self.assertAlmostEqual(pos_root_data[-1, 2], 0.0, 2) self.assertAlmostEqual(pos_root_data[-1, 3], 0.0, 2) def tearDown(self): # pass files_to_delete = [name + '.aero.h5', name + '.dyn.h5', name + '.fem.h5', name + '.mb.h5', name + '.sharpy'] for f in files_to_delete: os.remove(folder + '/' + f) shutil.rmtree(folder + '/output/') ================================================ FILE: tests/coupled/multibody/fix_node_velocity_wrtG/__init__.py ================================================ ================================================ FILE: tests/coupled/multibody/fix_node_velocity_wrtG/test_fix_node_velocity_wrtG.py ================================================ import os import shutil import unittest import numpy as np import sharpy.utils.generate_cases as gc import sharpy.sharpy_main folder = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) class TestFixNodeVelocitywrtG(unittest.TestCase): """ """ def setUp(self): nodes_per_elem = 3 # beam1: uniform and symmetric with aerodynamic properties equal to zero nnodes1 = 11 length1 = 10. mass_per_unit_length = 0.1 mass_iner = 1e-4 EA = 1e9 GA = 1e9 GJ = 1e3 EI = 1e4 # Create beam1 beam1 = gc.AeroelasticInformation() # Structural information beam1.StructuralInformation.num_node = nnodes1 beam1.StructuralInformation.num_node_elem = nodes_per_elem beam1.StructuralInformation.compute_basic_num_elem() beam1.StructuralInformation.set_to_zero(beam1.StructuralInformation.num_node_elem, beam1.StructuralInformation.num_node, beam1.StructuralInformation.num_elem) node_pos = np.zeros((nnodes1, 3), ) node_pos[:, 0] = np.linspace(0.0, length1, nnodes1) beam1.StructuralInformation.generate_uniform_sym_beam(node_pos, mass_per_unit_length, mass_iner, EA, GA, GJ, EI, num_node_elem = 3, y_BFoR = 'y_AFoR', num_lumped_mass=2) beam1.StructuralInformation.boundary_conditions[0] = 1 beam1.StructuralInformation.boundary_conditions[-1] = -1 beam1.StructuralInformation.lumped_mass_nodes = np.array([0, nnodes1-1], dtype=int) beam1.StructuralInformation.lumped_mass = np.array([2., 1.]) beam1.StructuralInformation.lumped_mass_inertia = np.zeros((2, 3, 3),) beam1.StructuralInformation.lumped_mass_position = np.zeros((2, 3),) # Aerodynamic information airfoil = np.zeros((1, 20, 2),) airfoil[0, :, 0] = np.linspace(0., 1., 20) beam1.AerodynamicInformation.create_one_uniform_aerodynamics( beam1.StructuralInformation, chord=1., twist=0., sweep=0., num_chord_panels=4, m_distribution='uniform', elastic_axis=0.5, num_points_camber=20, airfoil=airfoil) # SOLVER CONFIGURATION SimInfo = gc.SimulationInformation() SimInfo.set_default_values() SimInfo.define_uinf(np.array([0.0,1.0,0.0]), 10.) SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'DynamicCoupled'] self.name = 'fix_node_velocity_wrtG' SimInfo.solvers['SHARPy']['case'] = self.name SimInfo.solvers['SHARPy']['write_screen'] = 'off' SimInfo.solvers['SHARPy']['route'] = folder + '/' SimInfo.solvers['SHARPy']['log_folder'] = folder + '/output/' SimInfo.set_variable_all_dicts('dt', 0.1) SimInfo.set_variable_all_dicts('rho', 0.0) SimInfo.set_variable_all_dicts('velocity_field_input', SimInfo.solvers['SteadyVelocityField']) SimInfo.set_variable_all_dicts('folder', folder + '/output/') SimInfo.solvers['BeamLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['mstar'] = 2 SimInfo.solvers['AerogridLoader']['wake_shape_generator'] = 'StraightWake' SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] = {'u_inf':10., 'u_inf_direction': np.array([0., 1., 0.]), 'dt': 0.1} SimInfo.solvers['NonLinearStatic']['print_info'] = False SimInfo.solvers['StaticCoupled']['structural_solver'] = 'NonLinearStatic' SimInfo.solvers['StaticCoupled']['structural_solver_settings'] = SimInfo.solvers['NonLinearStatic'] SimInfo.solvers['StaticCoupled']['aero_solver'] = 'StaticUvlm' SimInfo.solvers['StaticCoupled']['aero_solver_settings'] = SimInfo.solvers['StaticUvlm'] SimInfo.solvers['WriteVariablesTime']['structure_nodes'] = np.array([0, int((nnodes1-1)/2), -1], dtype = int) SimInfo.solvers['WriteVariablesTime']['structure_variables'] = ['pos'] SimInfo.solvers['BeamPlot']['include_FoR'] = True SimInfo.solvers['NonLinearDynamicMultibody']['relaxation_factor'] = 0.0 SimInfo.solvers['NonLinearDynamicMultibody']['min_delta'] = 1e-5 SimInfo.solvers['NonLinearDynamicMultibody']['max_iterations'] = 200 # SimInfo.solvers['NonLinearDynamicMultibody']['gravity_on'] = 'off' SimInfo.solvers['NonLinearDynamicMultibody']['time_integrator'] = 'NewmarkBeta' SimInfo.solvers['NonLinearDynamicMultibody']['time_integrator_settings'] = {'newmark_damp': 1e-3, 'dt': 0.1} SimInfo.solvers['NonLinearDynamicMultibody']['relaxation_factor'] = 0.0 SimInfo.solvers['DynamicCoupled']['structural_solver'] = 'NonLinearDynamicMultibody' SimInfo.solvers['DynamicCoupled']['structural_solver_settings'] = SimInfo.solvers['NonLinearDynamicMultibody'] SimInfo.solvers['DynamicCoupled']['aero_solver'] = 'StepUvlm' SimInfo.solvers['DynamicCoupled']['aero_solver_settings'] = SimInfo.solvers['StepUvlm'] SimInfo.solvers['DynamicCoupled']['postprocessors'] = ['WriteVariablesTime', 'BeamPlot', 'AerogridPlot'] SimInfo.solvers['DynamicCoupled']['postprocessors_settings'] = {'WriteVariablesTime': SimInfo.solvers['WriteVariablesTime'], 'BeamPlot': SimInfo.solvers['BeamPlot'], 'AerogridPlot': SimInfo.solvers['AerogridPlot']} ntimesteps = 10 SimInfo.define_num_steps(ntimesteps) # Define dynamic simulation SimInfo.with_forced_vel = False SimInfo.with_dynamic_forces = False LC2 = gc.LagrangeConstraint() LC2.behaviour = 'lin_vel_node_wrtG' LC2.velocity = np.zeros((ntimesteps, 3)) LC2.velocity[:int(ntimesteps/2),1] = 0.5 LC2.velocity[int(ntimesteps/2):,1] = -0.5 LC2.body_number = 0 LC2.node_number = int((nnodes1-1)/2) LC = [] # LC.append(LC1) LC.append(LC2) # Define the multibody infromation for the tower and the rotor MB1 = gc.BodyInformation() MB1.body_number = 0 MB1.FoR_position = np.zeros((6,),) MB1.FoR_velocity = np.zeros((6,),) MB1.FoR_acceleration = np.zeros((6,),) MB1.FoR_movement = 'free' MB1.quat = np.array([1.0,0.0,0.0,0.0]) MB = [] MB.append(MB1) gc.clean_test_files( SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(ntimesteps) beam1.generate_h5_files( SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) gc.generate_multibody_file(LC, MB,SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) def test_testfixnodevelocitywrtg(self): """ """ solver_path = folder + '/fix_node_velocity_wrtG.sharpy' sharpy.sharpy_main.main(['', solver_path]) # read output and compare output_path = folder + '/output/fix_node_velocity_wrtG/WriteVariablesTime/' pos_tip_data = np.loadtxt(("%sstruct_pos_node-1.dat" % output_path), ) self.assertAlmostEqual(pos_tip_data[-1, 1], 9.999386, 4) self.assertAlmostEqual(pos_tip_data[-1, 2], -0.089333, 4) self.assertAlmostEqual(pos_tip_data[-1, 3], 0., 4) def tearDown(self): files_to_delete = [self.name + '.aero.h5', self.name + '.dyn.h5', self.name + '.fem.h5', self.name + '.mb.h5', self.name + '.sharpy'] for f in files_to_delete: os.remove(folder + '/' + f) shutil.rmtree(folder + '/output/') ================================================ FILE: tests/coupled/multibody/floating_forces/test_floatingforces.py ================================================ import numpy as np import unittest import os import shutil from scipy import fft import sharpy.generators.floatingforces as ff class TestFloatingForces(unittest.TestCase): """ Some tests of the floating forces library Check references therein """ def test_compute_xf_zf(self): """ This function tests based on by hand computations and data from the MooringLineFD.txt file from the OC3 task. Jonkman, J. Definition of the Floating System for Phase IV of OC3 NREL/TP-500-47535 """ # Values for OC3 l = 902.2 # Initial length [m] w = 698.094 # Aparent weight 77.7066*9.81 # Apparent mass per unit length times gravity EA = 384243000. # Extensional stiffness cb = 0.1 # Seabed friction coefficient # No mooring line on the Seabed vf = 1.1*l*w # 692802.4475 hf = vf xf_byhand = 784.5965853 + 1.626695524 zf_byhand = 406.9813526 + 0.887288467 xf, zf = ff.compute_xf_zf(hf, vf, l, w, EA, cb) self.assertAlmostEqual(xf_byhand, xf, 4) self.assertAlmostEqual(zf_byhand, zf, 4) # Some mooring line on the Seabed lb_div_l = 0.1 # 10% of the mooring line on the seabed vf = (1-lb_div_l)*l*w hf = vf xf_byhand = 90.22 + 715.6577252 + 1.330932701 - 7.298744381e-4 zf_byhand = 336.3331284 + 0.598919715 xf, zf = ff.compute_xf_zf(hf, vf, l, w, EA, cb) self.assertAlmostEqual(xf_byhand, xf, 4) self.assertAlmostEqual(zf_byhand, zf, 4) def test_generate_mooringlinefd(self): """ This function generates a file similar to MoorinLinesFD.txt for compiarison File obtained from the google drive directory of the OC3 benchmark """ # Values for OC3 l = 902.2 # Initial length [m] w = 698.094 # Aparent weight 77.7066*9.81 # Apparent mass per unit length times gravity EA = 384243000. # Extensional stiffness cb = 0. # Seabed friction coefficient zf = 320. - 70. # xf0 = 853.87 # xf_list = np.arange(653.00, 902.50 + 1., 1.) xf_list = np.arange(653.00, 902.50, 10.) npoints = xf_list.shape[0] output = np.zeros((npoints, 4)) for i in range(npoints): hf, vf = ff.quasisteady_mooring(xf_list[i], zf, l, w, EA, cb, hf0=None, vf0=None) # print(xf0, zf0, hf0, vf0) lb = np.maximum(l - vf/w, 0) # print("Suspended lenght = %f" % (l - lb)) output[i, :] = np.array([xf_list[i], np.sqrt(vf**2 + hf**2)*1e-3, hf*1e-3, (l - lb)]) of_results = np.loadtxt("MooringLineFD.dat", skiprows=9) for i in range(npoints): for icol in range(4): of_value = np.interp(xf_list[i], of_results[:, 0], of_results[:, icol]) error = np.abs((np.round_(output[i, icol], 1) - of_value)/of_value) self.assertLess(error, 0.1) save_txt = False if save_txt: np.savetxt("sharpy_mooringlinefd.txt", output, header="# DISTANCE(m) TENSION(kN) HTENSION(kN) SUSPL(m)") def test_change_system(self): # Wind turbine degrees of freedom: Surge, sway, heave, roll, pitch, yaw. # SHARPy axis associated: z, y, x, z, y, x wt_matrix_num = np.zeros((6,6),) for idof in range(6): for jdof in range(6): wt_matrix_num[idof, jdof] = 10.*idof + jdof sharpy_matrix = ff.change_of_to_sharpy(wt_matrix_num) undo_sharpy_matrix = ff.change_of_to_sharpy(sharpy_matrix) for idof in range(6): for jdof in range(6): self.assertEqual(wt_matrix_num[idof, jdof], undo_sharpy_matrix[idof, jdof]) def test_time_wave_forces(self): Tp = 14.656 #10. Hs = 5.49 #6. nrealisations = 100 dt = 2*np.pi/4 # 1./20 To get max freq equal to 10Hz ntime_steps = 1000 time = np.arange(ntime_steps)*dt # Get the zero-noise specturm w_js = np.arange(0, 4, 0.01) zero_noise_spectrum = ff.jonswap_spectrum(Tp, Hs, w_js) # Compute different realisations xi = np.zeros((2, 6), dtype=complex) xi[0, 0] = 1. + 0j xi[1, 0] = 1. + 0j w_xi = np.array([0., 4.]) wave_force = np.zeros((ntime_steps, nrealisations), dtype=np.complex) for ireal in range(nrealisations): wave_force[:, ireal] = ff.time_wave_forces(Tp, Hs, dt, time, xi, w_xi)[:, 0] # Keep only on dimension # Compute the spectrum of the realisations ns = np.zeros((ntime_steps//2, nrealisations), dtype=np.complex) for ireal in range(nrealisations): ns[:, ireal] = dt/ntime_steps*np.abs(fft(wave_force[:, ireal])[:ntime_steps//2])**2 ns[1:, ireal] *= 2 # To rad/s ns /= 2.*np.pi w_ns = (np.fft.fftfreq(ntime_steps, d=dt)[:ntime_steps//2])*2.*np.pi # Compare the zero noise with the realisations average avg_noise_spectrum = np.average(ns, axis=1) for iomega in range(w_js.shape[0]): error = (np.interp(w_js[iomega], w_ns, avg_noise_spectrum) - zero_noise_spectrum[iomega]) if error > 0.1: error /= zero_noise_spectrum[iomega] # 0.3 is a large error but otherwise I need to increment nrealisations a lot # Use ``save_fig = True`` for visual inspection self.assertLess(error, 0.3) save_fig = False if save_fig: np.savetxt("zero_noise_jonswap.txt", np.column_stack((w_js, zero_noise_spectrum))) np.savetxt("realisations_jonswap.txt", np.column_stack((w_ns, np.abs(ns)))) np.savetxt("average_jonswap.txt", np.column_stack((w_ns, avg_noise_spectrum))) import matplotlib.pyplot as plt fig, ax = plt.subplots(1, 1, figsize=(4, 3)) ax.grid() ax.set_xlabel("omega [rad/s]") ax.set_ylabel("specturm") ax.set_xlim(0, 4) ax.set_ylim(0, 12) for ireal in range(nrealisations): ax.plot(w_ns, np.abs(ns[:, ireal]), 'bo') ax.plot(w_ns, avg_noise_spectrum, '-', label="avg") ax.plot(w_js, zero_noise_spectrum, '--', label="JONSWAP") fig.legend() fig.tight_layout() fig.show() fig.savefig("spectrum.png") plt.close() # def tearDown(self): # solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) # solver_path += '/' # files_to_delete = [name + '.aero.h5', # name + '.dyn.h5', # name + '.fem.h5', # name + '.mb.h5', # name + '.sharpy'] # for f in files_to_delete: # os.remove(solver_path + f) # shutil.rmtree(solver_path + 'output/') ================================================ FILE: tests/coupled/multibody/floating_wind_turbine/test_floating_wind_turbine.py ================================================ """ spar_from_excel_type04 Example of a file used to generate a floating wind turbine case """ # Load libraries import sharpy.utils.generate_cases as gc import cases.templates.template_wt as template_wt import numpy as np import os import sharpy.utils.algebra as algebra from sharpy.utils.constants import deg2rad import sharpy.sharpy_main import unittest import shutil case = 'flex_wsp18_val' class TestFloatingWindTrubine(unittest.TestCase): """ Test and example to run floating wind turbines """ def generate_floating_wind_turbine(self, restart=False): ###################################################################### ########################### PARAMETERS ############################# ###################################################################### # Case route = os.path.dirname(os.path.realpath(__file__)) + '/' # Geometry discretization chord_panels = np.array([16], dtype=int) revs_in_wake = 1 # Operation rotation_velocity = 12.1*2*np.pi/60 pitch_deg = 14.6 #degrees yaw_deg = 0. #degrees # Wind WSP = 18. air_density = 1.225 gravity_on = True grav_value = 9.80665 # Simulation dphi = 4.*deg2rad revs_to_simulate = 120 structural_substeps = 0 ###################################################################### ########################## GENERATE WT ############################# ###################################################################### dt = dphi/rotation_velocity # time_steps = int(revs_to_simulate*2.*np.pi/dphi) if not restart: time_steps = 1 else: time_steps = 2 struct_dt = dt/(structural_substeps + 1) op_params = {} op_params['rotation_velocity'] = rotation_velocity op_params['pitch_deg'] = pitch_deg op_params['wsp'] = WSP op_params['dt'] = dt geom_params = {} geom_params['chord_panels'] = chord_panels geom_params['tol_remove_points'] = 1e-8 geom_params['n_points_camber'] = 100 geom_params['h5_cross_sec_prop'] = None geom_params['m_distribution'] = 'uniform' options = {} options['camber_effect_on_twist'] = False options['user_defined_m_distribution_type'] = None options['include_polars'] = True options['separate_blades'] = True options['concentrate_spar'] = True options['twist_in_aero'] = False excel_description = {} excel_description['excel_file_name'] = '../../../../docs/source/content/example_notebooks/source/type04_db_nrel5mw_oc3_v06.xlsx' excel_description['excel_sheet_parameters'] = 'parameters' excel_description['excel_sheet_structural_tower'] = 'structural_tower' excel_description['excel_sheet_structural_spar'] = 'structural_spar' excel_description['excel_sheet_structural_blade'] = 'structural_blade' excel_description['excel_sheet_discretization_blade'] = 'discretization_blade' excel_description['excel_sheet_aero_blade'] = 'aero_blade' excel_description['excel_sheet_airfoil_info'] = 'airfoil_info' excel_description['excel_sheet_airfoil_chord'] = 'airfoil_coord' spar, LC, MB = template_wt.spar_from_excel_type04(op_params, geom_params, excel_description, options) tower_top_node = 24 hub_node = 25 blade_tip_node = 51 for ilc in range(len(LC)): LC[ilc].behaviour = "hinge_node_FoR_pitch" LC[ilc].rotor_vel = rotation_velocity del LC[ilc].rot_vect LC[ilc].scalingFactor = 1e6 LC[ilc].penaltyFactor = 0. MB[0].FoR_movement = 'free' ###################################################################### ###################### DEFINE SIMULATION ########################### ###################################################################### SimInfo = gc.SimulationInformation() SimInfo.set_default_values() if not restart: SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader', 'AerogridLoader', 'StaticUvlm', 'BeamPlot', 'AerogridPlot', 'DynamicCoupled', 'PickleData',] SimInfo.solvers['SHARPy']['case'] = case else: SimInfo.solvers['SHARPy']['flow'] = ['DynamicCoupled', 'PickleData',] SimInfo.solvers['SHARPy']['case'] = "restart_%s" % case SimInfo.solvers['SHARPy']['route'] = route SimInfo.solvers['SHARPy']['write_log'] = True SimInfo.solvers['SHARPy']['write_screen'] = False SimInfo.solvers['SHARPy']['log_file'] = ("log_%s" % SimInfo.solvers['SHARPy']['case']) SimInfo.set_variable_all_dicts('dt', dt) SimInfo.set_variable_all_dicts('rho', air_density) SimInfo.solvers['SteadyVelocityField']['u_inf'] = WSP SimInfo.solvers['SteadyVelocityField']['u_inf_direction'] = np.array([0., -np.sin(yaw_deg*deg2rad), np.cos(yaw_deg*deg2rad)]) SimInfo.solvers['BeamLoader']['unsteady'] = 'on' centre_rot_ini = np.array([87.6, 0., -5.0191]) rbm_vel_g_ini = np.zeros((6)) rbm_vel_g_ini[3:6] = np.array([0., 0., rotation_velocity]) quat0 = algebra.euler2quat(np.array([0., -2.28*deg2rad, 0.])) for_pos0 = np.array([0., 0., 11.6]) SimInfo.solvers['BeamLoader']['orientation'] = quat0.copy() SimInfo.solvers['BeamLoader']['for_pos'] = for_pos0.copy() rot_mat0 = algebra.quat2rotation(quat0) centre_rot = np.dot(rot_mat0, centre_rot_ini) + for_pos0 rbm_vel_g = np.zeros((6)) rbm_vel_g[3:6] = np.dot(rot_mat0, rbm_vel_g_ini[3:6]) MB[0].FoR_position[0:3] = for_pos0.copy() MB[0].quat = quat0.copy() for ibody in range(1, len(MB)): MB[ibody].FoR_position[0:3] = centre_rot.copy() MB[ibody].FoR_velocity[3:6] = rbm_vel_g[3:6] az = (360./3)*(ibody - 1) rot_mat_az = algebra.rotation3d_z(az*deg2rad) quat = algebra.rotation2quat(np.dot(rot_mat_az, rot_mat0)) MB[ibody].quat[0:4] = quat.copy() # Compute mstar SimInfo.solvers['AerogridLoader']['wake_shape_generator'] = 'HelicoidalWake' SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] = {'u_inf': WSP, 'u_inf_direction': SimInfo.solvers['SteadyVelocityField']['u_inf_direction'], 'rotation_velocity': rbm_vel_g[3:6], 'h_ref': centre_rot[0], 'h_corr': 0., 'dt': dt, 'dphi1': dphi, 'ndphi1': int(10), 'r': 1.05, 'dphimax': 10*deg2rad} import sharpy.utils.generator_interface as gi gi.dictionary_of_generators(print_info=False) hw = gi.dict_of_generators['HelicoidalWake'] wsg_in = SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] # for simplicity angle = 0 mstar = 0 while angle < (revs_in_wake*2*np.pi): mstar += 1 angle += hw.get_dphi(mstar, wsg_in['dphi1'], wsg_in['ndphi1'], wsg_in['r'], wsg_in['dphimax']) SimInfo.solvers['AerogridLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['mstar'] = mstar SimInfo.solvers['AerogridLoader']['freestream_dir'] = np.array([0., 0, 0]) struct_static_solver = 'NonLinearStatic' SimInfo.solvers[struct_static_solver]['gravity_on'] = gravity_on SimInfo.solvers[struct_static_solver]['gravity'] = grav_value SimInfo.solvers[struct_static_solver]['gravity_dir'] = np.array([1., 0., 0.]) SimInfo.solvers[struct_static_solver]['max_iterations'] = 100 SimInfo.solvers[struct_static_solver]['num_load_steps'] = 1 SimInfo.solvers[struct_static_solver]['min_delta'] = 1e-5 SimInfo.solvers[struct_static_solver]['newmark_damp'] = 1e-1 SimInfo.solvers[struct_static_solver]['dt'] = dt SimInfo.solvers['StaticCoupled']['structural_solver'] = struct_static_solver SimInfo.solvers['StaticCoupled']['structural_solver_settings'] = SimInfo.solvers[struct_static_solver] SimInfo.solvers['StaticCoupled']['aero_solver'] = 'StaticUvlm' SimInfo.solvers['StaticCoupled']['aero_solver_settings'] = SimInfo.solvers['StaticUvlm'] SimInfo.solvers['StaticCoupled']['tolerance'] = 1e-8 SimInfo.solvers['StaticCoupled']['n_load_steps'] = 0 SimInfo.solvers['StaticCoupled']['relaxation_factor'] = 0. SimInfo.solvers['StaticCoupled']['max_iter'] = 100 SimInfo.solvers['StaticUvlm']['horseshoe'] = False SimInfo.solvers['StaticUvlm']['num_cores'] = 8 SimInfo.solvers['StaticUvlm']['n_rollup'] = 0 SimInfo.solvers['StaticUvlm']['rollup_dt'] = dt SimInfo.solvers['StaticUvlm']['rollup_aic_refresh'] = 1 SimInfo.solvers['StaticUvlm']['rollup_tolerance'] = 1e-8 SimInfo.solvers['StaticUvlm']['rbm_vel_g'] = rbm_vel_g SimInfo.solvers['StaticUvlm']['centre_rot_g'] = centre_rot SimInfo.solvers['StaticUvlm']['cfl1'] = False SimInfo.solvers['StaticUvlm']['vortex_radius'] = 1e-6 SimInfo.solvers['StaticUvlm']['vortex_radius_wake_ind'] = 1e-3 SimInfo.solvers['StaticUvlm']['velocity_field_generator'] = 'SteadyVelocityField' SimInfo.solvers['StaticUvlm']['velocity_field_input'] = SimInfo.solvers['SteadyVelocityField'] SimInfo.solvers['StaticUvlm']['map_forces_on_struct'] = True SimInfo.solvers['FloatingForces']['n_time_steps'] = time_steps SimInfo.solvers['FloatingForces']['dt'] = dt SimInfo.solvers['FloatingForces']['water_density'] = 1025 # kg/m3 SimInfo.solvers['FloatingForces']['gravity'] = grav_value SimInfo.solvers['FloatingForces']['gravity_dir'] = [1., 0., 0.] SimInfo.solvers['FloatingForces']['floating_file_name'] = 'oc3_cs_v07.floating.h5' SimInfo.solvers['FloatingForces']['concentrate_spar'] = True SimInfo.solvers['FloatingForces']['method_matrices_freq'] = 'rational_function' SimInfo.solvers['FloatingForces']['matrices_freq'] = 2*np.pi/120. SimInfo.solvers['FloatingForces']['steps_constant_matrices'] = 0 SimInfo.solvers['FloatingForces']['method_wave'] = 'jonswap' SimInfo.solvers['FloatingForces']['wave_amplitude'] = 0. SimInfo.solvers['FloatingForces']['wave_freq'] = 0. SimInfo.solvers['FloatingForces']['wave_Tp'] = 10. SimInfo.solvers['FloatingForces']['wave_Hs'] = 6. SimInfo.solvers['FloatingForces']['wave_incidence'] = 0.*deg2rad SimInfo.solvers['FloatingForces']['added_mass_in_mass_matrix'] = True SimInfo.solvers['FloatingForces']['write_output'] = True SimInfo.solvers['StepUvlm']['convection_scheme'] = 2 SimInfo.solvers['StepUvlm']['num_cores'] = 8 SimInfo.solvers['StepUvlm']['velocity_field_generator'] = 'SteadyVelocityField' SimInfo.solvers['StepUvlm']['velocity_field_input'] = SimInfo.solvers['SteadyVelocityField'] SimInfo.solvers['StepUvlm']['cfl1'] = False SimInfo.solvers['StepUvlm']['interp_coords'] = 0 SimInfo.solvers['StepUvlm']['filter_method'] = 0 SimInfo.solvers['StepUvlm']['interp_method'] = 3 SimInfo.solvers['StepUvlm']['centre_rot'] = centre_rot SimInfo.solvers['StepUvlm']['vortex_radius'] = 1e-6 SimInfo.solvers['StepUvlm']['vortex_radius_wake_ind'] = 1e-3 struct_dyn_solver = 'NonLinearDynamicMultibody' SimInfo.solvers[struct_dyn_solver]['gravity_on'] = gravity_on SimInfo.solvers[struct_dyn_solver]['gravity'] = grav_value SimInfo.solvers[struct_dyn_solver]['gravity_dir'] = np.array([1., 0., 0.]) SimInfo.solvers[struct_dyn_solver]['max_iterations'] = 300 SimInfo.solvers[struct_dyn_solver]['min_delta'] = 1e-6 SimInfo.solvers[struct_dyn_solver]['newmark_damp'] = 1e-1 SimInfo.solvers[struct_dyn_solver]['dt'] = struct_dt SimInfo.solvers[struct_dyn_solver]['write_lm'] = True SimInfo.solvers[struct_dyn_solver]['allow_skip_step'] = False SimInfo.solvers[struct_dyn_solver]['relax_factor_lm'] = 0. SimInfo.solvers[struct_dyn_solver]['rigid_bodies'] = False SimInfo.solvers[struct_dyn_solver]['zero_ini_dot_ddot'] = True SimInfo.solvers[struct_dyn_solver]['time_integrator'] = 'NewmarkBeta' SimInfo.solvers[struct_dyn_solver]['time_integrator_settings'] = {'newmark_damp': 1e-1, 'dt': struct_dt} SimInfo.solvers['SaveData']['compress_float'] = True SimInfo.solvers['SaveData']['save_wake'] = False SimInfo.solvers['WriteVariablesTime']['FoR_variables'] = ['for_pos', 'for_vel', 'quat'] SimInfo.solvers['WriteVariablesTime']['structure_variables'] = ['pos'] SimInfo.solvers['WriteVariablesTime']['structure_nodes'] = [tower_top_node, blade_tip_node] SimInfo.solvers['PickleData']['stride'] = 180 SimInfo.solvers['AerogridPlot']['stride'] = 180 SimInfo.solvers['DynamicCoupled']['structural_solver'] = struct_dyn_solver SimInfo.solvers['DynamicCoupled']['structural_solver_settings'] = SimInfo.solvers[struct_dyn_solver] SimInfo.solvers['DynamicCoupled']['aero_solver'] = 'StepUvlm' SimInfo.solvers['DynamicCoupled']['aero_solver_settings'] = SimInfo.solvers['StepUvlm'] SimInfo.solvers['DynamicCoupled']['postprocessors'] = ['Cleanup', 'BeamPlot', 'AerogridPlot', 'PickleData', 'SaveData', 'WriteVariablesTime'] SimInfo.solvers['DynamicCoupled']['postprocessors_settings'] = {'Cleanup': SimInfo.solvers['Cleanup'], 'BeamPlot': SimInfo.solvers['BeamPlot'], 'AerogridPlot': SimInfo.solvers['AerogridPlot'], 'PickleData': SimInfo.solvers['PickleData'], 'SaveData': SimInfo.solvers['SaveData'], 'WriteVariablesTime': SimInfo.solvers['WriteVariablesTime'],} SimInfo.solvers['DynamicCoupled']['minimum_steps'] = 0 SimInfo.solvers['DynamicCoupled']['include_unsteady_force_contribution'] = True SimInfo.solvers['DynamicCoupled']['relaxation_factor'] = 0. SimInfo.solvers['DynamicCoupled']['final_relaxation_factor'] = 0. SimInfo.solvers['DynamicCoupled']['dynamic_relaxation'] = False SimInfo.solvers['DynamicCoupled']['relaxation_steps'] = 0 SimInfo.solvers['DynamicCoupled']['fsi_tolerance'] = 1e-6 SimInfo.solvers['DynamicCoupled']['structural_substeps'] = structural_substeps SimInfo.solvers['DynamicCoupled']['runtime_generators'] = {'FloatingForces': SimInfo.solvers['FloatingForces']} power = 5296609.984 GBR = 97. PID0 = [0.006275604, 0.0008965149, 0.] # Offshore # Gain scheduler pitch_deg_doubled_sens = 6.302336 # deg gk = 1./(1 + pitch_deg/pitch_deg_doubled_sens) PID = np.zeros((3)) for i in range(3): PID[i] = PID0[i]*gk SimInfo.solvers['DynamicCoupled']['controller_id']['colective_pitch'] = 'BladePitchPid' SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch'] = dict() SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['P'] = PID[0] SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['I'] = PID[1] SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['D'] = PID[2] SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['sp_type'] = 'gen_vel' SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['sp_source'] = 'const' SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['sp_const'] = rotation_velocity*GBR SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['gen_model_const_var'] = 'torque' SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['gen_model_const_value'] = power/rotation_velocity/GBR SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['GBR'] = GBR SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['inertia_dt'] = 43702538.05 SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['dt'] = dt SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['ntime_steps'] = time_steps SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['blade_num_body'] = [1, 2, 3] SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['min_pitch'] = 0. SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['max_pitch'] = 90.*deg2rad SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['initial_pitch'] = pitch_deg*deg2rad SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['pitch_sp'] = pitch_deg*deg2rad SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['initial_rotor_vel'] = rotation_velocity SimInfo.solvers['DynamicCoupled']['controller_settings']['colective_pitch']['nocontrol_steps'] = 0 SimInfo.define_num_steps(time_steps) # Define dynamic simulation SimInfo.with_forced_vel = False SimInfo.with_dynamic_forces = False ###################################################################### ####################### GENERATE FILES ############################# ###################################################################### gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) spar.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() gc.generate_multibody_file(LC, MB,SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) return SimInfo.solvers['SHARPy']['case'] def test_floating_wind_turbine(self): name = self.generate_floating_wind_turbine(restart=False) solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/' + name + '.sharpy') sharpy.sharpy_main.main(['', solver_path]) name_restart = self.generate_floating_wind_turbine(restart=True) solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/' + name_restart + '.sharpy') restart_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/output/' + name + '/' + name + '.pkl') sharpy.sharpy_main.main(['', '-r' + restart_path , solver_path]) self.clean_files(name) def clean_files(self, case): solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) solver_path += '/' files_to_delete = [case + '.aero.h5', case + '.fem.h5', case + '.mb.h5', case + '.sharpy', 'restart_' + case + '.aero.h5', 'restart_' + case + '.fem.h5', 'restart_' + case + '.mb.h5', 'restart_' + case + '.sharpy'] for f in files_to_delete: os.remove(solver_path + f) shutil.rmtree(solver_path + 'output/') ================================================ FILE: tests/coupled/prescribed/WindTurbine/__init__.py ================================================ ================================================ FILE: tests/coupled/prescribed/WindTurbine/test_rotor.py ================================================ import numpy as np import os import unittest import shutil import glob folder = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) class TestRotor(unittest.TestCase): """ NREL 5MW case J. Jonkman et al. "Definition of a 5-MW Reference Wind Turbine for Offshore System Development", NREL/TP-500-38060, 2009 In this report, the whole system natural frequencies are provided. This implies that rotor frequencies are provided and influenced by nacelle characterisitcs """ def setUp(self): import sharpy.utils.generate_cases as gc import sharpy.cases.templates.template_wt as template_wt from sharpy.utils.constants import deg2rad ###################################################################### ########################### PARAMETERS ############################# ###################################################################### # Case global case route = folder + '/' case = 'rotor' # Geometry discretization chord_panels = np.array([8], dtype=int) revs_in_wake = 1 # Operation rotation_velocity = 1.366190 pitch_deg = 0. #degrees # Wind WSP = 11.4 air_density = 1.225 # Simulation dphi = 4.*deg2rad revs_to_simulate = 5 ###################################################################### ########################## GENERATE WT ############################# ###################################################################### dt = dphi/rotation_velocity # time_steps = int(revs_to_simulate*2.*np.pi/dphi) time_steps = 2 # For the test cases mstar = int(revs_in_wake*2.*np.pi/dphi) op_params = {'rotation_velocity': rotation_velocity, 'pitch_deg': pitch_deg, 'wsp': WSP, 'dt': dt} geom_params = {'chord_panels':chord_panels, 'tol_remove_points': 1e-8, 'n_points_camber': 100, 'm_distribution': 'uniform'} excel_description = {'excel_file_name': route + '../../../../docs/source/content/example_notebooks/source/type04_db_nrel5mw_oc3_v06.xlsx', 'excel_sheet_parameters': 'parameters', 'excel_sheet_structural_blade': 'structural_blade', 'excel_sheet_discretization_blade': 'discretization_blade', 'excel_sheet_aero_blade': 'aero_blade', 'excel_sheet_airfoil_info': 'airfoil_info', 'excel_sheet_airfoil_chord': 'airfoil_coord'} options = {'camber_effect_on_twist': False, 'user_defined_m_distribution_type': None, 'include_polars': False, 'separate_blades': False} rotor, hub_nodes = template_wt.rotor_from_excel_type03(op_params, geom_params, excel_description, options) ###################################################################### ###################### DEFINE SIMULATION ########################### ###################################################################### SimInfo = gc.SimulationInformation() SimInfo.set_default_values() SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader', 'Modal'] SimInfo.solvers['SHARPy']['case'] = case SimInfo.solvers['SHARPy']['route'] = route SimInfo.solvers['SHARPy']['write_log'] = True SimInfo.solvers['SHARPy']['write_screen'] = 'off' SimInfo.solvers['SHARPy']['log_folder'] = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/output/' SimInfo.set_variable_all_dicts('dt', dt) SimInfo.set_variable_all_dicts('rho', air_density) SimInfo.solvers['BeamLoader']['unsteady'] = 'on' SimInfo.solvers['Modal']['write_modes_vtk'] = False SimInfo.solvers['Modal']['save_data'] = True ###################################################################### ####################### GENERATE FILES ############################# ###################################################################### gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) rotor.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() def test_rotor(self): import sharpy.sharpy_main solver_path = folder + '/' + case + '.sharpy' sharpy.sharpy_main.main(['', solver_path]) # read output and compare output_path = folder + '/output/' + case + '/beam_modal_analysis/' freq_data = np.atleast_2d(np.genfromtxt(output_path + "frequencies.dat")) # Data from reference. Several values are provided, the average is used flap_1 = np.average([0.6664, 0.6296, 0.6675, 0.6686, 0.6993, 0.7019])*2*np.pi edge_1 = np.average([1.0793, 1.0740, 1.0898, 1.0877])*2*np.pi flap_2 = np.average([1.9337, 1.6507, 1.9223, 1.8558, 2.0205, 1.9601])*2*np.pi self.assertAlmostEqual(freq_data[0, 0], flap_1, 0) # 1st flapwise self.assertAlmostEqual(freq_data[0, 3], edge_1, 0) # 1st edgewise self.assertAlmostEqual(freq_data[0, 6], flap_2, 0) # 2nd flapwise def tearDown(self): files_to_delete = [case + '.aero.h5', case + '.fem.h5', case + '.sharpy',] for f in files_to_delete: os.remove(folder +'/' + f) shutil.rmtree(folder + '/output/') ================================================ FILE: tests/coupled/prescribed/__init__.py ================================================ import tests.coupled.prescribed.WindTurbine ================================================ FILE: tests/coupled/prescribed/gamma_dot_test/test_gamma_dot.py ================================================ import os import numpy as np import unittest import cases.templates.flying_wings as wings import sharpy.sharpy_main def x_dot(x, dt, integration_order=2): x_dot_r = np.zeros(len(x)) if integration_order == 1: x_n = x.copy()[1:] x_m1 = x.copy()[:-1] x_dot_r[1:] = (x_n - x_m1) / dt else: x_n = x.copy()[2:] x_m1 = x.copy()[1:-1] x_m2 = x.copy()[:-2] x_dot_r[2:] = (3 * x_n - 4 * x_m1 + x_m2) / 2 / dt return x_dot_r class TestGammaDot(unittest.TestCase): def set_up_test_case(self, aero_type, predictor, sparse, integration_order): # aero_type = 'lin' global case_name case_name = 'goland_' + aero_type + '_'+'P%g_S%g_I%g' %(predictor, sparse, integration_order) ws = wings.Goland(M=12, N=4, Mstar_fact=50, u_inf=50, alpha=1., rho=1.225, sweep=0, physical_time=0.1, n_surfaces=2, route='cases', case_name=case_name) # Other test parameters ws.gust_intensity = 0.01 ws.sigma = 1 ws.dt_factor = 1 ws.clean_test_files() ws.update_derived_params() ws.update_aero_prop() ws.update_fem_prop() ws.set_default_config_dict() ws.generate_aero_file() ws.generate_fem_file() ws.config['SHARPy']['flow'] = ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'DynamicCoupled'] ws.config['SHARPy']['write_screen'] = 'off' ws.config['AerogridLoader']['wake_shape_generator'] = 'StraightWake' ws.config['AerogridLoader']['wake_shape_generator_input'] = {'u_inf': ws.u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': ws.dt} # Remove newmark damping from structural solver settings ws.config['DynamicCoupled']['structural_solver_settings']['newmark_damp'] = 0 if aero_type == 'lin': ws.config['DynamicCoupled']['aero_solver'] = 'StepLinearUVLM' ws.config['DynamicCoupled']['aero_solver_settings'] = {'dt': ws.dt, 'remove_predictor': predictor, 'use_sparse': sparse, 'integr_order': integration_order, 'velocity_field_generator': 'GustVelocityField', 'velocity_field_input': {'u_inf': ws.u_inf, 'u_inf_direction': [1., 0., 0.], 'gust_shape': 'continuous_sin', 'offset': 2., 'gust_parameters': {'gust_length': 2., 'gust_intensity': ws.gust_intensity * ws.u_inf, 'span': ws.main_chord * ws.aspect_ratio}}} else: ws.config['DynamicCoupled']['aero_solver'] = 'StepUvlm' ws.config['DynamicCoupled']['aero_solver_settings'] = { 'print_info': 'off', 'horseshoe': True, 'num_cores': 4, 'n_rollup': 100, 'convection_scheme': 0, 'rollup_dt': ws.dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'GustVelocityField', 'velocity_field_input': {'u_inf': ws.u_inf, 'u_inf_direction': [1., 0, 0], 'gust_shape': 'continuous_sin', 'gust_length': ws.gust_length, 'gust_intensity': ws.gust_intensity * ws.u_inf, 'offset': 2.0, 'span': ws.main_chord * ws.aspect_ratio}, 'rho': ws.rho, 'n_time_steps': ws.n_tstep, 'dt': ws.dt, 'gamma_dot_filtering': 0, 'track_body': True, 'track_body_number': -1} ws.config['DynamicCoupled']['include_unsteady_force_contribution'] = 'on' # Update settings file ws.config.write() self.case_name = ws.case_name self.case_route = ws.route self.ws = ws self.dt = ws.dt def run_test(self, aero_type, predictor, sparse, integration_order): self.set_up_test_case(aero_type, predictor, sparse, integration_order) ws = self.ws data = sharpy.sharpy_main.main(['', self.case_route + self.case_name + '.sharpy']) # Obtain gamma gamma = np.zeros((ws.n_tstep,)) gamma_dot = np.zeros((ws.n_tstep)) for N in range(ws.n_tstep): gamma[N] = data.aero.timestep_info[N].gamma[0][0, 0] gamma_dot[N] = data.aero.timestep_info[N].gamma_dot[0][0, 0] gamma_dot_fd = x_dot(gamma, self.dt, integration_order) error_derivative = np.max(np.abs(gamma_dot - gamma_dot_fd)) gamma_dot_at_max = gamma_dot_fd[np.argmax(np.abs(gamma_dot - gamma_dot_fd))] # The signal is close to zero if np.abs(gamma_dot_at_max) < 0.05: passed_test = error_derivative < 0.05 else: passed_test = error_derivative < 1e-2 * np.abs(gamma_dot_at_max) if not passed_test: try: import matplotlib.pyplot as plt plt.plot(gamma_dot) plt.plot(gamma_dot_fd, color='k') plt.show() plt.plot(gamma_dot - gamma_dot_fd) plt.show() except ModuleNotFoundError: import warnings warnings.warn('Unable to import matplotlib, skipping plot') assert passed_test == True, \ 'Discrepancy between gamma_dot and that calculated using FD, relative difference is %.2f' % ( error_derivative / np.abs(gamma_dot_at_max)) def setUp(self): pass def test_gammadot(self): for aero_type in ['lin', 'nlin']: if aero_type == 'lin': for predictor in [True, False]: for sparse in [True, False]: for integration_order in [1, 2]: with self.subTest( aero_type=aero_type, predictor=predictor, sparse=sparse, integration_order=integration_order): self.run_test(aero_type, predictor, sparse, integration_order) else: with self.subTest( aero_type=aero_type, predictor=False, sparse=False, integration_order=2): self.run_test(aero_type, predictor, sparse, integration_order) def tearDown(self): solver_path = os.path.dirname(os.path.realpath(__file__)) # solver_path += '/' # files_to_delete = [case + '.aero.h5', # case + '.dyn.h5', # case + '.fem.h5', # case + '.sharpy'] # for f in files_to_delete: # os.remove(solver_path + f) shutil.rmtree(solver_path + '/cases/') ================================================ FILE: tests/coupled/prescribed/rotating_wing/generate_rotating_wing.py ================================================ import h5py as h5 import numpy as np import configparser import os import sharpy.utils.algebra as algebra case_name = 'rotating_wing' route = os.path.dirname(os.path.realpath(__file__)) + '/' m_main = 7 amplitude = 0 period = 3 dt_factor = 1. # flight conditions u_inf = 5 rho = 1.225 alpha = 0 beta = 0 c_ref = 1 b_ref = 16 sweep = 0*np.pi/180. aspect_ratio = 32 # = total wing span (chord = 1) alpha_rad = alpha*np.pi/180 # dt = 1.0/m_main/u_inf*dt_factor dt = 0.01 num_steps = int(1./dt) num_steps = 20 # main geometry data main_span = aspect_ratio/2./np.cos(sweep) main_chord = 1.0 main_chord_tip = 0.5 main_ea = 0.5 main_sigma = 1 main_airfoil_P = 0 main_airfoil_M = 0 n_surfaces = 2 # discretisation data num_elem_main = 10 num_node_elem = 3 num_elem = num_elem_main + num_elem_main num_node_main = num_elem_main*(num_node_elem - 1) + 1 num_node = num_node_main + (num_node_main - 1) def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) dyn_file_name = route + '/' + case_name + '.dyn.h5' if os.path.isfile(dyn_file_name): os.remove(dyn_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def generate_dyn_file(): global dt global num_steps global route global case_name global num_elem global num_node_elem global num_node global amplitude global period dynamic_forces_time = None with_dynamic_forces = False with_forced_vel = True if with_dynamic_forces: angle = np.arctan(8.0/6.0) m1 = 80 f1 = 8 dynamic_forces = np.zeros((num_node, 6)) app_node = int(0) dynamic_forces[app_node, 0] = -f1*np.cos(angle) dynamic_forces[app_node, 1] = -f1*np.sin(angle) dynamic_forces[app_node, 5] = m1 force_time = np.zeros((num_steps, )) limit = round(2.5/dt) force_time[:limit] = 1 dynamic_forces_time = np.zeros((num_steps, num_node, 6)) for it in range(num_steps): dynamic_forces_time[it, :, :] = force_time[it]*dynamic_forces forced_for_vel = None if with_forced_vel: forced_for_vel = np.zeros((num_steps, 6)) forced_for_acc = np.zeros((num_steps, 6)) forced_for_vel[:, 3] = period/(2.0*np.pi) try: forced_for_vel[0:int(0.5/dt), 3] = np.linspace(0, period/(2*np.pi), int(0.5/dt)) except ValueError: pass # forced_for_acc[it, 2] = (2*np.pi/period)**2*amplitude*np.cos(2*np.pi*dt*it/period) # forced_for_vel[it, 2] = 2*np.pi/period*np.pi/180*amplitude*np.cos(2*np.pi*dt*it/period) with h5.File(route + '/' + case_name + '.dyn.h5', 'a') as h5file: if with_dynamic_forces: h5file.create_dataset( 'dynamic_forces', data=dynamic_forces_time) if with_forced_vel: h5file.create_dataset( 'for_vel', data=forced_for_vel) h5file.create_dataset( 'for_acc', data=forced_for_acc) h5file.create_dataset( 'num_steps', data=num_steps) def generate_fem_file(): # placeholders # coordinates global x, y, z x = np.zeros((num_node, )) y = np.zeros((num_node, )) z = np.zeros((num_node, )) # struct twist structural_twist = np.zeros_like(x) # beam number beam_number = np.zeros((num_elem, ), dtype=int) # frame of reference delta frame_of_reference_delta = np.zeros((num_elem, num_node_elem, 3)) # connectivities conn = np.zeros((num_elem, num_node_elem), dtype=int) # stiffness num_stiffness = 1 ea = 1e5 ga = 1e5 gj = 1e4*10 eiy = 2e4 eiz = 2e4 sigma = 1000 base_stiffness = sigma*np.diag([ea, ga, ga, gj, eiy, eiz]) stiffness = np.zeros((num_stiffness, 6, 6)) stiffness[0, :, :] = main_sigma*base_stiffness elem_stiffness = np.zeros((num_elem,), dtype=int) # mass num_mass = 1 m_base = 0.75 j_base = 0.1 base_mass = np.diag([m_base, m_base, m_base, j_base, j_base, j_base]) mass = np.zeros((num_mass, 6, 6)) mass[0, :, :] = base_mass elem_mass = np.zeros((num_elem,), dtype=int) # boundary conditions boundary_conditions = np.zeros((num_node, ), dtype=int) boundary_conditions[0] = 1 # applied forces # n_app_forces = 2 # node_app_forces = np.zeros((n_app_forces,), dtype=int) app_forces = np.zeros((num_node, 6)) spacing_param = 3 # right wing (beam 0) -------------------------------------------------------------- working_elem = 0 working_node = 0 beam_number[working_elem:working_elem + num_elem_main] = 0 domain = np.linspace(0, 1.0, num_node_main) # 16 - (np.geomspace(20, 4, 10) - 4) # x[working_node:working_node + num_node_main] = np.sin(sweep)*(main_span - (np.geomspace(main_span + spacing_param, # 0 + spacing_param, # num_node_main) # - spacing_param)) z[working_node:working_node + num_node_main] = np.abs((main_span - (np.geomspace(main_span + spacing_param, 0 + spacing_param, num_node_main) - spacing_param))) # y[0] = 0 # y[working_node:working_node + num_node_main] = np.cos(sweep)*np.linspace(0.0, main_span, num_node_main) # x[working_node:working_node + num_node_main] = np.sin(sweep)*np.linspace(0.0, main_span, num_node_main) for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[0] = 1 boundary_conditions[working_node + num_node_main - 1] = -1 working_elem += num_elem_main working_node += num_node_main # left wing (beam 1) -------------------------------------------------------------- beam_number[working_elem:working_elem + num_elem_main] = 1 domain = np.linspace(-1.0, 0.0, num_node_main) tempy = np.linspace(-main_span, 0.0, num_node_main) # x[working_node:working_node + num_node_main - 1] = -np.sin(sweep)*tempy[0:-1] # y[working_node:working_node + num_node_main - 1] = np.cos(sweep)*tempy[0:-1] z[working_node:working_node + num_node_main - 1] = -(main_span - (np.geomspace(0 + spacing_param, main_span + spacing_param, num_node_main)[:-1] - spacing_param)) # y[working_node:working_node + num_node_main - 1] = -np.abs(np.cos(sweep)*(main_span - (np.geomspace(0 + spacing_param, # main_span + spacing_param, # num_node_main)[:-1] # - spacing_param))) for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) + 1 conn[working_elem + num_elem_main - 1, 1] = 0 elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[working_node] = -1 working_elem += num_elem_main working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=num_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=num_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=num_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) # node_app_forces_handle = h5file.create_dataset( # 'node_app_forces', data=node_app_forces) def generate_aero_file(): global x, y, z airfoil_distribution = np.zeros((num_elem, num_node_elem), dtype=int) surface_distribution = np.zeros((num_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces, ), dtype=int) m_distribution = 'uniform' aero_node = np.zeros((num_node,), dtype=bool) twist = np.zeros((num_elem, num_node_elem)) chord = np.zeros((num_elem, num_node_elem)) elastic_axis = np.zeros((num_elem, num_node_elem,)) working_elem = 0 working_node = 0 # right wing (surface 0, beam 0) i_surf = 0 airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main] = True # chord[working_node:working_node + num_node_main] = np.linspace(main_chord, main_chord_tip, num_node_main) # twist[working_node:working_node + num_node_main] = np.linspace(0, 70*np.pi/180., num_node_main) temp_chord = np.linspace(main_chord, main_chord_tip, num_node_main) temp_twist = np.linspace(0, 70*np.pi/180., num_node_main) node_counter = 0 for i_elem in range(working_elem, working_elem + num_elem_main): for i_local_node in range(num_node_elem): if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = temp_chord[node_counter] elastic_axis[i_elem, i_local_node] = main_ea twist[i_elem, i_local_node] = temp_twist[node_counter] working_elem += num_elem_main working_node += num_node_main # # left wing (surface 1, beam 1) i_surf = 1 airfoil_distribution[working_node:working_node + num_node_main - 1] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main - 1] = True # chord[working_node:working_node + num_node_main - 1] = np.linspace(main_chord_tip, main_chord, num_node_main)[:-1] # chord[working_node:working_node + num_node_main - 1] = main_chord # elastic_axis[working_node:working_node + num_node_main - 1] = main_ea # twist[working_node:working_node + num_node_main - 1] = np.linspace(-70*np.pi/180., 0.0, num_node_main)[:-1] temp_chord = np.linspace(main_chord, main_chord_tip, num_node_main) temp_twist = np.linspace(0, -70*np.pi/180., num_node_main) node_counter = 0 for i_elem in range(working_elem, working_elem + num_elem_main): for i_local_node in range(num_node_elem): if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = temp_chord[node_counter] elastic_axis[i_elem, i_local_node] = main_ea twist[i_elem, i_local_node] = temp_twist[node_counter] working_elem += num_elem_main working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( generate_naca_camber(P=main_airfoil_P, M=main_airfoil_M))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input .attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) def generate_naca_camber(M=0, P=0): m = M*1e-2 p = P*1e-1 def naca(x, m, p): if x < 1e-6: return 0.0 elif x < p: return m/(p*p)*(2*p*x - x*x) elif x > p and x < 1+1e-6: return m/((1-p)*(1-p))*(1 - 2*p + 2*p*x - x*x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, m, p) for x in x_vec]) return x_vec, y_vec def generate_solver_file(horseshoe=False): file_name = route + '/' + case_name + '.sharpy' # config = configparser.ConfigParser() import configobj config = configobj.ConfigObj() config.filename = file_name config['SHARPy'] = {'case': case_name, 'route': route, 'flow': ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'DynamicPrescribedCoupled', # 'PrescribedUvlm', 'AerogridPlot', # 'NonLinearDynamic', 'BeamPlot',] # 'AeroForcesCalculator',], 'write_screen': 'off', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} config['BeamLoader'] = {'unsteady': 'on', 'orientation': algebra.euler2quat(np.array([0.0, alpha_rad, beta*np.pi/180]))} config['StaticCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 10, 'delta_curved': 1e-5, 'min_delta': 1e-5, 'gravity_on': 'off', 'gravity': 9.754, 'orientation': algebra.euler2quat(np.array([0.0, alpha_rad, beta*np.pi/180]))}, 'aero_solver': 'StaticUvlm', 'aero_solver_settings': {'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': main_chord/m_main/u_inf, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho, 'alpha': alpha_rad, 'beta': beta}, 'max_iter': 80, 'n_load_steps': 3, 'tolerance': 1e-4, 'relaxation_factor': 0.0} config['NonLinearDynamic'] = {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 4, 'delta_curved': 1e-5, 'min_delta': 1e-5, 'newmark_damp': 5e-4, 'gravity_on': 'on', 'gravity': 9.754, 'num_steps': num_steps, 'dt': dt, 'prescribed_motion': 'on'} config['PrescribedUvlm'] = {'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 100, 'convection_scheme': 3, 'rollup_dt': main_chord/m_main/u_inf, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho, 'alpha': alpha_rad, 'beta': beta, 'n_time_steps': num_steps, 'dt': dt} config['DynamicPrescribedCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearDynamicPrescribedStep', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 10, 'delta_curved': 1e-5, 'min_delta': 1e-5, 'newmark_damp': 1e-3, 'gravity_on': 'off', 'gravity': 9.754, 'num_steps': num_steps, 'dt': dt}, 'aero_solver': 'StepUvlm', 'aero_solver_settings': {'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 100, 'convection_scheme': 3, 'rollup_dt': main_chord/m_main/u_inf, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho, 'alpha': alpha_rad, 'beta': beta, 'n_time_steps': num_steps, 'dt': dt}, 'max_iter': 100, 'tolerance': 1e-6, 'relaxation_factor': 0., 'n_time_steps': num_steps, 'dt': dt, 'structural_substeps': 10} if horseshoe is True: config['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'mstar': 1, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': dt}} else: config['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'mstar': 150, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake' 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': dt}} config['AerogridPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0 } config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': case_name + '_aeroforces.csv', 'screen_output': 'on', 'unsteady': 'off' } config['BeamPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on'} config.write() clean_test_files() generate_fem_file() generate_dyn_file() generate_solver_file(horseshoe=False) generate_aero_file() ================================================ FILE: tests/coupled/prescribed/test_prescribed.py ================================================ # import sharpy.utils.settings as settings # import sharpy.utils.exceptions as exceptions # import sharpy.utils.cout_utils as cout import numpy as np import importlib import unittest import os import sharpy.utils.cout_utils as cout @unittest.skip('Placeholder - No test yet to run') class TestCoupledPrescribed(unittest.TestCase): """ """ @classmethod def setUpClass(cls): # run all the cases generators # case = 'smith_2deg_prescribed' # mod = importlib.import_module('tests.coupled.prescribed.' + case + '.generate_' + case) # case = 'rotating_wing' # mod1 = importlib.import_module('tests.coupled.prescribed.' + case + '.generate_' + case) pass @classmethod def tearDownClass(cls): pass # def test_smith2deg_prescribed(self): # import sharpy.sharpy_main # solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + # '/smith_2deg_prescribed/smith_2deg_prescribed.sharpy') # sharpy.sharpy_main.main(['', solver_path]) # # # read output and compare # output_path = os.path.dirname(solver_path) + 'output/aero/' # forces_data = np.genfromtxt(output_path + 'smith_2deg_prescribed_aeroforces.csv') # self.assertAlmostEqual(forces_data[-1, 3], -3.728e1, 1) def test_rotating_wing(self): # import sharpy.sharpy_main # solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + # '/rotating_wing/rotating_wing.sharpy') # sharpy.sharpy_main.main(['', solver_path]) print('No tests for prescribed dynamic configurations (yet)!', 1) pass ================================================ FILE: tests/coupled/static/__init__.py ================================================ ================================================ FILE: tests/coupled/static/pazy/generate_pazy.py ================================================ import sharpy.cases.templates.flying_wings as wings import sharpy.sharpy_main def generate_pazy(u_inf, case_name, output_folder='/output/', cases_folder='', **kwargs): # u_inf = 60 alpha_deg = kwargs.get('alpha', 0.) rho = 1.225 num_modes = 16 gravity_on = kwargs.get('gravity_on', True) # Lattice Discretisation M = kwargs.get('M', 4) N = kwargs.get('N', 32) M_star_fact = kwargs.get('Ms', 10) flag_multiple_mstar_input = kwargs.get('test_multiple_inputs', False) # SHARPy nonlinear reference solution ws = wings.PazyControlSurface(M=M, N=N, Mstar_fact=M_star_fact, u_inf=u_inf, alpha=alpha_deg, cs_deflection=[0, 0], rho=rho, sweep=0, physical_time=2, n_surfaces=2, route=cases_folder + '/' + case_name, case_name=case_name) ws.gust_intensity = 0.01 ws.sigma = 1 ws.clean_test_files() ws.update_derived_params() ws.set_default_config_dict() ws.generate_aero_file() ws.generate_fem_file() ws.config['SHARPy'] = { 'flow': ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'AerogridPlot', 'BeamPlot', 'WriteVariablesTime', ], 'case': ws.case_name, 'route': ws.route, 'write_screen': 'off', 'write_log': 'on', 'save_settings': 'on', 'log_folder': output_folder, 'log_file': ws.case_name + '.log'} ws.config['BeamLoader'] = { 'unsteady': 'off', 'orientation': ws.quat} ws.config['AerogridLoader'] = { 'unsteady': 'off', 'aligned_grid': 'on', 'mstar': ws.Mstar_fact * ws.M, 'freestream_dir': ws.u_inf_direction, 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction, 'dt': ws.dt}} if flag_multiple_mstar_input: ws.config['AerogridLoader']['mstar'] = [ws.config['AerogridLoader']['mstar'], ws.config['AerogridLoader']['mstar']] ws.config['SHARPy']['flow'] = ['BeamLoader','AerogridLoader'] else: ws.config['StaticUvlm'] = { 'rho': ws.rho, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}, 'rollup_dt': ws.dt, 'print_info': 'on', 'horseshoe': 'on', 'num_cores': 4, 'n_rollup': 0, 'rollup_aic_refresh': 0, 'rollup_tolerance': 1e-4} settings = dict() settings['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 200, 'num_load_steps': 5, 'delta_curved': 1e-6, 'min_delta': 1e-8, 'gravity_on': gravity_on, 'gravity': 9.81} ws.config['StaticCoupled'] = { 'print_info': 'on', 'max_iter': 200, 'n_load_steps': 4, 'tolerance': 1e-5, 'relaxation_factor': 0.1, 'aero_solver': 'StaticUvlm', 'aero_solver_settings': { 'rho': ws.rho, 'print_info': 'off', 'horseshoe': 'on', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': ws.dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}, 'vortex_radius': 1e-9}, 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': settings['NonLinearStatic']} ws.config['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0} ws.config['BeamPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on'} ws.config['WriteVariablesTime'] = {'structure_variables': ['pos'], 'structure_nodes': list(range(0, ws.num_node_surf)), 'cleanup_old_solution': 'on'} ws.config.write() sharpy.sharpy_main.main(['', ws.route + ws.case_name + '.sharpy']) ================================================ FILE: tests/coupled/static/smith_g_2deg/__init__.py ================================================ ================================================ FILE: tests/coupled/static/smith_g_2deg/generate_smith_g_2deg.py ================================================ import h5py as h5 import numpy as np import configparser import os np.set_printoptions(precision=16) import sharpy.utils.algebra as algebra case_name = 'smith_g_2deg' route = os.path.dirname(os.path.realpath(__file__)) + '/' # flight conditions u_inf = 25 rho = 0.08891 alpha = 2 beta = 0 c_ref = 1 b_ref = 16 sweep = 0*np.pi/180. aspect_ratio = 32 # = total wing span (chord = 1) alpha_rad = alpha*np.pi/180 # main geometry data main_span = aspect_ratio/2./np.cos(sweep) main_chord = 1.0 main_ea = 0.5 main_sigma = 1 main_airfoil_P = 0 main_airfoil_M = 0 n_surfaces = 2 # discretisation data num_elem_main = 10 num_node_elem = 3 num_elem = num_elem_main + num_elem_main num_node_main = num_elem_main*(num_node_elem - 1) + 1 num_node = num_node_main + (num_node_main - 1) m_main = 10 def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def generate_fem_file(): # placeholders # coordinates global x, y, z x = np.zeros((num_node, )) y = np.zeros((num_node, )) z = np.zeros((num_node, )) # struct twist structural_twist = np.zeros((num_elem, num_node_elem)) # beam number beam_number = np.zeros((num_elem, ), dtype=int) # frame of reference delta frame_of_reference_delta = np.zeros((num_elem, num_node_elem, 3)) # connectivities conn = np.zeros((num_elem, num_node_elem), dtype=int) # stiffness num_stiffness = 1 ea = 1e5 ga = 1e5 gj = 1e4 eiy = 2e4 eiz = 5e6 sigma = 1 base_stiffness = sigma*np.diag([ea, ga, ga, gj, eiy, eiz]) stiffness = np.zeros((num_stiffness, 6, 6)) stiffness[0, :, :] = main_sigma*base_stiffness elem_stiffness = np.zeros((num_elem,), dtype=int) # mass num_mass = 1 m_base = 0.75 j_base = 0.1 base_mass = np.diag([m_base, m_base, m_base, j_base, 0.5*j_base, 0.5*j_base]) mass = np.zeros((num_mass, 6, 6)) mass[0, :, :] = base_mass elem_mass = np.zeros((num_elem,), dtype=int) # boundary conditions boundary_conditions = np.zeros((num_node, ), dtype=int) boundary_conditions[0] = 1 # applied forces app_forces = np.zeros((num_node, 6)) # right wing (beam 0) -------------------------------------------------------------- working_elem = 0 working_node = 0 beam_number[working_elem:working_elem + num_elem_main] = 0 y[0] = 0 y[working_node:working_node + num_node_main] = np.cos(sweep)*np.linspace(0.0, main_span, num_node_main) x[working_node:working_node + num_node_main] = np.sin(sweep)*np.linspace(0.0, main_span, num_node_main) for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[0] = 1 boundary_conditions[working_node + num_node_main - 1] = -1 working_elem += num_elem_main working_node += num_node_main # left wing (beam 1) -------------------------------------------------------------- beam_number[working_elem:working_elem + num_elem_main] = 1 domain = np.linspace(-1.0, 0.0, num_node_main) tempy = np.linspace(-main_span, 0.0, num_node_main) x[working_node:working_node + num_node_main - 1] = -np.sin(sweep)*tempy[0:-1] y[working_node:working_node + num_node_main - 1] = np.cos(sweep)*tempy[0:-1] for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) + 1 conn[working_elem + num_elem_main - 1, 1] = 0 elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[working_node] = -1 working_elem += num_elem_main working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=num_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=num_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=num_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) # node_app_forces_handle = h5file.create_dataset( # 'node_app_forces', data=node_app_forces) def generate_aero_file(): global x, y, z airfoil_distribution = np.zeros((num_elem, num_node_elem), dtype=int) surface_distribution = np.zeros((num_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces, ), dtype=int) m_distribution = 'uniform' aero_node = np.zeros((num_node,), dtype=bool) twist = np.zeros((num_elem, 3)) chord = np.zeros((num_elem, 3)) elastic_axis = np.zeros((num_elem, 3,)) working_elem = 0 working_node = 0 # right wing (surface 0, beam 0) i_surf = 0 airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main] = True chord[:] = main_chord elastic_axis[:] = main_ea working_elem += num_elem_main working_node += num_node_main # right wing (surface 1, beam 1) i_surf = 1 airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main - 1] = True working_elem += num_elem_main working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( generate_naca_camber(P=main_airfoil_P, M=main_airfoil_M))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input .attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) def generate_naca_camber(M=0, P=0): m = M*1e-2 p = P*1e-1 def naca(x, m, p): if x < 1e-6: return 0.0 elif x < p: return m/(p*p)*(2*p*x - x*x) elif x > p and x < 1+1e-6: return m/((1-p)*(1-p))*(1 - 2*p + 2*p*x - x*x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, m, p) for x in x_vec]) return x_vec, y_vec def generate_solver_file(horseshoe=False): file_name = route + '/' + case_name + '.sharpy' import configobj config = configobj.ConfigObj() config.filename = file_name config['SHARPy'] = {'case': case_name, 'route': route, 'flow': ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'AeroForcesCalculator', 'WriteVariablesTime'], 'write_screen': 'off', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} config['BeamLoader'] = {'unsteady': 'off', 'orientation': algebra.euler2quat(np.array([0.0, alpha_rad, beta*np.pi/180]))} config['StaticCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-1, 'min_delta': 1e-6, 'gravity_on': 'on', 'gravity': 9.754}, 'aero_solver': 'StaticUvlm', 'aero_solver_settings': {'print_info': 'off', 'horseshoe': 'on', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': main_chord/m_main/u_inf, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho}, 'max_iter': 50, 'tolerance': 1e-9, 'relaxation_factor': 0.0} config['WriteVariablesTime'] = {'cleanup_old_solution': 'on', 'structure_variables': ['pos'], 'structure_nodes': [num_node_main - 1]} if horseshoe is True: config['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 1, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': main_chord/m_main/u_inf}} else: config['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 20, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': main_chord/m_main/u_inf}} config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': case_name + '_aeroforces.csv', 'screen_output': 'on', } config.write() clean_test_files() generate_fem_file() generate_solver_file(horseshoe=True) generate_aero_file() ================================================ FILE: tests/coupled/static/smith_g_4deg/__init__.py ================================================ ================================================ FILE: tests/coupled/static/smith_g_4deg/generate_smith_g_4deg.py ================================================ import h5py as h5 import numpy as np import configparser import os import sharpy.utils.algebra as algebra case_name = 'smith_g_4deg' route = os.path.dirname(os.path.realpath(__file__)) + '/' # flight conditions u_inf = 25 rho = 0.08891 alpha = 4 beta = 0 c_ref = 1 b_ref = 16 sweep = 0*np.pi/180. aspect_ratio = 32 # = total wing span (chord = 1) alpha_rad = alpha*np.pi/180 # main geometry data main_span = aspect_ratio/2./np.cos(sweep) main_chord = 1.0 main_ea = 0.5 main_sigma = 1 main_airfoil_P = 0 main_airfoil_M = 0 n_surfaces = 2 # discretisation data num_elem_main = 10 num_node_elem = 3 num_elem = num_elem_main + num_elem_main num_node_main = num_elem_main*(num_node_elem - 1) + 1 num_node = num_node_main + (num_node_main - 1) m_main = 10 def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def generate_fem_file(): # placeholders # coordinates global x, y, z x = np.zeros((num_node, )) y = np.zeros((num_node, )) z = np.zeros((num_node, )) # struct twist structural_twist = np.zeros((num_elem, num_node_elem)) # beam number beam_number = np.zeros((num_elem, ), dtype=int) # frame of reference delta frame_of_reference_delta = np.zeros((num_elem, num_node_elem, 3)) # connectivities conn = np.zeros((num_elem, num_node_elem), dtype=int) # stiffness num_stiffness = 1 ea = 1e5 ga = 1e5 gj = 1e4 eiy = 2e4 eiz = 5e6 sigma = 1. base_stiffness = sigma*np.diag([ea, ga, ga, gj, eiy, eiz]) stiffness = np.zeros((num_stiffness, 6, 6)) stiffness[0, :, :] = main_sigma*base_stiffness elem_stiffness = np.zeros((num_elem,), dtype=int) # mass num_mass = 1 m_base = 0.75 j_base = 0.1 base_mass = np.diag([m_base, m_base, m_base, j_base, j_base, j_base]) mass = np.zeros((num_mass, 6, 6)) mass[0, :, :] = base_mass elem_mass = np.zeros((num_elem,), dtype=int) # boundary conditions boundary_conditions = np.zeros((num_node, ), dtype=int) boundary_conditions[0] = 1 app_forces = np.zeros((num_node, 6)) spacing_param = 4 # right wing (beam 0) -------------------------------------------------------------- working_elem = 0 working_node = 0 beam_number[working_elem:working_elem + num_elem_main] = 0 y[0] = 0 y[working_node:working_node + num_node_main] = np.cos(sweep)*np.linspace(0.0, main_span, num_node_main) x[working_node:working_node + num_node_main] = np.sin(sweep)*np.linspace(0.0, main_span, num_node_main) for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[0] = 1 boundary_conditions[working_node + num_node_main - 1] = -1 working_elem += num_elem_main working_node += num_node_main # left wing (beam 1) -------------------------------------------------------------- beam_number[working_elem:working_elem + num_elem_main] = 1 tempy = np.linspace(-main_span, 0.0, num_node_main) x[working_node:working_node + num_node_main - 1] = -np.sin(sweep)*tempy[0:-1] y[working_node:working_node + num_node_main - 1] = np.cos(sweep)*tempy[0:-1] for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) + 1 conn[working_elem + num_elem_main - 1, 1] = 0 elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[working_node] = -1 working_elem += num_elem_main working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=num_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=num_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=num_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) def generate_aero_file(): global x, y, z airfoil_distribution = np.zeros((num_elem, num_node_elem), dtype=int) surface_distribution = np.zeros((num_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces, ), dtype=int) m_distribution = 'uniform' aero_node = np.zeros((num_node,), dtype=bool) twist = np.zeros((num_elem, 3)) chord = np.zeros((num_elem, 3)) elastic_axis = np.zeros((num_elem, 3,)) working_elem = 0 working_node = 0 # right wing (surface 0, beam 0) i_surf = 0 airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main] = True chord[:] = main_chord elastic_axis[:] = main_ea working_elem += num_elem_main working_node += num_node_main # left wing (surface 1, beam 1) i_surf = 1 airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main - 1] = True working_elem += num_elem_main working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( generate_naca_camber(P=main_airfoil_P, M=main_airfoil_M))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input .attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) def generate_naca_camber(M=0, P=0): m = M*1e-2 p = P*1e-1 def naca(x, m, p): if x < 1e-6: return 0.0 elif x < p: return m/(p*p)*(2*p*x - x*x) elif x > p and x < 1+1e-6: return m/((1-p)*(1-p))*(1 - 2*p + 2*p*x - x*x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, m, p) for x in x_vec]) return x_vec, y_vec def generate_solver_file(horseshoe=False): file_name = route + '/' + case_name + '.sharpy' import configobj config = configobj.ConfigObj() config.filename = file_name config['SHARPy'] = {'case': case_name, 'route': route, 'flow': ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'AeroForcesCalculator', 'WriteVariablesTime'], 'write_screen': 'off', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} config['BeamLoader'] = {'unsteady': 'off', 'orientation': algebra.euler2quat(np.array([0.0, alpha_rad, beta*np.pi/180]))} config['StaticCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-5, 'min_delta': 1e-8, 'gravity_on': 'on', 'gravity': 9.754}, 'aero_solver': 'StaticUvlm', 'aero_solver_settings': {'print_info': 'off', 'horseshoe': 'on', 'num_cores': 4, 'n_rollup': 100, 'rollup_dt': main_chord/m_main/u_inf, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho}, 'max_iter': 100, 'n_load_steps': 5, 'tolerance': 1e-5, 'relaxation_factor': 0.} config['WriteVariablesTime'] = {'cleanup_old_solution': 'on', 'structure_variables': ['pos'], 'structure_nodes': [num_node_main - 1]} if horseshoe is True: config['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 1, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': main_chord/m_main/u_inf}} else: config['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 80, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': main_chord/m_main/u_inf}} config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': case_name + '_aeroforces.csv', 'screen_output': 'on', } config.write() clean_test_files() generate_fem_file() generate_solver_file(horseshoe=True) generate_aero_file() ================================================ FILE: tests/coupled/static/smith_nog_2deg/__init__.py ================================================ ================================================ FILE: tests/coupled/static/smith_nog_2deg/generate_smith_nog_2deg.py ================================================ import h5py as h5 import numpy as np import configparser import os import sharpy.utils.algebra as algebra case_name = 'smith_nog_2deg' route = os.path.dirname(os.path.realpath(__file__)) + '/' # flight conditions u_inf = 25 rho = 0.08891 alpha = 2 beta = 0 c_ref = 1 b_ref = 16 sweep = 0*np.pi/180. aspect_ratio = 32 # = total wing span (chord = 1) alpha_rad = alpha*np.pi/180 # main geometry data main_span = aspect_ratio/2./np.cos(sweep) main_chord = 1.0 main_ea = 0.5 main_sigma = 1 main_airfoil_P = 0 main_airfoil_M = 0 n_surfaces = 2 # discretisation data num_elem_main = 10 num_node_elem = 3 num_elem = num_elem_main + num_elem_main num_node_main = num_elem_main*(num_node_elem - 1) + 1 num_node = num_node_main + (num_node_main - 1) m_main = 10 def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def generate_fem_file(): # placeholders # coordinates global x, y, z x = np.zeros((num_node, )) y = np.zeros((num_node, )) z = np.zeros((num_node, )) # struct twist structural_twist = np.zeros((num_elem, num_node_elem)) # beam number beam_number = np.zeros((num_elem, ), dtype=int) # frame of reference delta frame_of_reference_delta = np.zeros((num_elem, num_node_elem, 3)) # connectivities conn = np.zeros((num_elem, num_node_elem), dtype=int) # stiffness num_stiffness = 1 ea = 1e5 ga = 1e5 gj = 1e4 eiy = 2e4 eiz = 5e6 sigma = 1 base_stiffness = sigma*np.diag([ea, ga, ga, gj, eiy, eiz]) stiffness = np.zeros((num_stiffness, 6, 6)) stiffness[0, :, :] = main_sigma*base_stiffness elem_stiffness = np.zeros((num_elem,), dtype=int) # mass num_mass = 1 m_base = 0.75 j_base = 0.1 base_mass = np.diag([m_base, m_base, m_base, j_base, 0.5*j_base, 0.5*j_base]) mass = np.zeros((num_mass, 6, 6)) mass[0, :, :] = base_mass elem_mass = np.zeros((num_elem,), dtype=int) # boundary conditions boundary_conditions = np.zeros((num_node, ), dtype=int) boundary_conditions[0] = 1 # applied forces app_forces = np.zeros((num_node, 6)) # right wing (beam 0) -------------------------------------------------------------- working_elem = 0 working_node = 0 beam_number[working_elem:working_elem + num_elem_main] = 0 y[0] = 0 y[working_node:working_node + num_node_main] = np.cos(sweep)*np.linspace(0.0, main_span, num_node_main) x[working_node:working_node + num_node_main] = np.sin(sweep)*np.linspace(0.0, main_span, num_node_main) for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[0] = 1 boundary_conditions[working_node + num_node_main - 1] = -1 working_elem += num_elem_main working_node += num_node_main # left wing (beam 1) -------------------------------------------------------------- beam_number[working_elem:working_elem + num_elem_main] = 1 tempy = np.linspace(-main_span, 0.0, num_node_main) x[working_node:working_node + num_node_main - 1] = -np.sin(sweep)*tempy[0:-1] y[working_node:working_node + num_node_main - 1] = np.cos(sweep)*tempy[0:-1] for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) + 1 conn[working_elem + num_elem_main - 1, 1] = 0 elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[working_node] = -1 working_elem += num_elem_main working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=num_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=num_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=num_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) def generate_aero_file(): global x, y, z airfoil_distribution = np.zeros((num_elem, num_node_elem), dtype=int) surface_distribution = np.zeros((num_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces, ), dtype=int) m_distribution = 'uniform' aero_node = np.zeros((num_node,), dtype=bool) twist = np.zeros((num_elem, 3)) chord = np.zeros((num_elem, 3)) elastic_axis = np.zeros((num_elem, 3,)) working_elem = 0 working_node = 0 # right wing (surface 0, beam 0) i_surf = 0 airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main] = True chord[:] = main_chord elastic_axis[:] = main_ea working_elem += num_elem_main working_node += num_node_main # left wing (surface 1, beam 1) i_surf = 1 airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main - 1] = True working_elem += num_elem_main working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( generate_naca_camber(P=main_airfoil_P, M=main_airfoil_M))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input .attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) def generate_naca_camber(M=0, P=0): m = M*1e-2 p = P*1e-1 def naca(x, m, p): if x < 1e-6: return 0.0 elif x < p: return m/(p*p)*(2*p*x - x*x) elif x > p and x < 1+1e-6: return m/((1-p)*(1-p))*(1 - 2*p + 2*p*x - x*x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, m, p) for x in x_vec]) return x_vec, y_vec def generate_solver_file(horseshoe=False): import configobj file_name = route + '/' + case_name + '.sharpy' config = configobj.ConfigObj() np.set_printoptions(precision=16) config.filename = file_name config['SHARPy'] = {'case': case_name, 'route': route, 'flow': ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'AeroForcesCalculator', 'WriteVariablesTime'], 'write_screen': 'off', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} config['BeamLoader'] = {'unsteady': 'off', 'orientation': algebra.euler2quat(np.array([0.0, alpha_rad, beta*np.pi/180]))} config['StaticCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-1, 'min_delta': 1e-6, 'gravity_on': 'off', 'gravity': 9.754}, 'aero_solver': 'StaticUvlm', 'aero_solver_settings': {'print_info': 'off', 'horseshoe': 'on', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': main_chord/m_main/u_inf, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho}, 'max_iter': 50, 'tolerance': 1e-9, 'relaxation_factor': 0.0} config['WriteVariablesTime'] = {'cleanup_old_solution': 'on', 'structure_variables': ['pos'], 'structure_nodes': [num_node_main - 1]} if horseshoe is True: config['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 1, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': main_chord/m_main/u_inf}} else: config['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 20, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': main_chord/m_main/u_inf}} config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': case_name + '_aeroforces.csv', 'screen_output': 'on', } config.write() clean_test_files() generate_fem_file() generate_solver_file(horseshoe=True) generate_aero_file() ================================================ FILE: tests/coupled/static/smith_nog_4deg/__init__.py ================================================ ================================================ FILE: tests/coupled/static/smith_nog_4deg/generate_smith_nog_4deg.py ================================================ import h5py as h5 import numpy as np import configparser import os import sharpy.utils.algebra as algebra case_name = 'smith_nog_4deg' route = os.path.dirname(os.path.realpath(__file__)) + '/' # flight conditions u_inf = 25 rho = 0.08891 alpha = 4 beta = 0 c_ref = 1 b_ref = 16 sweep = 0*np.pi/180. aspect_ratio = 32 # = total wing span (chord = 1) alpha_rad = alpha*np.pi/180 # main geometry data main_span = aspect_ratio/2./np.cos(sweep) main_chord = 1.0 main_ea = 0.5 main_sigma = 1 main_airfoil_P = 0 main_airfoil_M = 0 n_surfaces = 2 # discretisation data num_elem_main = 10 num_node_elem = 3 num_elem = num_elem_main + num_elem_main num_node_main = num_elem_main*(num_node_elem - 1) + 1 num_node = num_node_main + (num_node_main - 1) m_main = 10 def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def generate_fem_file(): # placeholders # coordinates global x, y, z x = np.zeros((num_node, )) y = np.zeros((num_node, )) z = np.zeros((num_node, )) # struct twist structural_twist = np.zeros((num_elem, num_node_elem)) # beam number beam_number = np.zeros((num_elem, ), dtype=int) # frame of reference delta frame_of_reference_delta = np.zeros((num_elem, num_node_elem, 3)) # connectivities conn = np.zeros((num_elem, num_node_elem), dtype=int) # stiffness num_stiffness = 1 ea = 1e5 ga = 1e5 gj = 1e4 eiy = 2e4 eiz = 5e6 sigma = 1. base_stiffness = sigma*np.diag([ea, ga, ga, gj, eiy, eiz]) stiffness = np.zeros((num_stiffness, 6, 6)) stiffness[0, :, :] = main_sigma*base_stiffness elem_stiffness = np.zeros((num_elem,), dtype=int) # mass num_mass = 1 m_base = 0.75 j_base = 0.1 base_mass = np.diag([m_base, m_base, m_base, j_base, j_base, j_base]) mass = np.zeros((num_mass, 6, 6)) mass[0, :, :] = base_mass elem_mass = np.zeros((num_elem,), dtype=int) # boundary conditions boundary_conditions = np.zeros((num_node, ), dtype=int) boundary_conditions[0] = 1 # applied forces app_forces = np.zeros((num_node, 6)) # right wing (beam 0) -------------------------------------------------------------- working_elem = 0 working_node = 0 beam_number[working_elem:working_elem + num_elem_main] = 0 y[0] = 0 y[working_node:working_node + num_node_main] = np.cos(sweep)*np.linspace(0.0, main_span, num_node_main) x[working_node:working_node + num_node_main] = np.sin(sweep)*np.linspace(0.0, main_span, num_node_main) for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[0] = 1 boundary_conditions[working_node + num_node_main - 1] = -1 working_elem += num_elem_main working_node += num_node_main # left wing (beam 1) -------------------------------------------------------------- beam_number[working_elem:working_elem + num_elem_main] = 1 tempy = np.linspace(-main_span, 0.0, num_node_main) x[working_node:working_node + num_node_main - 1] = -np.sin(sweep)*tempy[0:-1] y[working_node:working_node + num_node_main - 1] = np.cos(sweep)*tempy[0:-1] for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) + 1 conn[working_elem + num_elem_main - 1, 1] = 0 elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[working_node] = -1 working_elem += num_elem_main working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=num_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=num_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=num_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) def generate_aero_file(): global x, y, z airfoil_distribution = np.zeros((num_elem, num_node_elem), dtype=int) surface_distribution = np.zeros((num_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces, ), dtype=int) m_distribution = 'uniform' aero_node = np.zeros((num_node,), dtype=bool) twist = np.zeros((num_elem, 3)) chord = np.zeros((num_elem, 3)) elastic_axis = np.zeros((num_elem, 3,)) working_elem = 0 working_node = 0 # right wing (surface 0, beam 0) i_surf = 0 airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main] = True chord[:] = main_chord elastic_axis[:] = main_ea working_elem += num_elem_main working_node += num_node_main # left wing (surface 1, beam 1) i_surf = 1 airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main - 1] = True working_elem += num_elem_main working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( generate_naca_camber(P=main_airfoil_P, M=main_airfoil_M))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input .attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) def generate_naca_camber(M=0, P=0): m = M*1e-2 p = P*1e-1 def naca(x, m, p): if x < 1e-6: return 0.0 elif x < p: return m/(p*p)*(2*p*x - x*x) elif x > p and x < 1+1e-6: return m/((1-p)*(1-p))*(1 - 2*p + 2*p*x - x*x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, m, p) for x in x_vec]) return x_vec, y_vec def generate_solver_file(horseshoe=False): file_name = route + '/' + case_name + '.sharpy' import configobj config = configobj.ConfigObj() config.filename = file_name config['SHARPy'] = {'case': case_name, 'route': route, 'flow': ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'AeroForcesCalculator', 'WriteVariablesTime'], 'write_screen': 'off', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} config['BeamLoader'] = {'unsteady': 'off', 'orientation': algebra.euler2quat(np.array([0.0, alpha_rad, beta*np.pi/180]))} config['StaticCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-5, 'min_delta': 1e-8, 'gravity_on': 'off', 'gravity': 9.754}, 'aero_solver': 'StaticUvlm', 'aero_solver_settings': {'print_info': 'off', 'horseshoe': 'on', 'num_cores': 4, 'n_rollup': 100, 'rollup_dt': main_chord/m_main/u_inf, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho, }, 'max_iter': 100, 'n_load_steps': 5, 'tolerance': 1e-5, 'relaxation_factor': 0.} config['WriteVariablesTime'] = {'cleanup_old_solution': 'on', 'structure_variables': ['pos'], 'structure_nodes': [num_node_main - 1]} if horseshoe is True: config['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 1, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': main_chord/m_main/u_inf}} else: config['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 80, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': main_chord/m_main/u_inf}} config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': case_name + '_aeroforces.csv', 'screen_output': 'on', } config.write() clean_test_files() generate_fem_file() generate_solver_file(horseshoe=True) generate_aero_file() ================================================ FILE: tests/coupled/static/test_pazy_static.py ================================================ import unittest import numpy as np import tests.coupled.static.pazy.generate_pazy as gp import os import shutil class TestPazyCoupledStatic(unittest.TestCase): """ Test Pazy wing static coupled case and compare against a benchmark result. As of the time of writing, benchmark result has not been verified but it serves as a backward compatibility check for code improvements. """ route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) def test_static_aoa(self): u_inf = 50 alpha = 7 case_name = 'pazy_uinf{:04g}_alpha{:04g}'.format(u_inf * 10, alpha * 10) M = 16 N = 64 Msf = 1 cases_folder = self.route_test_dir + '/pazy/cases/' output_folder = self.route_test_dir + '/pazy/cases/' # run case gp.generate_pazy(u_inf, case_name, output_folder, cases_folder, alpha=alpha, M=M, N=N, Msf=Msf) node_number = N / 2 # wing tip node # Get results in A frame tip_displacement = np.loadtxt(output_folder + '/' + case_name + '/WriteVariablesTime/struct_pos_node{:g}.dat'.format(node_number)) # current reference from Technion abstract ref_displacement = 2.033291e-1 # m np.testing.assert_almost_equal(tip_displacement[-1], ref_displacement, decimal=3, err_msg='Wing tip displacement not within 3 decimal points of reference.', verbose=True) def tearDown(self): cases_folder = self.route_test_dir + '/pazy/cases/' if os.path.isdir(cases_folder): import shutil shutil.rmtree(cases_folder) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/coupled/static/test_static.py ================================================ import numpy as np import importlib import unittest import os class TestCoupledStatic(unittest.TestCase): """ """ @classmethod def setUpClass(cls): # run all the cases generators case = 'smith_nog_2deg' mod = importlib.import_module('tests.coupled.static.' + case + '.generate_' + case) case = 'smith_g_2deg' mod2 = importlib.import_module('tests.coupled.static.' + case + '.generate_' + case) case = 'smith_g_4deg' mod3 = importlib.import_module('tests.coupled.static.' + case + '.generate_' + case) case = 'smith_nog_4deg' mod4 = importlib.import_module('tests.coupled.static.' + case + '.generate_' + case) @classmethod def tearDownClass(cls): pass def test_smith2deg_nog(self): """ Case and results from: Smith, M.J., Patil, M.J. and Hodges, D.H., 2001. CFD-based analysis of nonlinear aeroelastic behavior of high-aspect ratio wings. AIAA Paper, 1582, p.2001. :return: """ import sharpy.sharpy_main solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/smith_nog_2deg/smith_nog_2deg.sharpy') sharpy.sharpy_main.main(['', solver_path]) # read output and compare output_path = os.path.dirname(solver_path) + '/output/smith_nog_2deg/WriteVariablesTime/' pos_data = np.genfromtxt(output_path + 'struct_pos_node20.dat') self.assertAlmostEqual((pos_data[2] - 15.599)/15.599, 0.00, 2) self.assertAlmostEqual((pos_data[3] - 3.32600)/3.32600, 0.00, 2) # results: # N = 10 elements # M = 15 elements # full wake: # Nrollup = 100 # Mstar = 80 # pos last beam of the wing [ 0.02515625 15.62906166 3.20177985] # total forces: # tstep | fx_st | fy_st | fz_st # 0 | 1.464e+00 | -4.480e-03 | 2.389e+02 # 521 seconds def test_smith2deg_g(self): import sharpy.sharpy_main solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/smith_g_2deg/smith_g_2deg.sharpy') sharpy.sharpy_main.main(['', solver_path]) # read output and compare output_path = os.path.dirname(solver_path) + '/output/smith_g_2deg/WriteVariablesTime/' pos_data = np.genfromtxt(output_path + 'struct_pos_node20.dat') self.assertAlmostEqual((pos_data[2] - 15.98295)/15.98295, 0.00, 2) self.assertAlmostEqual((pos_data[3] - 0.682268)/0.682268, 0.00, 2) def test_smith4deg_g(self): """ Case from R. Simpson's PhD thesis. His wing tip displacements: 15.627927627927626, 3.3021978021978025 I always get higher deflection when using my gravity implementation instead of his. His results with gravity are not validated.*** :return: """ import sharpy.sharpy_main solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/smith_g_4deg/smith_g_4deg.sharpy') sharpy.sharpy_main.main(['', solver_path]) # read output and compare output_path = os.path.dirname(solver_path) + '/output/smith_g_4deg/WriteVariablesTime/' pos_data = np.genfromtxt(output_path + 'struct_pos_node20.dat') self.assertAlmostEqual((pos_data[2] - 15.55)/15.55, 0.00, 2) self.assertAlmostEqual((pos_data[3] - 3.671)/3.671, 0.00, 2) # results: # N = 10 elements # M = 15 elements # full wake: # Nrollup = 100 # Mstar = 80 # pos last node of the wing # 0.05668 15.5422 3.53971 # total forces: # tstep | fx_st | fy_st | fz_st # 0 | 3.229 | -1.059e-3| 3.766e2 # 7500 seconds def test_smith4deg_nog(self): """ Hodges result for Euler+Nonlinear is 14.668547249647393, 5.451612903225806 :return: """ import sharpy.sharpy_main solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/smith_nog_4deg/smith_nog_4deg.sharpy') sharpy.sharpy_main.main(['', solver_path]) # read output and compare output_path = os.path.dirname(solver_path) + '/output/smith_nog_4deg/WriteVariablesTime/' pos_data = np.genfromtxt(output_path + 'struct_pos_node20.dat') self.assertAlmostEqual((pos_data[2] - 14.87)/14.87, 0.00, 2) self.assertAlmostEqual((pos_data[3] - 5.5078)/5.5078, 0.00, 2) # results: # N = 10 elements # M = 15 elements # full wake: # Nrollup = 100 # Mstar = 80 # pos last beam of the wing # 5.59418e-2 1.49094e1 5.41518 # total forces: # tstep | fx_st | fy_st | fz_st # 0 | 3.045 | -1.089e-4 | 3.678e2 # 7380 seconds # will use this one for validation. # same discretisation, with horseshoe: # [ 0.05849521 14.80636555 5.65457501] # forces: # tstep | fx_st | fy_st | fz_st # 0 | 1.996e+00 | -2.116e-06 | 3.888e+02 # 142 seconds ================================================ FILE: tests/docs/__init__.py ================================================ ================================================ FILE: tests/docs/test_docs.py ================================================ import unittest import os # Skipped to avoid dependency of minimal environment with sphinx # we agreed that it is best to have the core of SHARPy only in the tests # ADC 14 Nov 2019 @unittest.skip class DocTest(unittest.TestCase): """ """ route = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/' source_dir = route + u'../../docs/source' config_dir = route + u'../../docs/source' output_dir = route + u'../../docs/build' doctree_dir = route + u'../../docs/build/doctrees' all_files = 1 def test_html_documentation(self): from sphinx.application import Sphinx app = Sphinx(self.source_dir, self.config_dir, self.output_dir, self.doctree_dir, buildername='html', warningiserror=False, ) app.build(force_all=self.all_files) # TODO: additional checks here if needed def test_text_documentation(self): from sphinx.application import Sphinx # The same, but with different buildername app = Sphinx(self.source_dir, self.config_dir, self.output_dir, self.doctree_dir, buildername='text', warningiserror=False, ) app.build(force_all=self.all_files) # TODO: additional checks if needed def tearDown(self): # TODO: clean up the output directory pass ================================================ FILE: tests/io/Example_simple_hale/Client_HALE.py ================================================ import socket import select import time import logging import struct import sharpy.io.message_interface as message_interface import numpy as np """ This is not a test but is to be used as client when testing the development of the input output capabilities of sharpy. It will just give back the initial control surface deflection. Run this script as client. Run ``python generate_hale_io.py`` as server from the folder tests/io/Example_simple_hale """ # sel = selectors.DefaultSelector() logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=20) logger = logging.getLogger(__name__) sharpy_incoming = ('127.0.0.1', 64011) # control side socket sharpy_outgoing = ('127.0.0.1', 64010) # output side socket own_control = ('127.0.0.1', 64000) own_receive = ('127.0.0.1', 64001) in_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) in_sock.bind(own_control) out_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) out_sock.bind(own_receive) # from https://stackoverflow.com/questions/2719017/how-to-set-timeout-on-pythons-socket-recv-method # ready_to_read = select.select([out_sock], [], [], 2) out_sock.settimeout(300) #initial values cs_deflection = [-2.08 * np.pi / 180, 0] thrust = 6.16 dt = 0.025 n_tstep = 0 #Counter for time x_pos = [] y_pos = [] z_pos = [] x_vel = [] y_vel = [] z_vel = [] p = [] q = [] r = [] root_OOP_moment = [] root_OOP_strain = [] time_vec = [] while True: if n_tstep > 401: break # send control input to sharpy ctrl_value = struct.pack('<5sif', b'RREF0', 0, cs_deflection[0]) ctrl_value += struct.pack('if', 1, cs_deflection[1]) ctrl_value += struct.pack('if', 2, thrust) logger.info('Sending control input of size {} bytes'.format(len(ctrl_value))) in_sock.sendto(ctrl_value, sharpy_incoming) logger.info('Sent control input to {}'.format(sharpy_incoming)) # time.sleep(2) # input('Continue loop') # receive output data. set msg_len to whatever length SHARPy is sending msg_len = 93 try: msg, conn = out_sock.recvfrom(msg_len) except socket.timeout: logger.info('Socket time out') break logger.info('Received {} data from {}'.format(msg, conn)) logger.info('Received data is {} bytes long'.format(len(msg))) # else: # break # decoding values = message_interface.decoder(msg) # Add the received values to the variables x_pos.append(-values[0][1]) y_pos.append(values[1][1]) z_pos.append(values[2][1]) x_vel.append(-values[3][1]) y_vel.append(values[4][1]) z_vel.append(values[5][1]) p.append(values[6][1] * 180 / np.pi) q.append(values[7][1] * 180 / np.pi) r.append(values[8][1] * 180 / np.pi) root_OOP_moment.append(values[9][1]) root_OOP_strain.append(values[10][1]) time_vec.append(dt * n_tstep) n_tstep += 1 ## out_sock.close() in_sock.close() logger.info('Closed input and output sockets') ================================================ FILE: tests/io/Example_simple_hale/__init__.py ================================================ ================================================ FILE: tests/io/Example_simple_hale/generate_hale_io.py ================================================ #! /usr/bin/env python3 import h5py as h5 import numpy as np import os import sharpy.utils.algebra as algebra import sharpy.sharpy_main case_name = 'simple_HALE' route = os.path.dirname(os.path.realpath(__file__)) + '/' # EXECUTION flow = ['BeamLoader', 'AerogridLoader', # 'NonLinearStatic', # 'StaticUvlm', 'StaticTrim', # 'StaticCoupled', 'BeamLoads', 'AerogridPlot', 'BeamPlot', 'DynamicCoupled', 'SaveData' # 'Modal', # 'LinearAssember', # 'AsymptoticStability', ] # if free_flight is False, the motion of the centre of the wing is prescribed. free_flight = True if not free_flight: case_name += '_prescribed' amplitude = 0 * np.pi / 180 period = 3 case_name += '_amp_' + str(amplitude).replace('.', '') + '_period_' + str(period) # FLIGHT CONDITIONS # the simulation is set such that the aircraft flies at a u_inf velocity while # the air is calm. u_inf = 10 rho = 1.225 # trim sigma = 1.5 alpha = 4.31 * np.pi / 180 beta = 0 roll = 0 gravity = 'on' cs_deflection = -2.08 * np.pi / 180 rudder_static_deflection = 0.0 rudder_step = 0.0 * np.pi / 180 thrust = 6.16 sigma = 1.5 lambda_dihedral = 20 * np.pi / 180 # gust settings gust_intensity = 0.20 gust_length = 1 * u_inf gust_offset = 0.5 * u_inf # numerics n_step = 5 structural_relaxation_factor = 0.6 relaxation_factor = 0.35 tolerance = 1e-6 fsi_tolerance = 1e-4 num_cores = 2 # MODEL GEOMETRY # beam span_main = 16.0 lambda_main = 0.25 ea_main = 0.3 ea = 1e7 ga = 1e5 gj = 1e4 eiy = 2e4 eiz = 4e6 m_bar_main = 0.75 j_bar_main = 0.075 length_fuselage = 10 offset_fuselage = 0 sigma_fuselage = 10 m_bar_fuselage = 0.2 j_bar_fuselage = 0.08 span_tail = 2.5 ea_tail = 0.5 fin_height = 2.5 ea_fin = 0.5 sigma_tail = 100 m_bar_tail = 0.3 j_bar_tail = 0.08 # lumped masses n_lumped_mass = 1 lumped_mass_nodes = np.zeros((n_lumped_mass,), dtype=int) lumped_mass = np.zeros((n_lumped_mass,)) lumped_mass[0] = 50 lumped_mass_inertia = np.zeros((n_lumped_mass, 3, 3)) lumped_mass_position = np.zeros((n_lumped_mass, 3)) # aero chord_main = 1.0 chord_tail = 0.5 chord_fin = 0.5 # DISCRETISATION # spatial discretisation # chordiwse panels m = 4 # spanwise elements n_elem_multiplier = 2 n_elem_main = int(4 * n_elem_multiplier) n_elem_tail = int(2 * n_elem_multiplier) n_elem_fin = int(2 * n_elem_multiplier) n_elem_fuselage = int(2 * n_elem_multiplier) n_surfaces = 5 # temporal discretisation physical_time = 5 tstep_factor = 1. dt = 1.0 / m / u_inf * tstep_factor n_tstep = round(physical_time / dt) # END OF INPUT----------------------------------------------------------------- # beam processing n_node_elem = 3 span_main1 = (1.0 - lambda_main) * span_main span_main2 = lambda_main * span_main n_elem_main1 = round(n_elem_main * (1 - lambda_main)) n_elem_main2 = n_elem_main - n_elem_main1 # total number of elements n_elem = 0 n_elem += n_elem_main1 + n_elem_main1 n_elem += n_elem_main2 + n_elem_main2 n_elem += n_elem_fuselage n_elem += n_elem_fin n_elem += n_elem_tail + n_elem_tail # number of nodes per part n_node_main1 = n_elem_main1 * (n_node_elem - 1) + 1 n_node_main2 = n_elem_main2 * (n_node_elem - 1) + 1 n_node_main = n_node_main1 + n_node_main2 - 1 n_node_fuselage = n_elem_fuselage * (n_node_elem - 1) + 1 n_node_fin = n_elem_fin * (n_node_elem - 1) + 1 n_node_tail = n_elem_tail * (n_node_elem - 1) + 1 # total number of nodes n_node = 0 n_node += n_node_main1 + n_node_main1 - 1 n_node += n_node_main2 - 1 + n_node_main2 - 1 n_node += n_node_fuselage - 1 n_node += n_node_fin - 1 n_node += n_node_tail - 1 n_node += n_node_tail - 1 # stiffness and mass matrices n_stiffness = 3 base_stiffness_main = sigma * np.diag([ea, ga, ga, gj, eiy, eiz]) base_stiffness_fuselage = base_stiffness_main.copy() * sigma_fuselage base_stiffness_fuselage[4, 4] = base_stiffness_fuselage[5, 5] base_stiffness_tail = base_stiffness_main.copy() * sigma_tail base_stiffness_tail[4, 4] = base_stiffness_tail[5, 5] n_mass = 3 base_mass_main = np.diag([m_bar_main, m_bar_main, m_bar_main, j_bar_main, 0.5 * j_bar_main, 0.5 * j_bar_main]) base_mass_fuselage = np.diag([m_bar_fuselage, m_bar_fuselage, m_bar_fuselage, j_bar_fuselage, j_bar_fuselage * 0.5, j_bar_fuselage * 0.5]) base_mass_tail = np.diag([m_bar_tail, m_bar_tail, m_bar_tail, j_bar_tail, j_bar_tail * 0.5, j_bar_tail * 0.5]) # PLACEHOLDERS # beam x = np.zeros((n_node,)) y = np.zeros((n_node,)) z = np.zeros((n_node,)) beam_number = np.zeros((n_elem,), dtype=int) frame_of_reference_delta = np.zeros((n_elem, n_node_elem, 3)) structural_twist = np.zeros((n_elem, 3)) conn = np.zeros((n_elem, n_node_elem), dtype=int) stiffness = np.zeros((n_stiffness, 6, 6)) elem_stiffness = np.zeros((n_elem,), dtype=int) mass = np.zeros((n_mass, 6, 6)) elem_mass = np.zeros((n_elem,), dtype=int) boundary_conditions = np.zeros((n_node,), dtype=int) app_forces = np.zeros((n_node, 6)) # aero airfoil_distribution = np.zeros((n_elem, n_node_elem), dtype=int) surface_distribution = np.zeros((n_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces,), dtype=int) m_distribution = 'uniform' aero_node = np.zeros((n_node,), dtype=bool) twist = np.zeros((n_elem, n_node_elem)) sweep = np.zeros((n_elem, n_node_elem)) chord = np.zeros((n_elem, n_node_elem,)) elastic_axis = np.zeros((n_elem, n_node_elem,)) # FUNCTIONS------------------------------------------------------------- def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) dyn_file_name = route + '/' + case_name + '.dyn.h5' if os.path.isfile(dyn_file_name): os.remove(dyn_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def generate_dyn_file(): global dt global n_tstep global route global case_name global num_elem global num_node_elem global num_node global amplitude global period global free_flight dynamic_forces_time = None with_dynamic_forces = False with_forced_vel = False if not free_flight: with_forced_vel = True if with_dynamic_forces: f1 = 100 dynamic_forces = np.zeros((num_node, 6)) app_node = [int(num_node_main - 1), int(num_node_main)] dynamic_forces[app_node, 2] = f1 force_time = np.zeros((n_tstep,)) limit = round(0.05 / dt) force_time[50:61] = 1 dynamic_forces_time = np.zeros((n_tstep, num_node, 6)) for it in range(n_tstep): dynamic_forces_time[it, :, :] = force_time[it] * dynamic_forces forced_for_vel = None if with_forced_vel: forced_for_vel = np.zeros((n_tstep, 6)) forced_for_acc = np.zeros((n_tstep, 6)) for it in range(n_tstep): # if dt*it < period: # forced_for_vel[it, 2] = 2*np.pi/period*amplitude*np.sin(2*np.pi*dt*it/period) # forced_for_acc[it, 2] = (2*np.pi/period)**2*amplitude*np.cos(2*np.pi*dt*it/period) forced_for_vel[it, 3] = 2 * np.pi / period * amplitude * np.sin(2 * np.pi * dt * it / period) forced_for_acc[it, 3] = (2 * np.pi / period) ** 2 * amplitude * np.cos(2 * np.pi * dt * it / period) if with_dynamic_forces or with_forced_vel: with h5.File(route + '/' + case_name + '.dyn.h5', 'a') as h5file: if with_dynamic_forces: h5file.create_dataset( 'dynamic_forces', data=dynamic_forces_time) if with_forced_vel: h5file.create_dataset( 'for_vel', data=forced_for_vel) h5file.create_dataset( 'for_acc', data=forced_for_acc) h5file.create_dataset( 'num_steps', data=n_tstep) def generate_fem(): stiffness[0, ...] = base_stiffness_main stiffness[1, ...] = base_stiffness_fuselage stiffness[2, ...] = base_stiffness_tail mass[0, ...] = base_mass_main mass[1, ...] = base_mass_fuselage mass[2, ...] = base_mass_tail we = 0 wn = 0 # inner right wing beam_number[we:we + n_elem_main1] = 0 y[wn:wn + n_node_main1] = np.linspace(0.0, span_main1, n_node_main1) for ielem in range(n_elem_main1): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main1] = 0 elem_mass[we:we + n_elem_main1] = 0 boundary_conditions[0] = 1 # remember this is in B FoR app_forces[0] = [0, thrust, 0, 0, 0, 0] we += n_elem_main1 wn += n_node_main1 # outer right wing beam_number[we:we + n_elem_main1] = 0 y[wn:wn + n_node_main2 - 1] = y[wn - 1] + np.linspace(0.0, np.cos(lambda_dihedral) * span_main2, n_node_main2)[1:] z[wn:wn + n_node_main2 - 1] = z[wn - 1] + np.linspace(0.0, np.sin(lambda_dihedral) * span_main2, n_node_main2)[1:] for ielem in range(n_elem_main2): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main2] = 0 elem_mass[we:we + n_elem_main2] = 0 boundary_conditions[wn + n_node_main2 - 2] = -1 we += n_elem_main2 wn += n_node_main2 - 1 # inner left wing beam_number[we:we + n_elem_main1 - 1] = 1 y[wn:wn + n_node_main1 - 1] = np.linspace(0.0, -span_main1, n_node_main1)[1:] for ielem in range(n_elem_main1): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = 0 elem_stiffness[we:we + n_elem_main1] = 0 elem_mass[we:we + n_elem_main1] = 0 we += n_elem_main1 wn += n_node_main1 - 1 # outer left wing beam_number[we:we + n_elem_main2] = 1 y[wn:wn + n_node_main2 - 1] = y[wn - 1] + np.linspace(0.0, -np.cos(lambda_dihedral) * span_main2, n_node_main2)[1:] z[wn:wn + n_node_main2 - 1] = z[wn - 1] + np.linspace(0.0, np.sin(lambda_dihedral) * span_main2, n_node_main2)[1:] for ielem in range(n_elem_main2): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] elem_stiffness[we:we + n_elem_main2] = 0 elem_mass[we:we + n_elem_main2] = 0 boundary_conditions[wn + n_node_main2 - 2] = -1 we += n_elem_main2 wn += n_node_main2 - 1 # fuselage beam_number[we:we + n_elem_fuselage] = 2 x[wn:wn + n_node_fuselage - 1] = np.linspace(0.0, length_fuselage, n_node_fuselage)[1:] z[wn:wn + n_node_fuselage - 1] = np.linspace(0.0, offset_fuselage, n_node_fuselage)[1:] for ielem in range(n_elem_fuselage): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [0.0, 1.0, 0.0] conn[we, 0] = 0 elem_stiffness[we:we + n_elem_fuselage] = 1 elem_mass[we:we + n_elem_fuselage] = 1 we += n_elem_fuselage wn += n_node_fuselage - 1 global end_of_fuselage_node end_of_fuselage_node = wn - 1 # fin beam_number[we:we + n_elem_fin] = 3 x[wn:wn + n_node_fin - 1] = x[end_of_fuselage_node] z[wn:wn + n_node_fin - 1] = z[end_of_fuselage_node] + np.linspace(0.0, fin_height, n_node_fin)[1:] for ielem in range(n_elem_fin): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_of_fuselage_node elem_stiffness[we:we + n_elem_fin] = 2 elem_mass[we:we + n_elem_fin] = 2 we += n_elem_fin wn += n_node_fin - 1 end_of_fin_node = wn - 1 # right tail beam_number[we:we + n_elem_tail] = 4 x[wn:wn + n_node_tail - 1] = x[end_of_fin_node] y[wn:wn + n_node_tail - 1] = np.linspace(0.0, span_tail, n_node_tail)[1:] z[wn:wn + n_node_tail - 1] = z[end_of_fin_node] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [-1.0, 0.0, 0.0] conn[we, 0] = end_of_fin_node elem_stiffness[we:we + n_elem_tail] = 2 elem_mass[we:we + n_elem_tail] = 2 boundary_conditions[wn + n_node_tail - 2] = -1 we += n_elem_tail wn += n_node_tail - 1 # left tail beam_number[we:we + n_elem_tail] = 5 x[wn:wn + n_node_tail - 1] = x[end_of_fin_node] y[wn:wn + n_node_tail - 1] = np.linspace(0.0, -span_tail, n_node_tail)[1:] z[wn:wn + n_node_tail - 1] = z[end_of_fin_node] for ielem in range(n_elem_tail): conn[we + ielem, :] = ((np.ones((3,)) * (we + ielem) * (n_node_elem - 1)) + [0, 2, 1]) for inode in range(n_node_elem): frame_of_reference_delta[we + ielem, inode, :] = [1.0, 0.0, 0.0] conn[we, 0] = end_of_fin_node elem_stiffness[we:we + n_elem_tail] = 2 elem_mass[we:we + n_elem_tail] = 2 boundary_conditions[wn + n_node_tail - 2] = -1 we += n_elem_tail wn += n_node_tail - 1 with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=n_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=n_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=n_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) lumped_mass_nodes_handle = h5file.create_dataset( 'lumped_mass_nodes', data=lumped_mass_nodes) lumped_mass_handle = h5file.create_dataset( 'lumped_mass', data=lumped_mass) lumped_mass_inertia_handle = h5file.create_dataset( 'lumped_mass_inertia', data=lumped_mass_inertia) lumped_mass_position_handle = h5file.create_dataset( 'lumped_mass_position', data=lumped_mass_position) def generate_aero_file(): global x, y, z # control surfaces n_control_surfaces = 2 control_surface = np.zeros((n_elem, n_node_elem), dtype=int) - 1 control_surface_type = np.zeros((n_control_surfaces,), dtype=int) control_surface_deflection = np.zeros((n_control_surfaces,)) control_surface_chord = np.zeros((n_control_surfaces,), dtype=int) control_surface_hinge_coord = np.zeros((n_control_surfaces,), dtype=float) # control surface type 0 = static # control surface type 1 = dynamic control_surface_type[0] = 2 control_surface_deflection[0] = cs_deflection control_surface_chord[0] = m control_surface_hinge_coord[0] = -0.25 # nondimensional wrt elastic axis (+ towards the trailing edge) control_surface_type[1] = 2 control_surface_deflection[1] = rudder_static_deflection control_surface_chord[1] = 1 control_surface_hinge_coord[1] = -0. # nondimensional wrt elastic axis (+ towards the trailing edge) we = 0 wn = 0 # right wing (surface 0, beam 0) i_surf = 0 airfoil_distribution[we:we + n_elem_main, :] = 0 surface_distribution[we:we + n_elem_main] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_main] = True temp_chord = np.linspace(chord_main, chord_main, n_node_main) temp_sweep = np.linspace(0.0, 0 * np.pi / 180, n_node_main) node_counter = 0 for i_elem in range(we, we + n_elem_main): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = temp_chord[node_counter] elastic_axis[i_elem, i_local_node] = ea_main sweep[i_elem, i_local_node] = temp_sweep[node_counter] we += n_elem_main wn += n_node_main # left wing (surface 1, beam 1) i_surf = 1 airfoil_distribution[we:we + n_elem_main, :] = 0 # airfoil_distribution[wn:wn + n_node_main - 1] = 0 surface_distribution[we:we + n_elem_main] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_main - 1] = True # chord[wn:wn + num_node_main - 1] = np.linspace(main_chord, main_tip_chord, num_node_main)[1:] # chord[wn:wn + num_node_main - 1] = main_chord # elastic_axis[wn:wn + num_node_main - 1] = main_ea temp_chord = np.linspace(chord_main, chord_main, n_node_main) node_counter = 0 for i_elem in range(we, we + n_elem_main): for i_local_node in range(n_node_elem): if not i_local_node == 0: node_counter += 1 chord[i_elem, i_local_node] = temp_chord[node_counter] elastic_axis[i_elem, i_local_node] = ea_main sweep[i_elem, i_local_node] = -temp_sweep[node_counter] we += n_elem_main wn += n_node_main - 1 we += n_elem_fuselage wn += n_node_fuselage - 1 - 1 # # # fin (surface 2, beam 3) i_surf = 2 airfoil_distribution[we:we + n_elem_fin, :] = 1 # airfoil_distribution[wn:wn + n_node_fin] = 0 surface_distribution[we:we + n_elem_fin] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_fin] = True # chord[wn:wn + num_node_fin] = fin_chord for i_elem in range(we, we + n_elem_fin): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_fin elastic_axis[i_elem, i_local_node] = ea_fin control_surface[i_elem, i_local_node] = 1 # twist[end_of_fuselage_node] = 0 # twist[wn:] = 0 # elastic_axis[wn:wn + num_node_main] = fin_ea we += n_elem_fin wn += n_node_fin - 1 # # # # right tail (surface 3, beam 4) i_surf = 3 airfoil_distribution[we:we + n_elem_tail, :] = 2 # airfoil_distribution[wn:wn + n_node_tail] = 0 surface_distribution[we:we + n_elem_tail] = i_surf surface_m[i_surf] = m # XXX not very elegant aero_node[wn:] = True # chord[wn:wn + num_node_tail] = tail_chord # elastic_axis[wn:wn + num_node_main] = tail_ea for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): twist[i_elem, i_local_node] = -0 for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_tail elastic_axis[i_elem, i_local_node] = ea_tail control_surface[i_elem, i_local_node] = 0 we += n_elem_tail wn += n_node_tail # # # left tail (surface 4, beam 5) i_surf = 4 airfoil_distribution[we:we + n_elem_tail, :] = 2 # airfoil_distribution[wn:wn + n_node_tail - 1] = 0 surface_distribution[we:we + n_elem_tail] = i_surf surface_m[i_surf] = m aero_node[wn:wn + n_node_tail - 1] = True # chord[wn:wn + num_node_tail] = tail_chord # elastic_axis[wn:wn + num_node_main] = tail_ea # twist[we:we + num_elem_tail] = -tail_twist for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): twist[i_elem, i_local_node] = -0 for i_elem in range(we, we + n_elem_tail): for i_local_node in range(n_node_elem): chord[i_elem, i_local_node] = chord_tail elastic_axis[i_elem, i_local_node] = ea_tail control_surface[i_elem, i_local_node] = 0 we += n_elem_tail wn += n_node_tail with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( generate_naca_camber(P=0, M=0))) naca_airfoil_tail = airfoils_group.create_dataset('1', data=np.column_stack( generate_naca_camber(P=0, M=0))) naca_airfoil_fin = airfoils_group.create_dataset('2', data=np.column_stack( generate_naca_camber(P=0, M=0))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input.attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # sweep sweep_input = h5file.create_dataset('sweep', data=sweep) dim_attr = sweep_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) control_surface_input = h5file.create_dataset('control_surface', data=control_surface) control_surface_deflection_input = h5file.create_dataset('control_surface_deflection', data=control_surface_deflection) control_surface_chord_input = h5file.create_dataset('control_surface_chord', data=control_surface_chord) control_surface_hinge_coord_input = h5file.create_dataset('control_surface_hinge_coord', data=control_surface_hinge_coord) control_surface_types_input = h5file.create_dataset('control_surface_type', data=control_surface_type) def generate_naca_camber(M=0, P=0): mm = M * 1e-2 p = P * 1e-1 def naca(x, mm, p): if x < 1e-6: return 0.0 elif x < p: return mm / (p * p) * (2 * p * x - x * x) elif x > p and x < 1 + 1e-6: return mm / ((1 - p) * (1 - p)) * (1 - 2 * p + 2 * p * x - x * x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, mm, p) for x in x_vec]) return x_vec, y_vec def generate_solver_file(): file_name = route + '/' + case_name + '.sharpy' settings = dict() settings['SHARPy'] = {'case': case_name, 'route': route, 'flow': flow, 'write_screen': 'on', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} settings['BeamLoader'] = {'unsteady': 'on', 'orientation': algebra.euler2quat(np.array([roll, alpha, beta]))} settings['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'mstar': int(20 / tstep_factor), 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': ['1', '0', '0'], 'dt': dt}} settings['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-1, 'min_delta': tolerance, 'gravity_on': gravity, 'gravity': 9.81} settings['StaticUvlm'] = {'print_info': 'on', 'horseshoe': 'off', 'num_cores': num_cores, 'n_rollup': 0, 'rollup_dt': dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho} settings['StaticCoupled'] = {'print_info': 'off', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': settings['NonLinearStatic'], 'aero_solver': 'StaticUvlm', 'aero_solver_settings': settings['StaticUvlm'], 'max_iter': 100, 'n_load_steps': n_step, 'tolerance': fsi_tolerance, 'relaxation_factor': structural_relaxation_factor} settings['StaticTrim'] = {'solver': 'StaticCoupled', 'solver_settings': settings['StaticCoupled'], 'initial_alpha': alpha, 'initial_deflection': cs_deflection, 'initial_thrust': thrust} settings['NonLinearDynamicCoupledStep'] = {'print_info': 'off', 'max_iterations': 950, 'delta_curved': 1e-1, 'min_delta': tolerance, 'newmark_damp': 5e-3, 'gravity_on': gravity, 'gravity': 9.81, 'num_steps': n_tstep, 'dt': dt, 'initial_velocity': u_inf} settings['NonLinearDynamicPrescribedStep'] = {'print_info': 'off', 'max_iterations': 950, 'delta_curved': 1e-1, 'min_delta': tolerance, 'newmark_damp': 5e-3, 'gravity_on': gravity, 'gravity': 9.81, 'num_steps': n_tstep, 'dt': dt, 'initial_velocity': u_inf * int(free_flight)} settings['SaveData'] = { 'save_aero': 'on', 'save_struct': 'on', 'save_linear': 'off', 'save_linear_uvlm': 'off'} relative_motion = 'off' if not free_flight: relative_motion = 'on' settings['StepUvlm'] = {'print_info': 'off', 'num_cores': num_cores, 'convection_scheme': 2, 'gamma_dot_filtering': 6, 'velocity_field_generator': 'GustVelocityField', 'velocity_field_input': {'u_inf': int(not free_flight) * u_inf, 'u_inf_direction': [1., 0, 0], 'gust_shape': '1-cos', 'gust_parameters': {'gust_length': gust_length, 'gust_intensity': gust_intensity * u_inf}, 'offset': gust_offset, 'relative_motion': relative_motion}, 'rho': rho, 'n_time_steps': n_tstep, 'dt': dt} if free_flight: solver = 'NonLinearDynamicCoupledStep' else: solver = 'NonLinearDynamicPrescribedStep' settings['DynamicCoupled'] = {'structural_solver': solver, 'structural_solver_settings': settings[solver], 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'fsi_substeps': 200, 'fsi_tolerance': fsi_tolerance, 'relaxation_factor': relaxation_factor, 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': 0.5, 'n_time_steps': n_tstep, 'dt': dt, 'include_unsteady_force_contribution': 'on', 'network_settings': { 'variables_filename': './variables_hale.yaml', 'send_output_to_all_clients': 'on', 'byte_ordering': 'little', 'received_data_filename': './output/' + case_name + '/input.dat', 'log_name': './output/' + case_name + '/sharpy_network.log', 'file_log_level': 'debug', 'console_log_level': 'debug', 'input_network_settings': {'address': '127.0.0.1', 'port': 64011}, 'output_network_settings': {'send_on_demand': 'off', 'destination_address': ['127.0.0.1'], 'address': '127.0.0.1', 'port': 64010, 'destination_ports': [64001]}, }, 'postprocessors': ['BeamLoads', 'BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'BeamLoads': {'csv_output': 'off'}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}, 'AerogridPlot': { 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, }} settings['BeamLoads'] = {'csv_output': 'off'} settings['BeamPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on'} settings['AerogridPlot'] = {'include_rbm': 'on', 'include_forward_motion': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0, 'u_inf': u_inf, 'dt': dt} settings['Modal'] = {'print_info': True, 'use_undamped_modes': True, 'NumLambda': 30, 'rigid_body_modes': True, 'write_modes_vtk': 'on', 'print_matrices': 'on', 'continuous_eigenvalues': 'off', 'dt': dt, 'plot_eigenvalues': False} settings['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', 'linear_system_settings': { 'beam_settings': {'modal_projection': False, 'inout_coords': 'nodes', 'discrete_time': True, 'newmark_damp': 0.05, 'discr_method': 'newmark', 'dt': dt, 'proj_modes': 'undamped', 'use_euler': 'off', 'num_modes': 40, 'print_info': 'on', 'gravity': 'on', 'remove_dofs': []}, 'aero_settings': {'dt': dt, 'integr_order': 2, 'density': rho, 'remove_predictor': False, 'use_sparse': True, 'remove_inputs': ['u_gust']} }} settings['AsymptoticStability'] = {'print_info': 'on', 'modes_to_plot': [], 'display_root_locus': 'off', 'frequency_cutoff': 0, 'export_eigenvalues': 'off', 'num_evals': 40} import configobj config = configobj.ConfigObj() config.filename = file_name for k, v in settings.items(): config[k] = v config.write() clean_test_files() generate_fem() generate_aero_file() generate_solver_file() generate_dyn_file() case_route = '' sharpy.sharpy_main.main(['', case_route + '' + case_name + '.sharpy']) ================================================ FILE: tests/io/Example_simple_hale/variables_hale.yaml ================================================ --- - name: 'control_surface_deflection' var_type: 'control_surface' inout: 'in' position: 0 - name: 'control_surface_deflection' var_type: 'control_surface' inout: 'in' position: 1 - name: 'app_forces' var_type: 'node' inout: 'in' position: 0 index: 1 - name: 'for_pos' inout: 'out' position: 0 var_type: 'node' - name: 'for_pos' var_type: 'node' inout: 'out' position: 1 - name: 'for_pos' var_type: 'node' inout: 'out' position: 2 - name: 'for_vel' var_type: 'node' inout: 'out' position: 0 - name: 'for_vel' var_type: 'node' inout: 'out' position: 1 - name: 'for_vel' var_type: 'node' inout: 'out' position: 2 - name: 'for_vel' var_type: 'node' inout: 'out' position: 3 - name: 'for_vel' var_type: 'node' inout: 'out' position: 4 - name: 'for_vel' var_type: 'node' inout: 'out' position: 5 - name: 'loads' var_type: 'node' inout: 'out' position: 0 index: 4 - name: 'strain' var_type: 'node' inout: 'out' position: 0 index: 4 ... ================================================ FILE: tests/io/__init__.py ================================================ ================================================ FILE: tests/io/generate_pazy_udpout.py ================================================ import numpy as np import os import unittest import sharpy.cases.templates.flying_wings as wings import sharpy.sharpy_main # Problem Set up def generate_pazy_udp(u_inf, case_name, output_folder='/output/', cases_folder='', **kwargs): # u_inf = 60 alpha_deg = kwargs.get('alpha', 0.) rho = 1.225 num_modes = 16 gravity_on = kwargs.get('gravity_on', True) cd = kwargs.get('cd', './') # current directory (useful for tests) # Lattice Discretisation M = kwargs.get('M', 4) N = kwargs.get('N', 32) M_star_fact = kwargs.get('Ms', 10) # Linear UVLM settings integration_order = 2 remove_predictor = False use_sparse = True # ROM Properties rom_settings = dict() rom_settings['algorithm'] = 'mimo_rational_arnoldi' rom_settings['r'] = 5 rom_settings['single_side'] = 'observability' frequency_continuous_k = np.array([0.]) # Case Admin - Create results folders # case_name = 'goland_cs' # case_nlin_info = 'M%dN%dMs%d_nmodes%d' % (M, N, M_star_fact, num_modes) # case_rom_info = 'rom_MIMORA_r%d_sig%04d_%04dj' % (rom_settings['r'], frequency_continuous_k[-1].real * 100, # frequency_continuous_k[-1].imag * 100) # case_name += case_nlin_info #+ case_rom_info # os.makedirs(fig_folder, exist_ok=True) # SHARPy nonlinear reference solution ws = wings.PazyControlSurface(M=M, N=N, Mstar_fact=M_star_fact, u_inf=u_inf, alpha=alpha_deg, cs_deflection=[0, 0], rho=rho, sweep=0, physical_time=0.2, n_surfaces=2, route=cases_folder + '/' + case_name, case_name=case_name) ws.gust_intensity = 0.01 ws.sigma = 1 ws.clean_test_files() ws.update_derived_params() ws.set_default_config_dict() ws.generate_aero_file() ws.generate_fem_file() frequency_continuous_w = 2 * u_inf * frequency_continuous_k / ws.c_ref rom_settings['frequency'] = frequency_continuous_w rom_settings['tangent_input_file'] = ws.route + '/' + ws.case_name + '.rom.h5' ws.config['SHARPy'] = { 'flow': ['BeamLoader', 'AerogridLoader', # 'NonLinearStatic', 'StaticUvlm', 'AerogridPlot', 'BeamPlot', 'WriteVariablesTime', 'DynamicCoupled', # 'Modal', # 'LinearAssembler', # 'FrequencyResponse', # 'AsymptoticStability', # 'SaveParametricCase', ], 'case': ws.case_name, 'route': ws.route, 'write_screen': 'off', 'write_log': 'on', 'save_settings': 'on', 'log_folder': output_folder + '/' + ws.case_name + '/', 'log_file': ws.case_name + '.log'} ws.config['BeamLoader'] = { 'unsteady': 'off', 'orientation': ws.quat} ws.config['AerogridLoader'] = { 'unsteady': 'off', 'aligned_grid': 'on', 'mstar': ws.Mstar_fact * ws.M, 'freestream_dir': ws.u_inf_direction, 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction, 'dt': ws.dt}} ws.config['StaticUvlm'] = { 'rho': ws.rho, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}, 'rollup_dt': ws.dt, 'print_info': 'on', 'horseshoe': 'on', 'num_cores': 4, 'n_rollup': 0, 'rollup_aic_refresh': 0, 'vortex_radius': 1e-9, 'rollup_tolerance': 1e-4} settings = dict() settings['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 200, 'num_load_steps': 5, 'delta_curved': 1e-6, 'min_delta': 1e-8, 'gravity_on': gravity_on, 'gravity': 9.81} ws.config['StaticCoupled'] = { 'print_info': 'on', 'max_iter': 200, 'n_load_steps': 4, 'tolerance': 1e-5, 'relaxation_factor': 0.1, 'aero_solver': 'StaticUvlm', 'aero_solver_settings': { 'rho': ws.rho, 'print_info': 'off', 'horseshoe': 'on', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': ws.dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}, 'vortex_radius': 1e-9}, 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': settings['NonLinearStatic']} ws.config['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0} ws.config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': ws.case_name + '_aeroforces.csv', 'screen_output': 'on'} ws.config['BeamPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on'} ws.config['WriteVariablesTime'] = {'structure_variables': ['pos'], 'structure_nodes': list(range(0, ws.num_node_surf)), 'cleanup_old_solution': 'on'} ws.config['Modal'] = {'NumLambda': 20, 'rigid_body_modes': 'off', 'print_matrices': 'on', 'save_data': 'off', 'rigid_modes_cg': 'off', 'continuous_eigenvalues': 'off', 'dt': 0, 'plot_eigenvalues': False, 'max_rotation_deg': 15., 'max_displacement': 0.15, 'write_modes_vtk': True, 'use_undamped_modes': True} ws.config['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', # 'modal_tstep': 0, 'linear_system_settings': { 'beam_settings': {'modal_projection': 'on', 'inout_coords': 'modes', 'discrete_time': 'on', 'newmark_damp': 0.5e-4, 'discr_method': 'newmark', 'dt': ws.dt, 'proj_modes': 'undamped', 'use_euler': 'off', 'num_modes': num_modes, 'print_info': 'off', 'gravity': gravity_on, 'remove_sym_modes': 'on', 'remove_dofs': []}, 'aero_settings': {'dt': ws.dt, # 'ScalingDict': {'length': 0.5 * ws.c_ref, # 'speed': u_inf, # 'density': rho}, 'integr_order': integration_order, 'density': ws.rho, 'remove_predictor': remove_predictor, 'use_sparse': use_sparse, 'rigid_body_motion': 'off', 'use_euler': 'off', 'remove_inputs': ['u_gust'], 'rom_method': ['Krylov'], 'rom_method_settings': {'Krylov': rom_settings}}, }} ws.config['AsymptoticStability'] = {'print_info': True, 'export_eigenvalues': 'on', 'target_system': ['aeroelastic', 'aerodynamic', 'structural'], # 'velocity_analysis': [160, 180, 20]} } ws.config['LinDynamicSim'] = {'dt': ws.dt, 'n_tsteps': ws.n_tstep, 'sys_id': 'LinearAeroelastic', 'postprocessors': ['BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'AerogridPlot': { 'u_inf': ws.u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}}} ws.config['FrequencyResponse'] = {'quick_plot': 'off', 'frequency_unit': 'w', 'frequency_bounds': [0.0001, 1.0], 'num_freqs': 100, 'frequency_spacing': 'log', 'target_system': ['aeroelastic', 'aerodynamic', 'structural'], } ws.config['SaveParametricCase'] = {'save_case':'off', 'parameters': {'u_inf': u_inf}} settings = dict() settings['NonLinearDynamicPrescribedStep'] = {'print_info': 'on', 'max_iterations': 950, 'delta_curved': 1e-2, 'min_delta': 1e-5, 'newmark_damp': 5e-2, 'gravity_on': 'on', 'gravity': 9.81, 'num_steps': ws.n_tstep, 'dt': ws.dt} settings['StepUvlm'] = {'print_info': 'on', 'num_cores': 4, 'convection_scheme': 2, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': ws.u_inf*1, 'u_inf_direction': [1., 0., 0.]}, 'rho': ws.rho, 'n_time_steps': ws.n_tstep, 'vortex_radius': 1e-9, 'dt': ws.dt, 'gamma_dot_filtering': 3} settings['DynamicCoupled'] = {'print_info': 'on', 'structural_substeps': 10, 'dynamic_relaxation': 'on', 'cleanup_previous_solution': 'on', 'structural_solver': 'NonLinearDynamicPrescribedStep', 'structural_solver_settings': settings['NonLinearDynamicPrescribedStep'], 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'fsi_substeps': 200, 'fsi_tolerance': 1e-6, 'relaxation_factor': ws.relaxation_factor, 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': 0.0, 'n_time_steps': 4, #ws.n_tstep, 'dt': ws.dt, 'include_unsteady_force_contribution': 'off', 'postprocessors': ['UDPout'], 'postprocessors_settings': {'BeamLoads': {'csv_output': 'off'}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}, 'StallCheck': {}, 'AerogridPlot': { 'u_inf': ws.u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, 'WriteVariablesTime': { 'structure_variables': ['pos', 'psi'], 'structure_nodes': [ws.num_node_surf - 1, ws.num_node_surf, ws.num_node_surf + 1]}, 'UDPout': {'variables_filename': cd + '/variables.yaml', 'output_network_settings': {'destination_address': ['127.0.0.1'], 'destination_ports': [65431]}, 'console_log_level': 'error', }, }} ws.config['DynamicCoupled'] = settings['DynamicCoupled'] ws.config.write() sharpy.sharpy_main.main(['', ws.route + ws.case_name + '.sharpy']) if __name__== '__main__': # u_inf = 1 # generate_pazi(u_inf) # from datetime import datetime # datetime object containing current date and time # u_inf_vec = np.array([46]) # u_inf_vec = np.linspace(28, 30, 50) # u_inf_vec = np.linspace(5, 30, 26) u_inf_vec = [50] # u_inf_vec = np.linspace(20, 60, 41) # u_inf_vec = np.linspace(1, 20, 20) # u_inf_vec = np.linspace(1, 60, 60) alpha = 1 gravity_on = True M = 4 N = 16 Ms = 1 # For comparison with Marc # M = 8 # N = 128 # Ms = 8 batch_log = 'batch_log_alpha{:04g}'.format(alpha*100) with open('./{:s}.txt'.format(batch_log), 'w') as f: # dd/mm/YY H:M:S now = datetime.now() dt_string = now.strftime("%d/%m/%Y %H:%M:%S") f.write('SHARPy launch - START\n') f.write("date and time = %s\n\n" % dt_string) for i, u_inf in enumerate(u_inf_vec): print('RUNNING SHARPY %f\n' % u_inf) case_name = 'pazi_uinf{:04g}_alpha{:04g}'.format(u_inf*10, alpha*100) try: generate_pazy_udp(u_inf, case_name, output_folder='/output/vortex_radius_M{:g}N{:g}Ms{:g}_alpha{:04g}/'.format(M, N, Ms, alpha * 100), cases_subfolder='/M{:g}N{:g}Ms{:g}/'.format(M, N, Ms), M=M, N=N, Ms=Ms, alpha=alpha, gravity_on=gravity_on) now = datetime.now() dt_string = now.strftime("%d/%m/%Y %H:%M:%S") with open('./{:s}.txt'.format(batch_log), 'a') as f: f.write('%s Ran case %i :::: u_inf = %f\n\n' % (dt_string, i, u_inf)) except AssertionError: now = datetime.now() dt_string = now.strftime("%d/%m/%Y %H:%M:%S") with open('./{:s}.txt'.format(batch_log), 'a') as f: f.write('%s ERROR RUNNING case %f\n\n' % (dt_string, u_inf)) ================================================ FILE: tests/io/sample_udp_inout/__init__.py ================================================ ================================================ FILE: tests/io/sample_udp_inout/client.py ================================================ import socket import select import time import logging import struct import sharpy.io.message_interface as message_interface import numpy as np """ This is not a test but is to be used as client when testing the development of the input output capabilities of sharpy. Run this script as client. Run ``python generate_pazy_test_io_local.py`` as server """ # sel = selectors.DefaultSelector() logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=20) logger = logging.getLogger(__name__) sharpy_incoming = ('127.0.0.1', 64011) # control side socket sharpy_outgoing = ('127.0.0.1', 64010) # output side socket own_control = ('127.0.0.1', 64000) own_receive = ('127.0.0.1', 64001) in_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) in_sock.bind(own_control) out_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) out_sock.bind(own_receive) # from https://stackoverflow.com/questions/2719017/how-to-set-timeout-on-pythons-socket-recv-method # ready_to_read = select.select([out_sock], [], [], 2) out_sock.settimeout(30) tsteps = 401 t = np.linspace(0, 0.2, tsteps) cs_deflection = 10 * np.sin(2 * t * np.pi / 0.2) * np.pi/180 # cs_deflection = np.linspace(0, ) # cs_deflection = np.array([0, 0, 90, 90, 90, 90]) * np.pi / 180 curr_ts = 0 wingtip_deflection = [] mid_wing_deflection = [] while True: if curr_ts > tsteps: break # send control input to sharpy ctrl_value = struct.pack('<5sif', b'RREF0', 0, cs_deflection[curr_ts]) logger.info('Sending control input of size {} bytes'.format(len(ctrl_value))) in_sock.sendto(ctrl_value, sharpy_incoming) logger.info('Sent control input to {}'.format(sharpy_incoming)) # time.sleep(2) # input('Continue loop') # receive output data. set msg_len to whatever length SHARPy is sending msg_len = 133 try: msg, conn = out_sock.recvfrom(msg_len) except socket.timeout: logger.info('Socket time out') break logger.info('Received {} data from {}'.format(msg, conn)) logger.info('Received data is {} bytes long'.format(len(msg))) # else: # break # decoding values = message_interface.decoder(msg) logger.info('Received {}'.format(values)) wingtip_deflection.append(values[0][1]) mid_wing_deflection.append(values[1][1]) # input('Next time step') curr_ts += 1 out_sock.close() in_sock.close() logger.info('Closed input and output sockets') ================================================ FILE: tests/io/sample_udp_inout/generate_pazy_test_io_local.py ================================================ import numpy as np import os import unittest import cases.templates.flying_wings as wings import sharpy.sharpy_main # Problem Set up def generate_pazy(u_inf, case_name, output_folder='/output/', cases_folder='', **kwargs): # u_inf = 60 alpha_deg = kwargs.get('alpha', 0.) rho = 1.225 num_modes = 16 gravity_on = kwargs.get('gravity_on', True) # Lattice Discretisation M = kwargs.get('M', 4) N = kwargs.get('N', 32) M_star_fact = kwargs.get('Ms', 10) # Linear UVLM settings integration_order = 2 remove_predictor = False use_sparse = True # ROM Properties rom_settings = dict() rom_settings['algorithm'] = 'mimo_rational_arnoldi' rom_settings['r'] = 5 rom_settings['single_side'] = 'observability' frequency_continuous_k = np.array([0.]) # Case Admin - Create results folders # case_name = 'goland_cs' # case_nlin_info = 'M%dN%dMs%d_nmodes%d' % (M, N, M_star_fact, num_modes) # case_rom_info = 'rom_MIMORA_r%d_sig%04d_%04dj' % (rom_settings['r'], frequency_continuous_k[-1].real * 100, # frequency_continuous_k[-1].imag * 100) # case_name += case_nlin_info #+ case_rom_info # os.makedirs(fig_folder, exist_ok=True) # SHARPy nonlinear reference solution ws = wings.PazyControlSurface(M=M, N=N, Mstar_fact=M_star_fact, u_inf=u_inf, alpha=alpha_deg, cs_deflection=[0, 0], rho=rho, sweep=0, physical_time=2.0, n_surfaces=2, route=cases_folder + '/' + case_name, case_name=case_name) ws.gust_intensity = 0.01 ws.sigma = 1 ws.control_surface_type = np.array([2]) ws.clean_test_files() ws.update_derived_params() ws.set_default_config_dict() ws.generate_aero_file() ws.generate_fem_file() frequency_continuous_w = 2 * u_inf * frequency_continuous_k / ws.c_ref rom_settings['frequency'] = frequency_continuous_w rom_settings['tangent_input_file'] = ws.route + '/' + ws.case_name + '.rom.h5' ws.config['SHARPy'] = { 'flow': ['BeamLoader', 'AerogridLoader', # 'NonLinearStatic', 'StaticUvlm', 'AerogridPlot', 'BeamPlot', 'WriteVariablesTime', 'DynamicCoupled', # 'Modal', # 'LinearAssembler', # 'FrequencyResponse', # 'AsymptoticStability', # 'SaveParametricCase', ], 'case': ws.case_name, 'route': ws.route, 'write_screen': 'off', 'write_log': 'on', 'save_settings': 'on', 'log_folder': output_folder + '/' + ws.case_name + '/', 'log_file': ws.case_name + '.log'} ws.config['BeamLoader'] = { 'unsteady': 'off', 'orientation': ws.quat} ws.config['AerogridLoader'] = { 'unsteady': 'off', 'aligned_grid': 'on', 'mstar': ws.Mstar_fact * ws.M, 'freestream_dir': ws.u_inf_direction, 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction, 'dt': ws.dt}} ws.config['StaticUvlm'] = { 'rho': ws.rho, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}, 'rollup_dt': ws.dt, 'print_info': 'on', 'horseshoe': 'off', 'num_cores': 4} settings = dict() settings['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 200, 'num_load_steps': 5, 'delta_curved': 1e-6, 'min_delta': 1e-8, 'gravity_on': gravity_on, 'gravity': 9.81} ws.config['StaticCoupled'] = { 'print_info': 'on', 'max_iter': 200, 'n_load_steps': 4, 'tolerance': 1e-5, 'relaxation_factor': 0.1, 'aero_solver': 'StaticUvlm', 'aero_solver_settings': { 'rho': ws.rho, 'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}, 'vortex_radius': 1e-9}, 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': settings['NonLinearStatic']} ws.config['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0} ws.config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': ws.case_name + '_aeroforces.csv', 'screen_output': 'on'} ws.config['BeamPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on'} ws.config['WriteVariablesTime'] = {'structure_variables': ['pos'], 'structure_nodes': list(range(0, ws.num_node_surf)), 'cleanup_old_solution': 'on'} ws.config['Modal'] = {'NumLambda': 20, 'rigid_body_modes': 'off', 'print_matrices': 'on', 'save_data': 'off', 'rigid_modes_cg': 'off', 'continuous_eigenvalues': 'off', 'dt': 0, 'plot_eigenvalues': False, 'max_rotation_deg': 15., 'max_displacement': 0.15, 'write_modes_vtk': True, 'use_undamped_modes': True} ws.config['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', # 'modal_tstep': 0, 'linear_system_settings': { 'beam_settings': {'modal_projection': 'on', 'inout_coords': 'modes', 'discrete_time': 'on', 'newmark_damp': 0.5e-4, 'discr_method': 'newmark', 'dt': ws.dt, 'proj_modes': 'undamped', 'use_euler': 'off', 'num_modes': num_modes, 'print_info': 'off', 'gravity': gravity_on, 'remove_sym_modes': 'on', 'remove_dofs': []}, 'aero_settings': {'dt': ws.dt, # 'ScalingDict': {'length': 0.5 * ws.c_ref, # 'speed': u_inf, # 'density': rho}, 'integr_order': integration_order, 'density': ws.rho, 'remove_predictor': remove_predictor, 'use_sparse': use_sparse, 'rigid_body_motion': 'off', 'use_euler': 'off', 'remove_inputs': ['u_gust'], 'rom_method': ['Krylov'], 'rom_method_settings': {'Krylov': rom_settings}}, }} ws.config['AsymptoticStability'] = {'print_info': True, 'export_eigenvalues': 'on', 'target_system': ['aeroelastic', 'aerodynamic', 'structural'], # 'velocity_analysis': [160, 180, 20]} } ws.config['LinDynamicSim'] = {'dt': ws.dt, 'n_tsteps': ws.n_tstep, 'sys_id': 'LinearAeroelastic', 'postprocessors': ['BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'AerogridPlot': { 'u_inf': ws.u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}}} ws.config['FrequencyResponse'] = {'quick_plot': 'off', 'frequency_unit': 'w', 'frequency_bounds': [0.0001, 1.0], 'num_freqs': 100, 'frequency_spacing': 'log', 'target_system': ['aeroelastic', 'aerodynamic', 'structural'], } ws.config['SaveParametricCase'] = {'save_case':'off', 'parameters': {'u_inf': u_inf}} settings = dict() settings['NonLinearDynamicPrescribedStep'] = {'print_info': 'on', 'max_iterations': 950, 'delta_curved': 1e-2, 'min_delta': 1e-5, 'newmark_damp': 5e-2, 'gravity_on': 'on', 'gravity': 9.81, 'num_steps': ws.n_tstep, 'dt': ws.dt} settings['StepUvlm'] = {'print_info': 'on', 'num_cores': 4, 'convection_scheme': 0, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': ws.u_inf*1, 'u_inf_direction': [1., 0., 0.]}, 'rho': ws.rho, 'n_time_steps': ws.n_tstep, 'vortex_radius': 1e-9, 'dt': ws.dt, 'gamma_dot_filtering': 3} settings['DynamicCoupled'] = {'print_info': 'on', 'structural_substeps': 0, 'dynamic_relaxation': 'on', 'cleanup_previous_solution': 'on', 'structural_solver': 'NonLinearDynamicPrescribedStep', 'structural_solver_settings': settings['NonLinearDynamicPrescribedStep'], 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'fsi_substeps': 200, 'fsi_tolerance': 1e-6, 'relaxation_factor': ws.relaxation_factor, 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': 0.0, 'n_time_steps': 5, #ws.n_tstep, 'dt': ws.dt, 'include_unsteady_force_contribution': 'on', 'steps_without_unsteady_force': 2, 'network_settings': {'variables_filename': './variables_coarse.yaml', 'send_output_to_all_clients': 'on', 'byte_ordering': 'little', 'received_data_filename': output_folder + './input.dat', 'log_name': output_folder + '/sharpy_network.log', 'file_log_level': 'debug', 'console_log_level': 'debug', 'input_network_settings': {'address': '127.0.0.1', 'port': 64011}, 'output_network_settings': {'send_on_demand': 'off', 'destination_address': ['127.0.0.1'], 'address': '127.0.0.1', 'port': 64010, 'destination_ports': [64001], }, }, 'postprocessors': ['AerogridPlot', 'BeamPlot', 'WriteVariablesTime'], 'postprocessors_settings': {'BeamLoads': {'csv_output': 'off'}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}, 'StallCheck': {}, 'AerogridPlot': { 'u_inf': ws.u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, 'WriteVariablesTime': { 'structure_variables': ['pos', 'psi'], 'structure_nodes': [ws.num_node_surf - 1, ws.num_node_surf, ws.num_node_surf + 1]}, }} ws.config['DynamicCoupled'] = settings['DynamicCoupled'] ws.config.write() sharpy.sharpy_main.main(['', ws.route + ws.case_name + '.sharpy']) if __name__== '__main__': u_inf = 50 alpha = 4 gravity_on = True M = 4 N = 32 Ms = 4 case_name = 'pazy_uinf{:04g}_alpha{:04g}'.format(u_inf*10, alpha*100) generate_pazy(u_inf, case_name, output_folder='./output/test_65001_wake3_M{:g}N{:g}Ms{:g}_alpha{:04g}/'.format(M, N, Ms, alpha*100), cases_folder='./cases/test_M{:g}N{:g}Ms{:g}wake3/'.format(M, N, Ms), M=M, N=N, Ms=Ms, alpha=alpha, gravity_on=gravity_on) ================================================ FILE: tests/io/sample_udp_inout/variables_coarse.yaml ================================================ --- - name: 'control_surface_deflection' var_type: 'control_surface' inout: 'in' position: 0 - name: 'dt' inout: 'out' - name: 'nt' inout: 'out' - name: 'pos_dot' var_type: 'node' inout: 'out' position: 4 index: 0 - name: 'pos_dot' inout: 'out' position: 4 index: 1 var_type: 'node' - name: 'pos_dot' inout: 'out' position: 4 index: 2 var_type: 'node' - name: 'pos_dot' var_type: 'node' inout: 'out' position: 8 index: 0 - name: 'pos_dot' inout: 'out' position: 8 index: 1 var_type: 'node' - name: 'pos_dot' inout: 'out' position: 8 index: 2 var_type: 'node' - name: 'pos_dot' var_type: 'node' inout: 'out' position: 16 index: 0 - name: 'pos_dot' inout: 'out' position: 16 index: 1 var_type: 'node' - name: 'pos_dot' inout: 'out' position: 16 index: 2 var_type: 'node' - name: 'pos_dot' var_type: 'node' inout: 'out' position: 20 index: 0 - name: 'pos_dot' inout: 'out' position: 20 index: 1 var_type: 'node' - name: 'pos_dot' inout: 'out' position: 20 index: 2 var_type: 'node' - name: 'pos' inout: 'out' position: 20 index: 2 var_type: 'node' - name: 'psi' inout: 'out' position: 20 index: 2 var_type: 'node' ... ================================================ FILE: tests/io/test_pazy_udpout.py ================================================ import unittest import tests.io.generate_pazy_udpout as gp import os import shutil class TestPazyCoupledStatic(unittest.TestCase): """ Test Pazy wing static coupled case and compare against a benchmark result. As of the time of writing, benchmark result has not been verified but it serves as a backward compatibility check for code improvements. """ route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) def test_dynamic_aoa(self): u_inf = 50 alpha = 0 case_name = 'pazy_uinf{:04g}_alpha{:04g}'.format(u_inf * 10, alpha * 10) M = 4 N = 16 Msf = 1 cases_folder = self.route_test_dir + '/cases/' output_folder = self.route_test_dir + '/cases/' # run case gp.generate_pazy_udp(u_inf, case_name, output_folder, cases_folder, alpha=alpha, M=M, N=N, Msf=Msf, cd=self.route_test_dir) def tearDown(self): cases_folder = self.route_test_dir + '/cases/' if os.path.isdir(cases_folder): shutil.rmtree(cases_folder) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/io/variables.yaml ================================================ --- - name: 'control_surface_deflection' var_type: 'control_surface' inout: 'in' position: 0 - name: 'pos' var_type: 'node' inout: 'out' position: 5 index: 2 - name: 'pos' inout: 'out' position: 16 index: 2 var_type: 'node' - name: 'psi_dot' inout: 'out' position: 16 index: 2 var_type: 'node' ... ================================================ FILE: tests/linear/__init__.py ================================================ ================================================ FILE: tests/linear/assembly/__init__.py ================================================ ================================================ FILE: tests/linear/assembly/test_assembly.py ================================================ """ Test assembly S. Maraniello, 29 May 2018 """ import os import copy import warnings import unittest import itertools import numpy as np import scipy.linalg as scalg import sharpy.utils.h5utils as h5utils import sharpy.linear.src.assembly as assembly import sharpy.linear.src.multisurfaces as multisurfaces import sharpy.linear.src.surface as surface import sharpy.utils.algebra as algebra import sharpy.utils.cout_utils as cout import sharpy.sharpy_main np.set_printoptions(linewidth=200, precision=3) vortex_radius = 1e-4 def max_error_tensor(Pder_an, Pder_num): """ Finds the maximum error analytical derivatives Pder_an. The error is: - relative, if the element of Pder_an is nonzero - absolute, otherwise The function returns the absolute and relative error tensors, and the maximum error. @warning: The relative error tensor may contain NaN or Inf if the analytical derivative is zero. These elements are filtered out during the search for maximum error, and absolute error is checked. """ Eabs = np.abs(Pder_num - Pder_an) nnzvec = Pder_an != 0 Erel = np.zeros(Pder_an.shape) Erel[nnzvec] = np.abs(Eabs[nnzvec] / Pder_an[nnzvec]) # Relative error check: remove NaN and inf... iifinite = np.isfinite(Erel) err_max = 0.0 for err_here in Erel[iifinite]: if np.abs(err_here) > err_max: err_max = err_here # Zero elements check iizero = np.abs(Pder_an) < 1e-15 for der_here in Pder_num[iizero]: if np.abs(der_here) > err_max: err_max = der_here return err_max, Eabs, Erel class Test_assembly(unittest.TestCase): """ Test methods into assembly module """ print_info = False # useful for debugging. Leave False to keep test log clean def setUp(self): # select test case fname = os.path.dirname(os.path.abspath(__file__)) + '/h5input/goland_mod_Nsurf01_M003_N004_a040.aero_state.h5' haero = h5utils.readh5(fname) tsdata = haero.ts00000 # # Rotating cases # fname = './basic_rotating_wing/basic_wing.data.h5' # haero = h5utils.readh5(fname) # tsdata = haero.data.aero.timestep_info[-1] # tsdata.omega = [] # for ss in range(haero.data.aero.n_surf): # tsdata.omega.append(haero.data.structure.timestep_info[-1].for_vel[3:6]) MS = multisurfaces.MultiAeroGridSurfaces(tsdata, vortex_radius) MS.get_normal_ind_velocities_at_collocation_points() MS.verify_non_penetration(print_info=self.print_info) MS.verify_aic_coll(print_info=self.print_info) MS.get_joukovski_qs() MS.verify_joukovski_qs(print_info=self.print_info) self.MS = MS def test_nc_dqcdzeta(self): """ For each output surface, where induced velocity is computed, all other surfaces are looped. For wakes, only TE is displaced. """ if self.print_info: print('----------------------------- Testing assembly.test_nc_dqcdzeta') MS = self.MS n_surf = MS.n_surf # analytical Dercoll_list, Dervert_list = assembly.nc_dqcdzeta(MS.Surfs, MS.Surfs_star) # check option Der_all_exp = np.block(Dervert_list) + scalg.block_diag(*Dercoll_list) Der_all = np.block(assembly.nc_dqcdzeta(MS.Surfs, MS.Surfs_star, Merge=True)) _, ErAbs, ErRel = max_error_tensor(Der_all, Der_all_exp) # max absolute error ermax = np.max(ErAbs) # relative error at max abs error point iimax = np.unravel_index(np.argmax(ErAbs), ErAbs.shape) ermax_rel = ErRel[iimax] assert ermax_rel < 1e-16, \ 'option Merge=True not working correctly, relative error (%.3e) too high!' % ErRel # allocate numerical Derlist_num = [] for ii in range(n_surf): sub = [] for jj in range(n_surf): sub.append(0.0 * Dervert_list[ii][jj]) Derlist_num.append(sub) # store reference circulation and normal induced velocities MS.get_normal_ind_velocities_at_collocation_points() Zeta0 = [] Zeta0_star = [] Vind0 = [] N0 = [] ZetaC0 = [] for ss in range(n_surf): Zeta0.append(MS.Surfs[ss].zeta.copy()) ZetaC0.append(MS.Surfs[ss].zetac.copy('F')) Zeta0_star.append(MS.Surfs_star[ss].zeta.copy()) Vind0.append(MS.Surfs[ss].u_ind_coll_norm.copy()) N0.append(MS.Surfs[ss].normals.copy()) # calculate vis FDs Steps = [1e-6, ] step = Steps[0] ### loop input surfs for ss_in in range(n_surf): Surf_in = MS.Surfs[ss_in] Surf_star_in = MS.Surfs_star[ss_in] M_in, N_in = Surf_in.maps.M, Surf_in.maps.N # perturb for kk in range(3 * Surf_in.maps.Kzeta): cc, mm, nn = np.unravel_index(kk, (3, M_in + 1, N_in + 1)) # perturb bound. vertices and collocation Surf_in.zeta = Zeta0[ss_in].copy() Surf_in.zeta[cc, mm, nn] += step Surf_in.generate_collocations() # perturb wake TE if mm == M_in: Surf_star_in.zeta = Zeta0_star[ss_in].copy() Surf_star_in.zeta[cc, 0, nn] += step ### prepare output surfaces # - ensure normals are unchanged # - del ind. vel on output to ensure they are re-computed for ss_out in range(n_surf): Surf_out = MS.Surfs[ss_out] Surf_out.normals = N0[ss_out].copy() del Surf_out.u_ind_coll_norm try: del Surf_out.u_ind_coll except AttributeError: pass ### recalculate MS.get_normal_ind_velocities_at_collocation_points() # restore Surf_in.zeta = Zeta0[ss_in].copy() Surf_in.zetac = ZetaC0[ss_in].copy('F') Surf_star_in.zeta = Zeta0_star[ss_in].copy() # estimate derivatives for ss_out in range(n_surf): Surf_out = MS.Surfs[ss_out] dvind = (Surf_out.u_ind_coll_norm - Vind0[ss_out]) / step Derlist_num[ss_out][ss_in][:, kk] = dvind.reshape(-1, order='C') ### check error for ss_out in range(n_surf): for ss_in in range(n_surf): Der_an = Dervert_list[ss_out][ss_in].copy() if ss_in == ss_out: Der_an = Der_an + Dercoll_list[ss_out] Der_num = Derlist_num[ss_out][ss_in] _, ErAbs, ErRel = max_error_tensor(Der_an, Der_num) # max absolute error ermax = np.max(ErAbs) # relative error at max abs error point iimax = np.unravel_index(np.argmax(ErAbs), ErAbs.shape) ermax_rel = ErRel[iimax] if self.print_info: print('Bound%.2d->Bound%.2d\tFDstep\tErrAbs\tErrRel' % (ss_in, ss_out)) print('\t\t\t%.1e\t%.1e\t%.1e' % (step, ermax, ermax_rel)) assert ermax < 50 * step and ermax_rel < 50 * step, embed() # 'Test failed!' # fig=plt.figure('Spy Er vs coll derivs',figsize=(12,4)) # ax1=fig.add_subplot(131) # ax1.spy(ErAbs,precision=1e2*step) # ax1.set_title('error abs %d to %d' %(ss_in,ss_out)) # ax2=fig.add_subplot(132) # ax2.spy(ErRel,precision=1e2*step) # ax2.set_title('error rel %d to %d' %(ss_in,ss_out)) # ax3=fig.add_subplot(133) # ax3.spy(Dercoll_list[ss_out],precision=50*step) # ax3.set_title('Dcoll an. %d to %d' %(ss_out,ss_out)) # #plt.show() # plt.close() def test_uc_dncdzeta(self, PlotFlag=False): if self.print_info: print('---------------------------------- Testing assembly.uc_dncdzeta') MS = self.MS n_surf = MS.n_surf MS.get_ind_velocities_at_collocation_points() MS.get_normal_ind_velocities_at_collocation_points() for ss in range(n_surf): if self.print_info: print('Surface %.2d:' % ss) Surf = MS.Surfs[ss] # generate non-zero field of external force Surf.u_ext[0, :, :] = Surf.u_ext[0, :, :] - 20.0 Surf.u_ext[1, :, :] = Surf.u_ext[1, :, :] + 60.0 Surf.u_ext[2, :, :] = Surf.u_ext[2, :, :] + 30.0 Surf.u_ext = Surf.u_ext + np.random.rand(*Surf.u_ext.shape) ### analytical derivative # ind velocities computed already Surf.get_input_velocities_at_collocation_points() Der = assembly.uc_dncdzeta(Surf) ### numerical derivative # Surf.get_normal_input_velocities_at_collocation_points() u_tot0 = Surf.u_ind_coll + Surf.u_input_coll u_norm0 = Surf.project_coll_to_normal(u_tot0) u_norm0_vec = u_norm0.reshape(-1, order='C') zeta0 = Surf.zeta DerNum = np.zeros(Der.shape) Steps = np.array([1e-2, 1e-3, 1e-4, 1e-5, 1e-6]) Er_max = 0.0 * Steps for ss in range(len(Steps)): step = Steps[ss] for jj in range(3 * Surf.maps.Kzeta): # perturb cc_pert = Surf.maps.ind_3d_vert_vect[0][jj] mm_pert = Surf.maps.ind_3d_vert_vect[1][jj] nn_pert = Surf.maps.ind_3d_vert_vect[2][jj] zeta_pert = zeta0.copy() zeta_pert[cc_pert, mm_pert, nn_pert] += step # calculate new normal velocity Surf_pert = surface.AeroGridSurface(Surf.maps, zeta=zeta_pert, u_ext=Surf.u_ext, gamma=Surf.gamma, vortex_radius=vortex_radius) u_norm = Surf_pert.project_coll_to_normal(u_tot0) u_norm_vec = u_norm.reshape(-1, order='C') # FD derivative DerNum[:, jj] = (u_norm_vec - u_norm0_vec) / step er_max = np.max(np.abs(Der - DerNum)) if self.print_info: print('FD step: %.2e ---> Max error: %.2e' % (step, er_max)) assert er_max < 5e1 * step, 'Error larger than 50 times step size' Er_max[ss] = er_max # assert error decreases with step size for ss in range(1, len(Steps)): assert Er_max[ss] < Er_max[ss - 1], \ 'Error not decreasing as FD step size is reduced' if self.print_info: print('------------------------------------------------------------ OK') if PlotFlag: pass # fig = plt.figure('Spy Der',figsize=(10,4)) # ax1 = fig.add_subplot(121) # ax1.spy(Der,precision=step) # ax2 = fig.add_subplot(122) # ax2.spy(DerNum,precision=step) # plt.show() def test_nc_domegazetadzeta(self): """ Variation at colocation points due to geometrical variations at vertices Needs to be tested with a case that actually rotates """ if self.print_info: print('----------------------------- Testing assembly.test_nc_domegazetadzeta') MS = self.MS n_surf = MS.n_surf # analytical Dervert_list = assembly.nc_domegazetadzeta(MS.Surfs, MS.Surfs_star) # allocate numerical # Derlist_num=[] # for ii in range(n_surf): # sub=[] # for jj in range(n_surf): # sub.append(0.0*Dervert_list[ii][jj]) # Derlist_num.append(sub) # Store the initial values of the variabes Zeta0 = [] Zeta0_star = [] N0 = [] ZetaC0 = [] for ss in range(n_surf): Zeta0.append(MS.Surfs[ss].zeta.copy()) ZetaC0.append(MS.Surfs[ss].zetac.copy('F')) Zeta0_star.append(MS.Surfs_star[ss].zeta.copy()) N0.append(MS.Surfs[ss].normals.copy()) # Computation Steps = [1e-2, 1e-4, 1e-6] nsteps = len(Steps) error = np.zeros((nsteps,)) for istep in range(nsteps): step = Steps[istep] for ss in range(n_surf): Surf = MS.Surfs[ss] Surf_star = MS.Surfs_star[ss] M, N = Surf.maps.M, Surf.maps.N perturb_vector = np.zeros(3 * Surf.maps.Kzeta) # PERTURBATION OF THE SURFACE for kk in range(3 * Surf.maps.Kzeta): # generate a random perturbation between the 90% and the 110% of the step perturb_vector[kk] += step * (0.2 * np.random.rand() + 0.9) cc, mm, nn = np.unravel_index(kk, (3, M + 1, N + 1)) # perturb bound. vertices and collocation Surf.zeta = Zeta0[ss].copy() Surf.zeta[cc, mm, nn] += perturb_vector[kk] # perturb wake TE if mm == M: Surf_star.zeta = Zeta0_star[ss].copy() Surf_star.zeta[cc, 0, nn] += perturb_vector[kk] Surf.generate_collocations() # COMPUTE THE DERIVATIVES Der_an = np.zeros(Surf.maps.K) Der_an = np.dot(Dervert_list[ss], perturb_vector) Der_num = np.zeros(Surf.maps.K) ipanel = 0 skew_omega = algebra.skew(Surf.omega) for mm in range(M): for nn in range(N): Der_num[ipanel] = (np.dot(N0[ss][:, mm, nn], np.dot(skew_omega, ZetaC0[ss][:, mm, nn])) - np.dot(N0[ss][:, mm, nn], np.dot(skew_omega, Surf.zetac[:, mm, nn]))) ipanel += 1 # COMPUTE THE ERROR error[istep] = np.maximum(error[istep], np.absolute(Der_num - Der_an).max()) if self.print_info: print('FD step: %.2e ---> Max error: %.2e' % (step, error[istep])) assert error[istep] < 5e1 * step, 'Error larger than 50 times the step size' if istep > 0: assert error[istep] <= error[istep - 1], \ 'Error not decreasing as FD step size is reduced' if self.print_info: print('------------------------------------------------------------ OK') def test_dfqsdgamma_vrel0(self): if self.print_info: print('----------------------------- Testing assembly.dfqsdgamma_vrel0') MS = self.MS n_surf = MS.n_surf Der_list, Der_star_list = assembly.dfqsdgamma_vrel0(MS.Surfs, MS.Surfs_star) Er_max = [] Er_max_star = [] Steps = [1e-2, 1e-4, 1e-6, ] for ss in range(n_surf): Der_an = Der_list[ss] Der_star_an = Der_star_list[ss] Surf = MS.Surfs[ss] Surf_star = MS.Surfs_star[ss] M, N = Surf.maps.M, Surf.maps.N K = Surf.maps.K fqs0 = Surf.fqs.copy() gamma0 = Surf.gamma.copy() for step in Steps: Der_num = 0.0 * Der_an Der_star_num = 0.0 * Der_star_an ### Bound for pp in range(K): mm = Surf.maps.ind_2d_pan_scal[0][pp] nn = Surf.maps.ind_2d_pan_scal[1][pp] Surf.gamma = gamma0.copy() Surf.gamma[mm, nn] += step Surf.get_joukovski_qs(gammaw_TE=Surf_star.gamma[0, :]) df = (Surf.fqs - fqs0) / step Der_num[:, pp] = df.reshape(-1, order='C') er_max = np.max(np.abs(Der_an - Der_num)) if self.print_info: print('Surface %.2d - bound:' % ss) print('FD step: %.2e ---> Max error: %.2e' % (step, er_max)) assert er_max < 5e1 * step, 'Error larger than 50 times step size' Er_max.append(er_max) ### Wake Surf.gamma = gamma0.copy() gammaw_TE0 = Surf_star.gamma[0, :].copy() M_star, N_star = Surf_star.maps.M, Surf_star.maps.N K_star = Surf_star.maps.K for nn in range(N): pp = np.ravel_multi_index((0, nn), (M_star, N_star)) gammaw_TE = gammaw_TE0.copy() gammaw_TE[nn] += step Surf.get_joukovski_qs(gammaw_TE=gammaw_TE) df = (Surf.fqs - fqs0) / step Der_star_num[:, pp] = df.reshape(-1, order='C') er_max = np.max(np.abs(Der_star_an - Der_star_num)) if self.print_info: print('Surface %.2d - wake:' % ss) print('FD step: %.2e ---> Max error: %.2e' % (step, er_max)) assert er_max < 5e1 * step, 'Error larger than 50 times step size' Er_max_star.append(er_max) Surf.gamma = gamma0.copy() ### Warning: this test fails: the dependency on gamma is linear, hence # great accuracy is obtained even with large steps. In fact, reducing # the step quickly introduced round-off error. # # assert error decreases with step size # for ii in range(1,len(Steps)): # assert Er_max[ii] Max error: %.2e' % (step, er_max)) assert er_max < 5e1 * step, 'Error larger than 50 times step size' Er_max.append(er_max) def test_dfqsdzeta_omega(self): """ Note: the get_joukovski_qs method re-computes the induced velocity at the panel segments. A copy of Surf is required to ensure that other tests are not affected. Needs to be tested with a case that actually rotates """ if self.print_info: print('------------------------------ Testing assembly.dfqsdzeta_omega') # rename MS = self.MS n_surf = MS.n_surf # Compute the anaytical derivative of the case Der_an_list = assembly.dfqsdzeta_omega(MS.Surfs, MS.Surfs_star) # Initialize Er_max = [] # Define steps to run Steps = [1e-2, 1e-4, 1e-6, ] for ss in range(n_surf): # Select the surface with the analytica derivatives Der_an = Der_an_list[ss] # Copy to avoid modifying the original for other tests Surf = copy.deepcopy(MS.Surfs[ss]) # Define variables M, N = Surf.maps.M, Surf.maps.N K = Surf.maps.K Kzeta = Surf.maps.Kzeta # Save the reference values at equilibrium fqs0 = Surf.fqs.copy() zeta0 = Surf.zeta.copy() u_input_seg0 = Surf.u_input_seg.copy() for step in Steps: # Initialize Der_num = 0.0 * Der_an # Loop through the different grid modifications (three directions per vertex point) for kk in range(3 * Kzeta): # Initialize to remove previous movements Surf.zeta = zeta0.copy() # Define DoFs where modifications will take place and modify the grid ind_3d = np.unravel_index(kk, (3, M + 1, N + 1)) Surf.zeta[ind_3d] += step # Recompute get_ind_velocities_at_segments and recover the previous grid Surf.get_input_velocities_at_segments() Surf.zeta = zeta0.copy() # Compute new forces Surf.get_joukovski_qs(gammaw_TE=MS.Surfs_star[ss].gamma[0, :]) df = (Surf.fqs - fqs0) / step Der_num[:, kk] = df.reshape(-1, order='C') er_max = np.max(np.abs(Der_an - Der_num)) if self.print_info: print('Surface %.2d - bound:' % ss) print('FD step: %.2e ---> Max error: %.2e' % (step, er_max)) assert er_max < 5e1 * step, 'Error larger than 50 times step size' Er_max.append(er_max) def test_dfqsduinput(self): """ Step change in input velocity is allocated to both u_ext and zeta_dot """ if self.print_info: print('---------------------------------- Testing assembly.dfqsduinput') MS = self.MS n_surf = MS.n_surf Der_list = assembly.dfqsduinput(MS.Surfs, MS.Surfs_star) Er_max = [] Steps = [1e-2, 1e-4, 1e-6, ] for ss in range(n_surf): Der_an = Der_list[ss] # Surf=copy.deepcopy(MS.Surfs[ss]) Surf = MS.Surfs[ss] # Surf_star=MS.Surfs_star[ss] M, N = Surf.maps.M, Surf.maps.N K = Surf.maps.K Kzeta = Surf.maps.Kzeta fqs0 = Surf.fqs.copy() u_ext0 = Surf.u_ext.copy() zeta_dot0 = Surf.zeta_dot.copy() for step in Steps: Der_num = 0.0 * Der_an for kk in range(3 * Kzeta): Surf.u_ext = u_ext0.copy() Surf.zeta_dot = zeta_dot0.copy() ind_3d = np.unravel_index(kk, (3, M + 1, N + 1)) Surf.u_ext[ind_3d] += 0.5 * step Surf.zeta_dot[ind_3d] += -0.5 * step Surf.get_input_velocities_at_segments() Surf.get_joukovski_qs(gammaw_TE=MS.Surfs_star[ss].gamma[0, :]) df = (Surf.fqs - fqs0) / step Der_num[:, kk] = df.reshape(-1, order='C') er_max = np.max(np.abs(Der_an - Der_num)) if self.print_info: print('Surface %.2d - bound:' % ss) print('FD step: %.2e ---> Max error: %.2e' % (step, er_max)) assert er_max < 5e1 * step, 'Error larger than 50 times step size' Er_max.append(er_max) def test_dfqsdvind_gamma(self): if self.print_info: print('------------------------------ Testing assembly.dfqsdvind_gamma') MS = self.MS n_surf = MS.n_surf # analytical Der_list, Der_star_list = assembly.dfqsdvind_gamma(MS.Surfs, MS.Surfs_star) # allocate numerical Der_list_num = [] Der_star_list_num = [] for ii in range(n_surf): sub = [] sub_star = [] for jj in range(n_surf): sub.append(0.0 * Der_list[ii][jj]) sub_star.append(0.0 * Der_star_list[ii][jj]) Der_list_num.append(sub) Der_star_list_num.append(sub_star) # store reference circulation and force Gamma0 = [] Gammaw0 = [] Fqs0 = [] for ss in range(n_surf): Gamma0.append(MS.Surfs[ss].gamma.copy()) Gammaw0.append(MS.Surfs_star[ss].gamma.copy()) Fqs0.append(MS.Surfs[ss].fqs.copy()) # calculate vis FDs # Steps=[1e-2,1e-4,1e-6,] Steps = [1e-5, ] step = Steps[0] ###### bound for ss_in in range(n_surf): Surf_in = MS.Surfs[ss_in] # perturb for pp in range(Surf_in.maps.K): mm = Surf_in.maps.ind_2d_pan_scal[0][pp] nn = Surf_in.maps.ind_2d_pan_scal[1][pp] Surf_in.gamma = Gamma0[ss_in].copy() Surf_in.gamma[mm, nn] += step # recalculate induced velocity everywhere MS.get_ind_velocities_at_segments(overwrite=True) # restore circulation: (include only induced velocity contrib.) Surf_in.gamma = Gamma0[ss_in].copy() # estimate derivatives for ss_out in range(n_surf): Surf_out = MS.Surfs[ss_out] fqs0 = Fqs0[ss_out].copy() Surf_out.get_joukovski_qs( gammaw_TE=MS.Surfs_star[ss_out].gamma[0, :]) df = (Surf_out.fqs - fqs0) / step Der_list_num[ss_out][ss_in][:, pp] = df.reshape(-1, order='C') ###### wake for ss_in in range(n_surf): Surf_in = MS.Surfs_star[ss_in] # perturb for pp in range(Surf_in.maps.K): mm = Surf_in.maps.ind_2d_pan_scal[0][pp] nn = Surf_in.maps.ind_2d_pan_scal[1][pp] Surf_in.gamma = Gammaw0[ss_in].copy() Surf_in.gamma[mm, nn] += step # recalculate induced velocity everywhere MS.get_ind_velocities_at_segments(overwrite=True) # restore circulation: (include only induced velocity contrib.) Surf_in.gamma = Gammaw0[ss_in].copy() # estimate derivatives for ss_out in range(n_surf): Surf_out = MS.Surfs[ss_out] fqs0 = Fqs0[ss_out].copy() Surf_out.get_joukovski_qs( gammaw_TE=MS.Surfs_star[ss_out].gamma[0, :]) # <--- gammaw_0 needs to be used here! df = (Surf_out.fqs - fqs0) / step Der_star_list_num[ss_out][ss_in][:, pp] = df.reshape(-1, order='C') ### check error Er_max = [] Er_max_star = [] for ss_out in range(n_surf): for ss_in in range(n_surf): Der_an = Der_list[ss_out][ss_in] Der_num = Der_list_num[ss_out][ss_in] ErMat = Der_an - Der_num ermax = np.max(np.abs(ErMat)) if self.print_info: print('Bound%.2d->Bound%.2d\tFDstep\tError' % (ss_in, ss_out)) print('\t\t\t%.1e\t%.1e' % (step, ermax)) assert ermax < 50 * step, 'Test failed!' Der_an = Der_star_list[ss_out][ss_in] Der_num = Der_star_list_num[ss_out][ss_in] ErMat = Der_an - Der_num ermax = np.max(np.abs(ErMat)) if self.print_info: print('Wake%.2d->Bound%.2d\tFDstep\tError' % (ss_in, ss_out)) print('\t\t\t%.1e\t%.1e' % (step, ermax)) assert ermax < 50 * step, 'Test failed!' # fig = plt.figure('Spy Der',figsize=(10,4)) # ax1 = fig.add_subplot(111) # ax1.spy(ErMat,precision=50*step) # plt.show() def test_dvinddzeta(self): """ For each output surface, there induced velocity is computed, all other surfaces are looped. For wakes, only TE is displaced. """ def comp_vind(zetac, MS): # comute induced velocity V = np.zeros((3,)) for ss in range(n_surf): Surf_in = MS.Surfs[ss] Surf_star_in = MS.Surfs_star[ss] V += Surf_in.get_induced_velocity(zetac) V += Surf_star_in.get_induced_velocity(zetac) return V if self.print_info: print('----------------------------------- Testing assembly.dvinddzeta') MS = self.MS n_surf = MS.n_surf zetac = .5 * (MS.Surfs[0].zeta[:, 1, 2] + MS.Surfs[0].zeta[:, 1, 3]) Dercoll = np.zeros((3, 3)) Dervert_list = [] for ss_in in range(n_surf): dcoll_b, dvert_b = assembly.dvinddzeta(zetac, MS.Surfs[ss_in], IsBound=True) dcoll_w, dvert_w = assembly.dvinddzeta(zetac, MS.Surfs_star[ss_in], IsBound=False, M_in_bound=MS.Surfs[ss_in].maps.M) Dercoll += dcoll_b + dcoll_w Dervert_list.append(dvert_b + dvert_w) # allocate numerical Dercoll_num = np.zeros((3, 3)) Dervert_list_num = [] for ii in range(n_surf): Dervert_list_num.append(0.0 * Dervert_list[ii]) # store reference grid Zeta0 = [] Zeta0_star = [] for ss in range(n_surf): Zeta0.append(MS.Surfs[ss].zeta.copy()) Zeta0_star.append(MS.Surfs_star[ss].zeta.copy()) V0 = comp_vind(zetac, MS) # calculate vis FDs # Steps=[1e-2,1e-4,1e-6,] Steps = [1e-6, ] step = Steps[0] ### vertices for ss_in in range(n_surf): Surf_in = MS.Surfs[ss_in] Surf_star_in = MS.Surfs_star[ss_in] M_in, N_in = Surf_in.maps.M, Surf_in.maps.N # perturb for kk in range(3 * Surf_in.maps.Kzeta): cc, mm, nn = np.unravel_index(kk, (3, M_in + 1, N_in + 1)) # perturb bound Surf_in.zeta = Zeta0[ss_in].copy() Surf_in.zeta[cc, mm, nn] += step # perturb wake TE if mm == M_in: Surf_star_in.zeta = Zeta0_star[ss_in].copy() Surf_star_in.zeta[cc, 0, nn] += step # recalculate induced velocity everywhere Vnum = comp_vind(zetac, MS) dv = (Vnum - V0) / step Dervert_list_num[ss_in][:, kk] = dv.reshape(-1, order='C') # restore Surf_in.zeta = Zeta0[ss_in].copy() if mm == M_in: Surf_star_in.zeta = Zeta0_star[ss_in].copy() ### check error at colloc Dercoll_num = np.zeros((3, 3)) for cc in range(3): zetac_pert = zetac.copy() zetac_pert[cc] += step Vnum = comp_vind(zetac_pert, MS) Dercoll_num[:, cc] = (Vnum - V0) / step ercoll = np.max(np.abs(Dercoll - Dercoll_num)) if self.print_info: print('Error coll.\tFDstep\tErrAbs') print('\t\t%.1e\t%.1e' % (step, ercoll)) # if ercoll>10*step: embed() assert ercoll < 10 * step, 'Error at collocation point' ### check error at vert for ss_in in range(n_surf): Der_an = Dervert_list[ss_in] Der_num = Dervert_list_num[ss_in] ermax, ErAbs, ErRel = max_error_tensor(Der_an, Der_num) # max absolute error ermax = np.max(ErAbs) # relative error at max abs error point iimax = np.unravel_index(np.argmax(ErAbs), ErAbs.shape) ermax_rel = ErRel[iimax] if self.print_info: print('Bound and wake%.2d\tFDstep\tErrAbs\tErrRel' % ss_in) print('\t\t\t%.1e\t%.1e\t%.1e' % (step, ermax, ermax_rel)) assert ercoll < 10 * step, 'Error at vertices' # fig=plt.figure('Spy Er vs coll derivs',figsize=(12,4)) # ax1=fig.add_subplot(121) # ax1.spy(ErAbs,precision=1e2*step) # ax1.set_title('error abs %d' %(ss_in)) # ax2=fig.add_subplot(122) # ax2.spy(ErRel,precision=1e2*step) # ax2.set_title('error rel %d' %(ss_in)) # #plt.show() # plt.close() def test_dfqsdvind_zeta(self): """ For each output surface, there induced velocity is computed, all other surfaces are looped. For wakes, only TE is displaced. """ if self.print_info: print('------------------------------- Testing assembly.dfqsdvind_zeta') MS = self.MS n_surf = MS.n_surf # analytical Dercoll_list, Dervert_list = assembly.dfqsdvind_zeta(MS.Surfs, MS.Surfs_star) # allocate numerical Derlist_num = [] for ii in range(n_surf): sub = [] for jj in range(n_surf): sub.append(0.0 * Dervert_list[ii][jj]) Derlist_num.append(sub) # store reference circulation and force Zeta0 = [] Zeta0_star = [] Fqs0 = [] for ss in range(n_surf): Zeta0.append(MS.Surfs[ss].zeta.copy()) Zeta0_star.append(MS.Surfs_star[ss].zeta.copy()) Fqs0.append(MS.Surfs[ss].fqs.copy()) # calculate vis FDs # Steps=[1e-2,1e-4,1e-6,] Steps = [1e-6, ] step = Steps[0] ### loop input surfs for ss_in in range(n_surf): Surf_in = MS.Surfs[ss_in] Surf_star_in = MS.Surfs_star[ss_in] M_in, N_in = Surf_in.maps.M, Surf_in.maps.N # perturb for kk in range(3 * Surf_in.maps.Kzeta): cc, mm, nn = np.unravel_index(kk, (3, M_in + 1, N_in + 1)) # perturb bound Surf_in.zeta = Zeta0[ss_in].copy() Surf_in.zeta[cc, mm, nn] += step # perturb wake TE if mm == M_in: Surf_star_in.zeta = Zeta0_star[ss_in].copy() Surf_star_in.zeta[cc, 0, nn] += step # recalculate induced velocity everywhere MS.get_ind_velocities_at_segments(overwrite=True) # restore zeta: (include only induced velocity contrib.) Surf_in.zeta = Zeta0[ss_in].copy() Surf_star_in.zeta = Zeta0_star[ss_in].copy() # estimate derivatives for ss_out in range(n_surf): Surf_out = MS.Surfs[ss_out] fqs0 = Fqs0[ss_out].copy() Surf_out.get_joukovski_qs( gammaw_TE=MS.Surfs_star[ss_out].gamma[0, :]) df = (Surf_out.fqs - fqs0) / step Derlist_num[ss_out][ss_in][:, kk] = df.reshape(-1, order='C') ### check error for ss_out in range(n_surf): for ss_in in range(n_surf): Der_an = Dervert_list[ss_out][ss_in].copy() if ss_in == ss_out: Der_an = Der_an + Dercoll_list[ss_out] Der_num = Derlist_num[ss_out][ss_in] ermax, ErAbs, ErRel = max_error_tensor(Der_an, Der_num) # max absolute error ermax = np.max(ErAbs) # relative error at max abs error point iimax = np.unravel_index(np.argmax(ErAbs), ErAbs.shape) ermax_rel = ErRel[iimax] if self.print_info: print('Bound%.2d->Bound%.2d\tFDstep\tErrAbs\tErrRel' % (ss_in, ss_out)) print('\t\t\t%.1e\t%.1e\t%.1e' % (step, ermax, ermax_rel)) assert ermax < 5e2 * step and ermax_rel < 50 * step, 'Test failed!' # fig=plt.figure('Spy Er vs coll derivs',figsize=(12,4)) # ax1=fig.add_subplot(131) # ax1.spy(ErAbs,precision=1e2*step) # ax1.set_title('error abs %d to %d' %(ss_in,ss_out)) # ax2=fig.add_subplot(132) # ax2.spy(ErRel,precision=1e2*step) # ax2.set_title('error rel %d to %d' %(ss_in,ss_out)) # ax3=fig.add_subplot(133) # ax3.spy(Dercoll_list[ss_out],precision=50*step) # ax3.set_title('Dcoll an. %d to %d' %(ss_out,ss_out)) # #plt.show() # plt.close() def test_dfunstdgamma_dot(self): """ Test derivative of unsteady aerodynamic force with respect to changes in panel circulation. Warning: test assumes the derivative of the unsteady force only depends on Gamma_dot, which is true only for steady-state linearisation points """ MS = self.MS Ders_an = assembly.dfunstdgamma_dot(MS.Surfs) step = 1e-6 Ders_num = [] n_surf = len(MS.Surfs) for ss in range(n_surf): Surf = MS.Surfs[ss] Kzeta, K = Surf.maps.Kzeta, Surf.maps.K M, N = Surf.maps.M, Surf.maps.N Dnum = np.zeros((3 * Kzeta, K)) # get refernce values Surf.get_joukovski_unsteady() Gamma_dot0 = Surf.gamma_dot.copy() F0 = Surf.funst.copy() for pp in range(K): mm, nn = np.unravel_index(pp, (M, N)) Surf.gamma_dot = Gamma_dot0.copy() Surf.gamma_dot[mm, nn] += step Surf.get_joukovski_unsteady() dF = (Surf.funst - F0) / step Dnum[:, pp] = dF.reshape(-1) # restore Surf.gamma_dot = Gamma_dot0.copy() ### verify ermax, ErAbs, ErRel = max_error_tensor(Ders_an[ss], Dnum) # max absolute error ermax = np.max(ErAbs) # relative error at max abs error point iimax = np.unravel_index(np.argmax(ErAbs), ErAbs.shape) ermax_rel = ErRel[iimax] if self.print_info: print('Bound%.2d\t\t\tFDstep\tErrAbs\tErrRel' % (ss,)) print('\t\t\t%.1e\t%.1e\t%.1e' % (step, ermax, ermax_rel)) assert ermax < 5e2 * step and ermax_rel < 50 * step, 'Test failed!' def test_wake_prop(self): self.start_writer() MS = self.MS C_list, Cstar_list = assembly.wake_prop(MS) n_surf = len(MS.Surfs) for ss in range(n_surf): Surf = MS.Surfs[ss] Surf_star = MS.Surfs_star[ss] N = Surf.maps.N K_star = Surf_star.maps.K C = C_list[ss] Cstar = Cstar_list[ss] # add noise to circulations gamma = Surf.gamma + np.random.rand(*Surf.gamma.shape) gamma_star = Surf_star.gamma + np.random.rand(*Surf_star.gamma.shape) gvec = np.dot(C, gamma.reshape(-1)) + np.dot(Cstar, gamma_star.reshape(-1)) gvec_ref = np.concatenate((gamma[-1, :], gamma_star[:-1, :].reshape(-1))) assert np.max(np.abs(gvec - gvec_ref)) < 1e-15, \ 'Prop. from trailing edge not correct' def start_writer(self): # Over write writer with print_file False to avoid I/O errors global cout_wrap cout_wrap = cout.Writer() # cout_wrap.initialise(print_screen=False, print_file=False) cout_wrap.cout_quiet() sharpy.utils.cout_utils.cout_wrap = cout_wrap def tearDown(self): cout.finish_writer() if __name__ == '__main__': unittest.main() ================================================ FILE: tests/linear/control_surfaces/__init__.py ================================================ ================================================ FILE: tests/linear/control_surfaces/test_control_surfaces.py ================================================ import numpy as np import os import unittest import sharpy.cases.templates.flying_wings as wings import sharpy.sharpy_main import pickle # @unittest.skip('Control Surface Test for visual inspection') class TestGolandControlSurface(unittest.TestCase): def setup(self): self.deflection_degrees = 5 # Problem Set up u_inf = 1. alpha_deg = 0. rho = 1.02 num_modes = 2 # Lattice Discretisation M = 4 N = 16 M_star_fact = 1 # Linear UVLM settings integration_order = 2 remove_predictor = False use_sparse = True # Case Admin - Create results folders case_name = 'goland_cs' case_nlin_info = 'M%dN%dMs%d_nmodes%d' % (M, N, M_star_fact, num_modes) case_name += case_nlin_info self.route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) fig_folder = self.route_test_dir + '/figures/' os.makedirs(fig_folder, exist_ok=True) # SHARPy nonlinear reference solution ws = wings.GolandControlSurface(M=M, N=N, Mstar_fact=M_star_fact, u_inf=u_inf, alpha=alpha_deg, cs_deflection=[0, 0], rho=rho, sweep=0, physical_time=2, n_surfaces=2, route=self.route_test_dir + '/cases', case_name=case_name) ws.gust_intensity = 0.01 ws.sigma = 1 ws.clean_test_files() ws.update_derived_params() ws.set_default_config_dict() # Full moving control surface on the right wing ws.control_surface_chord[0] = M ws.control_surface_hinge_coord[0] = 0.5 ws.control_surface[4, :] = 1 ws.generate_aero_file() ws.generate_fem_file() x0 = np.zeros(256) lin_tsteps = 10 u_vec = np.zeros((lin_tsteps, 8)) # elevator u_vec[:, 4] = self.deflection_degrees * np.pi / 180 np.savetxt(self.route_test_dir + '/cases/elevator.txt', u_vec[:, 4]) ws.create_linear_files(x0, u_vec) ws.config['SHARPy'] = { 'flow': ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'AerogridPlot', 'BeamPlot', 'Modal', 'LinearAssembler', # 'FrequencyResponse', 'LinDynamicSim', 'PickleData', ], 'case': ws.case_name, 'route': ws.route, 'write_screen': 'off', 'write_log': 'on', 'log_folder': self.route_test_dir + '/output/' + ws.case_name + '/', 'log_file': ws.case_name + '.log'} ws.config['BeamLoader'] = { 'unsteady': 'off', 'orientation': ws.quat} ws.config['AerogridLoader'] = { 'unsteady': 'off', 'aligned_grid': 'on', 'mstar': ws.Mstar_fact * ws.M, 'freestream_dir': ws.u_inf_direction, 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction, 'dt': ws.dt}} ws.config['StaticUvlm'] = { 'rho': ws.rho, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}, 'rollup_dt': ws.dt, 'print_info': 'on', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_aic_refresh': 0, 'rollup_tolerance': 1e-4} ws.config['StaticCoupled'] = { 'print_info': 'on', 'max_iter': 200, 'n_load_steps': 1, 'tolerance': 1e-10, 'relaxation_factor': 0., 'aero_solver': 'StaticUvlm', 'aero_solver_settings': { 'rho': ws.rho, 'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': ws.dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}}, 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 4, 'delta_curved': 1e-1, 'min_delta': 1e-10, 'gravity_on': 'on', 'gravity': 9.81}} ws.config['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0} ws.config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': ws.case_name + '_aeroforces.csv', 'screen_output': 'on', 'unsteady': 'off'} ws.config['BeamPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on'} ws.config['Modal'] = {'NumLambda': 20, 'rigid_body_modes': 'off', 'print_matrices': 'on', 'save_data': 'off', 'rigid_modes_cg': 'off', 'continuous_eigenvalues': 'off', 'dt': 0, 'plot_eigenvalues': False, 'max_rotation_deg': 15., 'max_displacement': 0.15, 'write_modes_vtk': True, 'use_undamped_modes': True} ws.config['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', 'linear_system_settings': { 'beam_settings': {'modal_projection': 'on', 'inout_coords': 'modes', 'discrete_time': 'on', 'newmark_damp': 0.5e-4, 'discr_method': 'newmark', 'dt': ws.dt, 'proj_modes': 'undamped', 'use_euler': 'off', 'num_modes': num_modes, 'print_info': 'on', 'gravity': 'on', 'remove_sym_modes': 'on', 'remove_dofs': []}, 'aero_settings': {'dt': ws.dt, 'integr_order': integration_order, 'density': ws.rho, 'remove_predictor': remove_predictor, 'use_sparse': use_sparse, 'remove_inputs': ['u_gust']}, } } ws.config['LinDynamicSim'] = {'n_tsteps': lin_tsteps, 'dt': ws.dt, 'input_generators': [{'name': 'control_surface_deflection', 'index': 0, 'file_path': self.route_test_dir + '/cases/elevator.txt'}, {'name': 'control_surface_deflection', 'index': 1, 'file_path': self.route_test_dir + '/cases/elevator.txt'}], 'postprocessors': ['AerogridPlot'], 'postprocessors_settings': {'AerogridPlot': {'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, } } ws.config['PickleData'] = {} ws.config.write() restart = False # useful for debugging to load pickle if not restart: self.data = sharpy.sharpy_main.main(['', ws.route + ws.case_name + '.sharpy']) else: with open(self.route_test_dir + '/output/' + ws.case_name + '.pkl', 'rb') as f: self.data = pickle.load(f) def test_control_surface(self): self.setup() for i_surf in range(2): with self.subTest(i_surf=i_surf): zeta = self.data.aero.timestep_info[-1].zeta[i_surf] zeta0 = self.data.linear.tsaero0.zeta[i_surf] # zeta indices [(xyz), chord, span] if i_surf == 0: # Full moving control surface with hinge at c=0.5 zeta_elev = zeta[:, -1, -1] - zeta[:, 0, -1] # elevator starts at chordwise node 0 zeta_0elev = zeta0[:, -1, -1] - zeta0[:, 0, -1] elif i_surf == 1: # mirrored surface. span index 0 is the wing tip zeta_elev = zeta[:, -1, 0] - zeta[:, 2, 0] # elevator starts at chordwise node 2 zeta_0elev = zeta0[:, -1, 0] - zeta0[:, 2, 0] deflection = np.arccos((zeta_elev.dot(zeta_0elev)) / np.linalg.norm(zeta_elev) / np.linalg.norm(zeta_0elev)) deflection_actual_deg = deflection * 180 / np.pi np.testing.assert_array_almost_equal(self.deflection_degrees, deflection_actual_deg, decimal=2) def tearDown(self): import shutil folders = ['cases', 'figures', 'output'] for folder in folders: shutil.rmtree(self.route_test_dir + '/' + folder) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/linear/derivatives/__init__.py ================================================ ================================================ FILE: tests/linear/derivatives/test_ders.py ================================================ """ Test elementary derivative methods S. Maraniello, 4 Jun 2018 """ import numpy as np import unittest import sharpy.aero.utils.uvlmlib import sharpy.linear.src.lib_dbiot as dbiot import sharpy.linear.src.uvlmutils as uvlmutils vortex_radius = 1e-6 class Test_ders(unittest.TestCase): """ Test methods into assembly module """ print_info = False # useful for debugging. Leave False to keep test log clean def setUp(self): self.zetaP = np.array([3.0, 5.5, 2.0]) self.zeta0 = np.array([1.0, 3.0, 0.9]) self.zeta1 = np.array([5.0, 3.1, 1.9]) self.zeta2 = np.array([4.8, 8.1, 2.5]) self.zeta3 = np.array([0.9, 7.9, 1.7]) def test_dbiot_segment(self): if self.print_info: print('\n-------------------------------------- Testing dbiot.eval_seg') gamma = 2.4 zetaP = self.zetaP zetaA = self.zeta1 zetaB = self.zeta2 Q0 = uvlmutils.biot_segment(zetaP, zetaA, zetaB, vortex_radius, gamma) ### compare different analytical derivative DerP_an, DerA_an, DerB_an = dbiot.eval_seg_exp(zetaP, zetaA, zetaB, vortex_radius, gamma) DerP_an2, DerA_an2, DerB_an2 = dbiot.eval_seg_comp(zetaP, zetaA, zetaB, vortex_radius, gamma) er_max = max(np.max(np.abs(DerP_an2 - DerP_an)), np.max(np.abs(DerA_an2 - DerA_an)), np.max(np.abs(DerB_an2 - DerB_an))) assert er_max < 1e-16, 'Analytical models not matching' ### compare vs numerical derivative Steps = np.linspace(vortex_radius * 0.99, vortex_radius * 1e-2, 4) Er_max = 0.0 * Steps for ss in range(len(Steps)): step = Steps[ss] DerP_num = 0.0 * DerP_an DerA_num = 0.0 * DerA_an DerB_num = 0.0 * DerB_an for cc_zeta in range(3): dzeta = np.zeros((3,)) dzeta[cc_zeta] = step DerP_num[:, cc_zeta] = ( uvlmutils.biot_segment(zetaP + dzeta, zetaA, zetaB, vortex_radius, gamma) - Q0) / step DerA_num[:, cc_zeta] = ( uvlmutils.biot_segment(zetaP, zetaA + dzeta, zetaB, vortex_radius, gamma) - Q0) / step DerB_num[:, cc_zeta] = ( uvlmutils.biot_segment(zetaP, zetaA, zetaB + dzeta, vortex_radius, gamma) - Q0) / step er_max = max(np.max(np.abs(DerP_num - DerP_an)), np.max(np.abs(DerA_num - DerA_an)), np.max(np.abs(DerB_num - DerB_an))) if self.print_info: print('FD step: %.2e ---> Max error: %.2e' % (step, er_max)) assert er_max < 5e1 * step, 'Error larger than 50 times step size' Er_max[ss] = er_max def test_dbiot_segment_mid(self): if self.print_info: print('\n------------------------- Testing dbiot.eval_seg at mid-point') gamma = 2.4 zetaA = self.zeta1 zetaB = self.zeta2 zetaP = .3 * zetaA + 0.7 * zetaB Q0 = uvlmutils.biot_segment(zetaP, zetaA, zetaB, vortex_radius, gamma) ### compare different analytical derivative DerP_an, DerA_an, DerB_an = dbiot.eval_seg_exp(zetaP, zetaA, zetaB, vortex_radius, gamma) DerP_an2, DerA_an2, DerB_an2 = dbiot.eval_seg_comp(zetaP, zetaA, zetaB, gamma) er_max = max(np.max(np.abs(DerP_an2 - DerP_an)), np.max(np.abs(DerA_an2 - DerA_an)), np.max(np.abs(DerB_an2 - DerB_an))) assert er_max < 1e-16, 'Analytical models not matching' ### compare vs numerical derivative # first step must be smaller than vortex radius Steps = np.linspace(vortex_radius * 0.99, vortex_radius * 1e-2, 4) Er_max = 0.0 * Steps for ss in range(len(Steps)): step = Steps[ss] DerP_num = 0.0 * DerP_an DerA_num = 0.0 * DerA_an DerB_num = 0.0 * DerB_an for cc_zeta in range(3): dzeta = np.zeros((3,)) dzeta[cc_zeta] = step DerP_num[:, cc_zeta] = ( uvlmutils.biot_segment(zetaP + dzeta, zetaA, zetaB, vortex_radius, gamma) - Q0) / step DerA_num[:, cc_zeta] = ( uvlmutils.biot_segment(zetaP, zetaA + dzeta, zetaB, vortex_radius, gamma) - Q0) / step DerB_num[:, cc_zeta] = ( uvlmutils.biot_segment(zetaP, zetaA, zetaB + dzeta, vortex_radius, gamma) - Q0) / step er_max = max(np.max(np.abs(DerP_num - DerP_an)), np.max(np.abs(DerA_num - DerA_an)), np.max(np.abs(DerB_num - DerB_an))) if self.print_info: print('FD step: %.2e ---> Max error: %.2e' % (step, er_max)) assert er_max < 5e1 * step, 'Error larger than 50 times step size' Er_max[ss] = er_max def test_dbiot_panel(self): if self.print_info: print('\n---------------------------------- Testing dbiot.eval_panel_*') gamma = 2.4 zetaP = self.zetaP zeta0 = self.zeta0 zeta1 = self.zeta1 zeta2 = self.zeta2 zeta3 = self.zeta3 ZetaPanel = np.array([zeta0, zeta1, zeta2, zeta3]) Q0 = uvlmutils.biot_panel(zetaP, ZetaPanel, vortex_radius, gamma) # compare analytical derivatives models DerP_an, DerVer_an = dbiot.eval_panel_exp(zetaP, ZetaPanel, vortex_radius, gamma) DerP_an2, DerVer_an2 = dbiot.eval_panel_comp(zetaP, ZetaPanel, vortex_radius, gamma) DerP_an3, DerVer_an3 = dbiot.eval_panel_fast(zetaP, ZetaPanel, vortex_radius, gamma) DerP_an4, DerVer_an4 = sharpy.aero.utils.uvlmlib.eval_panel_cpp(zetaP, ZetaPanel, vortex_radius, gamma) er_max = max(np.max(np.abs(DerP_an2 - DerP_an)), np.max(np.abs(DerVer_an2 - DerVer_an))) assert er_max < 1e-16, 'eval_panel_comp not matching with eval_panel_exp' er_max = max(np.max(np.abs(DerP_an3 - DerP_an)), np.max(np.abs(DerVer_an3 - DerVer_an))) assert er_max < 1e-16, 'eval_panel_fast not matching with eval_panel_exp' er_max = max(np.max(np.abs(DerP_an4 - DerP_an)), np.max(np.abs(DerVer_an4 - DerVer_an))) assert er_max < 1e-16, 'eval_panel_cpp not matching with eval_panel_exp' # compare vs. numerical derivative Steps = np.linspace(vortex_radius * 0.99, vortex_radius * 1e-2, 4) ErP_max = 0.0 * Steps ErVer_max = 0.0 * Steps for ss in range(len(Steps)): step = Steps[ss] DerP_num = 0.0 * DerP_an DerVer_num = 0.0 * DerVer_an ### Perturb component for cc in range(3): dzeta = np.zeros((3,)) dzeta[cc] = step # derivative w.r.t. target point DerP_num[:, cc] = \ (uvlmutils.biot_panel(zetaP + dzeta, ZetaPanel, vortex_radius, gamma) - Q0) / step # derivative w.r.t panel vertices for vv in range(4): ZetaPanel_pert = ZetaPanel.copy() ZetaPanel_pert[vv, :] += dzeta DerVer_num[vv, :, cc] = \ (uvlmutils.biot_panel(zetaP, ZetaPanel_pert, vortex_radius, gamma) - Q0) / step erP_max = np.max(np.abs(DerP_num - DerP_an)) erVer_max = np.max(np.abs(DerVer_num - DerVer_an)) if self.print_info: print('FD step: %.2e ---> Max error (P,Vert): (%.2e,%.2e)' \ % (step, erP_max, erVer_max)) assert erP_max < 5e1 * step, \ 'Error w.r.t. zetaP larger than 50 times step size' assert erVer_max < 5e1 * step, \ 'Error w.r.t. ZetaPanel larger than 50 times step size' ErP_max[ss] = erP_max ErVer_max[ss] = erVer_max # assert monothony for ss in range(len(Steps) - 1): assert ErP_max[ss + 1] < ErP_max[ss], \ 'Error of derivative w.r.t. zetaP not decreasing monothonically' assert ErVer_max[ss + 1] < ErVer_max[ss], \ 'Error of derivative w.r.t. ZetaPanel not decreasing monothonically' def test_dbiot_panel_mid_segment(self): if self.print_info: print('\n-------------- Testing dbiot.eval_panel with zetaP on segment') gamma = 2.4 zeta0 = self.zeta0 zeta1 = self.zeta1 zeta2 = self.zeta2 zeta3 = self.zeta3 zetaP = 0.3 * zeta1 + 0.7 * zeta2 ZetaPanel = np.array([zeta0, zeta1, zeta2, zeta3]) Q0 = uvlmutils.biot_panel(zetaP, ZetaPanel, vortex_radius, gamma) # compare analytical derivatives models DerP_an, DerVer_an = dbiot.eval_panel_exp(zetaP, ZetaPanel, vortex_radius, gamma) DerP_an2, DerVer_an2 = dbiot.eval_panel_comp(zetaP, ZetaPanel, vortex_radius, gamma) DerP_an3, DerVer_an3 = dbiot.eval_panel_fast(zetaP, ZetaPanel, vortex_radius, gamma) DerP_an4, DerVer_an4 = sharpy.aero.utils.uvlmlib.eval_panel_cpp(zetaP, ZetaPanel, vortex_radius, gamma) er_max = max(np.max(np.abs(DerP_an2 - DerP_an)), np.max(np.abs(DerVer_an2 - DerVer_an))) assert er_max < 1e-16, 'eval_panel_comp not matching with eval_panel_exp' er_max = max(np.max(np.abs(DerP_an3 - DerP_an)), np.max(np.abs(DerVer_an3 - DerVer_an))) assert er_max < 1e-16, 'eval_panel_fast not matching with eval_panel_exp' er_max = max(np.max(np.abs(DerP_an4 - DerP_an)), np.max(np.abs(DerVer_an4 - DerVer_an))) assert er_max < 1e-16, 'eval_panel_cpp not matching with eval_panel_exp' # compare vs. numerical derivative # first step must be smaller than vortex radius Steps = np.linspace(vortex_radius * 0.99, vortex_radius * 1e-2, 4) ErP_max = 0.0 * Steps ErVer_max = 0.0 * Steps for ss in range(len(Steps)): step = Steps[ss] DerP_num = 0.0 * DerP_an DerVer_num = 0.0 * DerVer_an ### Perturb component for cc in range(3): dzeta = np.zeros((3,)) dzeta[cc] = step # derivative w.r.t. target point DerP_num[:, cc] = \ (uvlmutils.biot_panel(zetaP + dzeta, ZetaPanel, vortex_radius, gamma) - Q0) / step # derivative w.r.t panel vertices for vv in range(4): ZetaPanel_pert = ZetaPanel.copy() ZetaPanel_pert[vv, :] += dzeta DerVer_num[vv, :, cc] = \ (uvlmutils.biot_panel(zetaP, ZetaPanel_pert, vortex_radius, gamma) - Q0) / step erP_max = np.max(np.abs(DerP_num - DerP_an)) erVer_max = np.max(np.abs(DerVer_num - DerVer_an)) if self.print_info: print('FD step: %.2e ---> Max error (P,Vert): (%.2e,%.2e)' \ % (step, erP_max, erVer_max)) assert erP_max < 5e1 * step, \ 'Error w.r.t. zetaP larger than 50 times step size' assert erVer_max < 5e1 * step, \ 'Error w.r.t. ZetaPanel larger than 50 times step size' ErP_max[ss] = erP_max ErVer_max[ss] = erVer_max # assert monothony for ss in range(len(Steps) - 1): assert ErP_max[ss + 1] < ErP_max[ss], \ 'Error of derivative w.r.t. zetaP not decreasing monothonically' assert ErVer_max[ss + 1] < ErVer_max[ss], \ 'Error of derivative w.r.t. ZetaPanel not decreasing monothonically' ================================================ FILE: tests/linear/derivatives/test_stabilityderivatives.py ================================================ import numpy as np import os import unittest import sharpy.cases.templates.flying_wings as wings import sharpy.sharpy_main import h5py import sharpy.utils.h5utils as h5utils import sharpy.utils.algebra as algebra import sharpy.linear.src.libss as libss class TestLinearDerivatives(unittest.TestCase): print_info = False def run_sharpy(self, alpha_deg, flow, target_system, not_run=False): # Problem Set up u_inf = 10. rho = 1.02 num_modes = 4 # Lattice Discretisation M = 4 N = 4 M_star_fact = 10 # Linear UVLM settings integration_order = 2 remove_predictor = False use_sparse = True # ROM Properties rom_settings = dict() rom_settings['algorithm'] = 'mimo_rational_arnoldi' rom_settings['r'] = 6 rom_settings['single_side'] = 'observability' frequency_continuous_k = np.array([0.]) # Case Admin - Create results folders case_name = 'wing' if target_system == 'aerodynamic': case_name += '_uvlm' elif target_system == 'aeroelastic': case_name += '_coupled' else: NameError('Unrecognised system') case_nlin_info = '_uinf{:04g}_a{:04g}'.format(u_inf*10, alpha_deg*100) case_name += case_nlin_info self.route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) # SHARPy nonlinear reference solution ws = wings.QuasiInfinite(M=M, aspect_ratio=10, N=N, Mstar_fact=M_star_fact, u_inf=u_inf, alpha=alpha_deg, rho=rho, sweep=0, n_surfaces=2, route=self.route_test_dir + '/cases', case_name=case_name) ws.gust_intensity = 0.01 ws.sigma = 1e-1 ws.clean_test_files() ws.update_derived_params() ws.set_default_config_dict() ws.generate_aero_file() ws.generate_fem_file() frequency_continuous_w = 2 * u_inf * frequency_continuous_k / ws.c_ref rom_settings['frequency'] = frequency_continuous_w ws.config['SHARPy'] = { 'flow': flow, 'case': ws.case_name, 'route': ws.route, 'write_screen': 'off', 'write_log': 'on', 'log_folder': self.route_test_dir + '/output/', 'log_file': ws.case_name + '.log'} ws.config['BeamLoader'] = { 'unsteady': 'off', 'orientation': ws.quat} ws.config['AerogridLoader'] = { 'unsteady': 'off', 'aligned_grid': 'on', 'mstar': ws.Mstar_fact * ws.M, 'freestream_dir': ws.u_inf_direction, 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction, 'dt': ws.dt}} ws.config['StaticUvlm'] = { 'rho': ws.rho, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}, 'rollup_dt': ws.dt, 'print_info': 'on', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_aic_refresh': 0, 'rollup_tolerance': 1e-4} ws.config['StaticCoupled'] = { 'print_info': 'on', 'max_iter': 200, 'n_load_steps': 1, 'tolerance': 1e-10, 'relaxation_factor': 0., 'aero_solver': 'StaticUvlm', 'aero_solver_settings': { 'rho': ws.rho, 'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': ws.dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}}, 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 4, 'delta_curved': 1e-1, 'min_delta': 1e-10, 'gravity_on': 'off', 'gravity': 9.81}} ws.config['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0} ws.config['AeroForcesCalculator'] = {'write_text_file': 'on', 'screen_output': 'on'} ws.config['BeamPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on'} ws.config['BeamCsvOutput'] = {'output_pos': 'on', 'output_psi': 'on', 'screen_output': 'on'} ws.config['Modal'] = {'print_info': 'on', 'use_undamped_modes': 'on', 'NumLambda': 20, 'rigid_body_modes': 'on', 'write_modes_vtk': 'on', 'print_matrices': 'off', 'save_data': 'on', 'rigid_modes_cg': 'off'} settings = {} settings['NonLinearDynamicCoupledStep'] = {'print_info': 'off', 'max_iterations': 950, 'delta_curved': 1e-1, 'min_delta': ws.tolerance, 'newmark_damp': 5e-3, 'gravity_on': 'on', 'gravity': 9.81, 'num_steps': 4, 'dt': ws.dt, 'initial_velocity': u_inf} relative_motion = 'off' settings['StepUvlm'] = {'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'convection_scheme': 2, 'rollup_dt': ws.dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'gamma_dot_filtering': 6, 'vortex_radius': 1e-8, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': 0 * u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho, 'n_time_steps': 1, 'dt': ws.dt} solver = 'NonLinearDynamicCoupledStep' ws.config['DynamicCoupled'] = {'structural_solver': solver, 'structural_solver_settings': settings[solver], 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'fsi_substeps': 200, 'fsi_tolerance': ws.tolerance, 'relaxation_factor': ws.relaxation_factor, 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': 0.5, 'n_time_steps': 1, 'dt': ws.dt, 'include_unsteady_force_contribution': 'off', } ws.config['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', 'linear_system_settings': { 'track_body': 'off', 'beam_settings': {'modal_projection': 'on', 'inout_coords': 'modes', 'discrete_time': 'off', 'newmark_damp': 5e-4, 'discr_method': 'newmark', 'dt': ws.dt, 'proj_modes': 'undamped', 'use_euler': 'on', 'num_modes': 20, 'print_info': 'on', 'gravity': 'off', 'remove_dofs': [], 'remove_rigid_states': 'on'}, 'aero_settings': {'dt': ws.dt, # 'ScalingDict': {'density': rho, # 'length': ws.c_ref * 0.5, # 'speed': u_inf}, 'integr_order': 2, 'density': rho, 'remove_predictor': 'off', 'use_sparse': 'off', 'vortex_radius': 1e-8, 'convert_to_ct': 'on', 'remove_inputs': ['u_gust'], # 'rom_method': ['Krylov'], 'rom_method_settings': { 'Krylov': {'algorithm': 'mimo_rational_arnoldi', 'frequency': [0.], 'r': 4, 'single_side': 'observability'}} }, 'use_euler': 'on', }, } ws.config['FrequencyResponse'] = {'quick_plot': 'off', 'frequency_unit': 'k', 'frequency_bounds': [0.0001, 1.0], 'num_freqs': 100, 'frequency_spacing': 'log', 'target_system': ['aeroelastic'], } ws.config['StabilityDerivatives'] = {'u_inf': ws.u_inf, 'c_ref': ws.main_chord, 'b_ref': ws.wing_span, 'S_ref': ws.wing_span * ws.main_chord, } ws.config['AsymptoticStability'] = {'print_info': 'on'} ws.config['SaveParametricCase'] = {'save_case': 'off', 'parameters': {'alpha': alpha_deg}, 'save_pmor_items': 'on', 'save_pmor_subsystems': 'on'} ws.config['SaveData'] = {'save_aero': 'off', 'save_struct': 'off', 'save_linear': 'on', 'save_linear_uvlm': 'off', 'format': 'mat', } ws.config.write() if not not_run: self.data = sharpy.sharpy_main.main(['', ws.route + ws.case_name + '.sharpy']) return ws def test_derivatives(self): # target_system_list = ['aerodynamic']#, 'aeroelastic'] # target_system_list = ['aeroelastic'] target_system_list = ['aerodynamic', 'aeroelastic'] for system in target_system_list: with self.subTest(target_system=system): self.run_case(target_system=system) def run_case(self, target_system): if target_system == 'aerodynamic': nonlinear_solver = ['StaticUvlm'] elif target_system == 'aeroelastic': nonlinear_solver = ['StaticCoupled'] else: NameError('Unrecognised system') case_name_db = [] not_run = False # for debugging # Reference Case at 4 degrees # Run nonlinear simulation and save linerised ROM alpha_deg_ref = 0 alpha0 = alpha_deg_ref * np.pi/180 ref = self.run_sharpy(alpha_deg=alpha_deg_ref, flow=['BeamLoader', 'AerogridLoader', ] + nonlinear_solver + ['AeroForcesCalculator', 'Modal', 'LinearAssembler', # 'AsymptoticStability', 'StabilityDerivatives', 'SaveData', 'SaveParametricCase'], target_system=target_system, not_run=False) u_inf = ref.u_inf ref_case_name = ref.case_name case_name_db.append(ref_case_name) qS = 0.5 * ref.rho * u_inf ** 2 * ref.c_ref * ref.wing_span if self.print_info: print(f'Reference span: {ref.wing_span} m') print(f'Reference chord: {ref.main_chord} m') print(f'Aspect ratio: {ref.aspect_ratio}') # Run nonlinear cases in the vicinity nonlinear_sim_flow = ['BeamLoader', 'AerogridLoader'] + nonlinear_solver + ['AeroForcesCalculator', 'SaveParametricCase'] alpha_deg_min = alpha_deg_ref - 5e-3 alpha_deg_max = alpha_deg_ref + 5e-3 n_evals = 11 alpha_vec = np.linspace(alpha_deg_min, alpha_deg_max, n_evals) nlin_forces_g = np.zeros((n_evals, 3)) nlin_forces_a = np.zeros((n_evals, 3)) for ith, alpha in enumerate(alpha_vec): if alpha == alpha_deg_ref: case_name = ref_case_name continue else: case = self.run_sharpy(alpha, flow=nonlinear_sim_flow, target_system=target_system, not_run=not_run) case_name_db.append(case.case_name) case_name = case.case_name nlin_forces = np.loadtxt(self.route_test_dir + '/output/{:s}/forces/forces_aeroforces.txt'.format(case_name), skiprows=1, delimiter=',') nlin_forces_g[ith, :3] = nlin_forces[1:4] nlin_forces_a[ith, :3] = nlin_forces[7:10] nlin_forces_g /= qS nlin_forces_a /= qS lift_poly = np.polyfit(alpha_vec * np.pi/180, nlin_forces_g[:, 2], deg=1) nonlin_cla = lift_poly[0] drag_poly = np.polyfit(alpha_vec * np.pi/180, nlin_forces_g[:, 0], deg=2) nonlin_cda = 2 * drag_poly[0] * alpha0 + drag_poly[1] if self.print_info: print('Nonlinear coefficients') print('CLa', nonlin_cla) print('CDa', nonlin_cda) lift_poly = np.polyfit(alpha_vec * np.pi/180, nlin_forces_a[:, 2], deg=1) nonlin_cza = lift_poly[0] drag_poly = np.polyfit(alpha_vec * np.pi/180, nlin_forces_a[:, 0], deg=2) nonlin_cxa = 2 * drag_poly[0] * alpha0 + drag_poly[1] if self.print_info: print('Nonlinear coefficients - body axes') print('CZa', nonlin_cza) print('CXa', nonlin_cxa) # Get Linear ROM at reference case import scipy.io as scio linss_data = scio.loadmat(self.route_test_dir + '/output/{:s}/savedata/{:s}.linss.mat'.format(ref_case_name, ref_case_name)) # Steady State transfer function if target_system == 'aerodynamic': ss = libss.StateSpace.load_from_h5(self.route_test_dir + f'/output/{ref_case_name}/' + f'save_pmor_data/{ref_case_name}_aerostatespace.h5') else: ss = libss.StateSpace.load_from_h5(self.route_test_dir + f'/output/{ref_case_name}/' + f'save_pmor_data/{ref_case_name}_statespace.h5') H0 = ss.freqresp(np.array([1e-5]))[:, :, 0].real cga = algebra.quat2rotation(algebra.euler2quat(np.array([0, alpha0, 0]))) vx_ind = 20 # x_a input index vz_ind = 20 + 2 # z_a input index n_evals = 2 forces = np.zeros((n_evals, 4)) moments = np.zeros_like(forces) body_forces = np.zeros_like(forces) phi = libss.Gain.load_from_h5(self.route_test_dir + f'/output/{ref_case_name}/' + f'save_pmor_data/{ref_case_name}_modal_structrob.h5').value.real # other Gain features not needed eps = 1e-5 dalpha_vec = np.array([-eps, +eps]) for i_alpha, dalpha in enumerate(dalpha_vec): alpha = alpha0 + dalpha # rad deuler = np.array([0, dalpha, 0]) euler0 = np.array([0, alpha0, 0]) u = np.zeros(ss.inputs) # input vector V0 = np.array([-1, 0, 0], dtype=float) * u_inf # G Vp = u_inf * np.array([-np.cos(dalpha), 0, -np.sin(dalpha)]) # G dvg = Vp - V0 # G dva = cga.T.dot(dvg) # A dvz = dva[2] dvx = dva[0] # print(Vp) # print(algebra.euler2rot(deuler).T.dot(V0)) # print(algebra.der_Peuler_by_v(euler0*0, V0)) Vp2 = algebra.euler2rot(euler0 * 0).T.dot(V0) + algebra.der_Peuler_by_v(euler0 * 0, V0).dot(deuler) dva2 = cga.T.dot(algebra.euler2rot(deuler).T.dot(V0) - V0) dva3 = cga.T.dot(Vp2 - V0) # print('{:.4f}\t'.format((alpha0 + dalpha) * 180 / np.pi), dva2, dva3, dva3-dva2) dvz = dva3[2] dvx = dva3[0] # Need to scale the mode shapes by the rigid body mode factor u[vx_ind] = dvx / phi[-9, 0] u[vz_ind] = dvz / phi[-7, 2] # and the same with the output forces flin = H0.dot(u)[:3].real / phi[-9, 0] # A mlin = np.linalg.inv(phi[-6:-3, 3:6].T).dot(H0.dot(u)[3:6].real) # A F0A = linss_data['forces_aero_beam_dof'][0, :3].real / phi[-9, 0] # A - forces at the linearisation M0A = linss_data['forces_aero_beam_dof'][0, 3:6].real / phi[-6, 3] # A - forces at the linearisation LD0 = cga.dot(F0A) # Lift and drag at the linearisation point M0G = cga.dot(M0A) forces[i_alpha, 0] = (alpha0 + dalpha) * 180 / np.pi # deg LD = LD0 + algebra.der_Ceuler_by_v(euler0, F0A).dot(deuler) + cga.dot(flin) # stability axes forces[i_alpha, 1:] = LD / qS u_body = np.zeros_like(u) u_body[vz_ind] = dvz / phi[-7, 2] body_forces[i_alpha, 1:] = H0.dot(u_body)[:3].real / phi[-9, 0] / qS # C_Z_w in A frame if self.print_info: print('Transfer function coefficients (normalised by qS)') print(H0[:3, vx_ind:vx_ind+3].real / phi[-7, 2] / phi[-9, 0] / qS) MD = M0G + algebra.der_Ceuler_by_v(euler0, M0A).dot(deuler) + cga.dot(mlin) moments[i_alpha, 0] = forces[i_alpha, 0] moments[i_alpha, 1:] = MD / qS / ref.main_chord cla = (forces[-1, -1] - forces[0, -1]) / (forces[-1, 0] - forces[0, 0]) * 180 / np.pi cda = (forces[-1, 1] - forces[0, 1]) / (forces[-1, 0] - forces[0, 0]) * 180 / np.pi cma = (moments[-1, 2] - moments[0, 2]) / (moments[-1, 0] - moments[0, 0]) * 180 / np.pi if self.print_info: print('Lift curve slope perturbation {:.6e}'.format(cla)) print('Drag curve slope perturbation {:.6e}'.format(cda)) print('Moment curve slope perturbation {:.6e}'.format(cma)) # body derivative # czwa = (body_forces[-1, -1] - body_forces[0, -1]) / (forces[-1, 0] - forces[0, 0]) * 180 / np.pi # print('Vertical force with vertical velocity perturbation {:.6e}'.format(czwa)) with h5py.File(self.route_test_dir + '/output/' + ref_case_name + '/derivatives/aerodynamic_stability_derivatives.h5', 'r') as f: sharpy_force_angle = h5utils.load_h5_in_dict(f)['force_angle_velocity'] linsubtests = ((2, cla, 'lift'), (0, cda, 'drag'), (4, cma, 'moment')) for test in linsubtests: with self.subTest(test): try: np.testing.assert_almost_equal(sharpy_force_angle[test[0], 1], test[1], decimal=3) except AssertionError as e: print('Error Linear perturbation in {:s}'.format(test[2])) print(e) print('Pct error', np.abs(sharpy_force_angle[test[0], 1] - test[1]) / test[1] * 100) raise AssertionError nonlinsubtests = ((2, nonlin_cla, 'lift'), (0, nonlin_cda, 'drag'), ) for test in nonlinsubtests: with self.subTest(test): try: np.testing.assert_almost_equal(sharpy_force_angle[test[0], 1], test[1], decimal=2) except AssertionError as e: print('Error nonlinear perturbation in {:s}'.format(test[2])) print(e) print('Pct error', np.abs(sharpy_force_angle[test[0], 1] - test[1]) / test[1] * 100) raise AssertionError return forces, moments def tearDown(self): import shutil folders = ['cases', 'output'] for folder in folders: shutil.rmtree(self.route_test_dir + '/' + folder) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/linear/frequencyutils/__init__.py ================================================ ================================================ FILE: tests/linear/frequencyutils/test_frequency_utils.py ================================================ import unittest import sharpy.linear.src.libss as libss import scipy.linalg as sclalg import numpy as np import os import sharpy.utils.frequencyutils as frequencyutils class TestFrequencyUtils(unittest.TestCase): test_dir = os.path.abspath(os.path.dirname(__file__)) def setUp(self): # This particular system is a good test as it requires a few iterations of the Hinf # solver. In addition it is known that its SVD peak occurs between 0.1 and 1.0 rad/s # allowing us to increase the resolution in the graphical method around that vicinity. a = np.load(self.test_dir + '/src/a.npy') b = np.load(self.test_dir + '/src/b.npy') c = np.load(self.test_dir + '/src/c.npy') d = np.load(self.test_dir + '/src/d.npy') self.sys = libss.StateSpace(a, b, c, d, dt=None) # self.sys = libss.random_ss(10, 4, 3, dt=0.1, stable=True) # self.sys = libss.disc2cont(self.sys) def test_hinfinity_norm(self): h_inf_iterative = frequencyutils.h_infinity_norm(self.sys, print_info=False) # Compare against graphical method # Hinf norm is the maximum SVD across all frequencies wv_vec = np.logspace(-1, 0, 100) svd_val = np.zeros_like(wv_vec) for i in range(len(svd_val)): frequency_response = self.sys.transfer_function_evaluation(1j*wv_vec[i]) svd_val[i] = np.max(sclalg.svd(frequency_response, compute_uv=False)) h_inf_graph = np.max(svd_val) np.testing.assert_almost_equal(h_inf_iterative, h_inf_graph, decimal=3, verbose=True, err_msg='H-infinity norm using iterative method vs graphical one not ' 'equal to 3 decimal places') # >>>>>>>>>>> Useful debugging # print(h_inf_iterative) # print(h_inf_graph) # # import matplotlib.pyplot as plt # plt.semilogx(wv_vec, svd_val) # plt.show() # <<<<<<<<<<<<<<<<<<<<<<<<<<<< # a = np.save(self.test_dir + '/src/a.npy', self.sys.A) # b = np.save(self.test_dir + '/src/b.npy', self.sys.B) # c = np.save(self.test_dir + '/src/c.npy', self.sys.C) # d = np.save(self.test_dir + '/src/d.npy', self.sys.D) ================================================ FILE: tests/linear/goland_wing/__init__.py ================================================ ================================================ FILE: tests/linear/goland_wing/test_goland_flutter.py ================================================ import numpy as np import os import unittest import sharpy.cases.templates.flying_wings as wings import sharpy.sharpy_main class TestGolandFlutter(unittest.TestCase): def setup(self): # Problem Set up u_inf = 1. alpha_deg = 0. rho = 1.02 num_modes = 4 # Lattice Discretisation M = 16 N = 32 M_star_fact = 10 # Linear UVLM settings integration_order = 2 remove_predictor = False use_sparse = True # ROM Properties rom_settings = dict() rom_settings['algorithm'] = 'mimo_rational_arnoldi' rom_settings['r'] = 6 rom_settings['single_side'] = 'observability' frequency_continuous_k = np.array([0.]) # Case Admin - Create results folders case_name = 'goland_cs' case_nlin_info = 'M%dN%dMs%d_nmodes%d' % (M, N, M_star_fact, num_modes) case_rom_info = 'rom_MIMORA_r%d_sig%04d_%04dj' % (rom_settings['r'], frequency_continuous_k[-1].real * 100, frequency_continuous_k[-1].imag * 100) case_name += case_nlin_info + case_rom_info self.route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) fig_folder = self.route_test_dir + '/figures/' os.makedirs(fig_folder, exist_ok=True) # SHARPy nonlinear reference solution ws = wings.GolandControlSurface(M=M, N=N, Mstar_fact=M_star_fact, u_inf=u_inf, alpha=alpha_deg, cs_deflection=[0, 0], rho=rho, sweep=0, physical_time=2, n_surfaces=2, route=self.route_test_dir + '/cases', case_name=case_name) ws.gust_intensity = 0.01 ws.sigma = 1 ws.clean_test_files() ws.update_derived_params() ws.set_default_config_dict() ws.generate_aero_file() ws.generate_fem_file() frequency_continuous_w = 2 * u_inf * frequency_continuous_k / ws.c_ref rom_settings['frequency'] = frequency_continuous_w rom_settings['tangent_input_file'] = ws.route + '/' + ws.case_name + '.rom.h5' ws.config['SHARPy'] = { 'flow': ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'AerogridPlot', 'BeamPlot', 'Modal', 'LinearAssembler', 'FrequencyResponse', 'AsymptoticStability', 'SaveData', ], 'case': ws.case_name, 'route': ws.route, 'write_screen': 'off', 'write_log': 'on', 'log_folder': self.route_test_dir + '/output/', 'log_file': ws.case_name + '.log'} ws.config['BeamLoader'] = { 'unsteady': 'off', 'orientation': ws.quat} ws.config['AerogridLoader'] = { 'unsteady': 'off', 'aligned_grid': 'on', 'mstar': ws.Mstar_fact * ws.M, 'freestream_dir': ws.u_inf_direction, 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction, 'dt': ws.dt}} ws.config['StaticUvlm'] = { 'rho': ws.rho, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}, 'rollup_dt': ws.dt, 'print_info': 'on', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_aic_refresh': 0, 'rollup_tolerance': 1e-4} ws.config['StaticCoupled'] = { 'print_info': 'on', 'max_iter': 200, 'n_load_steps': 1, 'tolerance': 1e-10, 'relaxation_factor': 0., 'aero_solver': 'StaticUvlm', 'aero_solver_settings': { 'rho': ws.rho, 'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': ws.dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}}, 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 4, 'delta_curved': 1e-1, 'min_delta': 1e-10, 'gravity_on': 'on', 'gravity': 9.81}} ws.config['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0} ws.config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': ws.case_name + '_aeroforces.csv', 'screen_output': 'on', 'unsteady': 'off'} ws.config['BeamPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on'} ws.config['Modal'] = {'NumLambda': 20, 'rigid_body_modes': 'off', 'print_matrices': 'on', 'save_data': 'off', 'rigid_modes_cg': 'off', 'continuous_eigenvalues': 'off', 'dt': 0, 'plot_eigenvalues': False, 'max_rotation_deg': 15., 'max_displacement': 0.15, 'write_modes_vtk': True, 'use_undamped_modes': True} ws.config['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', 'linear_system_settings': { 'beam_settings': {'modal_projection': 'on', 'inout_coords': 'modes', 'discrete_time': 'on', 'newmark_damp': 0.5e-4, 'discr_method': 'newmark', 'dt': ws.dt, 'proj_modes': 'undamped', 'use_euler': 'off', 'num_modes': num_modes, 'print_info': 'on', 'gravity': 'on', 'remove_sym_modes': 'on', 'remove_dofs': []}, 'aero_settings': {'dt': ws.dt, 'ScalingDict': {'length': 0.5 * ws.c_ref, 'speed': u_inf, 'density': rho}, 'integr_order': integration_order, 'density': ws.rho, 'remove_predictor': remove_predictor, 'use_sparse': use_sparse, 'remove_inputs': ['u_gust'], 'rom_method': ['Krylov'], 'rom_method_settings': {'Krylov': rom_settings}}, }} ws.config['AsymptoticStability'] = {'print_info': True, 'velocity_analysis': [160, 180, 20]} ws.config['LinDynamicSim'] = {'dt': ws.dt, 'n_tsteps': ws.n_tstep, 'sys_id': 'LinearAeroelastic', 'postprocessors': ['BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'AerogridPlot': { 'u_inf': ws.u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}}} ws.config['FrequencyResponse'] = {'quick_plot': 'off', 'frequency_unit': 'k', 'frequency_bounds': [0.0001, 1.0], 'num_freqs': 100, 'frequency_spacing': 'log', 'target_system': ['aeroelastic'], } ws.config['SaveData'] = {'save_aero': 'off', 'save_struct': 'off', 'save_rom': 'on'} ws.config.write() self.data = sharpy.sharpy_main.main(['', ws.route + ws.case_name + '.sharpy']) def run_rom_stable(self): ssrom = self.data.linear.linear_system.uvlm.rom['Krylov'].ssrom eigs_rom = np.linalg.eigvals(ssrom.A) assert all(np.abs(eigs_rom) <= 1.), 'UVLM Krylov ROM is unstable - flutter speed may not be correct. Change' \ 'ROM settings to achieve stability' # print('ROM is stable') def run_flutter(self): flutter_ref_speed = 166 # at current discretisation # load results file - variables below determined by ``velocity_analysis`` setting in AsymptoticStability ulb = 160 # velocity lower bound uub = 180 # velocity upper bound num_u = 20 # n_speeds res = np.loadtxt(self.route_test_dir + '/output/%s/stability/' % self.data.settings['SHARPy']['case'] + '/velocity_analysis_min%04d_max%04d_nvel%04d.dat' % (ulb * 10, uub * 10, num_u), ) u_inf = res[:, 0] eval_real = res[:, 1] eval_imag = res[:, 2] # Flutter onset ind_zero_real = np.where(eval_real >= 0)[0][0] assert ind_zero_real > 0, 'Flutter speed not below 165.00 m/s' flutter_speed = 0.5 * (u_inf[ind_zero_real] + u_inf[ind_zero_real - 1]) flutter_frequency = np.sqrt(eval_real[ind_zero_real] ** 2 + eval_imag[ind_zero_real] ** 2) # print('Flutter speed = %.1f m/s' % flutter_speed) # print('Flutter frequency = %.2f rad/s' % flutter_frequency) assert np.abs( flutter_speed - flutter_ref_speed) / flutter_ref_speed < 1e-2, ' Flutter speed error greater than ' \ '1 percent. \nFlutter speed: %.2f m/s\n' \ 'Frequency: %.2f rad/s' % (flutter_speed, flutter_frequency) def test_flutter(self): self.setup() self.run_rom_stable() self.run_flutter() def tearDown(self): import shutil folders = ['cases', 'figures', 'output'] for folder in folders: shutil.rmtree(self.route_test_dir + '/' + folder) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/linear/gusts/__init__.py ================================================ ================================================ FILE: tests/linear/gusts/test_external_gust.py ================================================ import numpy as np import sharpy.utils.algebra as algebra import sharpy.sharpy_main as smain import unittest import sharpy.cases.templates.flying_wings as wings import os import shutil def generate_sharpy(alpha=0., case_name='hale_static', case_route='./', **kwargs): output_route = kwargs.get('output_route', './output/') m = kwargs.get('M', 4) rho = kwargs.get('rho', 1.225) tolerance = kwargs.get('tolerance', 1e-5) u_inf = kwargs.get('u_inf', 10) tsteps = kwargs.get('tsteps', 100) ws = wings.QuasiInfinite(M=m, aspect_ratio=10, N=8, Mstar_fact=10, u_inf=u_inf, alpha=alpha, rho=rho, sweep=0, n_surfaces=2, route=case_route, case_name=case_name) ws.gust_intensity = 0.01 ws.sigma = 1e-1 ws.clean_test_files() ws.update_derived_params() ws.set_default_config_dict() ws.generate_aero_file() ws.generate_fem_file() settings = dict() settings['SHARPy'] = {'case': case_name, 'route': case_route, 'flow': kwargs.get('flow', []), 'write_screen': 'off', 'write_log': 'on', 'log_folder': output_route, 'log_file': case_name + '.log'} settings['BeamLoader'] = {'unsteady': 'on', 'orientation': algebra.euler2quat(np.array([0., alpha, 0.]))} settings['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'mstar': int(kwargs.get('wake_length', 10) * m), 'control_surface_deflection': ['', ''], 'control_surface_deflection_generator_settings': {'0': {}, '1': {}}, 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': { 'u_inf': u_inf, 'u_inf_direction': [1., 0., 0.], 'dt': ws.dt, }, } settings['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-1, 'min_delta': tolerance, 'gravity_on': kwargs.get('gravity', 'on'), 'gravity': 9.81, 'initial_position': [0., 0., 0.]} settings['StaticUvlm'] = {'print_info': 'on', 'horseshoe': kwargs.get('horseshoe', 'off'), 'num_cores': 4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho} settings['StaticCoupled'] = {'print_info': 'off', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': settings['NonLinearStatic'], 'aero_solver': 'StaticUvlm', 'aero_solver_settings': settings['StaticUvlm'], 'max_iter': 100, 'n_load_steps': kwargs.get('n_load_steps', 1), 'tolerance': kwargs.get('fsi_tolerance', 1e-5), 'relaxation_factor': kwargs.get('relaxation_factor', 0.2)} settings['BeamPlot'] = {'include_FoR': 'on'} settings['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0, 'u_inf': u_inf } settings['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': 'aeroforces.txt', 'screen_output': 'on', 'coefficients': True, 'q_ref': 0.5 * rho * u_inf ** 2, 'S_ref': 12.809, } settings['BeamPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on', 'include_FoR': 'on'} struct_solver_settings = {'print_info': 'off', 'max_iterations': 950, 'delta_curved': 1e-6, 'min_delta': tolerance, 'newmark_damp': 5e-3, 'gravity_on': kwargs.get('gravity', 'on'), 'gravity': 9.81, 'num_steps': 1, 'dt': ws.dt} gust_vec = kwargs.get('nl_gust', None) if gust_vec is not None: t_dom = np.linspace(0, ws.dt * (tsteps-1), tsteps) np.savetxt(ws.route + '/gust.txt', np.column_stack(( t_dom, np.zeros_like(t_dom), np.zeros_like(t_dom), gust_vec ))) step_uvlm_settings = {'print_info': 'on', 'num_cores': 4, 'convection_scheme': 0, #ws.wake_type, 'vortex_radius': 1e-7, 'velocity_field_generator': 'GustVelocityField', 'velocity_field_input': {'u_inf': u_inf * 1, 'u_inf_direction': [1., 0., 0.], 'relative_motion': 'on', 'gust_shape': 'time varying', 'gust_parameters': { 'file': ws.route + '/gust.txt', }}, 'rho': rho, 'n_time_steps': tsteps, 'dt': ws.dt, 'gamma_dot_filtering': 3} settings['DynamicCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearDynamicPrescribedStep', 'structural_solver_settings': struct_solver_settings, 'aero_solver': 'StepUvlm', 'aero_solver_settings': step_uvlm_settings, 'fsi_substeps': 200, 'fsi_tolerance': tolerance, 'relaxation_factor': 0.3, 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': 0.5, 'n_time_steps': tsteps, # ws.n_tstep, 'dt': ws.dt, 'include_unsteady_force_contribution': 'on', 'steps_without_unsteady_force': 5, 'postprocessors': ['BeamPlot', 'AerogridPlot', 'WriteVariablesTime'], 'postprocessors_settings': {'BeamLoads': {'csv_output': 'off'}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}, 'AerogridPlot': { 'u_inf': u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, 'WriteVariablesTime': { 'cleanup_old_solution': 'on', 'delimiter': ',', 'FoR_variables': ['total_forces', 'total_gravity_forces', 'for_pos', 'quat'], }}} settings['Modal'] = {'print_info': True, 'use_undamped_modes': True, 'NumLambda': 50, 'rigid_body_modes': 'off', 'write_modes_vtk': 'on', 'print_matrices': 'on', 'save_data': 'on', 'continuous_eigenvalues': 'off', 'dt': ws.dt, 'plot_eigenvalues': False, 'rigid_modes_ppal_axes': 'on'} # ROM settings rom_settings = dict() rom_settings['algorithm'] = 'mimo_rational_arnoldi' rom_settings['r'] = 10 rom_settings['frequency'] = np.array([0], dtype=float) rom_settings['single_side'] = 'observability' settings['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', 'linearisation_tstep': -1, 'linear_system_settings': { 'beam_settings': {'modal_projection': 'off', 'inout_coords': 'modes', 'discrete_time': 'on', 'newmark_damp': 0.5e-4, 'discr_method': 'newmark', 'dt': ws.dt, 'proj_modes': 'undamped', 'use_euler': 'on', 'num_modes': 20, 'print_info': 'on', 'gravity': kwargs.get('gravity', 'on'), 'remove_dofs': [], 'remove_rigid_states': 'off'}, 'aero_settings': {'dt': ws.dt, 'integr_order': 2, 'density': rho, 'remove_predictor': 'off', 'use_sparse': False, 'vortex_radius': 1e-7, 'convert_to_ct': 'off', 'gust_assembler': kwargs.get('gust_name', ''), 'gust_assembler_inputs': kwargs.get('gust_settings', {}), }, 'track_body': 'on', 'use_euler': 'on', }, } settings['AsymptoticStability'] = { 'print_info': 'on', 'modes_to_plot': [], # 'velocity_analysis': [27, 29, 3], 'display_root_locus': 'off', 'frequency_cutoff': 0, 'export_eigenvalues': 'on', 'num_evals': 100} settings['FrequencyResponse'] = {'target_system': ['aeroelastic', 'aerodynamic', 'structural'], 'quick_plot': 'off', 'frequency_spacing': 'log', 'frequency_unit': 'w', 'frequency_bounds': [1e-3, 1e3], 'num_freqs': 200, 'print_info': 'on'} settings['PickleData'] = {} settings['LinDynamicSim'] = {'n_tsteps': tsteps, 'dt': ws.dt, 'write_dat': ['x', 'y', 'u'], 'input_generators': kwargs.get('linear_input_generators', []), 'postprocessors': ['AerogridPlot'], 'postprocessors_settings': {'AerogridPlot': {'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, } } case_file = ws.settings_to_config(settings) return case_file def run_case(case_file, pickle_file=None): if pickle_file is not None: restart = True else: restart = False if restart: data = smain.main(['', case_file, '-r', pickle_file]) else: data = smain.main(['', case_file]) return data class TestGustAssembly(unittest.TestCase): route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) directories = ['cases', 'output', 'inputs'] time_steps = 80 gust_intensity = 0.01 gust_ramp = 5 M = 4 u_inf = 1 gust_vec = np.zeros(time_steps) @classmethod def setUpClass(cls): for folder in cls.directories: os.makedirs(cls.route_test_dir + '/' + folder, exist_ok=True) # Gust input profile cls.gust_vec[5:cls.gust_ramp+5] = np.linspace(0, cls.gust_intensity, cls.gust_ramp) cls.gust_vec[5+ cls.gust_ramp:] = cls.gust_intensity @classmethod def tearDownClass(cls): for folder in cls.directories: if os.path.isdir(cls.route_test_dir + '/' + folder): shutil.rmtree(cls.route_test_dir + '/' + folder) def linear_response(self): np.savetxt(self.route_test_dir + '/inputs/gust_linear.txt', self.gust_vec) input_generators = [{'name': 'u_gust', 'index': 0, 'file_path': self.route_test_dir + '/inputs/gust_linear.txt'}] data = self.run_sharpy_linear(gust_name='LeadingEdge', linear_input_generators=input_generators)# return data def nonlinear_response(self): data = self.run_sharpy_nonlinear(nl_gust=self.gust_vec) return data def test_gust(self): linear = self.linear_response() nonlinear = self.nonlinear_response() final_linear = linear.aero.timestep_info[-1] final_nonlinear = nonlinear.aero.timestep_info[-1] with self.subTest('zeta'): np.testing.assert_almost_equal(final_linear.zeta[0][2, 0, -1], final_nonlinear.zeta[0][2, 0, -1], decimal=4) with self.subTest('forces'): np.testing.assert_almost_equal(np.sum(final_linear.forces[0][2, :, 0]), np.sum(final_nonlinear.forces[0][2, :, 0]), decimal=4) with self.subTest('gamma'): np.testing.assert_almost_equal(final_linear.gamma[0][2, 2], final_nonlinear.gamma[0][2, 2], decimal=4) def wingtip_timeseries(self, data): nsteps = len(data.aero.timestep_info) wingtip = np.zeros(nsteps) for i in range(nsteps): wingtip[i] = data.aero.timestep_info[i].zeta[0][2, 0, -1] return wingtip def gamma_timeseries(self, data): nsteps = len(data.aero.timestep_info) wingtip = np.zeros(nsteps) for i in range(nsteps): wingtip[i] = data.aero.timestep_info[i].gamma[0][0, -1] return wingtip def run_sharpy_nonlinear(self, **kwargs): flow = [ 'BeamLoader', 'AerogridLoader', 'StaticUvlm', 'DynamicCoupled', # 'AeroForcesCalculator', 'PickleData', ] alpha = 0 #2 * np.pi / 180 # Discretisation n_elem_multiplier = 1.5 wake_length = 10 horseshoe = False case_file = generate_sharpy(alpha=alpha, case_name='nonlinear', case_route=self.route_test_dir + '/cases/', output_route=self.route_test_dir + '/output/', flow=flow, u_inf=self.u_inf, M=self.M, n_elem_multiplier=n_elem_multiplier, horseshoe=horseshoe, wake_length=wake_length, relaxation_factor=0.6, tolerance=1e-5, gravity='off', fsi_tolerance=1e-5, tsteps=self.time_steps, **kwargs, ) data = run_case(case_file) return data def run_sharpy_linear(self, **kwargs): flow = [ 'BeamLoader', 'AerogridLoader', 'StaticCoupled', 'Modal', 'AeroForcesCalculator', 'LinearAssembler', 'LinDynamicSim', 'PickleData', ] restart = False if restart: flow = ['LinDynamicSim'] pickle_file = self.route_test_dir + '/output/linear.pkl' else: pickle_file = None alpha = 0 #2 * np.pi / 180 elevator = 0 #-0.5 * np.pi / 180 thrust = 0 #5 # Discretisation M = 4 n_elem_multiplier = 1.5 wake_length = 10 horseshoe = False case_file = generate_sharpy(alpha=alpha, case_name='linear', case_route=self.route_test_dir + '/cases/', output_route=self.route_test_dir + '/output', flow=flow, u_inf=self.u_inf, M=self.M, n_elem_multiplier=n_elem_multiplier, horseshoe=horseshoe, wake_length=wake_length, relaxation_factor=0.6, tolerance=1e-5, gravity='off', fsi_tolerance=1e-5, tsteps=self.time_steps, **kwargs, ) data = run_case(case_file, pickle_file) return data def assert_gust_propagation(self, path_to_input, output_route): x_sharpy = np.loadtxt(output_route + '/lindynamicsim/x_out.dat') u_in = np.loadtxt(path_to_input) np.testing.assert_array_almost_equal(x_sharpy[1:, 0], u_in[:-1]) def tearDown(self): folders = ['cases', 'output'] import shutil for folder in folders: path = self.route_test_dir + '/' + folder if os.path.isdir(path): shutil.rmtree(path) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/linear/gusts/test_linear_gusts.py ================================================ import numpy as np import os import unittest import sharpy.cases.templates.flying_wings as wings import sharpy.sharpy_main from sharpy.linear.assembler.lineargustassembler import campbell import pickle class TestGolandControlSurface(unittest.TestCase): def setUp(self): self.route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) def run_sharpy(self, flow, **kwargs): # Problem Set up u_inf = 2. alpha_deg = 0. rho = 1.02 num_modes = 4 restart = kwargs.get('restart', False) # Lattice Discretisation M = 4 N = 16 M_star_fact = 1 # Linear UVLM settings integration_order = 2 remove_predictor = kwargs.get('remove_predictor', False) use_sparse = True # Case Admin - Create results folders case_name = 'goland_cs' case_nlin_info = 'M%dN%dMs%d_nmodes%d' % (M, N, M_star_fact, num_modes) case_name += case_nlin_info fig_folder = self.route_test_dir + '/figures/' os.makedirs(fig_folder, exist_ok=True) # SHARPy nonlinear reference solution ws = wings.GolandControlSurface(M=M, N=N, Mstar_fact=M_star_fact, u_inf=u_inf, alpha=alpha_deg, # cs_deflection=[0, 0], rho=rho, sweep=0, physical_time=2, n_surfaces=2, route=self.route_test_dir + '/cases', case_name=case_name) ws.gust_intensity = 0.01 ws.sigma = 1 ws.clean_test_files() ws.update_derived_params() ws.set_default_config_dict() ws.generate_aero_file() ws.generate_fem_file() x0 = np.zeros(256) lin_tsteps = 40 u_vec = np.zeros((lin_tsteps, num_modes // 2 * 3 + 1 + 2)) # elevator u_vec[5:, 5] = 10 * np.pi / 180 # gust u_vec[:, 4] = np.sin(np.linspace(0, 2*np.pi, lin_tsteps)) ws.create_linear_files(x0, u_vec) np.savetxt(self.route_test_dir + '/cases/elevator.txt', u_vec[:, 5]) np.savetxt(self.route_test_dir + '/cases/gust.txt', u_vec[:, 4]) ws.config['SHARPy'] = { 'flow': flow, 'case': ws.case_name, 'route': ws.route, 'write_screen': 'off', 'write_log': 'on', 'log_folder': self.route_test_dir + '/output/' + ws.case_name + '/', 'log_file': ws.case_name + '.log'} ws.config['BeamLoader'] = { 'unsteady': 'off', 'orientation': ws.quat} ws.config['AerogridLoader'] = { 'unsteady': 'off', 'aligned_grid': 'on', 'mstar': ws.Mstar_fact * ws.M, 'freestream_dir': ws.u_inf_direction, 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction, 'dt': ws.dt}} ws.config['StaticUvlm'] = { 'rho': ws.rho, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}, 'rollup_dt': ws.dt, 'print_info': 'on', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_aic_refresh': 0, 'rollup_tolerance': 1e-4} ws.config['StaticCoupled'] = { 'print_info': 'on', 'max_iter': 200, 'n_load_steps': 1, 'tolerance': 1e-10, 'relaxation_factor': 0., 'aero_solver': 'StaticUvlm', 'aero_solver_settings': { 'rho': ws.rho, 'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': ws.dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}}, 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 4, 'delta_curved': 1e-1, 'min_delta': 1e-10, 'gravity_on': 'on', 'gravity': 9.81}} ws.config['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0} ws.config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': ws.case_name + '_aeroforces.csv', 'screen_output': 'on', 'unsteady': 'off'} ws.config['BeamPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on'} ws.config['Modal'] = {'NumLambda': 20, 'rigid_body_modes': 'off', 'print_matrices': 'on', 'save_data': 'off', 'rigid_modes_cg': 'off', 'continuous_eigenvalues': 'off', 'dt': 0, 'plot_eigenvalues': False, 'max_rotation_deg': 15., 'max_displacement': 0.15, 'write_modes_vtk': True, 'use_undamped_modes': True} ws.config['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', 'linear_system_settings': { 'beam_settings': {'modal_projection': 'on', 'inout_coords': 'modes', 'discrete_time': 'on', 'newmark_damp': 0.5e-4, 'discr_method': 'newmark', 'dt': ws.dt, 'proj_modes': 'undamped', 'use_euler': 'off', 'num_modes': num_modes, 'print_info': 'off', 'gravity': 'on', 'remove_sym_modes': 'on', 'remove_dofs': []}, 'aero_settings': {'dt': ws.dt, 'integr_order': integration_order, 'density': ws.rho, 'remove_predictor': remove_predictor, 'use_sparse': use_sparse, 'remove_inputs': [], 'gust_assembler': 'LeadingEdge', }, } } ws.config['LinDynamicSim'] = {'n_tsteps': lin_tsteps, 'dt': ws.dt, 'input_generators': [ {'name': 'control_surface_deflection', 'index': 0, 'file_path': self.route_test_dir + '/cases/elevator.txt'}, {'name': 'u_gust', 'index': 0, 'file_path': self.route_test_dir + '/cases/gust.txt'} ], 'postprocessors': ['AerogridPlot'], 'postprocessors_settings': {'AerogridPlot': {'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, } } ws.config['PickleData'] = {} ws.config.write() if not restart: data = sharpy.sharpy_main.main(['', ws.route + ws.case_name + '.sharpy']) else: with open(self.route_test_dir + '/output/{:s}.pkl'.format(ws.case_name), 'rb') as f: data = pickle.load(f) return data def run_linear_sharpy(self, **kwargs): flow = ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'Modal', 'LinearAssembler', 'LinDynamicSim', 'PickleData'] restart = False if restart: flow = ['LinDynamicSim'] data = self.run_sharpy(flow, restart=restart, **kwargs) return data def extract_u_inf(self, data): ts = len(data.aero.timestep_info) _, m_chord, n_span = data.aero.timestep_info[0].u_ext[0].shape u_inf_ext = np.zeros((ts, m_chord, n_span)) for i_ts in range(ts): u_inf_ext[i_ts, :, :] = data.aero.timestep_info[i_ts].u_ext[0][2, :, :] return u_inf_ext def test_linear_gust(self): test_conditions = [ {'remove_predictor': True}, {'remove_predictor': False}, ] for test in test_conditions: with self.subTest(test): data = self.run_linear_sharpy(**test) u_gust_in = np.loadtxt(self.route_test_dir + '/cases/gust.txt') u_inf_ext = self.extract_u_inf(data) if test['remove_predictor']: predictor_offset = 0 else: # input defined at time step n+1 predictor_offset = 1 # test leading edge value is equal to input np.testing.assert_array_almost_equal(u_inf_ext[1 + predictor_offset:, 0, 0], u_gust_in[:-1-predictor_offset]) # check convection in panels downstream for i_chord in range(1, u_inf_ext.shape[1]): np.testing.assert_array_almost_equal(u_inf_ext[predictor_offset + 1 + i_chord:-i_chord, i_chord, 0], u_inf_ext[predictor_offset + 1 + i_chord - 1:-(i_chord + 1), i_chord - 1, 0]) def tearDown(self): import shutil folders = ['cases', 'figures', 'output'] for folder in folders: if os.path.isdir(self.route_test_dir + '/' + folder): shutil.rmtree(self.route_test_dir + '/' + folder) class TestGusts(unittest.TestCase): def test_campbell(self): """ Test that the Campell approximation to the Von Karman filter is equivalent in continuous and discrete time """ sigma_w = 1 length_scale = 1 velocity = 1 dt = 1e-1 omega_w = np.logspace(-3, 0, 10) ss_ct = campbell(sigma_w, length_scale, velocity) ss_dt = campbell(sigma_w, length_scale, velocity, dt=dt) G_ct = ss_ct.freqresp(omega_w) G_dt = ss_dt.freqresp(omega_w) np.testing.assert_array_almost_equal(G_ct[0, 0, :].real, G_dt[0, 0, :].real, decimal=3) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/linear/horten/__init__.py ================================================ ================================================ FILE: tests/linear/horten/test_horten.py ================================================ import sharpy.utils.algebra as algebra from sharpy.cases.hangar.richards_wing import Baseline import sharpy.sharpy_main import numpy as np import configobj import unittest import os import shutil route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) def run_rom_convergence(case_name, case_route='./cases/', output_folder='./output/', **kwargs): M = kwargs.get('M', 4) N = kwargs.get('N', 11) Msf = kwargs.get('Msf', 10) trim = kwargs.get('trim', True) rho_fact = 1. track_body = True payload = 0 u_inf = kwargs.get('u_inf', 30) use_euler = True case_name += 'M%gN%gMsf%g_u%g' % (M, N, Msf, u_inf) # M4N11Msf5 alpha_deg = 3.974 cs_deflection = 0.3582 thrust = 4.8062 # ROM settings rom_settings = dict() rom_settings['algorithm'] = 'mimo_rational_arnoldi' rom_settings['r'] = 10 rom_settings['frequency'] = np.array([0], dtype=float) rom_settings['single_side'] = 'observability' case_name += 'rom_%g_%s' % (rom_settings['r'], rom_settings['single_side'][:3]) ws = Baseline(M=M, N=N, Mstarfactor=Msf, u_inf=u_inf, rho=1.02, alpha_deg=alpha_deg, # 7.7563783342984385, roll_deg=0, cs_deflection_deg=cs_deflection, # -6.733360628875144, thrust=thrust, # 10.140622253017584, physical_time=20, case_name=case_name, case_route=case_route, case_name_format=2) ws.set_properties() ws.initialise() ws.clean_test_files() # ws.update_mass_stiffness(sigma=1., sigma_mass=1.5) ws.update_mass_stiffness(sigma=.5, sigma_mass=1.0, payload=payload) ws.update_fem_prop() ws.generate_fem_file() ws.update_aero_properties() ws.generate_aero_file() flow = ['BeamLoader', 'AerogridLoader', 'StaticTrim', 'BeamPlot', 'AerogridPlot', 'AeroForcesCalculator', 'DynamicCoupled', 'Modal', 'LinearAssembler', 'AsymptoticStability', 'SaveParametricCase' ] if not trim: flow[2] = 'StaticCoupled' settings = dict() settings['SHARPy'] = {'case': ws.case_name, 'route': ws.case_route, 'flow': flow, 'write_screen': 'off', 'write_log': 'on', 'log_folder': output_folder, 'log_file': ws.case_name + '.log'} settings['BeamLoader'] = {'unsteady': 'off', 'orientation': algebra.euler2quat(np.array([ws.roll, ws.alpha, ws.beta]))} settings['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': int(ws.M * ws.Mstarfactor), 'freestream_dir': ['1', '0', '0'], 'control_surface_deflection': [''], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0., 0.], 'dt': ws.dt}, } if ws.horseshoe is True: settings['AerogridLoader']['mstar'] = 1 settings['StaticCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 200, 'num_load_steps': 1, 'delta_curved': 1e-5, 'min_delta': ws.tolerance, 'gravity_on': 'on', 'gravity': 9.81}, 'aero_solver': 'StaticUvlm', 'aero_solver_settings': {'print_info': 'on', 'horseshoe': ws.horseshoe, 'num_cores': 4, 'n_rollup': int(0), 'rollup_dt': ws.dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'vortex_radius': 1e-6, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': ws.u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': ws.rho}, 'max_iter': 200, 'n_load_steps': 1, 'tolerance': ws.tolerance, 'relaxation_factor': 0.2} settings['StaticTrim'] = {'solver': 'StaticCoupled', 'solver_settings': settings['StaticCoupled'], 'thrust_nodes': ws.thrust_nodes, 'initial_alpha': ws.alpha, 'initial_deflection': ws.cs_deflection, 'initial_thrust': ws.thrust, 'max_iter': 200, 'fz_tolerance': 1e-2, 'fx_tolerance': 1e-2, 'm_tolerance': 1e-2, 'save_info': 'on'} settings['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0, 'u_inf': ws.u_inf } settings['AeroForcesCalculator'] = {'write_text_file': 'off', 'text_file_name': ws.case_name + '_aeroforces.csv', 'screen_output': 'on', 'coefficients': True, 'q_ref': 0.5 * ws.rho * ws.u_inf ** 2, 'S_ref': 12.809, } settings['BeamPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on', 'include_FoR': 'on'} struct_solver_settings = {'print_info': 'off', 'initial_velocity_direction': [-1., 0., 0.], 'max_iterations': 950, 'delta_curved': 1e-6, 'min_delta': ws.tolerance, 'newmark_damp': 5e-3, 'gravity_on': True, 'gravity': 9.81, 'num_steps': ws.n_tstep, 'dt': ws.dt, 'initial_velocity': ws.u_inf * 1} step_uvlm_settings = {'print_info': 'on', 'num_cores': 4, 'convection_scheme': ws.wake_type, 'vortex_radius': 1e-6, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': ws.u_inf * 0, 'u_inf_direction': [1., 0., 0.]}, 'rho': ws.rho, 'n_time_steps': ws.n_tstep, 'dt': ws.dt, 'gamma_dot_filtering': 3} settings['DynamicCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearDynamicCoupledStep', 'structural_solver_settings': struct_solver_settings, 'aero_solver': 'StepUvlm', 'aero_solver_settings': step_uvlm_settings, 'fsi_substeps': 200, 'fsi_tolerance': ws.fsi_tolerance, 'relaxation_factor': ws.relaxation_factor, 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': 0.5, 'n_time_steps': 1, # ws.n_tstep, 'dt': ws.dt, 'include_unsteady_force_contribution': 'off', 'postprocessors': ['BeamLoads', 'BeamPlot', 'AerogridPlot', 'WriteVariablesTime'], 'postprocessors_settings': {'BeamLoads': {'csv_output': 'off'}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}, 'AerogridPlot': { 'u_inf': ws.u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, 'WriteVariablesTime': { 'cleanup_old_solution': 'on', 'delimiter': ',', 'FoR_variables': ['total_forces', 'total_gravity_forces', 'for_pos', 'quat'], }}} settings['Modal'] = {'print_info': True, 'use_undamped_modes': True, 'NumLambda': 30, 'rigid_body_modes': True, 'write_modes_vtk': 'on', 'print_matrices': 'on', 'save_data': 'on', 'continuous_eigenvalues': 'off', 'dt': ws.dt, 'plot_eigenvalues': False, 'rigid_modes_cg': False} settings['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', 'linearisation_tstep': -1, 'linear_system_settings': { 'beam_settings': {'modal_projection': 'on', 'inout_coords': 'modes', 'discrete_time': True, 'newmark_damp': 0.5e-2, 'discr_method': 'newmark', 'dt': ws.dt, 'proj_modes': 'undamped', 'use_euler': use_euler, 'num_modes': 20, 'print_info': 'on', 'gravity': 'on', 'remove_dofs': []}, 'aero_settings': {'dt': ws.dt, 'integr_order': 2, 'density': ws.rho * rho_fact, 'remove_predictor': False, 'use_sparse': False, 'vortex_radius': 1e-6, 'remove_inputs': ['u_gust'], 'rom_method': ['Krylov'], 'rom_method_settings': {'Krylov': rom_settings}}, 'track_body': track_body, 'use_euler': use_euler, }} settings['AsymptoticStability'] = { 'print_info': 'on', 'modes_to_plot': [], # 'velocity_analysis': [27, 29, 3], 'display_root_locus': 'off', 'frequency_cutoff': 0, 'export_eigenvalues': 'on', 'target_system': ['aeroelastic', 'aerodynamic', 'structural'], 'output_file_format': 'dat', 'num_evals': 100} settings['FrequencyResponse'] = {'print_info': 'on', 'frequency_bounds': [0.1, 100]} settings['LinDynamicSim'] = {'dt': ws.dt, 'n_tsteps': ws.n_tstep, 'sys_id': 'LinearAeroelastic', # 'reference_velocity': ws.u_inf, 'write_dat': ['x', 'y', 't', 'u'], # 'write_dat': 'on', 'postprocessors': ['BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'AerogridPlot': { 'u_inf': ws.u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}}} settings['StabilityDerivatives'] = {'u_inf': ws.u_inf, 'S_ref': 12.809, 'b_ref': ws.span, 'c_ref': 0.719} settings['SaveData'] = {'save_aero': 'off', 'save_struct': 'off', 'save_linear': 'on', 'save_linear_uvlm': 'on'} settings['PickleData'] = {} settings['SaveParametricCase'] = {'parameters': {'r': rom_settings['r']}} config = configobj.ConfigObj() file_name = ws.case_route + '/' + ws.case_name + '.solver.txt' config.filename = file_name for k, v in settings.items(): config[k] = v config.write() return sharpy.sharpy_main.main(['', ws.case_route + '/' + ws.case_name + '.solver.txt']) class TestHortenWing(unittest.TestCase): def test_horten(self): M = 4 N = 11 Msf = 5 trim = False case_name = 'horten_' case_route = route_test_dir + '/cases/' output_route = route_test_dir + '/output/' data = run_rom_convergence(case_name=case_name, case_route=case_route, output_folder=output_route, M=M, N=N, Msf=Msf, trim=trim) # check first 10 eigs are zero (9 integro states + yaw) path_to_eigs = data.output_folder + '/stability/aeroelastic_eigenvalues.dat' eigs = np.loadtxt(path_to_eigs) # check that aerodynamic and structural eigs are also written target_system = ['aerodynamic', 'structural'] for sys in target_system: np.loadtxt(data.output_folder + f'/stability/{sys}_eigenvalues.dat') np.testing.assert_allclose(np.abs(eigs[:10]), 0, atol=1e-8) # check that the phugoid mode is there (more or less, very high tolerance) phugoid_eigs = eigs[11] wn_phugoid = np.linalg.norm(phugoid_eigs) period_phugoid = 2 * np.pi / wn_phugoid np.testing.assert_allclose(period_phugoid, 20.7, atol=0.1, rtol=0.1) def tearDown(self): folders = ['output/', 'cases/'] for folder in folders: if os.path.isdir(route_test_dir + '/' + folder): shutil.rmtree(route_test_dir + '/' + folder) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/linear/rom/__init__.py ================================================ ================================================ FILE: tests/linear/rom/test_balancing.py ================================================ import copy import unittest import sharpy.linear.src.libss as libss import sharpy.rom.utils.librom as librom import numpy as np import sharpy.linear.src.libsparse as libsp import scipy.linalg as scalg class TestBalancing(unittest.TestCase): """ Test Balancing ROM methods """ def test_balreal_direct_py(self): Nx, Nu, Ny = 6, 4, 2 ss = libss.random_ss(Nx, Nu, Ny, dt=0.1, stable=True) ### direct balancing hsv, T, Ti = librom.balreal_direct_py(ss.A, ss.B, ss.C, DLTI=True, full_outputs=False) ssb = copy.deepcopy(ss) # Note: notation below is correct and consistent with documentation # SHARPy historically uses notation different from regular literature notation (i.e. 'swapped') ssb.project(Ti, T) # Compare freq. resp. - inconclusive! # The system is consistently transformed using T and Tinv - system dynamics do not change, independent of # choice of T and Tinv. Frequency response will yield the same response: kv = np.linspace(0.01, 10) Y = ss.freqresp(kv) Yb = ssb.freqresp(kv) er_max = np.max(np.abs(Yb - Y)) assert er_max / np.max(np.abs(Y)) < 1e-10, 'Error too large in frequency response' # Compare grammians: Wc = scalg.solve_discrete_lyapunov(ssb.A, np.dot(ssb.B, ssb.B.T)) Wo = scalg.solve_discrete_lyapunov(ssb.A.T, np.dot(ssb.C.T, ssb.C)) er_grammians = np.max(np.abs(Wc - Wo)) # Print grammians to compare: if er_grammians / np.max(np.abs(Wc)) > 1e-10: print('Controllability grammian, Wc:\n', Wc) print('Observability grammian, Wo:\n', Wo) er_hankel = np.max(np.abs(np.diag(hsv) - Wc)) # Print hsv to compare: if er_hankel / np.max(np.abs(Wc)) > 1e-10: print('Controllability grammian, Wc:\n', Wc) print('Hankel values matrix, HSV:\n', hsv) assert er_grammians / np.max(np.abs(Wc)) < 1e-10, 'Relative error in Wc-Wo is too large -> Wc != Wo' assert er_hankel / np.max(np.abs(Wc)) < 1e-10, 'Relative error in Wc-HSV is too large -> Wc != HSV' # The test below is inconclusive for the direct procedure! T and Tinv are produced from svd(M) # This means that going back from svd(M) to T and Tinv will yield the same result for any choice of T and Tinv # Unless something else is wrong (e.g. a typo) - so leaving it in. # test full_outputs option hsv, U, Vh, Qc, Qo = librom.balreal_direct_py(ss.A, ss.B, ss.C, DLTI=True, full_outputs=True) # build M matrix and SVD sinv = hsv ** (-0.5) T2 = libsp.dot(Qc, Vh.T * sinv) Ti2 = np.dot((U * sinv).T, Qo.T) assert np.linalg.norm(T2 - T) < 1e-13, 'Error too large' assert np.linalg.norm(Ti2 - Ti) < 1e-13, 'Error too large' ssb2 = copy.deepcopy(ss) ssb2.project(Ti2, T2) Yb2 = ssb2.freqresp(kv) er_max = np.max(np.abs(Yb2 - Y)) assert er_max / np.max(np.abs(Y)) < 1e-10, 'Error too large' ================================================ FILE: tests/linear/rom/test_krylov.py ================================================ """ Test Krylov ROM using Hospital Building Model """ import os import unittest import numpy as np import sharpy.utils.frequencyutils import sharpy.utils.cout_utils as cout import scipy.io as scio import sharpy.utils.sharpydir as sharpydir import sharpy.linear.src.libss as libss import sharpy.rom.krylov as krylov import sharpy.linear.src.libsparse as libsp class TestKrylov(unittest.TestCase): test_dir = sharpydir.SharpyDir + '/tests/linear/rom' def setUp(self): cout.cout_wrap.initialise(False, False) A = scio.loadmat(TestKrylov.test_dir + '/src/' + 'A.mat') B = scio.loadmat(TestKrylov.test_dir + '/src/' + 'B.mat') C = scio.loadmat(TestKrylov.test_dir + '/src/' + 'C.mat') A = libsp.csc_matrix(A['A']) B = B['B'] C = C['C'] D = np.zeros((B.shape[1], C.shape[0])) A = A.todense() self.ss = libss.StateSpace(A, B, C, D) self.rom = krylov.Krylov() if not os.path.exists(self.test_dir + '/figs/'): os.makedirs(self.test_dir + '/figs/') def run_test(self, test_settings): self.rom.initialise(test_settings) ssrom = self.rom.run(self.ss) # self.rom.restart() frequency = test_settings['frequency'].imag if frequency[0] != 0.: wv = np.logspace(np.log10(np.min(frequency))-0.5, np.log10(np.max(frequency))+0.5, 100) else: wv = np.logspace(-1, 2, 100) Y_fom = self.ss.freqresp(wv) Y_rom = ssrom.freqresp(wv) max_error = sharpy.utils.frequencyutils.frequency_error(Y_fom, Y_rom, wv) # can be used for debugging # import matplotlib.pyplot as plt # fig = plt.figure() # plt.semilogx(wv, Y_fom[0, 0, :].real) # plt.semilogx(wv, Y_rom[0, 0, :].real) # # fig.savefig(self.test_dir + '/figs/%sfreqresp.png' %test_settings['algorithm']) assert np.log10(max_error) < -2, 'Significant mismatch in ROM frequency Response' # check saving function if os.path.isfile(self.test_dir + '/rom_data.h5'): os.remove(self.test_dir + '/rom_data.h5') self.rom.save(self.test_dir + '/rom_data.h5') def test_krylov(self): algorithm_list = { 'one_sided_arnoldi': {'r': 48, 'frequency': np.array([0])}, 'dual_rational_arnoldi': {'r': 48, 'frequency': np.array([0])} } for algorithm in list(algorithm_list.keys()): with self.subTest(algorithm=algorithm): test_settings = {'algorithm': algorithm, 'r': algorithm_list[algorithm]['r'], 'frequency': algorithm_list[algorithm]['frequency']} self.run_test(test_settings) def tearDown(self): import shutil shutil.rmtree(self.test_dir + '/figs/') os.remove(self.test_dir + '/rom_data.h5') if __name__ == '__main__': unittest.main() ================================================ FILE: tests/linear/rom/test_rom_framework.py ================================================ import numpy as np import os import unittest import sharpy.cases.templates.flying_wings as wings import sharpy.sharpy_main class TestROMFramework(unittest.TestCase): """ Test verifyies the execution of the balancing ROMs (i.e. checks that everything runs rather than checking against a benchmark case """ def setUp(self): # Problem Set up u_inf = 1. alpha_deg = 0. rho = 1.02 num_modes = 4 # Lattice Discretisation M = 4 N = 8 M_star_fact = 1 # Linear UVLM settings integration_order = 2 remove_predictor = False use_sparse = False # Case Admin - Create results folders case_name = 'goland_cs' case_nlin_info = 'M%dN%dMs%d_nmodes%d' % (M, N, M_star_fact, num_modes) case_name += case_nlin_info self.route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) fig_folder = self.route_test_dir + '/figures/' os.makedirs(fig_folder, exist_ok=True) # SHARPy nonlinear reference solution ws = wings.Goland(M=M, N=N, Mstar_fact=M_star_fact, u_inf=u_inf, alpha=alpha_deg, rho=rho, sweep=0, physical_time=2, n_surfaces=2, route=self.route_test_dir + '/cases', case_name=case_name) ws.gust_intensity = 0.01 ws.sigma = 1 ws.horseshoe = True ws.clean_test_files() ws.update_derived_params() ws.set_default_config_dict() ws.generate_aero_file() ws.generate_fem_file() ws.config['SHARPy'] = { 'flow': ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'AerogridPlot', 'BeamPlot', 'Modal', 'LinearAssembler', 'FrequencyResponse', ], 'case': ws.case_name, 'route': ws.route, 'write_screen': 'off', 'write_log': 'on', 'log_folder': self.route_test_dir + '/output/', 'log_file': ws.case_name + '.log'} ws.config['BeamLoader'] = { 'unsteady': 'off', 'orientation': ws.quat} ws.config['AerogridLoader'] = { 'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 1, 'freestream_dir': ws.u_inf_direction, 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction, 'dt': ws.dt} } ws.config['StaticUvlm'] = { 'rho': ws.rho, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}, 'rollup_dt': ws.dt, 'print_info': 'on', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_aic_refresh': 0, 'rollup_tolerance': 1e-4} ws.config['StaticCoupled'] = { 'print_info': 'on', 'max_iter': 200, 'n_load_steps': 1, 'tolerance': 1e-10, 'relaxation_factor': 0., 'aero_solver': 'StaticUvlm', 'aero_solver_settings': { 'rho': ws.rho, 'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': ws.dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': { 'u_inf': ws.u_inf, 'u_inf_direction': ws.u_inf_direction}}, 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 4, 'delta_curved': 1e-1, 'min_delta': 1e-10, 'gravity_on': 'on', 'gravity': 9.754}} ws.config['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0} ws.config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': ws.case_name + '_aeroforces.csv', 'screen_output': 'on', 'unsteady': 'off'} ws.config['BeamPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on'} ws.config['Modal'] = {'NumLambda': 20, 'rigid_body_modes': 'off', 'print_matrices': 'on', 'save_data': 'off', 'rigid_modes_cg': 'off', 'continuous_eigenvalues': 'off', 'dt': 0, 'plot_eigenvalues': False, 'max_rotation_deg': 15., 'max_displacement': 0.15, 'write_modes_vtk': True, 'use_undamped_modes': True} ws.config['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', 'linear_system_settings': { 'beam_settings': {'modal_projection': 'on', 'inout_coords': 'modes', 'discrete_time': 'on', 'newmark_damp': 0.5e-4, 'discr_method': 'newmark', 'dt': ws.dt, 'proj_modes': 'undamped', 'use_euler': 'off', 'num_modes': num_modes, 'print_info': 'on', 'gravity': 'on', 'remove_sym_modes': 'on', 'remove_dofs': []}, 'aero_settings': {'dt': ws.dt, 'ScalingDict': {'length': 0.5 * ws.c_ref, 'speed': u_inf, 'density': rho}, 'integr_order': integration_order, 'density': ws.rho, 'remove_predictor': remove_predictor, 'use_sparse': use_sparse, 'remove_inputs': ['u_gust'], }, } } ws.config['LinDynamicSim'] = {'dt': ws.dt, 'n_tsteps': ws.n_tstep, 'sys_id': 'LinearAeroelastic', 'postprocessors': ['BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'AerogridPlot': { 'u_inf': ws.u_inf, 'include_rbm': 'on', 'include_applied_forces': 'on', 'minus_m_star': 0}, 'BeamPlot': {'include_rbm': 'on', 'include_applied_forces': 'on'}}} ws.config['FrequencyResponse'] = {'quick_plot': 'off', 'frequency_unit': 'k', 'frequency_bounds': [0.0001, 1.0], 'target_system': ['aeroelastic'] } self.ws = ws def test_roms(self): settings = dict() settings['Direct'] = {'algorithm': 'Direct', 'print_info': 'off', 'algorithm_settings': {'tune': 'off', 'use_schur': 'on', 'reduction_method': 'realisation'}} settings['FrequencyLimited'] = {'algorithm': 'FrequencyLimited', 'print_info': 'off', 'algorithm_settings': {'frequency': 0.8, 'method_low': 'trapz', 'options_low': {'points': 3}, 'method_high': 'gauss', 'options_high': {'partitions': 2, 'order': 1}, 'check_stability': True}} settings['Iterative'] = {'algorithm': 'Iterative', 'print_info': 'off', 'algorithm_settings': {'lowrank': 'on'}} for rom in settings: with self.subTest(rom): self.ws.config['LinearAssembler']['linear_system_settings']['aero_settings']['rom_method'] = [ 'Balanced'] rom_settings = {'Balanced': settings[rom]} self.ws.config['LinearAssembler']['linear_system_settings']['aero_settings']['rom_method_settings'] = \ rom_settings self.ws.config.write() sharpy.sharpy_main.main(['', self.ws.route + self.ws.case_name + '.sharpy']) def tearDown(self): import shutil folders = ['cases', 'figures', 'output'] for folder in folders: try: shutil.rmtree(self.route_test_dir + '/' + folder) except FileNotFoundError: pass if __name__ == '__main__': unittest.main() ================================================ FILE: tests/linear/rom/test_schur.py ================================================ """Test Schur Removal of Unstable Eigenvalues """ import numpy as np import unittest import sharpy.rom.krylov as krylov import os import sharpy.utils.cout_utils as coututils class TestSchurDecomposition(unittest.TestCase): test_dir = os.path.abspath(os.path.dirname(__file__)) A = np.loadtxt(test_dir + '/src/schur_A.dat') eigsA = np.linalg.eigvals(A) def setUp(self): coututils.start_writer() def test_dt(self): """ Discrete time system test. Ensures that all eigenvalues inside the unit circle are preserved. """ A = TestSchurDecomposition.A eigsA = TestSchurDecomposition.eigsA rom = krylov.Krylov() TL, TR = rom.stable_realisation(A, ct=False) n_stable_fom = np.sum(np.abs(eigsA) <= 1) Ap = TL.T.dot(A.dot(TR)) eigsAp = np.linalg.eigvals(Ap) n_stable_rom = np.sum(np.abs(eigsAp) <= 1) assert n_stable_rom == n_stable_fom, 'Number of stable eigenvalues not preserved during decomposition' def test_ct(self): """ Continuous time system test. Ensures that all eigenvalues in the left hand plane are preserved. """ A = TestSchurDecomposition.A eigsA = TestSchurDecomposition.eigsA rom = krylov.Krylov() TL, TR = rom.stable_realisation(A, ct=True) n_stable_fom = np.sum(eigsA.real <= 0) Ap = TL.T.dot(A.dot(TR)) eigsAp = np.linalg.eigvals(Ap) n_stable_rom = np.sum(eigsAp.real <= 0) assert n_stable_rom == n_stable_fom, 'Number of stable eigenvalues not preserved during decomposition' def tearDown(self): coututils.finish_writer() ================================================ FILE: tests/linear/rom/test_springmasssystem.py ================================================ """ Generate a mass spring system NGoizueta 16 Feb 2019 """ import numpy as np import sharpy.linear.src.libss as libss import sharpy.linear.src.lingebm as lingebm import sharpy.rom.krylov as krylov import unittest # import matplotlib.pyplot as plt import sharpy.utils.cout_utils as cout cout.cout_wrap.initialise(False, False) @unittest.skip('Not a robust test case that is giving too many failures. Use test_krylov instead') class TestKrylovRom(unittest.TestCase): tolerance = 1e-3 display_output = True def test_siso_ct(self): system_inputs = 'SISO' system_time = 'ct' ss = self.build_system(system_inputs, system_time) algorithm = 'two_sided_arnoldi' interpolation_point = np.array([1.0j]) r = 7 print('\nTesting CT, SISO rational Arnoldi...') rom = self.run_rom(ss, algorithm, r, interpolation_point) wv = np.logspace(-1, 3, 1000) freq_error = self.compare_freq_resp(rom, wv, interpolation_point) print('Frequency Response Error at %.2f rad/s: %.2e' % (interpolation_point.imag, freq_error)) self.assertTrue(freq_error < self.tolerance) def test_siso_dt(self): system_inputs = 'SISO' system_time = 'dt' ss = self.build_system(system_inputs, system_time) algorithm = 'two_sided_arnoldi' interpolation_point_ct = np.array([0.8j]) r = 7 print('\nTesting DT, SISO rational Arnoldi...') rom = self.run_rom(ss, algorithm, r, interpolation_point_ct) wv = np.logspace(-1, 3, 1000) freq_error = self.compare_freq_resp(rom, wv, interpolation_point_ct) print('Frequency Response Error at %.2f rad/s: %.2e' % (interpolation_point_ct.imag, freq_error)) self.assertTrue(freq_error < self.tolerance) def test_siso_dt_multipoint(self): system_inputs = 'SISO' system_time = 'dt' ss = self.build_system(system_inputs, system_time) algorithm = 'dual_rational_arnoldi' interpolation_point_ct = np.array([0.0, 2.0j, 11.0j]) r = 7 print('\nTesting DT, SISO Multipoint rational Arnoldi...') rom = self.run_rom(ss, algorithm, r, interpolation_point_ct) wv = np.logspace(-1, 3, 1000) for i in range(len(interpolation_point_ct)): freq_error = self.compare_freq_resp(rom, wv, interpolation_point_ct[i]) print('Frequency Response Error at %.2f rad/s: %.2e' % (interpolation_point_ct[i].imag, freq_error)) self.assertTrue(freq_error < self.tolerance) def build_system(self, system_inputs, system_time): N = 5 # Number of masses/springs/dampers k_db = np.linspace(1, 10, N) # Stiffness database m_db = np.logspace(2, 0, N) # Mass database C_db = np.ones(N) * 1e-1 # Damping database # Build mass matrix m = np.zeros((N, N)) k = np.zeros((N, N)) C = np.zeros((N, N)) m[0, 0] = m_db[0] k[0, 0:2] = [k_db[0]+k_db[1], -k_db[1]] C[0, 0:2] = [C_db[0] + C_db[1], -C_db[1]] for i in range(1, N-1): k[i, i-1:i+2] = [-k_db[i-1], k_db[i]+k_db[i+1], -k_db[i+1]] C[i, i-1:i+2] = [-C_db[i-1], C_db[i]+C_db[i+1], -C_db[i+1]] m[i, i] = m_db[i] m[-1, -1] = m_db[-1] k[-1, -2:] = [-k_db[-1], k_db[-1]] C[-1, -2:] = [-C_db[-1], C_db[-1]] # Input: Forces, Output: Displacements if system_inputs == 'MIMO': b = np.zeros((2*N, N)) b[N:, :] = np.eye(N) # Output rn c = np.zeros((N, 2*N)) c[:, :N] = np.eye(N) d = np.zeros((N, N)) else: b = np.zeros((2*N,)) b[-1] = 1. c = np.zeros((1, 2*N)) c[0, N-1] = 1 d = np.zeros(1) # Plant matrix Minv = np.linalg.inv(m) MinvK = Minv.dot(k) A = np.zeros((2*N, 2*N)) A[:N, N:] = np.eye(N) A[N:, :N] = -MinvK A[N:, N:] = -Minv.dot(C) # Build State Space if system_time == 'ct': system = libss.StateSpace(A, b, c, d, dt=None) else: # Discrete time system dt = 1e-2 Adt, Bdt, Cdt, Ddt = lingebm.newmark_ss(m, C, k, dt=dt, num_damp=0) system = libss.StateSpace(Adt, Bdt, Cdt, Ddt, dt=dt) # SISO Gains for DT system if system_inputs == 'SISO': b_dt = np.zeros((N)) b_dt[-1] = 1 system.addGain(b_dt, 'in') system.addGain(c, where='out') return system def run_rom(self, system, algorithm, r, interpolation_point): rom = krylov.Krylov() rom_settings = {'algorithm': algorithm, 'r': r, 'frequency': interpolation_point} rom.initialise(in_settings=rom_settings) rom.run(system) return rom def compare_freq_resp(self, rom, wv, interpolation_frequency, show_plots=False): Y_fom = rom.ss.freqresp(wv) Y_rom = rom.ssrom.freqresp(wv) interpol_index = np.argwhere(wv >= interpolation_frequency.imag)[0] error = np.abs(Y_fom[0, 0, interpol_index] - Y_rom[0, 0, interpol_index]) if TestKrylovRom.display_output: pass # fig, ax = plt.subplots(nrows=2) # ax[0].semilogx(wv, np.abs(Y_fom[0, 0, :]), 'k-') # ax[0].semilogx(wv, np.abs(Y_rom[0, 0, :]), '--', color='0.2') # ax[1].semilogx(wv, np.angle(Y_fom[0, 0, :]), 'k-') # ax[1].semilogx(wv, np.angle(Y_rom[0, 0, :]), '--', color='0.2') # fig.show() return error def tearDown(self): cout.cout_wrap = cout.Writer() # evals_DT = np.linalg.eigvals(system_DT.A) # # evals_dt_conv = np.log(evals_DT) / dt # # # plt.scatter(evals_ss.real, evals_ss.imag, marker='s') # # plt.scatter(evals_dt_conv.real, evals_dt_conv.imag, marker='^') # # plt.show() # # wv = np.logspace(-1, 1, 1000) # freqresp = system_DT.freqresp(wv) # freqresp_ct = system_CT.freqresp(wv) # # # fig, ax = plt.subplots(nrows=1) # # bode_mag_dt = (freqresp[0, 0, :].real) # # bode_mag_ct = (freqresp_ct[0, 0, :].real) # # ax.semilogx(wv, bode_mag_dt) # # ax.semilogx(wv, bode_mag_ct, ls='--') # # # # fig.show() # # print('Routine Complete') # # # ROM # rom = krylov.KrylovReducedOrderModel() # rom.initialise(data=None,ss=system_DT) # # algorithm = 'dual_rational_arnoldi' # # algorithm = 'arnoldi' # r = 1 # # frequency = np.array([1.0, 1.005j]) # # frequency = np.array([np.inf]) # frequency = np.array([0.7j, 1.0j]) # z_interpolation = np.exp(frequency*dt) # # rom.run(algorithm,r, frequency=z_interpolation) # # plot_freq = freq_plots.FrequencyResponseComparison() # plot_settings = {'frequency_type': 'w', # 'plot_type': 'bode'} # # plot_freq.initialise(None, system_DT, rom, plot_settings) # if system_inputs == 'MIMO': # plot_freq.plot_frequency_response(wv, freqresp[:3, :3, :], rom.ssrom.freqresp(wv)[:3, :3, :], frequency) # else: # plot_freq.plot_frequency_response(wv, freqresp, rom.ssrom.freqresp(wv), frequency) # plot_freq.plot_frequency_response(wv, freqresp[4:, 4:, :], rom.ssrom.freqresp(wv), frequency) # plot_freq.save_figure('DT_07_1_r2.png') if __name__=='__main__': unittest.main() ================================================ FILE: tests/linear/statespace/__init__.py ================================================ ================================================ FILE: tests/linear/statespace/test_statespace.py ================================================ import copy import unittest import os import numpy as np from sharpy.linear.src import libsparse as libsp from sharpy.linear.src.libss import StateSpace, SSconv, compare_ss, scale_SS, Gain, random_ss, couple, join, disc2cont, series from sharpy.linear.utils.ss_interface import LinearVector, InputVariable, StateVariable, OutputVariable class Test_dlti(unittest.TestCase): """ Test methods into this module for DLTI systems """ def setUp(self): # allocate some state-space model (dense and sparse) dt = 0.3 Ny, Nx, Nu = 8, 3, 5 A = np.random.rand(Nx, Nx) B = np.random.rand(Nx, Nu) C = np.random.rand(Ny, Nx) D = np.random.rand(Ny, Nu) self.SS = StateSpace(A, B, C, D, dt=dt) self.SSsp = StateSpace(libsp.csc_matrix(A), libsp.csc_matrix(B), C, D, dt=dt) self.SS.input_variables = LinearVector([InputVariable('input1', size=3, index=0), InputVariable('input2', size=2, index=1)]) self.SS.state_variables = LinearVector([StateVariable('state1', size=3, index=0)]) self.SS.output_variables = LinearVector([OutputVariable('output1', size=3, index=0), OutputVariable('output2', size=5, index=1)]) self.SSsp.input_variables = self.SS.input_variables self.SSsp.output_variables = self.SS.output_variables self.SSsp.state_variables = self.SS.state_variables def test_SSconv(self): SS = self.SS SSsp = self.SSsp Nu, Nx, Ny = SS.inputs, SS.states, SS.outputs A, B, C, D = SS.get_mats() # remove predictor: try different scenario B1 = np.random.rand(Nx, Nu) SSpr0 = StateSpace(*SSconv(A, B, B1, C, D), dt=0.3) SSpr1 = StateSpace(*SSconv(A, B, libsp.csc_matrix(B1), C, D), dt=0.3) SSpr2 = StateSpace(*SSconv( libsp.csc_matrix(A), B, libsp.csc_matrix(B1), C, D), dt=0.3) SSpr3 = StateSpace(*SSconv( libsp.csc_matrix(A), libsp.csc_matrix(B), B1, C, D), dt=0.3) SSpr4 = StateSpace(*SSconv( libsp.csc_matrix(A), libsp.csc_matrix(B), libsp.csc_matrix(B1), C, D), dt=0.3) compare_ss(SSpr0, SSpr1) compare_ss(SSpr0, SSpr2) compare_ss(SSpr0, SSpr3) compare_ss(SSpr0, SSpr4) def test_scale_SS(self): SS = self.SS SSsp = self.SSsp Nu, Nx, Ny = SS.inputs, SS.states, SS.outputs # scale (hard-copy) insc = np.random.rand(Nu) stsc = np.random.rand(Nx) outsc = np.random.rand(Ny) SSadim = scale_SS(SS, insc, outsc, stsc, byref=False) SSadim_sp = scale_SS(SSsp, insc, outsc, stsc, byref=False) compare_ss(SSadim, SSadim_sp) # scale (by reference) SS.scale(insc, outsc, stsc) SSsp.scale(insc, outsc, stsc) compare_ss(SS, SSsp) def test_addGain(self): SS = self.SS SSsp = self.SSsp Nu, Nx, Ny = SS.inputs, SS.states, SS.outputs # add gains Kin = np.random.rand(Nu, 5) Kout = np.random.rand(4, Ny) gain_in = Gain(Kin) gain_in.input_variables = LinearVector([InputVariable('input1', size=5, index=0)]) gain_in.output_variables = LinearVector([OutputVariable('output1', size=Nu, index=0)]) gain_out = Gain(Kout) gain_out.input_variables = LinearVector.transform(self.SS.output_variables, InputVariable) gain_out.output_variables = LinearVector([OutputVariable('final_output', size=gain_out.outputs, index=0)]) SS.addGain(gain_in, 'in') SS.addGain(gain_out, 'out') SSsp.addGain(gain_in, 'in') SSsp.addGain(gain_out, 'out') compare_ss(SS, SSsp) def test_freqresp(self): # freq response: try different scenario SS = self.SS SSsp = self.SSsp Nu, Nx, Ny = SS.inputs, SS.states, SS.outputs kv = np.linspace(0, 1, 8) Y = SS.freqresp(kv) Ysp = SSsp.freqresp(kv) er = np.max(np.abs(Y - Ysp)) assert er < 1e-10, 'Test on freqresp failed' SS.D = libsp.csc_matrix(SS.D) Y1 = SS.freqresp(kv) er = np.max(np.abs(Y - Y1)) assert er < 1e-10, 'Test on freqresp failed' def test_couple(self): dt = .2 Nx1, Nu1, Ny1 = 3, 4, 2 Nx2, Nu2, Ny2 = 4, 3, 2 K12 = np.random.rand(Nu1, Ny2) K21 = np.random.rand(Nu2, Ny1) SS1 = random_ss(Nx1, Nu1, Ny1, dt=.2) SS2 = random_ss(Nx2, Nu2, Ny2, dt=.2) SS1sp = StateSpace(libsp.csc_matrix(SS1.A), libsp.csc_matrix(SS1.B), libsp.csc_matrix(SS1.C), libsp.csc_matrix(SS1.D), dt=dt) SS2sp = StateSpace(libsp.csc_matrix(SS2.A), libsp.csc_matrix(SS2.B), libsp.csc_matrix(SS2.C), libsp.csc_matrix(SS2.D), dt=dt) K12sp = libsp.csc_matrix(K12) K21sp = libsp.csc_matrix(K21) # SCref=couple_full(SS1,SS2,K12,K21) SC0 = couple(SS1, SS2, K12, K21) # compare_ss(SCref,SC0) for SSa in [SS1, SS1sp]: for SSb in [SS2, SS2sp]: for k12 in [K12, K12sp]: for k21 in [K21, K21sp]: SChere = couple(SSa, SSb, k12, k21) compare_ss(SC0, SChere) def test_join(self): Nx, Nu, Ny = 4, 3, 2 SS_list = [random_ss(Nx, Nu, Ny, dt=.2) for ii in range(3)] wv = [.3, .5, .2] SSjoin = join(SS_list, wv) kv = np.array([0., 1., 3.]) Yjoin = SSjoin.freqresp(kv) Yref = np.zeros_like(Yjoin) for ii in range(3): Yref += wv[ii] * SS_list[ii].freqresp(kv) er = np.max(np.abs(Yjoin - Yref)) assert er < 1e-12, 'test_join error %.3e too large' % er def test_disc2cont(self): # not the best test given that eigenvalue comparison is not great with random systems. (error grows near # nyquist frequency) # this test is for execution purposes only. sys = copy.deepcopy(self.SS) self.SS.disc2cont() ct_sys = disc2cont(sys) def test_remove_inputs(self): dt = 0.3 Ny, Nx, Nu = 4, 3, 10 A = np.random.rand(Nx, Nx) B = np.random.rand(Nx, Nu) C = np.random.rand(Ny, Nx) D = np.random.rand(Ny, Nu) self.SS = StateSpace(A, B, C, D, dt=dt) self.SSsp = StateSpace(libsp.csc_matrix(A), libsp.csc_matrix(B), C, D, dt=dt) self.SS.input_variables = LinearVector([InputVariable('input1', size=3, index=0), InputVariable('input2', size=4, index=1), InputVariable('input3', size=2, index=2), InputVariable('input4', size=1, index=3)]) self.SSsp.input_variables = self.SS.input_variables.copy() rows_loc = self.SS.input_variables.num_variables * [None] for ith, variable in enumerate(self.SS.input_variables): rows_loc[ith] = variable.rows_loc self.SS.remove_inputs('input2', 'input4') assert self.SS.B.shape == (Nx, self.SS.input_variables.size), 'B matrix not trimmed correctly' assert self.SS.D.shape == (Ny, self.SS.input_variables.size), 'D matrix not trimmed correctly' assert self.SS.input_variables[0].rows_loc == rows_loc[0], \ 'Rows of input 1 not retained correctly' assert self.SS.input_variables[1].rows_loc == rows_loc[2], \ 'Rows of input 3 not retained correctly' # sparse system self.SSsp.remove_inputs('input2', 'input4') assert self.SSsp.B.shape == (Nx, self.SSsp.input_variables.size), 'Bsp matrix not trimmed correctly' assert self.SSsp.D.shape == (Ny, self.SSsp.input_variables.size), 'Dsp matrix not trimmed correctly' assert self.SSsp.input_variables[0].rows_loc == rows_loc[0], \ 'Rows of input 1 not retained correctly in sparse system' assert self.SSsp.input_variables[1].rows_loc == rows_loc[2], \ 'Rows of input 3 not retained correctly in sparse system' def test_series(self): """ Test Series connection input11 -- >>> SS2 ----> input1, input2 -----> self.SS ----> y """ Nx2, Nu2, Ny2 = 4, 3, self.SS.inputs SS2 = random_ss(Nx2, Nu2, Ny2, dt=self.SS.dt) SS2.input_variables = LinearVector([InputVariable('input11', size=3, index=0)]) SS2.state_variables = LinearVector([StateVariable('state11', size=4, index=0)]) SS2.output_variables = LinearVector([OutputVariable('input1', size=3, index=0), OutputVariable('input2', size=2, index=1)]) SSnew = series(SS2, self.SS) state_vars = LinearVector.merge(SS2.state_variables, self.SS.state_variables) LinearVector.check_same_vectors(SSnew.state_variables, state_vars) assert SSnew.outputs == self.SS.outputs, 'Number of outputs not the same as self.SS' assert SSnew.inputs == SS2.inputs, 'Number of inputs not the same as SS2' class TestStateSpaceManipulation(unittest.TestCase): route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) files_created_by_test = [] def setUp(self): Ny, Nx, Nu = 6, 3, 10 A = np.random.rand(Nx, Nx) B = np.random.rand(Nx, Nu) C = np.random.rand(Ny, Nx) D = np.random.rand(Ny, Nu) self.ss = StateSpace(A, B, C, D) self.SSsp = StateSpace(libsp.csc_matrix(A), libsp.csc_matrix(B), C, D) self.ss.input_variables = LinearVector([InputVariable('input1', size=3, index=0), InputVariable('input2', size=4, index=1), InputVariable('input3', size=2, index=2), InputVariable('input4', size=1, index=3)]) self.ss.state_variables = LinearVector([StateVariable('state1', size=Nx, index=0)]) self.ss.output_variables = LinearVector([OutputVariable('output1', size=1, index=0), OutputVariable('output2', size=3, index=1), OutputVariable('output3', size=2, index=2)]) def test_remove_outputs(self): self.ss.remove_outputs('output1') # size 1 self.ss.remove_outputs('output2') # size 3 assert self.ss.outputs == 2, 'Number of outputs not correct' def test_retain_channels(self): """ Retain certain input and output channels only """ retain_input_channels = [0, 7] self.ss.retain_inout_channels(retain_input_channels, where='in') assert self.ss.inputs == len(retain_input_channels), 'Number of inputs not correct' assert self.ss.input_variables[0].size == 1, 'input1 not correct size' assert self.ss.input_variables[1].name == 'input3', 'input2 not properly removed' retain_output_channels = [0, 5] self.ss.retain_inout_channels(retain_output_channels, where='out') assert self.ss.outputs == len(retain_output_channels), 'Number of outputs not correct' assert self.ss.output_variables[0].size == 1, 'output1 not correct size' assert self.ss.output_variables[1].name == 'output3', 'output2 not properly removed' assert self.ss.output_variables[1].size == 1, 'output3 not properly trimmed' assert self.ss.output_variables[1].index == 1, 'output3 not properly indexed' def test_input_output(self): h5_filename = self.route_test_dir + '/file_test_statespace_inout.h5' self.files_created_by_test.append(h5_filename) self.ss.save(h5_filename) loaded_ss = StateSpace.load_from_h5(h5_filename) LinearVector.check_same_vectors(self.ss.input_variables, loaded_ss.input_variables) LinearVector.check_same_vectors(self.ss.output_variables, loaded_ss.output_variables) LinearVector.check_same_vectors(self.ss.state_variables, loaded_ss.state_variables) np.testing.assert_array_almost_equal(self.ss.A, loaded_ss.A) np.testing.assert_array_almost_equal(self.ss.B, loaded_ss.B) np.testing.assert_array_almost_equal(self.ss.C, loaded_ss.C) np.testing.assert_array_almost_equal(self.ss.D, loaded_ss.D) @classmethod def tearDownClass(cls): for file in cls.files_created_by_test: if os.path.exists(file): os.remove(file) class TestGains(unittest.TestCase): route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) files_created_by_test = [] def test_dot(self): """ u ---> K1 ----> K2 ---->y Returns: """ m1 = 4 p1 = 3 k1 = np.random.rand(p1, m1) gain1 = Gain(k1, input_vars=LinearVector([InputVariable('input', size=m1, index=0)]), output_vars=LinearVector([OutputVariable('output1', size=p1, index=0)])) m2 = p1 p2 = 5 k2 = np.random.rand(p2, m2) gain2 = Gain(k2, input_vars=LinearVector([InputVariable('output1', size=m2, index=0)]), output_vars=LinearVector([OutputVariable('output', size=p2, index=0)])) gain = gain2.dot(gain1) np.testing.assert_array_almost_equal(gain.value, k2.dot(k1)) @unittest.expectedFailure def test_fail_connection(self): """ This one should fail because of dimension mismatch u ---> K1 ----> K2 ---->y """ m1 = 4 p1 = 3 k1 = np.random.rand(p1, m1) gain1 = Gain(k1, input_vars=LinearVector([InputVariable('input', size=m1, index=0)]), output_vars=LinearVector([OutputVariable('output1', size=p1, index=0)])) m2 = 2 p2 = 5 k2 = np.random.rand(p2, m2) gain2 = Gain(k2, input_vars=LinearVector([InputVariable('output1', size=m2, index=0)]), output_vars=LinearVector([OutputVariable('output', size=p2, index=0)])) # check the check fails :) with self.subTest('ss_interface_check'): LinearVector.check_connection(gain1.output_variables, gain2.input_variables) with self.subTest('connection_check'): gain = gain2.dot(gain1) np.testing.assert_array_almost_equal(gain.value, k2.dot(k1)) def test_input_output_gains(self): m1 = 4 p1 = 3 k1 = np.random.rand(p1, m1) gain1 = Gain(k1, input_vars=LinearVector([InputVariable('input', size=m1, index=0)]), output_vars=LinearVector([OutputVariable('output1', size=p1, index=0)])) m2 = 2 p2 = 5 k2 = np.random.rand(p2, m2) gain2 = Gain(k2, input_vars=LinearVector([InputVariable('output1', size=m2, index=0)]), output_vars=LinearVector([OutputVariable('output', size=p2, index=0)])) with self.subTest('Save to single h5 file'): h5_file_name = self.route_test_dir + '/gain1.h5' self.files_created_by_test.append(h5_file_name) gain1.save(h5_file_name) with self.subTest('Load from single h5 file'): loaded_gain = Gain.load_from_h5(h5_file_name) self.check_gains_equal(gain1, loaded_gain) with self.subTest('Save both gains to single h5'): h5_file_name = self.route_test_dir + '/multi_gain.h5' self.files_created_by_test.append(h5_file_name) Gain.save_multiple_gains(h5_file_name, ('gain1', gain1), ('gain2', gain2)) with self.subTest('Load multiple gains from a single h5'): out_gains = Gain.load_multiple_gains(h5_file_name) self.check_gains_equal(gain1, out_gains['gain1']) self.check_gains_equal(gain2, out_gains['gain2']) @staticmethod def check_gains_equal(gain1, gain2): np.testing.assert_array_almost_equal(gain1.value, gain2.value) LinearVector.check_same_vectors(gain1.input_variables, gain2.input_variables) LinearVector.check_same_vectors(gain1.output_variables, gain2.output_variables) @classmethod def tearDownClass(cls): for file in cls.files_created_by_test: if os.path.exists(file): os.remove(file) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/linear/statespace/test_variable_tracker.py ================================================ import unittest import os import h5py import sharpy.utils.h5utils as h5utils from sharpy.linear.utils.ss_interface import InputVariable, LinearVector, OutputVariable class TestVariables(unittest.TestCase): route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) files_created_by_test = [] def setUp(self): # initialising with index out of order Kzeta = 4 input_variables_list = [InputVariable('zeta', size=3 * Kzeta, index=0), InputVariable('zeta_dot', size=3 * Kzeta, index=1), # this should be one InputVariable('u_gust', size=3 * Kzeta, index=2)] self.input_variables = LinearVector(input_variables_list) def test_initialisation(self): Kzeta = 4 input_variables_list = [InputVariable('zeta', size=3 * Kzeta, index=0), InputVariable('u_gust', size=3 * Kzeta, index=2), InputVariable('zeta_dot', size=3 * Kzeta, index=1)] input_variables = LinearVector(input_variables_list) sorted_indices = [variable.index for variable in input_variables] variable_names = [variable.name for variable in input_variables] assert sorted_indices == [0, 1, 2], 'Error sorting indices in initialisation' assert variable_names == ['zeta', 'zeta_dot', 'u_gust'], 'Error sorting indices in initialisation' def test_remove(self): self.input_variables.remove('zeta_dot') sorted_indices = [variable.index for variable in self.input_variables] assert sorted_indices == [0, 1], 'Error removing variable' def test_modify(self): new_values = {'name': 'new_u_gust', 'size': 5, } self.input_variables.modify('u_gust', **new_values) assert self.input_variables.get_variable_from_name('new_u_gust').size == 5, 'Variable not modified correctly' def test_add(self): new_variable = InputVariable('control_surface', size=2, index=3) self.input_variables.add(new_variable) self.input_variables.update_locations() # test it's properly included - it will raise an error internally if not included_var = self.input_variables.get_variable_from_name('control_surface') new_variable_repeated_index = InputVariable('control_surface_2', size=2, index=3) try: self.input_variables.add(new_variable_repeated_index) except IndexError: # correct behaviour pass else: raise IndexError('Variable was added when it should have been rejected because of repeated index') new_variable_inbetween = InputVariable('first_variable', size=2, index=-1) self.input_variables.add(new_variable_inbetween) self.input_variables.update_locations() assert self.input_variables[0].name == new_variable_inbetween.name, 'New variable not added at the start of' \ ' the list' self.input_variables.add('var_from_string', size=3, index=10) assert self.input_variables[-1].name == 'var_from_string', 'Variable from string not added correctly' self.input_variables.update_indices() def test_append(self): self.input_variables.append('last_var', size=3) self.input_variables.update_indices() assert self.input_variables[-1].name == 'last_var', 'Variable not properly appended' # test adding an incorrect input out_var = OutputVariable('out_var', size=1, index=4) try: self.input_variables.append(out_var) except TypeError: pass # this is what should happen else: raise TypeError('Error. Able to append an output variable to an input variable') self.input_variables.append(InputVariable(name='last_last_var', size=2, index=0)) assert self.input_variables[-1].name == 'last_last_var', 'Variable not properly appended' def test_merge(self): second_variables_list = [InputVariable('eta', size=3, index=0), InputVariable('eta_dot', size=3, index=1)] second_vector = LinearVector(second_variables_list) merged_vector = LinearVector.merge(self.input_variables, second_vector) assert merged_vector[-1].name == 'eta_dot', 'Vectors not coupled properly' assert merged_vector[2].name == 'u_gust', 'Vectors not coupled properly' def test_copy(self): vec1 = self.input_variables vec2 = vec1.copy() assert vec1 is not vec2, 'Object not deep copied' def test_transform(self): new_vector = LinearVector.transform(self.input_variables, to_type=OutputVariable) LinearVector.check_same_vectors(new_vector, self.input_variables) assert new_vector.num_variables == self.input_variables.num_variables, 'Number of variables not equal' assert new_vector.size == self.input_variables.size, 'Size not equal' for i_var in range(new_vector.num_variables): assert new_vector[i_var].name == self.input_variables[i_var].name, 'Name does not match' assert new_vector[i_var].index == self.input_variables[i_var].index, 'Index does not match' assert new_vector[i_var].size == self.input_variables[i_var].size, 'Size does not match' new_vector.append(OutputVariable('last_var', size=2, index=0)) assert new_vector.num_variables == self.input_variables.num_variables + 1, 'Number of variables got ' \ 'updated in the original object' assert new_vector[-1].name == 'last_var', 'Variable not correctly appended' assert new_vector[-1].index == new_vector.num_variables - 1, 'Variable not correctly appended' def test_save_to_h5(self): Kzeta = 4 input_variables_list = [InputVariable('zeta', size=3 * Kzeta, index=0), InputVariable('zeta_dot', size=3 * Kzeta, index=1), # this should be one InputVariable('u_gust', size=3 * Kzeta, index=2)] input_variables = LinearVector(input_variables_list) h5_file_name = self.route_test_dir + '/test_save_variables.h5' self.files_created_by_test.append(h5_file_name) with h5py.File(h5_file_name, 'w') as fid: input_variables.add_to_h5_file(fid) with h5py.File(h5_file_name, 'r') as fid: data_dict = h5utils.load_h5_in_dict(fid) loaded_variable = LinearVector.load_from_h5_file('InputVariable', data_dict['InputVariable']) LinearVector.check_same_vectors(input_variables, loaded_variable) def tearDown(self): for file in self.files_created_by_test: if os.path.exists(file): os.remove(file) if __name__ == '__main__': unittest.main() ================================================ FILE: tests/linear/uvlm/__init__.py ================================================ ================================================ FILE: tests/linear/uvlm/test_infinite_span.py ================================================ """Linearised UVLM 2D tests Test linear UVLM solver against analytical results for 2D wing Author: S. Maraniello, Dec 2018 Modified: N. Goizueta, Sep 2019 """ import unittest import os # import matplotlib.pyplot as plt import numpy as np import shutil import sharpy.sharpy_main import sharpy.utils.algebra as algebra import sharpy.utils.analytical as an import sharpy.linear.src.linuvlm as linuvlm import sharpy.cases.templates.flying_wings as flying_wings import sharpy.utils.sharpydir as sharpydir import sharpy.utils.cout_utils as cout class Test_infinite_span(unittest.TestCase): """ Test infinite-span flat wing at zero incidence against analytical solutions """ test_dir = sharpydir.SharpyDir + '/tests/linear/uvlm/' def setUp_from_params(self, Nsurf, integr_ord, RemovePred, UseSparse, RollNodes): """ Builds SHARPy solution for a rolled infinite span flat wing at zero incidence. Rolling can be obtained both by rotating the FoR A or modifying the nodes of the wing. """ # Flags self.ProducePlots = True # Define Parametrisation M, N, Mstar_fact = 8, 8, 50 # Flying properties if RollNodes: self.Roll0Deg = 0. else: self.Roll0Deg = 0. self.Alpha0Deg = 0.0 Uinf0 = 50. ### ----- build directories self.case_code = 'wagner' self.case_main = self.case_code + \ '_r%.4daeff%.2d_rnodes%s_Nsurf%.2dM%.2dN%.2dwk%.2d' \ % (int(np.round(100 * self.Roll0Deg)), int(np.round(100 * self.Alpha0Deg)), RollNodes, Nsurf, M, N, Mstar_fact) self.case_main += 'ord%.1d_rp%s_sp%s' % (integr_ord, RemovePred, UseSparse) self.route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) route_main = self.route_test_dir + '/res/' self.figfold = self.route_test_dir + '/figs/' if os.path.exists(route_main): shutil.rmtree(route_main) if os.path.exists(self.figfold): shutil.rmtree(self.figfold) os.makedirs(route_main) os.makedirs(self.figfold) ### ----- sharpy reference solution # Build wing model ws = flying_wings.QuasiInfinite( M=M, N=N, Mstar_fact=Mstar_fact, n_surfaces=Nsurf, u_inf=Uinf0, alpha=self.Alpha0Deg, roll=self.Roll0Deg, aspect_ratio=1e5, RollNodes=RollNodes, route=route_main, case_name=self.case_main) ws.main_ea = .4 ws.clean_test_files() ws.update_derived_params() ws.generate_fem_file() ws.generate_aero_file() # solution flow ws.set_default_config_dict() ws.config['SHARPy']['flow'] = ['BeamLoader', 'AerogridLoader', 'StaticUvlm', 'BeamPlot', 'AerogridPlot'] ws.config['SHARPy']['log_folder'] = self.route_test_dir + '/output/' + self.case_code + '/' ws.config['SHARPy']['write_screen'] = 'off' ws.config['SHARPy']['write_log'] = 'off' ws.config['LinearUvlm'] = {'dt': ws.dt, 'integr_order': integr_ord, 'density': ws.rho, 'remove_predictor': RemovePred, 'use_sparse': UseSparse, 'ScalingDict': {'length': 1., 'speed': 1., 'density': 1.}, 'vortex_radius': 1e-6} ws.config.write() # solve at linearistion point data0 = sharpy.sharpy_main.main(['...', route_main + self.case_main + '.sharpy']) tsaero0 = data0.aero.timestep_info[0] tsaero0.rho = ws.config['LinearUvlm']['density'] ### ---- normalisation parameters self.start_writer() # verify chord c_ext = np.linalg.norm(tsaero0.zeta[0][:, 0, 0] - tsaero0.zeta[0][:, -1, 0]) assert np.abs(ws.c_ref - c_ext) < 1e-8, 'Wrong reference chord' # reference force - total qinf = 0.5 * ws.rho * Uinf0 ** 2 span = Nsurf * np.linalg.norm(tsaero0.zeta[0][:, 0, 0] - tsaero0.zeta[0][:, 0, -1]) Stot = ws.c_ref * span Fref_tot = qinf * Stot # reference force - section sec_span = np.linalg.norm(tsaero0.zeta[0][:, 0, 0] - tsaero0.zeta[0][:, 0, 1]) S = ws.c_ref * sec_span Fref_span = qinf * S # save self.route_main = route_main self.tsaero0 = tsaero0 self.ws = ws self.Fref_tot = Fref_tot self.Fref_span = Fref_span self.M = M self.N = N self.Mstar_fact = Mstar_fact self.Uinf0 = Uinf0 def test_wagner(self): """ Step response (Wagner): - set linearisation point at 0 effective incidence but non-zero roll attitude. This can be obtained either by rotating the FoR A or by explicitely modifying the position of the wing nodes. - perturb. state so as to produce a small effective angle of attack. This is achieved combining changes of: - wing lattice orientation - wing lattice speed - incoming flow orientation - compare aerodynamic force time history to Wagner's analytical solution - compare steady state to analytical solution and ``StaticUvlm`` solver Notes: The function uses ``subTests`` to call ``run_wagner``. """ for Nsurf in [1, 2]: for integr_ord in [1, 2]: for RemovePred in [False, True]: for UseSparse in [True]: for RollNodes in [True, False]: with self.subTest( Nsurf=Nsurf, integr_ord=integr_ord, RemovePred=RemovePred, UseSparse=UseSparse, RollNodes=RollNodes): self.run_wagner( Nsurf, integr_ord, RemovePred, UseSparse, RollNodes) def run_wagner(self, Nsurf, integr_ord, RemovePred, UseSparse, RollNodes): """ see test_wagner """ ### ----- set reference solution self.setUp_from_params(Nsurf, integr_ord, RemovePred, UseSparse, RollNodes) tsaero0 = self.tsaero0 ws = self.ws M = self.M N = self.N Mstar_fact = self.Mstar_fact Uinf0 = self.Uinf0 ### ----- linearisation uvlm = linuvlm.Dynamic(tsaero0, dynamic_settings=ws.config['LinearUvlm']) uvlm.assemble_ss() zeta_pole = np.array([0., 0., 0.]) uvlm.get_total_forces_gain(zeta_pole=zeta_pole) uvlm.get_rigid_motion_gains(zeta_rotation=zeta_pole) uvlm.get_sect_forces_gain() ### ----- Scale gains Fref_tot = self.Fref_tot Fref_span = self.Fref_span uvlm.Kftot = uvlm.Kftot / Fref_tot uvlm.Kmtot = uvlm.Kmtot / Fref_tot / ws.c_ref uvlm.Kfsec /= Fref_span uvlm.Kmsec /= (Fref_span * ws.c_ref) ### ----- step input # rotate incoming flow, wing lattice and wing lattice speed about # the (rolled) wing elastic axis to create an effective angle of attack. # Rotation is expressed through a CRV. delta_AlphaEffDeg = 1e-2 delta_AlphaEffRad = 1e-2 * np.pi / 180. Roll0Rad = self.Roll0Deg / 180. * np.pi dcrv = -delta_AlphaEffRad * np.array([0., np.cos(Roll0Rad), np.sin(Roll0Rad)]) uvec0 = np.array([Uinf0, 0, 0]) uvec = np.dot(algebra.crv2rotation(dcrv), uvec0) duvec = uvec - uvec0 dzeta = np.zeros((Nsurf, 3, M + 1, N // Nsurf + 1)) dzeta_dot = np.zeros((Nsurf, 3, M + 1, N // Nsurf + 1)) du_ext = np.zeros((Nsurf, 3, M + 1, N // Nsurf + 1)) for ss in range(Nsurf): for mm in range(M + 1): for nn in range(N // Nsurf + 1): dzeta_dot[ss, :, mm, nn] = -1. / 3 * duvec du_ext[ss, :, mm, nn] = +1. / 3 * duvec dzeta = 1. / 3 * np.dot(uvlm.Krot, dcrv) Uaero = np.concatenate((dzeta.reshape(-1), dzeta_dot.reshape(-1), du_ext.reshape(-1))) ### ----- Steady state solution xste, yste = uvlm.solve_steady(Uaero) Ftot_ste = np.dot(uvlm.Kftot, yste) Mtot_ste = np.dot(uvlm.Kmtot, yste) # first check of gain matrices... Ftot_ste_ref = np.zeros((3,)) Mtot_ste_ref = np.zeros((3,)) fnodes = yste.reshape((Nsurf, 3, M + 1, N // Nsurf + 1)) for ss in range(Nsurf): for nn in range(N // Nsurf + 1): for mm in range(M + 1): Ftot_ste_ref += fnodes[ss, :, mm, nn] Mtot_ste_ref += np.cross( uvlm.MS.Surfs[ss].zeta[:, mm, nn], fnodes[ss, :, mm, nn]) Ftot_ste_ref /= Fref_tot Mtot_ste_ref /= (Fref_tot * ws.c_ref) Fmag = np.linalg.norm(Ftot_ste_ref) er_f = np.max(np.abs(Ftot_ste - Ftot_ste_ref)) / Fmag er_m = np.max(np.abs(Mtot_ste - Mtot_ste_ref)) / Fmag / ws.c_ref assert (er_f < 1e-8 and er_m < 1e-8), \ 'Error of total forces (%.2e) and moment (%.2e) too large!' % (er_f, er_m) + \ 'Verify gains produced by linuvlm.Dynamic.get_total_forces_gain.' # then compare against analytical ... Cl_inf = delta_AlphaEffRad * np.pi * 2. Cfvec_inf = Cl_inf * np.array([0., -np.sin(Roll0Rad), np.cos(Roll0Rad)]) er_f = np.abs(np.linalg.norm(Ftot_ste) / Cl_inf - 1.) assert (er_f < 1e-2), \ 'Error of total lift coefficient (%.2e) too large!' % (er_f,) + \ 'Verify linuvlm.Dynamic.' er_f = np.abs(np.linalg.norm(Ftot_ste - Cfvec_inf) / Cl_inf) assert (er_f < 1e-2), \ 'Error of total aero force (%.2e) too large!' % (er_f,) + \ 'Verify linuvlm.Dynamic.' # ... and finally compare against non-linear UVLM # ps: here we roll the wing and rotate the incoming flow to generate an effective # angle of attack case_pert = 'wagner_r%.4daeff%.2d_rnodes%s_Nsurf%.2dM%.2dN%.2dwk%.2d' \ % (int(np.round(100 * self.Roll0Deg)), int(np.round(100 * delta_AlphaEffDeg)), RollNodes, Nsurf, M, N, Mstar_fact) ws_pert = flying_wings.QuasiInfinite( M=M, N=N, Mstar_fact=Mstar_fact, n_surfaces=Nsurf, u_inf=Uinf0, alpha=self.Alpha0Deg, roll=self.Roll0Deg, aspect_ratio=1e5, route=self.route_main, case_name=case_pert, RollNodes=RollNodes) ws_pert.u_inf_direction = uvec / Uinf0 ws_pert.main_ea = ws.main_ea ws_pert.clean_test_files() ws_pert.update_derived_params() ws_pert.generate_fem_file() ws_pert.generate_aero_file() # solution flow ws_pert.set_default_config_dict() ws_pert.config['SHARPy']['flow'] = ws.config['SHARPy']['flow'] ws_pert.config['SHARPy']['write_screen'] = 'off' ws_pert.config['SHARPy']['write_log'] = 'off' ws_pert.config['SHARPy']['log_folder'] = self.route_test_dir + '/output/' + self.case_code + '/' ws_pert.config.write() # solve at perturbed point data_pert = sharpy.sharpy_main.main(['...', self.route_main + case_pert + '.sharpy']) tsaero = data_pert.aero.timestep_info[0] self.start_writer() # get total forces Ftot_ste_pert = np.zeros((3,)) Mtot_ste_pert = np.zeros((3,)) for ss in range(Nsurf): for nn in range(N // Nsurf + 1): for mm in range(M + 1): Ftot_ste_pert += tsaero.forces[ss][:3, mm, nn] Mtot_ste_pert += np.cross( uvlm.MS.Surfs[ss].zeta[:, mm, nn], tsaero.forces[ss][:3, mm, nn]) Ftot_ste_pert /= Fref_tot Mtot_ste_pert /= (Fref_tot * ws.c_ref) Fmag = np.linalg.norm(Ftot_ste_pert) er_f = np.max(np.abs(Ftot_ste - Ftot_ste_pert)) / Fmag er_m = np.max(np.abs(Mtot_ste - Mtot_ste_pert)) / Fmag / ws.c_ref assert (er_f < 2e-4 and er_m < 2e-4), \ 'Error of total forces (%.2e) and moment (%.2e) ' % (er_f, er_m) + \ 'with respect to geometrically-exact UVLM too large!' # and check non-linear uvlm against analytical solution er_f = np.abs(np.linalg.norm(Ftot_ste_pert - Cfvec_inf) / Cl_inf) assert (er_f <= 1.5e-2), \ 'Error of total aero force components (%.2e) too large!' % (er_f,) + \ 'Verify StaticUvlm' ### ----- Analytical step response (Wagner solution) NT = 251 tv = np.linspace(0., uvlm.dt * (NT - 1), NT) Clv_an = an.wagner_imp_start(delta_AlphaEffRad, Uinf0, ws.c_ref, tv) assert np.abs(Clv_an[-1] / Cl_inf - 1.) < 1e-2, \ 'Did someone modify this test case?! The time should be enough to reach ' \ 'the steady-state CL with a 1 perc. tolerance...' Cfvec_an = np.zeros((NT, 3)) Cfvec_an[:, 1] = -np.sin(Roll0Rad) * Clv_an Cfvec_an[:, 2] = np.cos(Roll0Rad) * Clv_an ### ----- Dynamic step response Fsect = np.zeros((NT, Nsurf, 3, N // Nsurf + 1)) # Fbeam=np.zeros((NT,6,N//Nsurf+1)) Ftot = np.zeros((NT, 3)) Er_f_tot = np.zeros((NT,)) # Ybeam=[] gamma = np.zeros((NT, Nsurf, M, N // Nsurf)) gamma_dot = np.zeros((NT, Nsurf, M, N // Nsurf)) gamma_star = np.zeros((NT, Nsurf, int(M * Mstar_fact), N // Nsurf)) xold = np.zeros((uvlm.SS.A.shape[0],)) for tt in range(1, NT): xnew, ynew = uvlm.solve_step(xold, Uaero) change = np.linalg.norm(xnew - xold) xold = xnew # record state ? if uvlm.remove_predictor is False: gv, gvstar, gvdot = uvlm.unpack_state(xnew) gamma[tt, :, :, :] = gv.reshape((Nsurf, M, N // Nsurf)) gamma_dot[tt, :, :, :] = gvdot.reshape((Nsurf, M, N // Nsurf)) gamma_star[tt, :, :, :] = gvstar.reshape((Nsurf, int(M * Mstar_fact), N // Nsurf)) # calculate forces (and error) Ftot[tt, :3] = np.dot(uvlm.Kftot, ynew) Er_f_tot[tt] = np.linalg.norm(Ftot[tt, :] - Cfvec_an[tt, :]) / Clv_an[tt] Fsect[tt, :, :, :] = np.dot(uvlm.Kfsec, ynew).reshape((Nsurf, 3, N // Nsurf + 1)) # ### beam forces # Ybeam.append(np.dot(Sol.Kforces[:-10,:],ynew)) # Fdummy=Ybeam[-1].reshape((N//Nsurf,6)).T # Fbeam[tt,:,:N//Nsurf]=Fdummy[:,:N//Nsurf] # Fbeam[tt,:,N//Nsurf+1:]=Fdummy[:,N//Nsurf:] if RemovePred: ts2perc, ts1perc = 6, 6 else: ts2perc, ts1perc = 16, 36 er_th_2perc = np.max(Er_f_tot[ts2perc:]) er_th_1perc = np.max(Er_f_tot[ts1perc:]) ### ----- generate plot if self.ProducePlots: # sections to plot if Nsurf == 1: Nplot = [0, N // 2, N] labs = [r'tip', r'root', r'tip'] elif Nsurf == 2: Nplot = [0, N // 2 - 1] labs = [r'tip', r'near root', r'tip', r'near root'] axtitle = [r'$C_{F_y}$', r'$C_{F_z}$'] # non-dimensional time sv = 2.0 * Uinf0 * tv / ws.c_ref # generate figure clist = ['#003366', '#CC3333', '#336633', '#FF6600'] * 4 fontlabel = 12 std_params = {'legend.fontsize': 10, 'font.size': fontlabel, 'xtick.labelsize': fontlabel - 2, 'ytick.labelsize': fontlabel - 2, 'figure.autolayout': True, 'legend.numpoints': 1} # plt.rcParams.update(std_params) # fig = plt.figure('Lift time-history', (12, 6)) # axvec = fig.subplots(1, 2) # for aa in [0, 1]: # comp = aa + 1 # axvec[aa].set_title(axtitle[aa]) # axvec[aa].plot(sv, Cfvec_an[:, comp] / Cl_inf, lw=4, ls='-', # alpha=0.5, color='r', label=r'Wagner') # axvec[aa].plot(sv, Ftot[:, comp] / Cl_inf, lw=5, ls=':', # alpha=0.7, color='k', label=r'Total') # cc = 0 # for ss in range(Nsurf): # for nn in Nplot: # axvec[aa].plot(sv, Fsect[:, ss, comp, nn] / Cl_inf, # lw=4 - cc, ls='--', alpha=0.7, color=clist[cc], # label=r'Surf. %.1d, n=%.2d (%s)' % (ss, nn, labs[cc])) # cc += 1 # axvec[aa].grid(color='0.8', ls='-') # axvec[aa].grid(color='0.85', ls='-', which='minor') # axvec[aa].set_xlabel(r'normalised time $t=2 U_\infty \tilde{t}/c$') # axvec[aa].set_ylabel(axtitle[aa] + r'$/C_{l_\infty}$') # axvec[aa].set_xlim(0, sv[-1]) # if Cfvec_inf[comp] > 0.: # axvec[aa].set_ylim(0, 1.1) # else: # axvec[aa].set_ylim(-1.1, 0) # plt.legend(ncol=1) # # plt.show() # fig.savefig(self.figfold + self.case_main + '.png') # fig.savefig(self.figfold + self.case_main + '.pdf') # plt.close() assert er_th_2perc < 2e-2 and er_th_1perc < 1e-2, \ 'Error of dynamic step response at time-steps 16 and 36 ' + \ '(%.2e and %.2e) too large. Verify Linear UVLM.' % (er_th_2perc, er_th_1perc) def start_writer(self): # Over write writer with print_file False to avoid I/O errors global cout_wrap cout_wrap = cout.Writer() # cout_wrap.initialise(print_screen=False, print_file=False) cout_wrap.cout_quiet() sharpy.utils.cout_utils.cout_wrap = cout_wrap def tearDown(self): cout.finish_writer() try: shutil.rmtree(self.route_test_dir + '/res/') shutil.rmtree(self.route_test_dir + '/figs/') shutil.rmtree(self.route_test_dir + '/output/') except FileNotFoundError: pass if __name__ == '__main__': if os.path.exists('./figs/infinite_span'): shutil.rmtree('./figs/infinite_span') unittest.main() ================================================ FILE: tests/sourcepanelmethod/__init__.py ================================================ ================================================ FILE: tests/sourcepanelmethod/geometry_parameter_models.json ================================================ {"phantom_wing": {"length_radius_ratio": 50.0, "radius_chord_ratio": 0.2, "vertical_wing_position": 0.0, "radius_half_wingspan_ratio": 0.02, "length_offset_nose_to_wing_ratio": 0.5, "fuselage_shape": "cylindrical"}, "low_wing": {"length_radius_ratio": 16.0, "radius_chord_ratio": 0.5, "vertical_wing_position": -0.5, "radius_half_wingspan_ratio": 0.222, "fuselage_shape": "cylindrical", "length_offset_nose_to_wing_ratio": 0.5}} ================================================ FILE: tests/sourcepanelmethod/test_data/results_low_wing_coupled_0.csv ================================================ 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 5.630631000000000386e-01 2.637046000000000112e-01 7.038288000000000322e-01 2.587818000000000063e-01 8.445945999999999732e-01 2.560145999999999811e-01 9.853604000000000251e-01 2.521720000000000073e-01 1.126125999999999960e+00 2.474606000000000028e-01 1.266891999999999907e+00 2.419823000000000113e-01 1.407658000000000076e+00 2.357548999999999895e-01 1.548423000000000105e+00 2.287254999999999983e-01 1.689189000000000052e+00 2.207763000000000086e-01 1.829954999999999998e+00 2.117207000000000117e-01 1.970720999999999945e+00 2.012865000000000071e-01 2.111486000000000196e+00 1.890803000000000067e-01 2.252251999999999921e+00 1.745153000000000121e-01 2.393018000000000089e+00 1.566525000000000001e-01 2.533783999999999814e+00 1.337860999999999911e-01 2.674549999999999983e+00 1.019361000000000017e-01 2.815315000000000012e+00 8.357517000000000418e-02 ================================================ FILE: tests/sourcepanelmethod/test_data/results_low_wing_coupled_1.csv ================================================ 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 5.630631000000000386e-01 2.636864999999999903e-01 7.038288000000000322e-01 2.592814999999999981e-01 8.445945999999999732e-01 2.565006000000000230e-01 9.853604000000000251e-01 2.526663000000000103e-01 1.126125999999999960e+00 2.479610999999999899e-01 1.266891999999999907e+00 2.424882999999999900e-01 1.407658000000000076e+00 2.362653000000000114e-01 1.548423000000000105e+00 2.292386999999999897e-01 1.689189000000000052e+00 2.212899000000000116e-01 1.829954999999999998e+00 2.122312000000000087e-01 1.970720999999999945e+00 2.017887999999999904e-01 2.111486000000000196e+00 1.895677000000000056e-01 2.252251999999999921e+00 1.749785000000000090e-01 2.393018000000000089e+00 1.570786999999999878e-01 2.533783999999999814e+00 1.341562000000000032e-01 2.674549999999999983e+00 1.022175000000000028e-01 2.815315000000000012e+00 8.376727999999999952e-02 ================================================ FILE: tests/sourcepanelmethod/test_source_panel_method.py ================================================ import unittest import os import numpy as np from sharpy.cases.templates.fuselage_wing_configuration.fuselage_wing_configuration import Fuselage_Wing_Configuration from sharpy.cases.templates.fuselage_wing_configuration.fwc_get_settings import define_simulation_settings class TestSourcePanelMethod(unittest.TestCase): route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) def test_ellipsoid(self): """ Computes the pressure distribution over an ellipsoid. The results should match the analyitcal solution according to potential flow theory (see Chapter 5 Katz, J., and Plotkin, A., Low-speed aerodynamics, Vol. 13, Cambridge University Press, 2001). """ # define model variables radius_ellipsoid = 0.2 length_ellipsoid = 2. u_inf = 10 alpha_deg = 0 n_elem = 30 num_radial_panels = 24 lifting_only= False fuselage_shape = 'ellipsoid' fuselage_discretisation = 'uniform' # define case name and folders case_route = self.route_test_dir + '/cases/' output_route = self.route_test_dir + '/output/' case_name = 'ellipsoid' enforce_uniform_fuselage_discretisation = True # generate ellipsoid model ellipsoidal_body = Fuselage_Wing_Configuration(case_name, case_route, output_route) ellipsoidal_body.init_aeroelastic(lifting_only=lifting_only, max_radius=radius_ellipsoid, fuselage_length=length_ellipsoid, offset_nose_wing=length_ellipsoid/2, n_elem_fuselage=n_elem, num_radial_panels=num_radial_panels, fuselage_shape=fuselage_shape, enforce_uniform_fuselage_discretisation=enforce_uniform_fuselage_discretisation, fuselage_discretisation=fuselage_discretisation) ellipsoidal_body.generate() # define settings flow = ['BeamLoader', 'NonliftingbodygridLoader', 'StaticUvlm', 'WriteVariablesTime' ] settings = define_simulation_settings(flow, ellipsoidal_body, alpha_deg, u_inf, lifting_only=False, nonlifting_only=True, horseshoe=True, writeCpVariables=True) ellipsoidal_body.create_settings(settings) # run simulation ellipsoidal_body.run() # postprocess cp_distribution_SHARPy = self.load_pressure_distribution(output_route + '/' + case_name + '/WriteVariablesTime/', ellipsoidal_body.structure.n_node_fuselage) dx = length_ellipsoid/(ellipsoidal_body.structure.n_node_fuselage-1) x_collocation_points = np.linspace(-length_ellipsoid/2+dx/2, length_ellipsoid/2-dx/2, ellipsoidal_body.structure.n_node_fuselage) cp_distribution_analytcal = self.get_analytical_pressure_distribution(radius_ellipsoid, x_collocation_points) # check results with self.subTest('pressure_coefficient'): # Higher deviations near the nose and tail due to local coarser discretisation. Check only mid-body values! np.testing.assert_array_almost_equal(cp_distribution_SHARPy[4:-4], cp_distribution_analytcal[4:-4], decimal=3) def load_pressure_distribution(self, output_folder, n_collocation_points): """ Loads the resulting pressure coefficients saved in txt-files. """ cp_distribution = np.zeros((n_collocation_points,)) for i_collocation_point in range(n_collocation_points): cp_distribution[i_collocation_point] = np.loadtxt(output_folder + 'nonlifting_pressure_coefficients_panel_isurf0_im0_in{}.dat'.format(i_collocation_point))[1] return cp_distribution def get_analytical_pressure_distribution(self, radius, x_coordinates): """ Computes the analytical solution of the pressure distribution over an ellipsoid in potential flow for the previous specified ellipsoid model. Equations used are taken from https://www.symscape.com/examples/panel/potential_flow_ellipsoid """ a = np.sqrt(1 - radius**2) b = 2 * ((1-a**2)/a**3) * (np.arctanh(a)-a) u = 2./(2.-b) * np.sqrt((1-x_coordinates**2)/(1-x_coordinates**2 * a**2)) return 1-u**2 def tearDown(self): """ Removes all created files within this test. """ import shutil folders = ['cases', 'output'] for folder in folders: shutil.rmtree(self.route_test_dir + '/' + folder) if __name__ == '__main__': import unittest unittest.main() ================================================ FILE: tests/sourcepanelmethod/test_vlm_coupled_spm.py ================================================ import unittest import os import numpy as np from sharpy.cases.templates.fuselage_wing_configuration.fuselage_wing_configuration import Fuselage_Wing_Configuration from sharpy.cases.templates.fuselage_wing_configuration.fwc_get_settings import define_simulation_settings import json class TestUvlmCoupledWithSourcePanelMethod(unittest.TestCase): def test_phantom_panels(self): """ Phantom Panel Test (Steady and Dynamic) The lift distribution over a rectangular high-aspect ratio wing is computed. First a wing_only configuration is considered, while second, we activate the phantom panels created within the fuselage although, the effect of the source panels is omitted. With the defined interpolation scheme, the same lift distribution must be obtained. """ self.define_folder() model = 'phantom_wing' fuselage_length = 10 dict_geometry_parameters = self.get_geometry_parameters(model, fuselage_length=fuselage_length) # Freestream Conditions alpha_deg = 5 u_inf = 10 # Discretization dict_discretization = { 'n_elem_per_wing': 20, 'n_elem_fuselage': 10, 'num_chordwise_panels': 4 } # Simulation settings horseshoe = True list_phantom_test = [False, True] list_dynamic_test = [False, True] dynamic_structural_solver = 'NonLinearDynamicPrescribedStep' # Numerical Settings for DynamicCoupled (very small for faster test runs) fsi_substeps = 2 fsi_tolerance = 1e-2 # Simlation Solver Flow flow = ['BeamLoader', 'AerogridLoader', 'NonliftingbodygridLoader', 'StaticUvlm', 'StaticCoupled', 'BeamLoads', 'LiftDistribution', 'DynamicCoupled', 'LiftDistribution', ] # define model variables for dynamic in list_dynamic_test: list_results_lift_distribution = [] for phantom_test in list_phantom_test: horseshoe = not dynamic lifting_only = not phantom_test case_name = model + '_phantom{}_dynamic{}'.format(int(phantom_test), int(dynamic)) # generate ellipsoid model phantom_wing = self.generate_model(case_name, dict_geometry_parameters, dict_discretization, lifting_only) # Adjust flow for case flow_case = flow.copy() if lifting_only: n_tsteps = 5 flow_case.remove('NonliftingbodygridLoader') if not dynamic: n_tsteps = 0 flow_case.remove('StaticCoupled') flow_case.remove('DynamicCoupled') self.generate_simulation_settings(flow_case, phantom_wing, alpha_deg, u_inf, lifting_only, n_tsteps = n_tsteps, horseshoe=horseshoe, phantom_test=phantom_test, dynamic_structural_solver=dynamic_structural_solver, fsi_substeps=fsi_substeps, fsi_tolerance=fsi_tolerance) # # run simulation phantom_wing.run() # get results list_results_lift_distribution.append(self.load_lift_distribution( self.output_route + '/' + case_name, phantom_wing.structure.n_node_right_wing, n_tsteps )) # check results with self.subTest(self.get_test_name('phantom', dynamic)): np.testing.assert_array_almost_equal(list_results_lift_distribution[0][3:, 1], list_results_lift_distribution[1][3:, 1], decimal=2) #3 - int(dynamic)) def test_fuselage_wing_configuration(self): """ Test spanwise lift distribution on a fuselage-wing configuration. The lift distribution on low wing configuration is computed considering fuselage effects. The final results are compared to a previous solution (backward compatibility) that matches the experimental lift distribution for this case. """ self.define_folder() model = 'low_wing' fuselage_length = 10 dict_geometry_parameters = self.get_geometry_parameters(model, fuselage_length=fuselage_length) # Freestream Conditions alpha_deg = 2.9 u_inf = 10 # Discretization dict_discretization = { 'n_elem_per_wing': 10, 'n_elem_fuselage': 30, 'num_chordwise_panels': 8, 'num_radial_panels': 36 } # Simulation settings horseshoe = True phantom_test = False lifting_only = False # Simlation Solver Flow flow = ['BeamLoader', 'AerogridLoader', 'NonliftingbodygridLoader', 'StaticUvlm', 'StaticCoupled', 'BeamLoads', 'LiftDistribution', ] for static_coupled_solver in [False, True]: case_name = '{}_coupled_{}'.format(model, int(static_coupled_solver)) wing_fuselage_model = self.generate_model(case_name, dict_geometry_parameters, dict_discretization, lifting_only) flow_case = flow.copy() if static_coupled_solver: flow_case.remove('StaticUvlm') else: flow_case.remove('StaticCoupled') self.generate_simulation_settings(flow_case, wing_fuselage_model, alpha_deg, u_inf, lifting_only, horseshoe=horseshoe, phantom_test=phantom_test) # run simulation wing_fuselage_model.run() # get results lift_distribution = self.load_lift_distribution( self.output_route + case_name, wing_fuselage_model.structure.n_node_right_wing ) # check results lift_distribution_test = np.loadtxt(self.route_test_dir + "/test_data/results_{}.csv".format(case_name)) with self.subTest(self.get_test_name('lift distribution and spanwise wing deformation', static_coupled=static_coupled_solver)): np.testing.assert_array_almost_equal(lift_distribution_test, lift_distribution, decimal=3) def get_test_name(self, base_name, dynamic=False, static_coupled=False): if static_coupled: base_name += ' coupled' else: if dynamic: base_name += ' uvlm' else: base_name += ' vlm' return base_name def get_geometry_parameters(self, model_name,fuselage_length=10): """ Geometry parameters are loaded from json init file for the specified model. Next, final geoemtry parameteres, depending on the fuselage length are calculated and return within a dict. """ with open(self.route_test_dir + '/geometry_parameter_models.json', 'r') as fp: parameter_models = json.load(fp)[model_name] geometry_parameters = { 'fuselage_length': fuselage_length, 'max_radius': fuselage_length/parameter_models['length_radius_ratio'], 'fuselage_shape': parameter_models['fuselage_shape'], } geometry_parameters['chord']=geometry_parameters['max_radius']/parameter_models['radius_chord_ratio'] geometry_parameters['half_wingspan'] = geometry_parameters['max_radius']/parameter_models['radius_half_wingspan_ratio'] geometry_parameters['offset_nose_wing'] = parameter_models['length_offset_nose_to_wing_ratio'] * fuselage_length geometry_parameters['vertical_wing_position'] = parameter_models['vertical_wing_position'] * geometry_parameters['max_radius'] return geometry_parameters def generate_model(self, case_name, dict_geometry_parameters, dict_discretisation, lifting_only): """ Aircraft model object is generated and structural and aerodynamic (lifting and nonlifting) input files are generated. """ aircraft_model = Fuselage_Wing_Configuration(case_name, self.case_route, self.output_route) aircraft_model.init_aeroelastic(lifting_only=lifting_only, **dict_discretisation, **dict_geometry_parameters) aircraft_model.generate() return aircraft_model def generate_simulation_settings(self, flow, aircraft_model, alpha_deg, u_inf, lifting_only, n_tsteps=0, horseshoe=True, nonlifting_only=False, phantom_test=False, dynamic_structural_solver='NonLinearDynamicPrescribedStep', fsi_substeps=200, fsi_tolerance=1e-6 ): """ Simulation settings are defined and written to the ".sharpy" input file. """ settings = define_simulation_settings(flow, aircraft_model, alpha_deg, u_inf, dt=self.get_timestep(aircraft_model, u_inf), n_tsteps=n_tsteps, lifting_only=lifting_only, phantom_test=phantom_test, nonlifting_only=nonlifting_only, horseshoe=horseshoe, dynamic_structural_solver=dynamic_structural_solver, fsi_substeps=fsi_substeps, fsi_tolerance=fsi_tolerance) aircraft_model.create_settings(settings) def get_timestep(self, model, u_inf): return model.aero.chord_wing / model.aero.num_chordwise_panels / u_inf def define_folder(self): """ Initializes all folder path needed and creates case folder. """ self.route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) self.case_route = self.route_test_dir + '/cases/' self.output_route = self.route_test_dir + '/output/' if not os.path.exists(self.case_route): os.makedirs(self.case_route) def load_lift_distribution(self, output_folder, n_node_wing, n_tsteps=0): """ Loads the resulting pressure coefficients saved in txt-files. """ lift_distribution = np.loadtxt(output_folder + '/liftdistribution/liftdistribution_ts{}.txt'.format(str(n_tsteps)), delimiter=',') return lift_distribution[:n_node_wing,[1,-1]] def tearDown(self): """ Removes all created files within this test. """ import shutil folders = ['cases', 'output'] for folder in folders: shutil.rmtree(self.route_test_dir + '/' + folder) if __name__ == '__main__': import unittest unittest.main() ================================================ FILE: tests/utils/__init__.py ================================================ ================================================ FILE: tests/utils/test_algebra.py ================================================ import sharpy.utils.algebra as algebra import numpy as np import unittest import random class TestAlgebra(unittest.TestCase): """ Tests the algebra module """ def test_unit_vector(self): """ Tests the routine for normalising vectors :return: """ vector_in = 1 result = algebra.unit_vector(vector_in) self.assertAlmostEqual(np.linalg.norm(result), 1.0, 5) vector_in = 0 result = algebra.unit_vector(vector_in) self.assertAlmostEqual(np.linalg.norm(result), 0.0, 5) vector_in = np.array([1, 0, 0]) result = algebra.unit_vector(vector_in) self.assertAlmostEqual(np.linalg.norm(result), 1.0, 5) vector_in = np.array([2, -1, 1]) result = algebra.unit_vector(vector_in) self.assertAlmostEqual(np.linalg.norm(result), 1.0, 5) vector_in = np.array([1e-8, 0, 0]) result = algebra.unit_vector(vector_in) self.assertAlmostEqual(np.linalg.norm(result), 1e-8, 5) vector_in = 'aa' with self.assertRaises(ValueError): algebra.unit_vector(vector_in) def test_rotation_vectors_conversions(self): """ Checks routine to convert rotation vectors. Note: test only includes CRV <-> quaternions conversions """ N = 1000 for nn in range(N): # def random rotation in [-pi,pi] a = np.pi * (2. * np.random.rand() - 1) nv = 2. * np.random.rand(3) - 1 nv = nv / np.linalg.norm(nv) # reference fv0 = a * nv quat0 = np.zeros((4,)) quat0[0] = np.cos(.5 * a) quat0[1:] = np.sin(.5 * a) * nv # check against reference assert np.linalg.norm(fv0 - algebra.quat2crv(quat0)) < 1e-12, \ 'Error in quat2crv' assert np.linalg.norm(quat0 - algebra.crv2quat(fv0)) < 1e-12, \ 'Error in crv2quat' def test_rotation_matrices(self): """ Checks routines and consistency of functions to generate rotation matrices. Note: test only includes triad <-> CRV <-> quaternions conversions """ ### Verify that function build rotation matrix (not projection matrix) # set an easy rotation (x axis) a = np.pi / 6. nv = np.array([1, 0, 0]) sa, ca = np.sin(a), np.cos(a) Cab_exp = np.array([[1, 0, 0], [0, ca, -sa], [0, sa, ca], ]) ### rot from triad Cab_num = algebra.triad2rotation(Cab_exp[:, 0], Cab_exp[:, 1], Cab_exp[:, 2]) assert np.linalg.norm(Cab_num - Cab_exp) < 1e-15, \ 'crv2rotation not producing the right result' ### rot from crv fv = a * nv Cab_num = algebra.crv2rotation(fv) assert np.linalg.norm(Cab_num - Cab_exp) < 1e-15, \ 'crv2rotation not producing the right result' ### rot from quat quat = algebra.crv2quat(fv) Cab_num = algebra.quat2rotation(quat) assert np.linalg.norm(Cab_num - Cab_exp) < 1e-15, \ 'quat2rotation not producing the right result' ### inverse relations # check crv2rotation and rotation2crv are biunivolcal in [-pi,pi] # check quat2rotation and rotation2quat are biunivocal in [-pi,pi] N = 100 for nn in range(N): # def random rotation in [-pi,pi] a = np.pi * (2. * np.random.rand() - 1) nv = 2. * np.random.rand(3) - 1 nv = nv / np.linalg.norm(nv) # inverse crv fv0 = a * nv Cab = algebra.crv2rotation(fv0) fv = algebra.rotation2crv(Cab) assert np.linalg.norm(fv - fv0) < 1e-12, \ 'rotation2crv not producing the right result' # triad2crv xa, ya, za = Cab[:, 0], Cab[:, 1], Cab[:, 2] assert np.linalg.norm( algebra.triad2crv(xa, ya, za) - fv0) < 1e-12, \ 'triad2crv not producing the right result' # inverse quat quat0 = np.zeros((4,)) quat0[0] = np.cos(.5 * a) quat0[1:] = np.sin(.5 * a) * nv quat = algebra.rotation2quat(algebra.quat2rotation(quat0)) assert np.linalg.norm(quat - quat0) < 1e-12, \ 'rotation2quat not producing the right result' ### combined rotation # assume 3 FoR, G, A and B where: # - G is the initial FoR # - A derives from a 90 deg rotation about zG # - B derives from a 90 deg rotation about yA crv_G_to_A = .5 * np.pi * np.array([0, 0, 1]) crv_A_to_B = .5 * np.pi * np.array([0, 1, 0]) Cga = algebra.crv2rotation(crv_G_to_A) Cab = algebra.crv2rotation(crv_A_to_B) # rotation G to B (i.e. projection B onto G) Cgb = np.dot(Cga, Cab) Cgb_exp = np.array([[0, -1, 0], [0, 0, 1], [-1, 0, 0]]) assert np.linalg.norm(Cgb - Cgb_exp) < 1e-15, \ 'combined rotation not as expected!' def test_rotation_matrices_derivatives(self): """ Checks derivatives of rotation matrix derivatives with respect to quaternions and Cartesian rotation vectors Note: test only includes CRV <-> quaternions conversions """ ### linearisation point # fi0=np.pi/6 # nv0=np.array([1,3,1]) fi0 = 2.0 * np.pi * random.random() - np.pi nv0 = np.array([random.random(), random.random(), random.random()]) nv0 = nv0 / np.linalg.norm(nv0) fv0 = fi0 * nv0 qv0 = algebra.crv2quat(fv0) ev0 = algebra.quat2euler(qv0) # direction of perturbation # fi1=np.pi/3 # nv1=np.array([-2,4,1]) fi1 = 2.0 * np.pi * random.random() - np.pi nv1 = np.array([random.random(), random.random(), random.random()]) nv1 = nv1 / np.linalg.norm(nv1) fv1 = fi1 * nv1 qv1 = algebra.crv2quat(fv1) ev1 = algebra.quat2euler(qv1) # linearsation point Cga0 = algebra.quat2rotation(qv0) Cag0 = Cga0.T Cab0 = algebra.crv2rotation(fv0) Cba0 = Cab0.T Cga0_euler = algebra.euler2rot(ev0) Cag0_euler = Cga0_euler.T # derivatives # xv=np.ones((3,)) # dummy vector xv = np.array([random.random(), random.random(), random.random()]) # dummy vector derCga = algebra.der_Cquat_by_v(qv0, xv) derCag = algebra.der_CquatT_by_v(qv0, xv) derCab = algebra.der_Ccrv_by_v(fv0, xv) derCba = algebra.der_CcrvT_by_v(fv0, xv) derCga_euler = algebra.der_Ceuler_by_v(ev0, xv) derCag_euler = algebra.der_Peuler_by_v(ev0, xv) A = np.array([1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6]) er_ag = 10. er_ga = 10. er_ab = 10. er_ba = 10. er_ag_euler = 10. er_ga_euler = 10. for a in A: # perturbed qv = a * qv1 + (1. - a) * qv0 fv = a * fv1 + (1. - a) * fv0 ev = a * ev1 + (1. - a) * ev0 dqv = qv - qv0 dfv = fv - fv0 dev = ev - ev0 Cga = algebra.quat2rotation(qv) Cag = Cga.T Cab = algebra.crv2rotation(fv) Cba = Cab.T Cga_euler = algebra.euler2rot(ev) Cag_euler = Cga_euler.T dCag_num = np.dot(Cag - Cag0, xv) dCga_num = np.dot(Cga - Cga0, xv) dCag_an = np.dot(derCag, dqv) dCga_an = np.dot(derCga, dqv) er_ag_new = np.max(np.abs(dCag_num - dCag_an)) er_ga_new = np.max(np.abs(dCga_num - dCga_an)) dCab_num = np.dot(Cab - Cab0, xv) dCba_num = np.dot(Cba - Cba0, xv) dCab_an = np.dot(derCab, dfv) dCba_an = np.dot(derCba, dfv) er_ab_new = np.max(np.abs(dCab_num - dCab_an)) er_ba_new = np.max(np.abs(dCba_num - dCba_an)) dCag_num_euler = np.dot(Cag_euler - Cag0_euler, xv) dCga_num_euler = np.dot(Cga_euler - Cga0_euler, xv) dCag_an_euler = np.dot(derCag_euler, dev) dCga_an_euler = np.dot(derCga_euler, dev) er_ag_euler_new = np.max(np.abs(dCag_num_euler - dCag_an_euler)) er_ga_euler_new = np.max(np.abs(dCga_num_euler - dCga_an_euler)) assert er_ga_new < er_ga, 'der_Cquat_by_v error not converging to 0' assert er_ag_new < er_ag, 'der_CquatT_by_v error not converging to 0' assert er_ab_new < er_ab, 'der_Ccrv_by_v error not converging to 0' assert er_ba_new < er_ba, 'der_CcrvT_by_v error not converging to 0' assert er_ga_euler_new < er_ga_euler, 'der_Ceuler_by_v error not converging to 0' assert er_ag_euler_new < er_ag_euler, 'der_Peuler_by_v error not converging to 0' er_ag = er_ag_new er_ga = er_ga_new er_ab = er_ab_new er_ba = er_ba_new er_ag_euler = er_ag_euler_new er_ga_euler = er_ga_euler_new assert er_ga < A[-2], 'der_Cquat_by_v error too large' assert er_ag < A[-2], 'der_CquatT_by_v error too large' assert er_ab < A[-2], 'der_Ccrv_by_v error too large' assert er_ba < A[-2], 'der_CcrvT_by_v error too large' assert er_ag_euler < A[-2], 'der_Peuler_by_v error too large' assert er_ga_euler < A[-2], 'der_Ceuler_by_v error too large' def test_crv_tangential_operator(self): """ Checks Cartesian rotation vector tangential operator """ # linearisation point fi0 = -np.pi / 6 nv0 = np.array([1, 3, 1]) nv0 = np.array([1, 0, 0]) nv0 = nv0 / np.linalg.norm(nv0) fv0 = fi0 * nv0 Cab = algebra.crv2rotation(fv0) # fv0 is rotation from A to B # dummy fi1 = np.pi / 3 nv1 = np.array([2, 4, 1]) nv1 = nv1 / np.linalg.norm(nv1) fv1 = fi1 * nv1 er_tan = 10. A = np.array([1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6]) for a in A: # perturbed fv = a * fv1 + (1. - a) * fv0 dfv = fv - fv0 ### Compute relevant quantities dCab = algebra.crv2rotation(fv0 + dfv) - Cab T = algebra.crv2tan(fv0) Tdfv = np.dot(T, dfv) Tdfv_skew = algebra.skew(Tdfv) dCab_an = np.dot(Cab, Tdfv_skew) er_tan_new = np.max(np.abs(dCab - dCab_an)) / np.max(np.abs(dCab_an)) assert er_tan_new < er_tan, 'crv2tan error not converging to 0' er_tan = er_tan_new assert er_tan < A[-2], 'crv2tan error too large' def test_crv_tangetial_operator_derivative(self): """ Checks Cartesian rotation vector tangential operator """ # linearisation point fi0 = np.pi / 6 nv0 = np.array([1, 3, 1]) nv0 = nv0 / np.linalg.norm(nv0) fv0 = fi0 * nv0 T0 = algebra.crv2tan(fv0) # dummy vector xv = np.ones((3,)) T0xv = np.dot(T0, xv) # derT_an=dTxv(fv0,xv) derT_an = algebra.der_Tan_by_xv(fv0, xv) # derT_an=algebra.der_Tan_by_xv_an(fv0,xv) # dummy fi1 = np.pi / 3 nv1 = np.array([4, 1, -2]) nv1 = nv1 / np.linalg.norm(nv1) fv1 = fi1 * nv1 er = 10. A = np.array([1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6]) for a in A: # perturbed fv = a * fv1 + (1. - a) * fv0 dfv = fv - fv0 Tpert = algebra.crv2tan(fv) Tpertxv = np.dot(Tpert, xv) dT_num = Tpertxv - T0xv dT_an = np.dot(derT_an, dfv) er_new = np.max(np.abs(dT_num - dT_an)) / np.max(np.abs(dT_an)) assert er_new < er, 'der_Tan_by_xv error not converging to 0' er = er_new assert er < A[-2], 'der_Tan_by_xv error too large' def test_crv_tangetial_operator_transpose_derivative(self): """ Checks Cartesian rotation vector tangential operator transpose""" # dummy vector xv = np.random.rand(3) # linearisation point fi0 = 2.0 * np.pi * np.random.rand(1) nv0 = np.random.rand(3) nv0 = nv0 / np.linalg.norm(nv0) fv0 = fi0 * nv0 T0_T = np.transpose(algebra.crv2tan(fv0)) T0_Txv = np.dot(T0_T, xv) # Analytical solution derT_T_an = algebra.der_TanT_by_xv(fv0, xv) # End point fi1 = 2.0 * np.pi * np.random.rand() nv1 = np.random.rand(3) nv1 = nv1 / np.linalg.norm(nv1) fv1 = fi1 * nv1 er = 10. A = np.array([1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6]) for a in A: # perturbed fv = a * fv1 + (1. - a) * fv0 dfv = fv - fv0 Tpert_T = np.transpose(algebra.crv2tan(fv)) Tpert_Txv = np.dot(Tpert_T, xv) dT_T_num = Tpert_Txv - T0_Txv dT_T_an = np.dot(derT_T_an, dfv) # Error er_new = np.max(np.abs(dT_T_num - dT_T_an)) / np.max(np.abs(dT_T_an)) assert er_new < er, 'der_TanT_by_xv error not converging to 0' er = er_new assert er < A[-2], 'der_TanT_by_xv error too large' def test_quat_wrt_rot(self): """ We define: - G: initial frame - A: frame obtained upon rotation, Cga, defined by the quaternion q0 - B: frame obtained upon further rotation, Cab, of A defined by the "infinitesimal" Cartesian rotation vector dcrv The test verifies that: 1. the total rotation matrix Cgb(q0+dq) is equal to Cgb = Cga(q0) Cab(dcrv) where dq = algebra.der_quat_wrt_crv(q0) 2. the difference between analytically computed delta quaternion, dq, and the numerical delta dq_num = algebra.crv2quat(algebra.rotation2crv(Cgb_ref))-q0 is comparable to the step used to compute the delta dcrv 3. The equality: d(Cga(q0)*v)*dq = Cga(q0) * d(Cab*dv)*dcrv where d(Cga(q0)*v) and d(Cab*dv) are the derivatives computed through algebra.der_Cquat_by_v and algebra.der_Ccrv_by_v for a random vector v. Warning: - The relation dcrv->dquat is not uniquely defined. However, the resulting rotation matrix is, namely: Cga(q0+dq)=Cga(q0)*Cab(dcrv) """ ### case 1: simple rotation about the same axis # linearisation point a0 = 30. * np.pi / 180 n0 = np.array([0, 0, 1]) n0 = n0 / np.linalg.norm(n0) q0 = algebra.crv2quat(a0 * n0) Cga = algebra.quat2rotation(q0) # direction of perturbation n2 = n0 A = np.array([1e-2, 1e-3, 1e-4, 1e-5, 1e-6]) for a in A: drot = a * n2 # build rotation manually atot = a0 + a Cgb_exp = algebra.crv2rotation(atot * n0) # ok # build combined rotation Cab = algebra.crv2rotation(drot) Cgb_ref = np.dot(Cga, Cab) # verify expected vs combined rotation matrices assert np.linalg.norm(Cgb_exp - Cgb_ref) / a < 1e-8, \ 'Verify test case - these matrices need to be identical' # verify analytical rotation matrix dq_an = np.dot(algebra.der_quat_wrt_crv(q0), drot) Cgb_an = algebra.quat2rotation(q0 + dq_an) erel_rot = np.linalg.norm(Cgb_an - Cgb_ref) / a assert erel_rot < 3e-3, \ 'Relative error of rotation matrix (%.2e) too large!' % erel_rot # verify delta quaternion erel_dq = np.linalg.norm(Cgb_an - Cgb_ref) dq_num = algebra.crv2quat(algebra.rotation2crv(Cgb_ref)) - q0 erel_dq = np.linalg.norm(dq_num - dq_an) / np.linalg.norm(dq_an) / a assert erel_dq < .3, \ 'Relative error delta quaternion (%.2e) too large!' % erel_dq # verify algebraic relation v = np.ones((3,)) D1 = algebra.der_Cquat_by_v(q0, v) D2 = algebra.der_Ccrv_by_v(np.zeros((3,)), v) res = np.dot(D1, dq_num) - np.dot(np.dot(Cga, D2), drot) erel_res = np.linalg.norm(res) / a assert erel_res < 5e-1 * a, \ 'Relative error of residual (%.2e) too large!' % erel_res ### case 2: random rotation # linearisation point a0 = 30. * np.pi / 180 n0 = np.array([-2, -1, 1]) n0 = n0 / np.linalg.norm(n0) q0 = algebra.crv2quat(a0 * n0) Cga = algebra.quat2rotation(q0) # direction of perturbation n2 = np.array([0.5, 1., -2.]) n2 = n2 / np.linalg.norm(n2) A = np.array([1e-2, 1e-3, 1e-4, 1e-5, 1e-6]) for a in A: drot = a * n2 # build combined rotation Cab = algebra.crv2rotation(drot) Cgb_ref = np.dot(Cga, Cab) # verify analytical rotation matrix dq_an = np.dot(algebra.der_quat_wrt_crv(q0), drot) Cgb_an = algebra.quat2rotation(q0 + dq_an) erel_rot = np.linalg.norm(Cgb_an - Cgb_ref) / a assert erel_rot < 3e-3, \ 'Relative error of rotation matrix (%.2e) too large!' % erel_rot # verify delta quaternion erel_dq = np.linalg.norm(Cgb_an - Cgb_ref) dq_num = algebra.crv2quat(algebra.rotation2crv(Cgb_ref)) - q0 erel_dq = np.linalg.norm(dq_num - dq_an) / np.linalg.norm(dq_an) / a assert erel_dq < .3, \ 'Relative error delta quaternion (%.2e) too large!' % erel_dq # verify algebraic relation v = np.ones((3,)) D1 = algebra.der_Cquat_by_v(q0, v) D2 = algebra.der_Ccrv_by_v(np.zeros((3,)), v) res = np.dot(D1, dq_num) - np.dot(np.dot(Cga, D2), drot) erel_res = np.linalg.norm(res) / a assert erel_res < 5e-1 * a, \ 'Relative error of residual (%.2e) too large!' % erel_res def test_rotation_about_axis(self): """ Tests the rotations about the cartesian axes """ i = np.array([1, 0, 0]) j = np.array([0, 1, 0]) k = np.array([0, 0, 1]) angle = 90 * np.pi / 180 # Testing rotations about the x axis - rotation3d_x out = algebra.rotation3d_x(angle).dot(j) for ax in range(3): self.assertAlmostEqual(out[ax], k[ax]) out = algebra.rotation3d_x(angle).dot(k) for ax in range(3): self.assertAlmostEqual(out[ax], -j[ax]) # Testing rotations about the y axis - rotation3d_y out = algebra.rotation3d_y(angle).dot(i) for ax in range(3): self.assertAlmostEqual(out[ax], -k[ax]) out = algebra.rotation3d_y(angle).dot(k) for ax in range(3): self.assertAlmostEqual(out[ax], i[ax]) # Testing rotations about the z axis - rotation3d_z out = algebra.rotation3d_z(angle).dot(i) for ax in range(3): self.assertAlmostEqual(out[ax], j[ax]) out = algebra.rotation3d_z(angle).dot(j) for ax in range(3): self.assertAlmostEqual(out[ax], -i[ax]) def test_rotation_G_to_A(self): """ Tests the rotation of a vector in G frame to a vector in A frame by a roll, pitch and yaw considering that SHARPy employs an SEU frame. In the inertial frame, the ``x_g`` axis is aligned with the longitudinal axis of the aircraft pointing backwards, ``z_g`` points upwards and ``y_g`` completes the set Returns: """ aircraft_nose = np.array([-1, 0, 0]) z_A = np.array([0, 0, 1]) euler = np.array([90, 90, 90]) * np.pi / 180 quat = algebra.euler2quat(euler) aircraft_nose_rotated = np.array([0, 0, 1]) # in G frame pointing upwards z_A_rotated_g = np.array([1, 0, 0]) Cga = algebra.euler2rot(euler) Cga_quat = algebra.quat2rotation(quat) np.testing.assert_array_almost_equal(Cga.dot(aircraft_nose), aircraft_nose_rotated, err_msg='Rotation using Euler angles not performed properly') np.testing.assert_array_almost_equal(Cga_quat.dot(aircraft_nose), aircraft_nose_rotated, err_msg='Rotation using quaternions not performed properly') np.testing.assert_array_almost_equal(Cga.dot(z_A), z_A_rotated_g, err_msg='Rotation using Euler angles not performed properly') np.testing.assert_array_almost_equal(Cga_quat.dot(z_A), z_A_rotated_g, err_msg='Rotation using quaternions not performed properly') # Check projections Pag = Cga.T Pag_quat = Cga_quat.T np.testing.assert_array_almost_equal(Pag.dot(z_A_rotated_g), z_A, err_msg='Error in projection from A to G using Euler angles') np.testing.assert_array_almost_equal(Pag.dot(aircraft_nose_rotated), aircraft_nose, err_msg='Error in projection from A to G using Euler angles') np.testing.assert_array_almost_equal(Pag_quat.dot(z_A_rotated_g), z_A, err_msg='Error in projection from A to G using quaternions') np.testing.assert_array_almost_equal(Pag_quat.dot(aircraft_nose_rotated), aircraft_nose, err_msg='Error in projection from A to G using quaternions') # if __name__=='__main__': # unittest.main() # # T=TestAlgebra() # # # T.setUp() # # T.test_rotation_vectors_conversions() # # T.test_rotation_matrices() # # T.test_rotation_matrices_derivatives() # # T.test_crv_tangetial_operator() # # T.test_crv_tangetial_operator_derivative() # # T.test_crv_tangetial_operator_transpose_derivative() ================================================ FILE: tests/utils/test_generate_cases.py ================================================ import unittest import numpy as np import os import shutil import sharpy.utils.generate_cases as gc import sharpy.cases.templates.template_wt as template_wt from sharpy.utils.constants import deg2rad class TestGenerateCases(unittest.TestCase): """ Tests the generate_cases module Based on the NREL-5MW wind turbine rotor """ def setUp(self): # remove_terminal_output = True ###################################################################### ########################### PARAMETERS ############################# ###################################################################### # Case global case case = 'rotor' route = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/' # Geometry discretization chord_panels = np.array([4], dtype=int) revs_in_wake = 5 # Operation rotation_velocity = 1.366190 pitch_deg = 0. #degrees # Wind WSP = 11.4 air_density = 1.225 # Simulation dphi = 4.*deg2rad revs_to_simulate = 5 ###################################################################### ########################## GENERATE WT ############################# ###################################################################### dt = dphi/rotation_velocity time_steps = int(revs_to_simulate*2.*np.pi/dphi) time_steps = 1 # For the test cases mstar = int(revs_in_wake*2.*np.pi/dphi) mstar = 2 # For the test cases # Remove screen output # if remove_terminal_output: # sys.stdout = open(os.devnull, "w") op_params = {'rotation_velocity': rotation_velocity, 'pitch_deg': pitch_deg, 'wsp': WSP, 'dt': dt} geom_params = {'chord_panels':chord_panels, 'tol_remove_points': 1e-8, 'n_points_camber': 100, 'm_distribution': 'uniform'} excel_description = {'excel_file_name': route + '../../docs/source/content/example_notebooks/source/type04_db_nrel5mw_oc3_v06.xlsx', 'excel_sheet_parameters': 'parameters', 'excel_sheet_structural_blade': 'structural_blade', 'excel_sheet_discretization_blade': 'discretization_blade', 'excel_sheet_aero_blade': 'aero_blade', 'excel_sheet_airfoil_info': 'airfoil_info', 'excel_sheet_airfoil_chord': 'airfoil_coord'} options = {'camber_effect_on_twist': False, 'user_defined_m_distribution_type': None, 'include_polars': False, 'separate_blades': False} rotor, hub_nodes = template_wt.rotor_from_excel_type03(op_params, geom_params, excel_description, options) # Return the standard output to the terminal # if remove_terminal_output: # sys.stdout.close() # sys.stdout = sys.__stdout__ ###################################################################### ###################### DEFINE SIMULATION ########################### ###################################################################### SimInfo = gc.SimulationInformation() SimInfo.set_default_values() SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'DynamicCoupled', 'SaveData'] SimInfo.solvers['SHARPy']['case'] = case SimInfo.solvers['SHARPy']['write_screen'] = 'off' SimInfo.solvers['SHARPy']['route'] = route SimInfo.solvers['SHARPy']['write_log'] = True SimInfo.solvers['SHARPy']['log_folder'] = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) + '/output/' SimInfo.set_variable_all_dicts('dt', dt) SimInfo.set_variable_all_dicts('rho', air_density) SimInfo.solvers['SteadyVelocityField']['u_inf'] = WSP SimInfo.solvers['SteadyVelocityField']['u_inf_direction'] = np.array([0., 0., 1.]) SimInfo.set_variable_all_dicts('velocity_field_input', SimInfo.solvers['SteadyVelocityField']) SimInfo.set_variable_all_dicts('output', os.path.abspath(os.path.dirname(os.path.realpath(__file__)))) SimInfo.solvers['BeamLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['mstar'] = mstar SimInfo.solvers['AerogridLoader']['freestream_dir'] = np.array([0.,0.,0.]) SimInfo.solvers['AerogridLoader']['wake_shape_generator'] = 'HelicoidalWake' SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] = {'u_inf': WSP, 'u_inf_direction': np.array([0., 0., 1.]), 'dt': dt, 'rotation_velocity': rotation_velocity*np.array([0., 0., 1.])} SimInfo.solvers['StaticCoupled']['structural_solver'] = 'RigidDynamicPrescribedStep' SimInfo.solvers['StaticCoupled']['structural_solver_settings'] = SimInfo.solvers['RigidDynamicPrescribedStep'] # SimInfo.solvers['StaticCoupled']['structural_solver'] = 'NonLinearDynamicPrescribedStep' # SimInfo.solvers['StaticCoupled']['structural_solver_settings'] = SimInfo.solvers['NonLinearDynamicPrescribedStep'] SimInfo.solvers['StaticCoupled']['aero_solver'] = 'StaticUvlm' SimInfo.solvers['StaticCoupled']['aero_solver_settings'] = SimInfo.solvers['StaticUvlm'] SimInfo.solvers['StaticCoupled']['tolerance'] = 1e-6 SimInfo.solvers['StaticCoupled']['n_load_steps'] = 0 SimInfo.solvers['StaticCoupled']['relaxation_factor'] = 0. SimInfo.solvers['StaticUvlm']['horseshoe'] = False SimInfo.solvers['StaticUvlm']['num_cores'] = 8 SimInfo.solvers['StaticUvlm']['n_rollup'] = 0 SimInfo.solvers['StaticUvlm']['rollup_dt'] = dt SimInfo.solvers['StaticUvlm']['rollup_aic_refresh'] = 1 SimInfo.solvers['StaticUvlm']['rollup_tolerance'] = 1e-8 SimInfo.solvers['StaticUvlm']['rbm_vel_g'] = np.array([0., 0., 0., 0., 0., rotation_velocity]) SimInfo.solvers['StepUvlm']['convection_scheme'] = 2 SimInfo.solvers['StepUvlm']['num_cores'] = 1 SimInfo.solvers['StepUvlm']['cfl1'] = False # SimInfo.solvers['DynamicCoupled']['structural_solver'] = 'NonLinearDynamicMultibody' # SimInfo.solvers['DynamicCoupled']['structural_solver_settings'] = SimInfo.solvers['NonLinearDynamicMultibody'] SimInfo.solvers['DynamicCoupled']['structural_solver'] = 'RigidDynamicPrescribedStep' SimInfo.solvers['DynamicCoupled']['structural_solver_settings'] = SimInfo.solvers['RigidDynamicPrescribedStep'] SimInfo.solvers['DynamicCoupled']['aero_solver'] = 'StepUvlm' SimInfo.solvers['DynamicCoupled']['aero_solver_settings'] = SimInfo.solvers['StepUvlm'] SimInfo.solvers['DynamicCoupled']['postprocessors'] = ['BeamPlot', 'AerogridPlot', 'Cleanup', 'SaveData'] SimInfo.solvers['DynamicCoupled']['postprocessors_settings'] = {'BeamPlot': SimInfo.solvers['BeamPlot'], 'AerogridPlot': SimInfo.solvers['AerogridPlot'], 'Cleanup': SimInfo.solvers['Cleanup'], 'SaveData': SimInfo.solvers['SaveData']} SimInfo.solvers['DynamicCoupled']['minimum_steps'] = 0 SimInfo.solvers['DynamicCoupled']['include_unsteady_force_contribution'] = True SimInfo.solvers['DynamicCoupled']['relaxation_factor'] = 0. SimInfo.solvers['DynamicCoupled']['final_relaxation_factor'] = 0. SimInfo.solvers['DynamicCoupled']['dynamic_relaxation'] = False SimInfo.solvers['DynamicCoupled']['relaxation_steps'] = 0 SimInfo.define_num_steps(time_steps) # Define dynamic simulation SimInfo.with_forced_vel = True SimInfo.for_vel = np.zeros((time_steps,6), dtype=float) SimInfo.for_vel[:,5] = rotation_velocity SimInfo.for_acc = np.zeros((time_steps,6), dtype=float) SimInfo.with_dynamic_forces = True SimInfo.dynamic_forces = np.zeros((time_steps,rotor.StructuralInformation.num_node,6), dtype=float) ###################################################################### ####################### GENERATE FILES ############################# ###################################################################### gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) rotor.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(time_steps) def test_generatecases(self): import sharpy.sharpy_main solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/' + case +'.sharpy') sharpy.sharpy_main.main(['', solver_path]) def tearDown(self): solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) solver_path += '/' files_to_delete = [case + '.aero.h5', case + '.dyn.h5', case + '.fem.h5', case + '.sharpy', '/output/' + case + '/log'] for f in files_to_delete: os.remove(solver_path + f) output_path = solver_path + 'output/' if os.path.isdir(output_path): shutil.rmtree(output_path) ================================================ FILE: tests/utils/test_settings.py ================================================ import sharpy.utils.settings as settings import sharpy.utils.exceptions as exceptions import sharpy.utils.cout_utils as cout from copy import deepcopy import numpy as np import unittest import tests.coupled.static.pazy.generate_pazy as gp import os class TestSettings(unittest.TestCase): """ Tests the settings utilities module """ route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) def setUp(self): cout.start_writer() def tearDown(self): cout.finish_writer() def test_settings_to_custom_types(self): in_dict = dict() default_dict = dict() types_dict = dict() in_dict['integer_var'] = '1234' types_dict['integer_var'] = 'int' default_dict['integer_var'] = 0 in_dict['float_var'] = '1.234' types_dict['float_var'] = 'float' default_dict['float_var'] = 0.0 in_dict['str_var'] = 'aaaa' types_dict['str_var'] = 'str' default_dict['str_var'] = 'default_string' in_dict['bool_var'] = 'on' types_dict['bool_var'] = 'bool' default_dict['bool_var'] = False in_dict['list_var'] = ['aa', 'bb', '11', 'ss'] types_dict['list_var'] = 'list(str)' default_dict['list_var'] = ['a', 'b'] split_list = ['aa', 'bb', '11', 'ss'] in_dict['float_list_var'] = ['1.1', '2.2', '3.3'] types_dict['float_list_var'] = 'list(float)' default_dict['float_list_var'] = np.array([0.0, -1.1]) split_float_list = np.array([1.1, 2.2, 3.3]) original_dict = deepcopy(in_dict) # assigned values test result = settings.to_custom_types(in_dict, types_dict, default_dict) # integer variable self.assertEqual(in_dict['integer_var'], 1234, 'Integer test for assigned values not passed') # float variable self.assertEqual(in_dict['float_var'], 1.234, 'Float test for assigned values not passed') # string variable self.assertEqual(in_dict['str_var'], 'aaaa', 'String test for assigned values not passed') # bool variable self.assertEqual(in_dict['bool_var'], True, 'Bool test for assigned values not passed') # list variable for i in range(4): self.assertEqual(in_dict['list_var'][i], split_list[i], 'List test for assigned values not passed') # float list variable for i in range(3): self.assertEqual(in_dict['float_list_var'][i], split_float_list[i], 'Floating point list test for assigned values not passed') # default values test in_default_dict = dict() result = settings.to_custom_types(in_default_dict, types_dict, default_dict) # integer variable self.assertEqual(in_default_dict['integer_var'], default_dict['integer_var'], 'Integer test for default values not passed') # float variable self.assertEqual(in_default_dict['float_var'], default_dict['float_var'], 'Float test for default values not passed') # string variable self.assertEqual(in_default_dict['str_var'], default_dict['str_var'], 'String test for default values not passed') # bool variable self.assertEqual(in_default_dict['bool_var'], default_dict['bool_var'], 'Bool test for default values not passed') # list(str) variable for i in range(2): self.assertEqual(in_default_dict['list_var'][i], default_dict['list_var'][i], 'String list test for default values not passed') # list(float) variable for i in range(2): self.assertEqual(in_default_dict['float_list_var'][i], default_dict['float_list_var'][i], 'float list test for default values not passed') # non-existant default values with self.assertRaises(exceptions.NoDefaultValueException): temp_default_dict = default_dict.copy() temp_default_dict['integer_var'] = None # remove value in in_dict temp_in_dict = deepcopy(original_dict) del temp_in_dict['integer_var'] result = settings.to_custom_types(temp_in_dict, types_dict, temp_default_dict) with self.assertRaises(exceptions.NoDefaultValueException): temp_default_dict = default_dict.copy() temp_default_dict['float_var'] = None # remove value in in_dict temp_in_dict = deepcopy(original_dict) del temp_in_dict['float_var'] result = settings.to_custom_types(temp_in_dict, types_dict, temp_default_dict) with self.assertRaises(exceptions.NoDefaultValueException): temp_default_dict = default_dict.copy() temp_default_dict['str_var'] = None # remove value in in_dict temp_in_dict = deepcopy(original_dict) del temp_in_dict['str_var'] result = settings.to_custom_types(temp_in_dict, types_dict, temp_default_dict) with self.assertRaises(exceptions.NoDefaultValueException): temp_default_dict = default_dict.copy() temp_default_dict['bool_var'] = None # remove value in in_dict temp_in_dict = deepcopy(original_dict) del temp_in_dict['bool_var'] result = settings.to_custom_types(temp_in_dict, types_dict, temp_default_dict) with self.assertRaises(exceptions.NoDefaultValueException): temp_default_dict = default_dict.copy() temp_default_dict['list_var'] = None # remove value in in_dict temp_in_dict = deepcopy(original_dict) del temp_in_dict['list_var'] result = settings.to_custom_types(temp_in_dict, types_dict, temp_default_dict) with self.assertRaises(exceptions.NoDefaultValueException): temp_default_dict = default_dict.copy() temp_default_dict['float_list_var'] = None # remove value in in_dict temp_in_dict = deepcopy(original_dict) del temp_in_dict['float_list_var'] result = settings.to_custom_types(temp_in_dict, types_dict, temp_default_dict) def test_multiple_setting_option(self): u_inf = 50 alpha = 7 case_name = 'pazy_uinf{:04g}_alpha{:04g}'.format(u_inf * 10, alpha * 10) M = 16 N = 64 Msf = 1 cases_folder = self.route_test_dir + '/pazy/cases/' output_folder = self.route_test_dir + '/pazy/cases/' # run case gp.generate_pazy(u_inf, case_name, output_folder, cases_folder, alpha=alpha, M=M, N=N, Msf=Msf, test_multiple_inputs=True) def tearDown(self): cases_folder = self.route_test_dir + '/pazy/cases/' if os.path.isdir(cases_folder): import shutil shutil.rmtree(cases_folder) ================================================ FILE: tests/uvlm/__init__.py ================================================ ================================================ FILE: tests/uvlm/dynamic/test_wake_cfl_n1.py ================================================ import numpy as np import os import sys import unittest # import shutil import sharpy.utils.generate_cases as gc import sharpy.sharpy_main # maths deg2rad = np.pi/180. # Generate the file containing the gust information gust = np.array([[-1e12, 0, 0, 0], [-1e-12, 0, 0, 0], [0, -1.523086e-6, 0, 1.745328e-3], [1e12, -1.523086e-6, 0, 1.745328e-3]]) np.savetxt("0p1_step_gust.txt", gust, header="Time[s] DeltaUx[m/s] DeltaUy[m/s] DeltaUz[m/s]") # Analytical kussner function as a function of the non-dimensional time kussner_function = np.array([[2.50000000e-01, 2.24788834e-01], [5.00000000e-01, 3.08322030e-01], [7.50000000e-01, 3.69409233e-01], [1.00000000e+00, 4.18591199e-01], [1.25000000e+00, 4.59608283e-01], [1.50000000e+00, 4.94597861e-01], [1.75000000e+00, 5.24998850e-01], [2.00000000e+00, 5.51822632e-01], [2.25000000e+00, 5.75792734e-01], [2.50000000e+00, 5.97434419e-01], [3.25000000e+00, 6.51811966e-01], [4.00000000e+00, 6.94721054e-01], [5.00000000e+00, 7.39586015e-01], [7.50000000e+00, 8.13230851e-01], [1.00000000e+01, 8.56395643e-01], [1.25000000e+01, 8.84426328e-01], [1.50000000e+01, 9.04257362e-01], [1.75000000e+01, 9.19155200e-01], [2.00000000e+01, 9.30758783e-01], [0.00000000e+00, 6.43293001e-02], [2.50000000e+01, 9.47358507e-01], [3.00000000e+01, 9.58126128e-01], [3.50000000e+01, 9.65279097e-01], [4.00000000e+01, 9.70275111e-01], [4.50000000e+01, 9.74053377e-01], [5.00000000e+01, 9.77137592e-01], [6.00000000e+01, 9.81903866e-01], [7.00000000e+01, 9.84820831e-01], [8.00000000e+01, 9.86297902e-01], [9.00000000e+01, 9.87557134e-01], [9.97500000e+01, 9.89298872e-01]]) class TestWakeCFLn1(unittest.TestCase): """ Validate an airfoil response to a Kussner gust Wake discretisation not complying with CFL=1 Non-linear and linear responses """ chord = 1. # AR = 1e7 # Wing aspect ratio nodes_AR = 4 # even number of nodes in the spanwise direction num_chord_panels = 8 # Number of chord panels uinf = 1. # Flow velocity uinf_dir = np.array([1., 0., 0.]) # Flow velocity direction air_density = 1.225 # Flow density aoa_ini_deg = 1. # Initial angle of attack wake_chords = 100 # Length of the wake in airfoil chords units # Time discretization final_ndtime = 12.0 # Final non-dimensional time time_steps_per_chord = 8 # Number of time steps required by the flow to cover the airfoil chord dt = chord/time_steps_per_chord/uinf # time step nd_dt = dt*uinf/chord*2 # non-dimensional time step offset_time_steps = 10 # Number of time steps before the gust gets to the airfoil offset = 0. def generate_geometry(self): # offset is just the distance between the gust edge and the first colocation point # It is just used for plotting purposes len_first_panel = (self.chord - self.dt*self.uinf)/(self.num_chord_panels - 1) self.offset = self.offset_time_steps*self.dt*self.uinf + (-0.25*self.chord + 0.75*len_first_panel)*np.cos(self.aoa_ini_deg*deg2rad) # should be 0.5*len_first_panel but to make sure it happens I increase it a bit offset_LE = self.offset - 0.25*self.chord*np.cos(self.aoa_ini_deg*deg2rad) # Compute span span = self.chord*self.AR assert self.nodes_AR%2 == 0, "nodes_AR must be even" # Compute number of nodes num_node = self.nodes_AR + 1 num_node_semispan = int((num_node - 1)/2 + 1) nodes_y_semispan = np.zeros((num_node_semispan,)) # Number of seminodes n2 = int(self.nodes_AR/2 + 1) # Nodes coordinates in the spanwise direction nodes_y_semispan[0:n2] = np.linspace(0, self.AR/2*self.chord, n2) # Nodes coordinates nodes_y = np.zeros((num_node,)) nodes_y[:num_node_semispan] = -1*nodes_y_semispan[::-1] nodes_y[num_node_semispan - 1:] = nodes_y_semispan assert not (np.diff(nodes_y) == 0).any(), "Repeated nodes" nodes = np.zeros((len(nodes_y), 3)) nodes[:, 1] = nodes_y # Irrelevant structural properties mass_per_unit_length = 1. mass_iner = 1e-4 EA = 1e9 GA = 1e9 GJ = 1e9 EI = 1e9 # Airfoil camber airfoil_camber = np.zeros((1, 100, 2)) airfoil_camber[0,:,0] = np.linspace(0.,1.,100) # Structure airfoil = gc.AeroelasticInformation() airfoil.StructuralInformation.num_node = len(nodes) airfoil.StructuralInformation.num_node_elem = 3 airfoil.StructuralInformation.compute_basic_num_elem() airfoil.StructuralInformation.generate_uniform_sym_beam(nodes, mass_per_unit_length, mass_iner, EA, GA, GJ, EI, num_node_elem = airfoil.StructuralInformation.num_node_elem, y_BFoR = 'x_AFoR', num_lumped_mass=0) airfoil.StructuralInformation.structural_twist = 0.*deg2rad*np.ones_like(airfoil.StructuralInformation.structural_twist) airfoil.StructuralInformation.boundary_conditions = np.zeros((airfoil.StructuralInformation.num_node), dtype = int) airfoil.StructuralInformation.boundary_conditions[0] = 1 # Clamped end airfoil.StructuralInformation.boundary_conditions[-1] = -1 # Free end # Generate blade aerodynamics airfoil.AerodynamicInformation.create_one_uniform_aerodynamics(airfoil.StructuralInformation, chord = self.chord, twist = self.aoa_ini_deg*deg2rad, sweep = 0., num_chord_panels = self.num_chord_panels, m_distribution = 'uniform', elastic_axis = 0.25, num_points_camber = 100, airfoil = airfoil_camber) return airfoil def generate_files(self, case_header, airfoil): # Define the simulation case = ('%s_aoa%.2f' % (case_header, self.aoa_ini_deg)).replace(".", "p") route = os.path.dirname(os.path.realpath(__file__)) + '/' SimInfo = gc.SimulationInformation() SimInfo.set_default_values() SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'DynamicCoupled'] SimInfo.solvers['SHARPy']['case'] = case SimInfo.solvers['SHARPy']['route'] = route SimInfo.solvers['SHARPy']['log_folder'] = "./output/%s" % case SimInfo.solvers['SHARPy']['write_log'] = False SimInfo.solvers['SHARPy']['write_screen'] = False SimInfo.set_variable_all_dicts('rho', self.air_density) SimInfo.set_variable_all_dicts('dt', self.dt) SimInfo.solvers['BeamLoader']['unsteady'] = 'on' import sharpy.utils.generator_interface as gi if case_header == 'traditional' or case_header == 'statespace_cfl1': SimInfo.solvers['AerogridLoader']['wake_shape_generator'] = 'StraightWake' SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] = {'u_inf': self.uinf, 'u_inf_direction': self.uinf_dir, 'dt': self.dt, 'dx1': self.chord/self.num_chord_panels, 'ndx1': int((self.wake_chords*self.chord)/(self.chord/self.num_chord_panels)), 'r':1., 'dxmax':10*self.chord} gi.dictionary_of_generators(print_info=False) hw = gi.dict_of_generators['StraightWake'] wsg_in = SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] # for simplicity length = 0 mstar = 0 while length < (self.wake_chords*self.chord): mstar += 1 length += hw.get_deltax(mstar, wsg_in['dx1'], wsg_in['ndx1'], wsg_in['r'], wsg_in['dxmax']) SimInfo.solvers['AerogridLoader']['unsteady'] = 'on' SimInfo.solvers['AerogridLoader']['mstar'] = mstar SimInfo.solvers['AerogridLoader']['freestream_dir'] = np.array([0.,0.,0.]) elif case_header in ['new_nonlinear', 'new_linear', 'statespace']: SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] = {'u_inf': self.uinf, 'u_inf_direction': self.uinf_dir, 'dt': self.dt, 'dx1': self.chord/self.num_chord_panels, 'ndx1': int((1*self.chord)/(self.chord/self.num_chord_panels)), 'r':2., 'dxmax':10*self.chord} gi.dictionary_of_generators(print_info=False) hw = gi.dict_of_generators['StraightWake'] wsg_in = SimInfo.solvers['AerogridLoader']['wake_shape_generator_input'] # for simplicity length = 0 mstar = 0 while length < (self.wake_chords*self.chord): mstar += 1 length += hw.get_deltax(mstar, wsg_in['dx1'], wsg_in['ndx1'], wsg_in['r'], wsg_in['dxmax']) SimInfo.solvers['AerogridLoader']['mstar'] = mstar SimInfo.solvers['SteadyVelocityField']['u_inf'] = self.uinf SimInfo.solvers['SteadyVelocityField']['u_inf_direction'] = self.uinf_dir SimInfo.solvers['StaticUvlm']['horseshoe'] = False SimInfo.solvers['StaticUvlm']['num_cores'] = 8 SimInfo.solvers['StaticUvlm']['n_rollup'] = 0 SimInfo.solvers['StaticUvlm']['rollup_dt'] = self.dt SimInfo.solvers['StaticUvlm']['velocity_field_generator'] = 'SteadyVelocityField' SimInfo.solvers['StaticUvlm']['velocity_field_input'] = SimInfo.solvers['SteadyVelocityField'] SimInfo.solvers['StaticCoupled']['structural_solver'] = 'RigidDynamicPrescribedStep' SimInfo.solvers['StaticCoupled']['structural_solver_settings'] = SimInfo.solvers['RigidDynamicPrescribedStep'] SimInfo.solvers['StaticCoupled']['aero_solver'] = 'StaticUvlm' SimInfo.solvers['StaticCoupled']['aero_solver_settings'] = SimInfo.solvers['StaticUvlm'] if case_header in ['traditional', 'new_nonlinear']: SimInfo.solvers['StepUvlm']['convection_scheme'] = 0 SimInfo.solvers['StepUvlm']['num_cores'] = 8 SimInfo.solvers['StepUvlm']['velocity_field_generator'] = 'GustVelocityField' SimInfo.solvers['StepUvlm']['velocity_field_input'] = {'u_inf' : self.uinf, 'u_inf_direction': np.array([1.,0.,0.]), 'gust_shape': 'time varying', 'offset': self.offset, 'relative_motion': True, 'gust_parameters': {'file' : '0p1_step_gust.txt', 'gust_length': 0., 'gust_intensity': 0.}} SimInfo.solvers['DynamicCoupled']['aero_solver'] = 'StepUvlm' SimInfo.solvers['DynamicCoupled']['aero_solver_settings'] = SimInfo.solvers['StepUvlm'] elif case_header == 'new_linear': SimInfo.solvers['StepLinearUVLM']['dt'] = self.dt SimInfo.solvers['StepLinearUVLM']['integr_order'] = 2 SimInfo.solvers['StepLinearUVLM']['remove_predictor'] = 'off' SimInfo.solvers['StepLinearUVLM']['use_sparse'] = True SimInfo.solvers['StepLinearUVLM']['density'] = self.air_density SimInfo.solvers['StepLinearUVLM']['vortex_radius'] = 1e-6 SimInfo.solvers['StepLinearUVLM']['vortex_radius_wake_ind'] = 1e-3 SimInfo.solvers['StepLinearUVLM']['velocity_field_generator'] = 'GustVelocityField' SimInfo.solvers['StepLinearUVLM']['velocity_field_input'] = {'u_inf' : self.uinf, 'u_inf_direction': self.uinf_dir, 'gust_shape': 'time varying', 'offset': self.offset, 'relative_motion': True, 'gust_parameters': {'file' : '0p1_step_gust.txt', 'gust_length': 0., 'gust_intensity': 0.}} SimInfo.solvers['DynamicCoupled']['aero_solver'] = 'StepLinearUVLM' SimInfo.solvers['DynamicCoupled']['aero_solver_settings'] = SimInfo.solvers['StepLinearUVLM'] if case_header == 'new_nonlinear': SimInfo.solvers['StaticUvlm']['cfl1'] = False SimInfo.solvers['StepUvlm']['cfl1'] = False elif case_header == 'new_linear': SimInfo.solvers['StaticUvlm']['cfl1'] = False SimInfo.solvers['StepLinearUVLM']['cfl1'] = False elif case_header == 'traditional': SimInfo.solvers['StaticUvlm']['cfl1'] = True SimInfo.solvers['StepUvlm']['cfl1'] = True SimInfo.solvers['DynamicCoupled']['structural_solver'] = 'RigidDynamicPrescribedStep' SimInfo.solvers['DynamicCoupled']['structural_solver_settings'] = SimInfo.solvers['RigidDynamicPrescribedStep'] SimInfo.solvers['DynamicCoupled']['postprocessors'] = ['Cleanup'] SimInfo.solvers['DynamicCoupled']['postprocessors_settings'] = {'Cleanup': SimInfo.solvers['Cleanup']} SimInfo.solvers['DynamicCoupled']['minimum_steps'] = 0 SimInfo.solvers['DynamicCoupled']['include_unsteady_force_contribution'] = True SimInfo.solvers['DynamicCoupled']['relaxation_factor'] = 0. SimInfo.solvers['DynamicCoupled']['final_relaxation_factor'] = 0. SimInfo.solvers['DynamicCoupled']['dynamic_relaxation'] = False SimInfo.solvers['DynamicCoupled']['relaxation_steps'] = 0 SimInfo.solvers['DynamicCoupled']['fsi_tolerance'] = 1e-6 # Time discretization time_steps = int(self.final_ndtime / self.nd_dt - 1) SimInfo.define_num_steps(time_steps) SimInfo.with_forced_vel = True SimInfo.for_vel = np.zeros((time_steps,6), dtype=float) SimInfo.for_acc = np.zeros((time_steps,6), dtype=float) SimInfo.with_dynamic_forces = True SimInfo.dynamic_forces = np.zeros((time_steps,airfoil.StructuralInformation.num_node,6), dtype=float) SimInfo.solvers['PickleData'] = {'folder': route + '/output/'} if 'statespace' in case_header: SimInfo.solvers['SHARPy']['flow'] = ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'Modal', 'LinearAssembler', 'AsymptoticStability', ] SimInfo.solvers['SHARPy']['write_screen'] = 'on' SimInfo.solvers['Modal'] = {'save_data': 'off', 'NumLambda': 50, 'rigid_body_modes': 'off'} SimInfo.solvers['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', 'linear_system_settings': { 'beam_settings': {'modal_projection': 'off', 'inout_coords': 'nodes', 'discrete_time': 'on', 'newmark_damp': 0.5e-4, 'discr_method': 'newmark', 'dt': self.dt, 'proj_modes': 'undamped', 'use_euler': 'on', 'num_modes': 2, 'print_info': 'on', 'gravity': 'on', 'remove_dofs': [], 'remove_rigid_states': 'off'}, 'aero_settings': {'dt': self.dt, 'integr_order': 2, 'density': self.air_density, 'remove_predictor': 'off', 'use_sparse': 'off', 'rigid_body_motion': 'off', 'use_euler': 'on', 'vortex_radius': 1e-6, 'convert_to_ct': 'off', 'gust_assembler': 'LeadingEdge', 'gust_assembler_inputs': {}, }, 'rigid_body_motion': 'off', 'track_body': 'on', 'use_euler': 'on', 'linearisation_tstep': -1 }, } SimInfo.solvers['AsymptoticStability'] = {'num_evals': 100} gc.clean_test_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) airfoil.generate_h5_files(SimInfo.solvers['SHARPy']['route'], SimInfo.solvers['SHARPy']['case']) SimInfo.generate_solver_file() SimInfo.generate_dyn_file(time_steps) return case, route def run_test(self, case_header): airfoil = self.generate_geometry() case, route = self.generate_files(case_header, airfoil) sharpy_file = route + case + '.sharpy' sharpy_output = sharpy.sharpy_main.main(['', sharpy_file]) lift = sharpy_output.structure.timestep_info[-1].steady_applied_forces[self.nodes_AR//2, 2] lift += sharpy_output.structure.timestep_info[-1].unsteady_applied_forces[self.nodes_AR//2, 2] nd_time = sharpy_output.ts*self.nd_dt daoa = 0.1 clsteady = 2*np.pi*(self.aoa_ini_deg + daoa)*deg2rad offset_plot = 0.84375 factor = 0.5*self.air_density*self.uinf**2*self.chord*2.5e6 sharpy_cl_clss = -lift/factor/clsteady kussner_cl_clss = 2*np.pi*self.aoa_ini_deg*deg2rad kussner_cl_clss += 2 * np.pi * daoa * deg2rad * np.interp(nd_time, kussner_function[:, 0], kussner_function[:, 1]) kussner_cl_clss /= clsteady self.assertAlmostEqual(sharpy_cl_clss, kussner_cl_clss, 1) def test_traditional(self): self.run_test('traditional') def test_new_nonlinear(self): self.run_test('new_nonlinear') def test_new_linear(self): self.run_test('new_linear') def test_statespace(self): results = {} for case in ['statespace', 'statespace_cfl1']: with self.subTest(case): results[case] = self.run_statespace(case) def run_statespace(self, case_header): airfoil = self.generate_geometry() case, route = self.generate_files(case_header, airfoil) sharpy_file = route + case + '.sharpy' return sharpy.sharpy_main.main(['', sharpy_file]) @classmethod def tearDownClass(cls): solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) solver_path += '/' files_to_delete = [] for name in ['traditional_aoa1p00', 'new_nonlinear_aoa1p00', 'new_linear_aoa1p00']: files_to_delete.extend((name + '.aero.h5', name + '.dyn.h5', name + '.fem.h5', name + '.sharpy')) files_to_delete.append("0p1_step_gust.txt") for f in files_to_delete: os.remove(solver_path + f) # shutil.rmtree(solver_path + 'output/') ================================================ FILE: tests/uvlm/static/__init__.py ================================================ ================================================ FILE: tests/uvlm/static/big_wake_planarwing/generate_big_wake_planarwing.py ================================================ import h5py as h5 import numpy as np import configparser import os case_name = 'big_wake_planarwing' route = os.path.dirname(os.path.realpath(__file__)) + '/' # flight conditions u_inf = 10 rho = 1.225 alpha = 15 beta = 0 c_ref = 1 b_ref = 16 sweep = 0*np.pi/180. aspect_ratio = 1 # = total wing span (chord = 1) alpha_rad = alpha*np.pi/180 # main geometry data main_span = aspect_ratio/2./np.cos(sweep) main_chord = 1.0 main_ea = 0.5 main_sigma = 1 main_airfoil_P = 0 main_airfoil_M = 0 n_surfaces = 2 # discretisation data num_elem_main = 10 num_node_elem = 3 num_elem = num_elem_main + num_elem_main num_node_main = num_elem_main*(num_node_elem - 1) + 1 num_node = num_node_main + (num_node_main - 1) m_main = 10 def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def generate_fem_file(): # placeholders # coordinates global x, y, z x = np.zeros((num_node, )) y = np.zeros((num_node, )) z = np.zeros((num_node, )) # struct twist structural_twist = np.zeros((num_elem, 3)) # beam number beam_number = np.zeros((num_elem, ), dtype=int) # frame of reference delta frame_of_reference_delta = np.zeros((num_elem, num_node_elem, 3)) # connectivities conn = np.zeros((num_elem, num_node_elem), dtype=int) # stiffness num_stiffness = 1 ea = 1e4 ga = 1e4 gj = 1e4 eiy = 2e4 eiz = 5e4 sigma = 1 base_stiffness = sigma*np.diag([ea, ga, ga, gj, eiy, eiz]) stiffness = np.zeros((num_stiffness, 6, 6)) stiffness[0, :, :] = main_sigma*base_stiffness elem_stiffness = np.zeros((num_elem,), dtype=int) # mass num_mass = 1 m_base = 0.75 j_base = 0.1 base_mass = np.diag([m_base, m_base, m_base, j_base, j_base, j_base]) mass = np.zeros((num_mass, 6, 6)) mass[0, :, :] = np.sqrt(main_sigma)*base_mass elem_mass = np.zeros((num_elem,), dtype=int) # boundary conditions boundary_conditions = np.zeros((num_node, ), dtype=int) boundary_conditions[0] = 1 # applied forces n_app_forces = 0 node_app_forces = np.zeros((n_app_forces,), dtype=int) app_forces = np.zeros((n_app_forces, 6)) spacing_param = 3 # right wing (beam 0) -------------------------------------------------------------- working_elem = 0 working_node = 0 beam_number[working_elem:working_elem + num_elem_main] = 0 domain = np.linspace(0, 1.0, num_node_main) # 16 - (np.geomspace(20, 4, 10) - 4) x[working_node:working_node + num_node_main] = np.sin(sweep)*(main_span - (np.geomspace(main_span + spacing_param, 0 + spacing_param, num_node_main) - spacing_param)) y[working_node:working_node + num_node_main] = np.abs(np.cos(sweep)*(main_span - (np.geomspace(main_span + spacing_param, 0 + spacing_param, num_node_main) - spacing_param))) y[0] = 0 # y[working_node:working_node + num_node_main] = np.cos(sweep)*np.linspace(0.0, main_span, num_node_main) # x[working_node:working_node + num_node_main] = np.sin(sweep)*np.linspace(0.0, main_span, num_node_main) # y[working_node:working_node + num_node_main] = np.cos(sweep)*np.linspace(0.0, main_span, num_node_main) for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[0] = 1 boundary_conditions[working_node + num_node_main - 1] = -1 working_elem += num_elem_main working_node += num_node_main # left wing (beam 1) -------------------------------------------------------------- beam_number[working_elem:working_elem + num_elem_main] = 1 domain = np.linspace(-1.0, 0.0, num_node_main) tempy = np.linspace(-main_span, 0.0, num_node_main) # x[working_node:working_node + num_node_main - 1] = -np.sin(sweep)*tempy[0:-1] # y[working_node:working_node + num_node_main - 1] = np.cos(sweep)*tempy[0:-1] x[working_node:working_node + num_node_main - 1] = -np.sin(sweep)*(main_span - (np.geomspace(0 + spacing_param, main_span + spacing_param, num_node_main)[:-1] - spacing_param)) y[working_node:working_node + num_node_main - 1] = -np.abs(np.cos(sweep)*(main_span - (np.geomspace(0 + spacing_param, main_span + spacing_param, num_node_main)[:-1] - spacing_param))) for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) + 1 conn[working_elem + num_elem_main - 1, 1] = 0 elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[working_node] = -1 working_elem += num_elem_main working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=num_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=num_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=num_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) node_app_forces_handle = h5file.create_dataset( 'node_app_forces', data=node_app_forces) def generate_aero_file(): global x, y, z airfoil_distribution = np.zeros((num_elem, num_node_elem), dtype=int) surface_distribution = np.zeros((num_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces, ), dtype=int) m_distribution = 'uniform' aero_node = np.zeros((num_node,), dtype=bool) # twist = np.zeros((num_node,)) twist= np.zeros((num_elem, num_node_elem)) chord = np.zeros((num_elem, num_node_elem)) elastic_axis = np.zeros((num_elem, num_node_elem)) # elastic_axis = np.zeros((num_node,)) working_elem = 0 working_node = 0 # right wing (surface 0, beam 0) i_surf = 0 airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main] = True chord[:] = main_chord elastic_axis[:] = main_ea working_elem += num_elem_main working_node += num_node_main # left wing (surface 1, beam 1) i_surf = 1 airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main - 1] = True # chord[working_node:working_node + num_node_main - 1] = main_chord # elastic_axis[working_node:working_node + num_node_main - 1] = main_ea working_elem += num_elem_main working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( generate_naca_camber(P=main_airfoil_P, M=main_airfoil_M))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input .attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) def generate_naca_camber(M=0, P=0): m = M*1e-2 p = P*1e-1 def naca(x, m, p): if x < 1e-6: return 0.0 elif x < p: return m/(p*p)*(2*p*x - x*x) elif x > p and x < 1+1e-6: return m/((1-p)*(1-p))*(1 - 2*p + 2*p*x - x*x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, m, p) for x in x_vec]) return x_vec, y_vec def generate_solver_file(horseshoe=False): import sharpy.utils.algebra as algebra file_name = route + '/' + case_name + '.sharpy' # config = configparser.ConfigParser() import configobj config = configobj.ConfigObj() config.filename = file_name config['SHARPy'] = {'case': case_name, 'route': route, 'flow': ['BeamLoader', 'AerogridLoader', 'StaticUvlm', 'AerogridPlot', 'AeroForcesCalculator'], 'write_screen': 'off', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} config['BeamLoader'] = {'unsteady': 'off', 'orientation': algebra.euler2quat(np.array([0.0, alpha_rad, beta*np.pi/180]))} if horseshoe is True: config['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 1, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': main_chord/m_main/u_inf}} config['StaticUvlm'] = {'print_info': 'off', 'horseshoe': 'on', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': main_chord/m_main/u_inf, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': 1.225, 'alpha': alpha_rad, 'beta': beta } else: config['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 90, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': main_chord/m_main/u_inf}} config['StaticUvlm'] = {'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 100, 'rollup_dt': main_chord/m_main/u_inf, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': 1.225, 'alpha': alpha_rad, 'beta': beta } minus_m_star = 0 if config['StaticUvlm']['horseshoe'] == 'on': minus_m_star = max(m_main - 1, 1) config['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0 } config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': case_name + '_aeroforces.csv', 'screen_output': 'on', 'unsteady': 'off' } config.write() clean_test_files() generate_fem_file() generate_solver_file(horseshoe=False) generate_aero_file() ================================================ FILE: tests/uvlm/static/planarwing/__init__.py ================================================ ================================================ FILE: tests/uvlm/static/planarwing/generate_planarwing.py ================================================ import h5py as h5 import numpy as np import configparser import os case_name = 'planarwing' route = os.path.dirname(os.path.realpath(__file__)) + '/' # flight conditions u_inf = 25 rho = 1.225 alpha = 4 beta = 0 c_ref = 1 b_ref = 16 sweep = 0*np.pi/180. aspect_ratio = 32 # = total wing span (chord = 1) alpha_rad = alpha*np.pi/180 # main geometry data main_span = aspect_ratio/2./np.cos(sweep) main_chord = 1.0 main_ea = 0.5 main_sigma = 1 main_airfoil_P = 0 main_airfoil_M = 0 n_surfaces = 2 # discretisation data num_elem_main = 10 num_node_elem = 3 num_elem = num_elem_main + num_elem_main num_node_main = num_elem_main*(num_node_elem - 1) + 1 num_node = num_node_main + (num_node_main - 1) m_main = 10 def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) flightcon_file_name = route + '/' + case_name + '.flightcon.txt' if os.path.isfile(flightcon_file_name): os.remove(flightcon_file_name) def generate_fem_file(): # placeholders # coordinates global x, y, z x = np.zeros((num_node, )) y = np.zeros((num_node, )) z = np.zeros((num_node, )) # struct twist structural_twist = np.zeros((num_elem, num_node_elem)) # beam number beam_number = np.zeros((num_elem, ), dtype=int) # frame of reference delta frame_of_reference_delta = np.zeros((num_elem, num_node_elem, 3)) # connectivities conn = np.zeros((num_elem, num_node_elem), dtype=int) # stiffness num_stiffness = 1 ea = 1e4 ga = 1e4 gj = 1e4 eiy = 2e4 eiz = 5e4 sigma = 1 base_stiffness = sigma*np.diag([ea, ga, ga, gj, eiy, eiz]) stiffness = np.zeros((num_stiffness, 6, 6)) stiffness[0, :, :] = main_sigma*base_stiffness elem_stiffness = np.zeros((num_elem,), dtype=int) # mass num_mass = 1 m_base = 0.75 j_base = 0.1 base_mass = np.diag([m_base, m_base, m_base, j_base, j_base, j_base]) mass = np.zeros((num_mass, 6, 6)) mass[0, :, :] = np.sqrt(main_sigma)*base_mass elem_mass = np.zeros((num_elem,), dtype=int) # boundary conditions boundary_conditions = np.zeros((num_node, ), dtype=int) boundary_conditions[0] = 1 # applied forces n_app_forces = 0 node_app_forces = np.zeros((n_app_forces,), dtype=int) app_forces = np.zeros((n_app_forces, 6)) spacing_param = 3 # right wing (beam 0) -------------------------------------------------------------- working_elem = 0 working_node = 0 beam_number[working_elem:working_elem + num_elem_main] = 0 domain = np.linspace(0, 1.0, num_node_main) # 16 - (np.geomspace(20, 4, 10) - 4) x[working_node:working_node + num_node_main] = np.sin(sweep)*(main_span - (np.geomspace(main_span + spacing_param, 0 + spacing_param, num_node_main) - spacing_param)) y[working_node:working_node + num_node_main] = np.abs(np.cos(sweep)*(main_span - (np.geomspace(main_span + spacing_param, 0 + spacing_param, num_node_main) - spacing_param))) y[0] = 0 # y[working_node:working_node + num_node_main] = np.cos(sweep)*np.linspace(0.0, main_span, num_node_main) # x[working_node:working_node + num_node_main] = np.sin(sweep)*np.linspace(0.0, main_span, num_node_main) # y[working_node:working_node + num_node_main] = np.cos(sweep)*np.linspace(0.0, main_span, num_node_main) for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[0] = 1 boundary_conditions[working_node + num_node_main - 1] = -1 working_elem += num_elem_main working_node += num_node_main # left wing (beam 1) -------------------------------------------------------------- beam_number[working_elem:working_elem + num_elem_main] = 1 domain = np.linspace(-1.0, 0.0, num_node_main) tempy = np.linspace(-main_span, 0.0, num_node_main) # x[working_node:working_node + num_node_main - 1] = -np.sin(sweep)*tempy[0:-1] # y[working_node:working_node + num_node_main - 1] = np.cos(sweep)*tempy[0:-1] x[working_node:working_node + num_node_main - 1] = -np.sin(sweep)*(main_span - (np.geomspace(0 + spacing_param, main_span + spacing_param, num_node_main)[:-1] - spacing_param)) y[working_node:working_node + num_node_main - 1] = -np.abs(np.cos(sweep)*(main_span - (np.geomspace(0 + spacing_param, main_span + spacing_param, num_node_main)[:-1] - spacing_param))) for ielem in range(num_elem_main): for inode in range(num_node_elem): frame_of_reference_delta[working_elem + ielem, inode, :] = [-1, 0, 0] # connectivity for ielem in range(num_elem_main): conn[working_elem + ielem, :] = ((np.ones((3,))*(working_elem + ielem)*(num_node_elem - 1)) + [0, 2, 1]) + 1 conn[working_elem + num_elem_main - 1, 1] = 0 elem_stiffness[working_elem:working_elem + num_elem_main] = 0 elem_mass[working_elem:working_elem + num_elem_main] = 0 boundary_conditions[working_node] = -1 working_elem += num_elem_main working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=num_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=num_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=num_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) node_app_forces_handle = h5file.create_dataset( 'node_app_forces', data=node_app_forces) def generate_aero_file(): global x, y, z airfoil_distribution = np.zeros((num_elem, num_node_elem), dtype=int) surface_distribution = np.zeros((num_elem,), dtype=int) - 1 surface_m = np.zeros((n_surfaces, ), dtype=int) m_distribution = 'uniform' aero_node = np.zeros((num_node,), dtype=bool) # twist = np.zeros((num_node,)) twist= np.zeros((num_elem, num_node_elem)) chord = np.zeros((num_elem, num_node_elem)) elastic_axis = np.zeros((num_elem, num_node_elem)) # elastic_axis = np.zeros((num_node,)) working_elem = 0 working_node = 0 # right wing (surface 0, beam 0) i_surf = 0 airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main] = True chord[:] = main_chord elastic_axis[:] = main_ea working_elem += num_elem_main working_node += num_node_main # left wing (surface 1, beam 1) i_surf = 1 airfoil_distribution[working_elem:working_elem + num_elem_main, :] = 0 surface_distribution[working_elem:working_elem + num_elem_main] = i_surf surface_m[i_surf] = m_main aero_node[working_node:working_node + num_node_main - 1] = True # chord[working_node:working_node + num_node_main - 1] = main_chord # elastic_axis[working_node:working_node + num_node_main - 1] = main_ea working_elem += num_elem_main working_node += num_node_main - 1 with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add one airfoil naca_airfoil_main = airfoils_group.create_dataset('0', data=np.column_stack( generate_naca_camber(P=main_airfoil_P, M=main_airfoil_M))) # chord chord_input = h5file.create_dataset('chord', data=chord) dim_attr = chord_input .attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=twist) dim_attr = twist_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=airfoil_distribution) surface_distribution_input = h5file.create_dataset('surface_distribution', data=surface_distribution) surface_m_input = h5file.create_dataset('surface_m', data=surface_m) m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=elastic_axis) def generate_naca_camber(M=0, P=0): m = M*1e-2 p = P*1e-1 def naca(x, m, p): if x < 1e-6: return 0.0 elif x < p: return m/(p*p)*(2*p*x - x*x) elif x > p and x < 1+1e-6: return m/((1-p)*(1-p))*(1 - 2*p + 2*p*x - x*x) x_vec = np.linspace(0, 1, 1000) y_vec = np.array([naca(x, m, p) for x in x_vec]) return x_vec, y_vec def generate_solver_file(horseshoe=False): import sharpy.utils.algebra as algebra file_name = route + '/' + case_name + '.sharpy' # config = configparser.ConfigParser() import configobj config = configobj.ConfigObj() config.filename = file_name config['SHARPy'] = {'case': case_name, 'route': route, 'flow': ['BeamLoader', 'AerogridLoader', 'StaticUvlm', 'AerogridPlot', 'AeroForcesCalculator'], 'write_screen': 'off', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} config['BeamLoader'] = {'unsteady': 'off', 'orientation': algebra.euler2quat(np.array([0.0, alpha_rad, beta*np.pi/180]))} if horseshoe is True: config['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 1, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': main_chord/m_main/u_inf}} config['StaticUvlm'] = {'print_info': 'off', 'horseshoe': 'on', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': main_chord/m_main/u_inf, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': 1.225, 'alpha': alpha_rad, 'beta': beta } else: config['AerogridLoader'] = {'unsteady': 'off', 'aligned_grid': 'on', 'mstar': 40, 'freestream_dir': ['1', '0', '0'], 'wake_shape_generator_input': {'u_inf': u_inf, 'u_inf_direction': np.array([1., 0., 0.]), 'dt': main_chord/m_main/u_inf}} config['StaticUvlm'] = {'print_info': 'off', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 50, 'rollup_dt': main_chord/m_main/u_inf, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': 1.225, 'alpha': alpha_rad, 'beta': beta } minus_m_star = 0 if config['StaticUvlm']['horseshoe'] == 'on': minus_m_star = max(m_main - 1, 1) config['AerogridPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0 } config['AeroForcesCalculator'] = {'write_text_file': 'on', 'text_file_name': case_name + '_aeroforces.csv', 'screen_output': 'on', 'unsteady': 'off' } config.write() clean_test_files() generate_fem_file() generate_solver_file(horseshoe=True) generate_aero_file() case_name = 'planarwing_discretewake' clean_test_files() generate_fem_file() generate_solver_file(horseshoe=False) generate_aero_file() ================================================ FILE: tests/uvlm/static/polars/__init__.py ================================================ ================================================ FILE: tests/uvlm/static/polars/generate_wing.py ================================================ import numpy as np import sharpy.cases.templates.flying_wings as wings import sharpy.utils.algebra as algebra import sharpy.sharpy_main def generate_infinite_wing(case_name, alpha, **kwargs): m = 8 n = 4 mstar = 500 u_inf = 1 alpha_deg = alpha alpha_rad = alpha_deg * np.pi / 180 rho = 1.225 gravity = False tolerance = 1e-5 case_route = kwargs.get('case_route', './') case_route += '/' + case_name output_route = kwargs.get('output_route', './output/') # if not os.path.isdir(case_route): # os.makedirs(case_route) polar_file = kwargs.get('polar_file', None) wing = wings.QuasiInfinite(M=m, N=n, Mstar_fact=mstar, u_inf=u_inf, alpha=alpha_rad, aspect_ratio=kwargs.get('aspect_ratio', 8), rho=rho, route=case_route, case_name=case_name, polar_file=polar_file) wing.main_ea = kwargs.get('main_ea', 0.25) wing.clean_test_files() wing.update_derived_params() wing.sigma = 1e25 wing.update_mass_stiff() wing.generate_aero_file() wing.generate_fem_file() settings = dict() settings['SHARPy'] = {'case': case_name, 'route': case_route, 'flow': kwargs.get('flow', []), 'write_screen': 'off', 'write_log': 'on', 'log_folder': output_route, 'log_file': case_name + '.log'} settings['BeamLoader'] = {'unsteady': 'on', 'orientation': algebra.euler2quat(np.array([0., alpha_rad, 0.]))} settings['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'mstar': int(kwargs.get('wake_length', 100) * m), 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': { 'u_inf': u_inf, 'u_inf_direction': [1., 0., 0.], 'dt': wing.dt, }, } settings['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-1, 'min_delta': tolerance, 'gravity_on': gravity, 'gravity': 9.81, 'initial_position': [0., 0., 0.]} settings['StaticUvlm'] = {'print_info': 'on', 'horseshoe': 'on', 'num_cores': 4, 'vortex_radius': 1e-6, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': {'u_inf': u_inf, 'u_inf_direction': [1., 0, 0]}, 'rho': rho} settings['StaticCoupled'] = {'print_info': 'off', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': settings['NonLinearStatic'], 'aero_solver': 'StaticUvlm', 'aero_solver_settings': settings['StaticUvlm'], 'max_iter': 100, 'n_load_steps': kwargs.get('n_load_steps', 1), 'tolerance': kwargs.get('fsi_tolerance', 1e-5), 'relaxation_factor': kwargs.get('relaxation_factor', 0.2)} if polar_file is not None: settings['StaticCoupled']['correct_forces_method'] = 'PolarCorrection' settings['StaticCoupled']['correct_forces_settings'] = {'cd_from_cl': 'off', 'correct_lift': 'on', 'moment_from_polar': 'on'} settings['AerogridPlot'] = {'include_incidence_angle': 'on', 'include_velocities': 'on'} settings['BeamPlot'] = {} settings['AeroForcesCalculator'] = {'write_text_file': 'on', 'coefficients': 'on', 'q_ref': 0.5 * rho * u_inf ** 2, 'S_ref': wing.main_chord * wing.wing_span} settings['Modal'] = {'print_info': True, 'use_undamped_modes': True, 'NumLambda': 50, 'rigid_body_modes': True, 'write_modes_vtk': 'on', 'print_matrices': 'on', 'write_data': 'on', 'continuous_eigenvalues': 'off', 'plot_eigenvalues': False, 'rigid_modes_ppal_axes': 'on', 'folder': output_route} # ROM settings rom_settings = dict() rom_settings['algorithm'] = 'mimo_rational_arnoldi' rom_settings['r'] = 4 rom_settings['frequency'] = np.array([0], dtype=float) rom_settings['single_side'] = 'observability' settings['LinearAssembler'] = {'linear_system': 'LinearAeroelastic', 'linear_system_settings': { 'beam_settings': {'modal_projection': 'off', 'inout_coords': 'modes', 'discrete_time': 'off', 'newmark_damp': 0.5e-3, 'discr_method': 'newmark', 'dt': wing.dt, 'proj_modes': 'undamped', 'use_euler': 'on', 'num_modes': 20, 'print_info': 'on', 'gravity': kwargs.get('gravity', 'on'), 'remove_dofs': [], 'remove_rigid_states': 'on'}, 'aero_settings': {'dt': wing.dt, 'integr_order': 2, 'density': rho, 'remove_predictor': 'off', 'use_sparse': False, 'rigid_body_motion': True, 'vortex_radius': 1e-7, 'remove_inputs': ['u_gust'], 'convert_to_ct': 'on', }, 'track_body': 'off', 'use_euler': 'on', }} settings['StabilityDerivatives'] = {'u_inf': u_inf, 'c_ref': wing.main_chord, 'b_ref': wing.wing_span, 'S_ref': wing.wing_span * wing.main_chord, } settings['SaveParametricCase'] = {'save_case': 'off', 'parameters': {'alpha': alpha_deg}} wing.settings_to_config(settings) sharpy.sharpy_main.main(['', f'{case_route}/{case_name}.sharpy']) return wing def get_case_header(polar, infinite_wing, compute_uind, high_re, main_ea, use2pi): if polar: case_header = 'polar' if high_re: case_header += '_highre' if use2pi: case_header += '_2pi' else: case_header = 'uvlm' if not infinite_wing: case_header += '_ar8' else: case_header += '_ari' if compute_uind and polar: case_header += '_uind' case_header += '_ea{:02g}'.format(main_ea * 100) return case_header def run(infinite_wing, compute_uind, main_ea, high_re, case_route_root, output_route_root, use2pi=False, polar_file=None): flow = ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'AeroForcesCalculator', 'SaveParametricCase'] if polar_file is not None: polar = True else: polar = False if not infinite_wing: ar = 8 else: ar = 1e7 case_header = get_case_header(polar, infinite_wing, compute_uind, high_re, main_ea, use2pi) case_route = case_route_root + '/' + case_header + '/' output_route = output_route_root + '/' + case_header + '/' for alpha in np.linspace(-5, 5, 6): case_name = '{:s}_alpha{:04g}'.format(case_header, alpha * 100).replace('-', 'M') generate_infinite_wing(case_name, alpha, flow=flow, case_route=case_route, polar_file=polar_file, aspect_ratio=ar, compute_uind=compute_uind, main_ea=main_ea, output_route=output_route, actual_aoa=not use2pi, ) # pc.main(case_header, output_route) def all_iter(case_route_root, output_route_root): for main_ea in [0.25, 0.5]: for polar in [True, False]: for infinite_wing in [True, False]: if polar: for high_re in [True, False]: for compute_uind in [True, False]: run(polar, infinite_wing, compute_uind, main_ea, high_re, case_route_root, output_route_root) else: pass # run(polar, infinite_wing, False, main_ea, False, case_route_root, output_route_root) if __name__ == '__main__': import postproc_infinite_wing as pc case_route_root = './cases_linear/' output_route_root = './output_linear/' polar = True infinite_wing = False compute_uind = False main_ea = 0.25 high_re = False use2pi = True run(polar, infinite_wing, compute_uind, main_ea, high_re, case_route_root, output_route_root, use2pi=use2pi) # all_iter(case_route_root, output_route_root) ================================================ FILE: tests/uvlm/static/polars/test_polars.py ================================================ import unittest import tests.uvlm.static.polars.generate_wing as gw import os import glob import configobj import numpy as np from sharpy.aero.utils.utils import local_stability_axes import sharpy.utils.algebra as algebra class InfiniteWing: area = 90000000.0 chord = 3. def force_coef(self, rho, uinf): return 0.5 * rho * uinf ** 2 * self.area def moment_coef(self, rho, uinf): return self.force_coef(rho, uinf) * self.chord class TestAirfoilPolars(unittest.TestCase): route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) polar_data = np.loadtxt(route_test_dir + '/xf-naca0018-il-50000.txt', skiprows=12) def test_infinite_wing(self): """ Infinite wing should yield same results as airfoil polar """ cases_route = self.route_test_dir + '/cases/' output_route = self.route_test_dir + '/output/' wing = InfiniteWing() gw.run(compute_uind=False, infinite_wing=True, main_ea=0.25, high_re=False, case_route_root=cases_route, output_route_root=output_route, use2pi=True, polar_file=self.route_test_dir + '/xf-naca0018-il-50000.txt') case_header = gw.get_case_header(polar=True, infinite_wing=True, compute_uind=False, high_re=False, main_ea=0.25, use2pi=True) results = postprocess(output_route + '/' + case_header + '/') results[:, 1:3] /= wing.force_coef(1.225, 1.) results[:, -1] /= wing.moment_coef(1.225, 1.) with self.subTest('lift'): cl_polar = np.interp(results[:, 0], self.polar_data[:, 0], self.polar_data[:, 1]) np.testing.assert_array_almost_equal(cl_polar, results[:, 1], decimal=3) with self.subTest('drag'): cd_polar = np.interp(results[:, 0], self.polar_data[:, 0], self.polar_data[:, 2]) np.testing.assert_array_almost_equal(cd_polar, results[:, 2], decimal=3) with self.subTest('moment'): cm_polar = np.interp(results[:, 0], self.polar_data[:, 0], self.polar_data[:, 4]) np.testing.assert_array_almost_equal(cm_polar, results[:, 3], decimal=3) def tearDown(self): import shutil folders = ['cases', 'output'] for folder in folders: shutil.rmtree(self.route_test_dir + '/' + folder) def postprocess(output_folder): cases = glob.glob(output_folder + '/*') n_cases = 0 for case in cases: alpha, lift, drag, moment = process_case(case) if n_cases == 0: results = np.array([alpha, lift, drag, moment], dtype=float) else: results = np.vstack((results, np.array([alpha, lift, drag, moment]))) n_cases += 1 results = results.astype(float) results = results[results[:, 0].argsort()] return results def process_case(path_to_case): case_name = path_to_case.split('/')[-1] pmor = configobj.ConfigObj(path_to_case + f'/{case_name}.pmor.sharpy') alpha = pmor['parameters']['alpha'] body_forces = np.loadtxt(f'{path_to_case}/forces/forces_aeroforces.txt', skiprows=1, delimiter=',', dtype=float)[7:10] body_moments = np.loadtxt(f'{path_to_case}/forces/moments_aeroforces.txt', skiprows=1, delimiter=',', dtype=float)[7:10] return alpha, body_forces[2], body_forces[0], body_moments[1] class TestStab(unittest.TestCase): def test_stability(self): """ | | / free stream | / |/ +------------ chord ax (const) """ dir_urel = np.array([1, 0, 0]) dir_chord = np.array([1, 0, 0]) alpha = 4 * np.pi / 180 # rotate the freestream dir_urel = algebra.rotation3d_y(alpha).T.dot(dir_urel) # Test free stream rotation lab = ['x', 'z'] with self.subTest(msg='Freestream test'): assert dir_urel[2] == np.sin(alpha), 'z component of freestream not properly rotated' with self.subTest(msg='Freestream test'): assert dir_urel[0] == np.cos(alpha), 'x component of freestream not properly rotated' # Stability axes c_bs = local_stability_axes(dir_urel, dir_chord) # Checking X_s for ax in [0, 2]: with self.subTest(msg='X_s', ax=ax): assert c_bs.dot(np.eye(3)[0])[ax] > 0, f'{ax}_b component of X_s not correct' # Checking Z_s ax = 0 with self.subTest(msg='Z_s', ax=ax): assert c_bs.dot(np.eye(3)[2])[ax] < 0, f'{ax}_b component of Z_s not correct' ax = 2 with self.subTest(msg='Z_s', ax=ax): assert c_bs.dot(np.eye(3)[2])[ax] > 0, f'{ax}_b component of Z_s not correct' if __name__ == '__main__': import unittest unittest.main() ================================================ FILE: tests/uvlm/static/polars/xf-naca0018-il-50000.txt ================================================ XFOIL Version 6.96 Calculated polar for: NACA 0018 1 1 Reynolds number fixed Mach number fixed xtrf = 1.000 (top) 1.000 (bottom) Mach = 0.000 Re = 0.050 e 6 Ncrit = 9.000 alpha CL CD CDp CM Top_Xtr Bot_Xtr ------ -------- --------- --------- -------- -------- -------- -10.500 -0.4876 0.10841 0.09936 -0.0104 1.0000 0.3138 -10.250 -0.5001 0.10247 0.09342 -0.0123 1.0000 0.3114 -10.000 -0.5715 0.08947 0.08050 -0.0181 1.0000 0.3040 -9.750 -0.7409 0.06466 0.05579 -0.0264 1.0000 0.2983 -9.500 -0.8387 0.05594 0.04699 -0.0198 1.0000 0.2967 -9.250 -0.8938 0.05095 0.04176 -0.0127 1.0000 0.2995 -9.000 -0.8627 0.05074 0.04168 -0.0120 1.0000 0.3110 -8.750 -0.8928 0.04743 0.03815 -0.0060 1.0000 0.3176 -8.500 -0.8721 0.04673 0.03754 -0.0044 1.0000 0.3286 -8.250 -0.8918 0.04411 0.03467 0.0010 1.0000 0.3375 -8.000 -0.8647 0.04386 0.03459 0.0022 1.0000 0.3500 -7.750 -0.8747 0.04172 0.03228 0.0068 1.0000 0.3604 -7.500 -0.8589 0.04109 0.03170 0.0091 1.0000 0.3734 -7.250 -0.8422 0.04046 0.03115 0.0113 1.0000 0.3863 -7.000 -0.8427 0.03895 0.02952 0.0151 1.0000 0.3991 -6.750 -0.8313 0.03820 0.02878 0.0178 1.0000 0.4133 -6.500 -0.8113 0.03791 0.02861 0.0198 1.0000 0.4275 -6.250 -0.8007 0.03710 0.02781 0.0226 1.0000 0.4419 -6.000 -0.7947 0.03608 0.02672 0.0257 1.0000 0.4573 -5.750 -0.7827 0.03548 0.02612 0.0283 1.0000 0.4731 -5.500 -0.7627 0.03537 0.02614 0.0303 1.0000 0.4885 -5.250 -0.7478 0.03498 0.02579 0.0327 1.0000 0.5042 -5.000 -0.7354 0.03446 0.02529 0.0352 1.0000 0.5207 -4.750 -0.7240 0.03389 0.02472 0.0378 1.0000 0.5378 -4.500 -0.7125 0.03338 0.02420 0.0402 1.0000 0.5555 -4.250 -0.6963 0.03323 0.02409 0.0424 1.0000 0.5727 -4.000 -0.6783 0.03326 0.02421 0.0445 1.0000 0.5894 -3.750 -0.6624 0.03317 0.02418 0.0467 1.0000 0.6066 -3.500 -0.6476 0.03305 0.02409 0.0488 1.0000 0.6244 -3.250 -0.6335 0.03293 0.02400 0.0510 1.0000 0.6426 -3.000 -0.6196 0.03285 0.02395 0.0532 1.0000 0.6612 -2.750 -0.6058 0.03281 0.02394 0.0553 1.0000 0.6802 -2.500 -0.5919 0.03285 0.02401 0.0574 1.0000 0.6995 -2.250 -0.5780 0.03294 0.02414 0.0595 1.0000 0.7191 -2.000 -0.5636 0.03313 0.02436 0.0615 1.0000 0.7390 -1.750 -0.5487 0.03340 0.02466 0.0634 1.0000 0.7591 -1.500 -0.5311 0.03382 0.02511 0.0649 0.9995 0.7797 -1.250 -0.4625 0.03554 0.02682 0.0580 0.9838 0.8026 -1.000 -0.3872 0.03723 0.02847 0.0498 0.9678 0.8246 -0.750 -0.3016 0.03882 0.03002 0.0397 0.9515 0.8452 -0.500 -0.2062 0.04015 0.03130 0.0276 0.9348 0.8641 -0.250 -0.1005 0.04102 0.03213 0.0134 0.9175 0.8820 0.000 0.0000 0.04128 0.03238 0.0000 0.8999 0.8999 0.250 0.1005 0.04102 0.03213 -0.0134 0.8820 0.9175 0.500 0.2061 0.04015 0.03130 -0.0276 0.8641 0.9348 0.750 0.3016 0.03882 0.03001 -0.0397 0.8452 0.9515 1.000 0.3873 0.03722 0.02847 -0.0498 0.8247 0.9678 1.250 0.4625 0.03553 0.02681 -0.0580 0.8026 0.9839 1.500 0.5311 0.03381 0.02511 -0.0649 0.7797 0.9996 1.750 0.5486 0.03339 0.02466 -0.0634 0.7592 1.0000 2.000 0.5635 0.03312 0.02436 -0.0615 0.7390 1.0000 2.250 0.5778 0.03294 0.02413 -0.0595 0.7191 1.0000 2.500 0.5918 0.03284 0.02400 -0.0574 0.6996 1.0000 2.750 0.6056 0.03281 0.02394 -0.0553 0.6802 1.0000 3.000 0.6194 0.03285 0.02395 -0.0532 0.6613 1.0000 3.250 0.6333 0.03293 0.02400 -0.0510 0.6426 1.0000 3.500 0.6475 0.03305 0.02409 -0.0488 0.6244 1.0000 3.750 0.6623 0.03316 0.02417 -0.0466 0.6066 1.0000 4.000 0.6782 0.03325 0.02420 -0.0445 0.5895 1.0000 4.250 0.6962 0.03322 0.02408 -0.0424 0.5728 1.0000 4.500 0.7123 0.03338 0.02419 -0.0402 0.5556 1.0000 4.750 0.7237 0.03389 0.02472 -0.0377 0.5378 1.0000 5.000 0.7352 0.03446 0.02529 -0.0352 0.5207 1.0000 5.250 0.7477 0.03498 0.02579 -0.0327 0.5043 1.0000 5.500 0.7626 0.03537 0.02613 -0.0303 0.4885 1.0000 5.750 0.7827 0.03547 0.02610 -0.0283 0.4732 1.0000 6.000 0.7944 0.03608 0.02672 -0.0257 0.4573 1.0000 6.250 0.8005 0.03710 0.02780 -0.0225 0.4420 1.0000 6.500 0.8111 0.03790 0.02860 -0.0198 0.4276 1.0000 6.750 0.8312 0.03819 0.02876 -0.0178 0.4134 1.0000 7.000 0.8424 0.03895 0.02952 -0.0151 0.3992 1.0000 7.250 0.8421 0.04045 0.03114 -0.0113 0.3863 1.0000 7.500 0.8589 0.04107 0.03169 -0.0091 0.3735 1.0000 7.750 0.8745 0.04172 0.03228 -0.0068 0.3604 1.0000 8.000 0.8647 0.04385 0.03458 -0.0021 0.3500 1.0000 8.250 0.8918 0.04410 0.03466 -0.0010 0.3375 1.0000 8.500 0.8722 0.04671 0.03752 0.0044 0.3286 1.0000 8.750 0.8928 0.04742 0.03814 0.0060 0.3176 1.0000 9.000 0.8629 0.05073 0.04166 0.0120 0.3110 1.0000 9.250 0.8938 0.05095 0.04175 0.0127 0.2995 1.0000 9.500 0.8388 0.05594 0.04699 0.0197 0.2966 1.0000 9.750 0.7409 0.06471 0.05583 0.0264 0.2983 1.0000 10.000 0.5703 0.08974 0.08077 0.0178 0.3041 1.0000 10.250 0.5015 0.10242 0.09337 0.0122 0.3111 1.0000 10.500 0.4886 0.10841 0.09937 0.0102 0.3137 1.0000 ================================================ FILE: tests/xbeam/__init__.py ================================================ ================================================ FILE: tests/xbeam/geradin/__init__.py ================================================ ================================================ FILE: tests/xbeam/geradin/generate_geradin.py ================================================ import h5py as h5 import numpy as np import configparser import os case_name = 'geradin' route = os.path.dirname(os.path.realpath(__file__)) + '/' def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) def generate_fem_file(route, case_name, num_elem, num_node_elem=3): length = 5 num_node = (num_node_elem - 1)*num_elem + 1 angle = 0*np.pi/180.0 x = (np.linspace(0, length, num_node))*np.cos(angle) y = (np.linspace(0, length, num_node))*np.sin(angle) z = np.zeros((num_node,)) structural_twist = np.zeros((num_elem, num_node_elem)) frame_of_reference_delta = np.zeros((num_elem, num_node_elem, 3)) for ielem in range(num_elem): for inode in range(num_node_elem): frame_of_reference_delta[ielem, inode, :] = [-np.sin(angle), np.cos(angle), 0] scale = 1 x *= scale y *= scale z *= scale conn = np.zeros((num_elem, num_node_elem), dtype=int) for ielem in range(num_elem): conn[ielem, :] = (np.ones((3,)) * ielem * (num_node_elem - 1) + [0, 2, 1]) # stiffness array num_stiffness = 1 ea = 4.8e8 ga = 3.231e8 gj = 1.0e6 ei = 9.346e6 base_stiffness = np.diag([ea, ga, ga, gj, ei, ei]) stiffness = np.zeros((num_stiffness, 6, 6)) for i in range(num_stiffness): stiffness[i, :, :] = base_stiffness # element stiffness elem_stiffness = np.zeros((num_elem,), dtype=int) # mass array num_mass = 1 m_bar = 0. j = 10 base_mass = np.diag([m_bar, m_bar, m_bar, j, j, j]) mass = np.zeros((num_mass, 6, 6)) for i in range(num_mass): mass[i, :, :] = base_mass # element masses elem_mass = np.zeros((num_elem,), dtype=int) # bocos boundary_conditions = np.zeros((num_node, 1), dtype=int) boundary_conditions[0] = 1 boundary_conditions[-1] = -1 # beam number beam_number = np.zeros((num_elem, 1), dtype=int) # new app forces scheme (only follower) app_forces = np.zeros((num_node, 6)) # lumped masses input n_lumped_mass = 1 lumped_mass_nodes = np.array([num_node - 1], dtype=int) lumped_mass = np.zeros((n_lumped_mass, )) lumped_mass[0] = 600e3/9.81 lumped_mass_inertia = np.zeros((n_lumped_mass, 3, 3)) lumped_mass_position = np.zeros((n_lumped_mass, 3)) with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: coordinates = h5file.create_dataset('coordinates', data = np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data = conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data = num_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data = num_node) num_elem_handle = h5file.create_dataset( 'num_elem', data = num_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data = stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data = elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data = mass) mass_handle = h5file.create_dataset( 'elem_mass', data = elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) lumped_mass_nodes_handle = h5file.create_dataset( 'lumped_mass_nodes', data=lumped_mass_nodes) lumped_mass_handle = h5file.create_dataset( 'lumped_mass', data=lumped_mass) lumped_mass_inertia_handle = h5file.create_dataset( 'lumped_mass_inertia', data=lumped_mass_inertia) lumped_mass_position_handle = h5file.create_dataset( 'lumped_mass_position', data=lumped_mass_position) return num_node, coordinates def generate_solver_file(): file_name = route + '/' + case_name + '.sharpy' import configobj config = configobj.ConfigObj() config.filename = file_name config['SHARPy'] = {'case': case_name, 'route': route, 'flow': ['BeamLoader', 'NonLinearStatic', 'WriteVariablesTime'], 'write_screen': 'off', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} config['BeamLoader'] = {'unsteady': 'off'} config['NonLinearStatic'] = {'print_info': 'off', 'max_iterations': 99, 'num_load_steps': 10, 'delta_curved': 1e-5, 'min_delta': 1e-8, 'gravity_on': 'on', 'gravity': 9.81, 'gravity_dir': ['0', '0', '1']} config['WriteVariablesTime'] = {'structure_variables': ['pos', 'psi'], 'cleanup_old_solution': 'on',} config['BeamPlot'] = {'include_rbm': 'off', 'include_applied_forces': 'on'} config.write() # run everything clean_test_files() generate_fem_file(route, case_name, 20) generate_solver_file() ================================================ FILE: tests/xbeam/modal/rotating_beam/generate_bielawa_baromega2_1e3.py ================================================ import h5py as h5 import numpy as np import configparser import os import sharpy.utils.algebra as algebra # Generate errors during execution import sys import sharpy.utils.cout_utils as cout case_name = 'bielawa_baromega2_1e3' route = os.path.dirname(os.path.realpath(__file__)) + '/' num_elem = 40 num_node_elem = 3 length = 1 # linear_factor: scaling factor to make the non linear solver behave as a linear one linear_factor = 1 E = 1e6 * linear_factor A = 1e4 I = 1e-4 ei = E * I m_bar = 1 * linear_factor rot_speed = np.sqrt(1e3 * ei / m_bar / length ** 4) steps_per_revolution = 180 dt = 2.0 * np.pi / rot_speed / steps_per_revolution n_tstep = 1 * steps_per_revolution + 1 n_tstep = 90 def clean_test_files(): fem_file_name = route + '/' + case_name + '.fem.h5' if os.path.isfile(fem_file_name): os.remove(fem_file_name) dyn_file_name = route + '/' + case_name + '.dyn.h5' if os.path.isfile(dyn_file_name): os.remove(dyn_file_name) aero_file_name = route + '/' + case_name + '.aero.h5' if os.path.isfile(aero_file_name): os.remove(aero_file_name) solver_file_name = route + '/' + case_name + '.sharpy' if os.path.isfile(solver_file_name): os.remove(solver_file_name) def generate_fem_file(route, case_name, num_elem, num_node_elem=3): global num_node num_node = (num_node_elem - 1) * num_elem + 1 angle = 0 * np.pi / 180.0 x = (np.linspace(0, length, num_node)) * np.cos(angle) y = (np.linspace(0, length, num_node)) * np.sin(angle) z = np.zeros((num_node,)) structural_twist = np.zeros((num_elem, num_node_elem)) frame_of_reference_delta = np.zeros((num_elem, num_node_elem, 3)) for ielem in range(num_elem): for inode in range(num_node_elem): frame_of_reference_delta[ielem, inode, :] = [-np.sin(angle), np.cos(angle), 0] scale = 1 x *= scale y *= scale z *= scale conn = np.zeros((num_elem, num_node_elem), dtype=int) for ielem in range(num_elem): conn[ielem, :] = (np.ones((3,)) * ielem * (num_node_elem - 1) + [0, 2, 1]) # stiffness array num_stiffness = 1 ea = E * A # APPROXIMATION!!! cout.cout_wrap("Assuming isotropic material", 2) G = E / 2. / (1. + 0.3) cout.cout_wrap("Using total cross-section area as shear area", 2) ga = G * A cout.cout_wrap("Assuming planar cross-sections", 2) J = 2. * I gj = G * J base_stiffness = np.diag([ea, ga, ga, gj, ei, ei]) stiffness = np.zeros((num_stiffness, 6, 6)) for i in range(num_stiffness): stiffness[i, :, :] = base_stiffness # element stiffness elem_stiffness = np.zeros((num_elem,), dtype=int) # mass array num_mass = 1 base_mass = m_bar * np.diag([1., 1., 1., J / A, I / A, I / A]) mass = np.zeros((num_mass, 6, 6)) for i in range(num_mass): mass[i, :, :] = base_mass # element masses elem_mass = np.zeros((num_elem,), dtype=int) # bocos boundary_conditions = np.zeros((num_node, 1), dtype=int) boundary_conditions[0] = 1 boundary_conditions[-1] = -1 # beam number beam_number = np.zeros((num_node, 1), dtype=int) # new app forces scheme (only follower) app_forces = np.zeros((num_node, 6)) # lumped masses input n_lumped_mass = 1 lumped_mass_nodes = np.array([num_node - 1], dtype=int) lumped_mass = np.zeros((n_lumped_mass,)) lumped_mass[0] = 0.0 lumped_mass_inertia = np.zeros((n_lumped_mass, 3, 3)) lumped_mass_position = np.zeros((n_lumped_mass, 3)) with h5.File(route + '/' + case_name + '.fem.h5', 'a') as h5file: # CHECKING if (elem_stiffness.shape[0] != num_elem): sys.exit("ERROR: Element stiffness must be defined for each element") if (elem_mass.shape[0] != num_elem): sys.exit("ERROR: Element mass must be defined for each element") if (frame_of_reference_delta.shape[0] != num_elem): sys.exit("ERROR: The first dimension of FoR does not match the number of elements") if (frame_of_reference_delta.shape[1] != num_node_elem): sys.exit("ERROR: The second dimension of FoR does not match the number of nodes element") if (frame_of_reference_delta.shape[2] != 3): sys.exit("ERROR: The third dimension of FoR must be 3") if (structural_twist.shape[0] != num_node): sys.exit("ERROR: The structural twist must be defined for each node") if (boundary_conditions.shape[0] != num_node): sys.exit("ERROR: The boundary conditions must be defined for each node") if (beam_number.shape[0] != num_node): sys.exit("ERROR: The beam number must be defined for each node") if (app_forces.shape[0] != num_node): sys.exit("ERROR: The first dimension of the applied forces matrix does not match the number of nodes") if (app_forces.shape[1] != 6): sys.exit("ERROR: The second dimension of the applied forces matrix must be 6") coordinates = h5file.create_dataset('coordinates', data=np.column_stack((x, y, z))) conectivities = h5file.create_dataset('connectivities', data=conn) num_nodes_elem_handle = h5file.create_dataset( 'num_node_elem', data=num_node_elem) num_nodes_handle = h5file.create_dataset( 'num_node', data=num_node) num_elem_handle = h5file.create_dataset( 'num_elem', data=num_elem) stiffness_db_handle = h5file.create_dataset( 'stiffness_db', data=stiffness) stiffness_handle = h5file.create_dataset( 'elem_stiffness', data=elem_stiffness) mass_db_handle = h5file.create_dataset( 'mass_db', data=mass) mass_handle = h5file.create_dataset( 'elem_mass', data=elem_mass) frame_of_reference_delta_handle = h5file.create_dataset( 'frame_of_reference_delta', data=frame_of_reference_delta) structural_twist_handle = h5file.create_dataset( 'structural_twist', data=structural_twist) bocos_handle = h5file.create_dataset( 'boundary_conditions', data=boundary_conditions) beam_handle = h5file.create_dataset( 'beam_number', data=beam_number) app_forces_handle = h5file.create_dataset( 'app_forces', data=app_forces) lumped_mass_nodes_handle = h5file.create_dataset( 'lumped_mass_nodes', data=lumped_mass_nodes) lumped_mass_handle = h5file.create_dataset( 'lumped_mass', data=lumped_mass) lumped_mass_inertia_handle = h5file.create_dataset( 'lumped_mass_inertia', data=lumped_mass_inertia) lumped_mass_position_handle = h5file.create_dataset( 'lumped_mass_position', data=lumped_mass_position) return num_node, coordinates def generate_dyn_file(): global num_node forced_for_vel = np.zeros((n_tstep, 6)) dynamic_forces_time = np.zeros((n_tstep, num_node, 6)) for it in range(n_tstep): forced_for_vel[it, 5] = rot_speed with h5.File(route + '/' + case_name + '.dyn.h5', 'a') as h5file: h5file.create_dataset( 'dynamic_forces', data=dynamic_forces_time) h5file.create_dataset( 'for_vel', data=forced_for_vel) h5file.create_dataset( 'num_steps', data=n_tstep) def generate_aero_file(): global num_node with h5.File(route + '/' + case_name + '.aero.h5', 'a') as h5file: airfoils_group = h5file.create_group('airfoils') # add the airfoils airfoils_group.create_dataset("0", data=np.column_stack((np.linspace(0.0, 1.0, 10), np.zeros(10)))) # chord chord_input = h5file.create_dataset('chord', data=np.ones((num_elem, num_node_elem), )) dim_attr = chord_input.attrs['units'] = 'm' # twist twist_input = h5file.create_dataset('twist', data=np.zeros((num_elem, num_node_elem), )) dim_attr = twist_input.attrs['units'] = 'rad' # airfoil distribution airfoil_distribution_input = h5file.create_dataset('airfoil_distribution', data=np.zeros((num_elem, num_node_elem), dtype=int)) surface_distribution_input = h5file.create_dataset('surface_distribution', data=np.zeros((num_elem,), dtype=int)) surface_m_input = h5file.create_dataset('surface_m', data=np.ones((1,), dtype=int)) m_distribution = 'uniform' m_distribution_input = h5file.create_dataset('m_distribution', data=m_distribution.encode('ascii', 'ignore')) aero_node = np.zeros((num_node,), dtype=bool) aero_node[-3:] = np.ones((3,), dtype=bool) aero_node_input = h5file.create_dataset('aero_node', data=aero_node) elastic_axis_input = h5file.create_dataset('elastic_axis', data=0.5 * np.ones((num_elem, num_node_elem), )) def generate_solver_file(): file_name = route + '/' + case_name + '.sharpy' aux_settings = dict() settings = dict() settings['SHARPy'] = {'case': case_name, 'route': route, 'flow': ['BeamLoader', 'AerogridLoader', 'StaticCoupled', 'BeamPlot', 'AerogridPlot', 'DynamicPrescribedCoupled', 'Modal'], 'write_screen': 'off', 'write_log': 'on', 'log_folder': route + '/output/', 'log_file': case_name + '.log'} # AUX DICTIONARIES aux_settings['velocity_field_input'] = {'u_inf': 100.0, 'u_inf_direction': [0.0, -1.0, 0.0]} # LOADERS settings['BeamLoader'] = {'unsteady': 'on', 'orientation': algebra.euler2quat(np.array([0.0, 0.0, 0.0]))} settings['AerogridLoader'] = {'unsteady': 'on', 'aligned_grid': 'on', 'mstar': 1, 'freestream_dir': ['0', '-1', '0'], 'wake_shape_generator': 'StraightWake', 'wake_shape_generator_input': {'u_inf': 100, 'u_inf_direction': np.array([0., -1., 0.]), 'dt': dt}} # POSTPROCESS settings['AerogridPlot'] = {'include_rbm': 'on', 'include_forward_motion': 'off', 'include_applied_forces': 'on', 'minus_m_star': 0, 'u_inf': 100.0, 'dt': dt} settings['BeamPlot'] = {'include_rbm': 'on', 'include_applied_forces': 'on', 'include_forward_motion': 'on'} settings['BeamLoads'] = {} # STATIC COUPLED settings['NonLinearStatic'] = {'print_info': 'on', 'max_iterations': 150, 'num_load_steps': 1, 'delta_curved': 1e-15, 'min_delta': 1e-8, 'gravity_on': 'off', 'gravity': 9.81} settings['StaticUvlm'] = {'print_info': 'on', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'rollup_dt': dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': aux_settings['velocity_field_input'], 'rho': 0.0} settings['StaticCoupled'] = {'print_info': 'on', 'structural_solver': 'NonLinearStatic', 'structural_solver_settings': settings['NonLinearStatic'], 'aero_solver': 'StaticUvlm', 'aero_solver_settings': settings['StaticUvlm'], 'max_iter': 100, 'n_load_steps': 4, 'tolerance': 1e-8, 'relaxation_factor': 0} # DYNAMIC PRESCRIBED COUPLED settings['NonLinearDynamicPrescribedStep'] = {'print_info': 'on', 'max_iterations': 95000, 'delta_curved': 1e-9, 'min_delta': 1e-6, 'newmark_damp': 1e-3, 'gravity_on': 'off', 'gravity': 9.81, 'num_steps': n_tstep, 'dt': dt} settings['StepUvlm'] = {'print_info': 'on', 'horseshoe': 'off', 'num_cores': 4, 'n_rollup': 0, 'convection_scheme': 2, 'rollup_dt': dt, 'rollup_aic_refresh': 1, 'rollup_tolerance': 1e-4, 'velocity_field_generator': 'SteadyVelocityField', 'velocity_field_input': aux_settings['velocity_field_input'], 'rho': 0.0, 'n_time_steps': n_tstep, 'dt': dt} settings['DynamicPrescribedCoupled'] = {'structural_solver': 'NonLinearDynamicPrescribedStep', 'structural_solver_settings': settings['NonLinearDynamicPrescribedStep'], 'aero_solver': 'StepUvlm', 'aero_solver_settings': settings['StepUvlm'], 'fsi_substeps': 20000, 'fsi_tolerance': 1e-9, 'relaxation_factor': 0, 'minimum_steps': 1, 'relaxation_steps': 150, 'final_relaxation_factor': 0.0, 'n_time_steps': n_tstep, 'dt': dt, 'postprocessors': ['BeamPlot', 'AerogridPlot'], 'postprocessors_settings': {'BeamPlot': settings['BeamPlot'], 'AerogridPlot': settings['AerogridPlot']}} settings['Modal'] = {'include_rbm': 'on', 'NumLambda': 10000, 'num_steps': 1, 'print_matrices': 'on'} import configobj config = configobj.ConfigObj() config.filename = file_name for k, v in settings.items(): config[k] = v config.write() # run everything clean_test_files() generate_fem_file(route, case_name, num_elem, num_node_elem) generate_aero_file() generate_dyn_file() generate_solver_file() cout.cout_wrap( 'Reference for validation: "Rotary wing structural dynamics and aeroelasticity", R.L. Bielawa. AIAA education series. Second edition', 1) ================================================ FILE: tests/xbeam/test_xbeam.py ================================================ import numpy as np import importlib import unittest import os import glob import shutil class TestGeradinXbeam(unittest.TestCase): """ Tests the xbeam library for the geradin clamped beam Validation values taken from Simpson, R.J. and Palacios, R., 2013. Numerical aspects of nonlinear flexible aircraft flight dynamics modeling. In 54th AIAA/ASME/ASCE/AHS/ASC Structures, Structural Dynamics, and Materials Conference (p. 1634). """ @classmethod def setUpClass(cls): # run all the cases generators case = 'geradin' mod = importlib.import_module('tests.xbeam.' + case + '.generate_' + case) def test_geradin(self): import sharpy.sharpy_main # suppress screen output solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/geradin/geradin.sharpy') sharpy.sharpy_main.main(['', solver_path]) # read output and compare output_path = os.path.dirname(solver_path) + '/output/geradin/WriteVariablesTime/' # pos_def pos_data = np.atleast_2d(np.genfromtxt(output_path + 'struct_pos_node-1.dat')) self.assertAlmostEqual(pos_data[0, 3], -2.159, 2) self.assertAlmostEqual(5.0 - pos_data[0, 1], 0.596, 3) # psi_def psi_data = np.atleast_2d(np.genfromtxt(output_path + 'struct_psi_node-1.dat')) self.assertAlmostEqual(psi_data[-1, 2], 0.6720, 3) def tearDown(self): solver_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/geradin/') files_to_delete = list() extensions = ('*.txt', '*.h5', '*.sharpy') for f in extensions: files_to_delete.extend(glob.glob(solver_path + '/' + f)) for f in files_to_delete: try: os.remove(f) except FileNotFoundError: pass try: shutil.rmtree(solver_path + '/output') except FileNotFoundError: pass ================================================ FILE: utils/docker/bashrc ================================================ # .bashrc # User specific aliases and functions alias rm='rm -i' alias cp='cp -i' alias mv='mv -i' # Source global definitions if [ -f /etc/bashrc ]; then . /etc/bashrc fi # >>> conda initialize >>> # !! Contents within this block are managed by 'conda init' !! __conda_setup="$('/miniconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)" if [ $? -eq 0 ]; then eval "$__conda_setup" else if [ -f "/miniconda3/etc/profile.d/conda.sh" ]; then . "/miniconda3/etc/profile.d/conda.sh" else export PATH="/miniconda3/bin:$PATH" fi fi unset __conda_setup # <<< conda initialize <<< conda activate sharpy # custom prompt PS1="SHARPy> " echo "=======================================================================" echo "Welcome to the Docker image of SHARPy" echo "SHARPy is located in /sharpy_dir/ and the" echo "environment is already set up!" echo "Copyright Imperial College London. Released under BSD 3-Clause license." echo "=======================================================================" ================================================ FILE: utils/environment.yml ================================================ name: sharpy channels: - conda-forge - defaults dependencies: - asn1crypto>=1.2.0 - colorama>=0.4.1 - control>=0.8.4 - dill>=0.3.1.1 - h5py>=2.9.0 - jupyterlab>=1.2.3 - lxml>=4.4.1 - nbsphinx>=0.4.3 - pandas>=0.25.3 - pyOpenSSL>=19.0.0 - PySocks>=1.7.1 - PyYAML>=5.1.2 - recommonmark>=0.6.0 - scipy>=1.11.4,<1.12 - slycot>=0.4.0 - sphinx_rtd_theme>=0.4.3 - wheel>=0.33.6 - xlrd>=1.2.0 - python=3.10 - openpyxl>=3.0.10 - cmake>=3.19.0