diff --git a/docs/documentation/examples/CrossBeam/crossbeam.md b/docs/documentation/examples/CrossBeam/crossbeam.md new file mode 100644 index 00000000..20f9b4df --- /dev/null +++ b/docs/documentation/examples/CrossBeam/crossbeam.md @@ -0,0 +1,167 @@ +# Crossbeam test case with FENIAX + +This notebook takes you through the steps of using FENIAX to simulation a flexible crossbeam. The test case is taken from Hilger (2024), [_A Dynamic Nonlinear Inertia Relief Formulation_](https://doi.org/10.2514/6.2024-2258). It involves a crossbeam under sinusoidal forcing vertically and has been validated with other nonlinear formulations. + +## Load modules +Here we load the relevant modules used in the simulation. + +``` python +import feniax.preprocessor.configuration as configuration +from feniax.preprocessor.inputs import Inputs +import feniax.feniax_main +import jax.numpy as jnp +import pathlib +import numpy as np +``` + +## Housekeeping +We then define a few useful variables. + +``` python +gravity_forces = False +gravity_label = "g" if gravity_forces else "" +label = 'm1' +label_name = label + gravity_label +``` + +## Case setup +We now set up a FENIAX input object for an intrinsic modal dynamic simulation. + +``` python +inp = Inputs() +# inp.log.level="debug" +inp.engine = "intrinsicmodal" +inp.driver.typeof = "intrinsic" +inp.driver.sol_path= pathlib.Path( + f"./results_{label_name}") +``` + +### Finite-element model file inputs +As part of the input we have a few choices in how to supply the the finite-element model files. + +Here the "inputs" flag is used to specify eigenvector and eigenvalue input by file paths. To produce these files refer to the useful functions `feniax.unastran.op2reader` and `feniax.unastran.matrixbuilder` for extracting the eigenvectors and eigenvalues from `.op2` files; and the mass and stiffness matrices from `.pch` files respectively. `num_modes` is used to limit number of modes used in the simulation. + +Connectivity is defined a priori in the description of the loads paths `BuildAsetModel` from `feniax.unastran.asetbuilder` and is supplied via `grid`. + +``` python +inp.fem.eig_type = "inputs" +inp.fem.Ka_name = f"./FEM/Ka_{label}.npy" +inp.fem.Ma_name = f"./FEM/Ma_{label}.npy" +inp.fem.eig_names = [f"./FEM/eigenvals_{label}.npy", + f"./FEM/eigenvecs_{label}.npy"] +inp.fem.num_modes = 60 + +inp.fem.connectivity = {'rbeam': None, 'lbeam': None, 'ubeam': None, 'dbeam': None} +inp.fem.grid = f"./FEM/structuralGrid_{label}" +``` + +### Boundary conditions +We then define the boundary conditions for the dynamic simulation. `bc1` can be "clamped" or "free" for the spatial boundary condition, whereas two of `t1`, `tn`, `dt` has to be set to define the temporal boundary condition. `t0` defaults to 0 if not specified. + +``` python +inp.simulation.typeof = "single" +inp.systems.sett.s1.solution = "dynamic" +inp.systems.sett.s1.bc1 = 'free' +inp.systems.sett.s1.t1 = 1.0 +inp.systems.sett.s1.dt = 0.0001 +# inp.system.tn = 10000 + 1 +``` + +### Solver settings +A number of different solvers are available. A Runge-Kutta routine is available via "runge_kutta"; for the [_Diffrax_](https://docs.kidger.site/diffrax) library use "diffrax". + +``` python +inp.systems.sett.s1.solver_library = "diffrax" # +inp.systems.sett.s1.solver_function = "ode" +inp.systems.sett.s1.solver_settings = dict(solver_name="Dopri5") +``` + +### Load cases +To define the load conditions, use `gravity_forces` to toggle gravity. Here we opt for a dead force applied at the root node `0` in the vertical component `2`. The load profile is interpolated from the supplied array. + +``` python +inp.systems.sett.s1.xloads.gravity_forces = gravity_forces +inp.systems.sett.s1.xloads.dead_forces = True +inp.systems.sett.s1.xloads.dead_points = [[0, 2]] +t = np.linspace(0.0, 1.0, 10001) +freq = 2 +amp = 0.1 +f = amp * np.sin(2*np.pi*freq*t) +inp.systems.sett.s1.xloads.x = t.tolist() +inp.systems.sett.s1.xloads.dead_interpolation = [f.tolist()] +``` + +### Run FENIAX +And last but not least, to supply the `config` object and launch FENIAX. + +``` python +config = configuration.Config(inp) +sol = feniax.feniax_main.main(input_obj=config) +``` + +## Postprocessing +Here we wish to extract the position and velocity of all beam nodes into csv files; `feniax.preprocessor.solution.IntrinsicReader` is used to load the solution and `feniax.plotools.utils.pickIntrinsic2D` is used to extract the requested data and return arrays. + +``` python +import os +import numpy as np +import feniax.plotools.utils as putils +import feniax.preprocessor.solution as solution + +sol0 = solution.IntrinsicReader("./results_m1") + +route_test_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) +route_export = os.path.join(route_test_dir, "csvoutput") +os.makedirs(route_export, exist_ok=True) + + +def export_data(x_data, y_data, axis, route_export, file_prefix): + n_nodes = y_data.shape[axis] + + for node_idx in range(n_nodes): + x, y = putils.pickIntrinsic2D( + x_data, + y_data, + fixaxis2=dict(node=node_idx, dim=axis) + ) + + dest_file = os.path.join(route_export, f"{file_prefix}_node_{node_idx}.txt") + np.savetxt( + dest_file, + np.column_stack((x, y)), + delimiter="," + ) + + print(f"Saved {file_prefix} node {node_idx} to {dest_file}") + + y_all = y_data[:, axis, :] + out = np.column_stack((x_data, y_all)) + + dest_file = os.path.join(route_export, f"{file_prefix}_all_nodes.txt") + np.savetxt(dest_file, out, delimiter=",") + + print(f"Saved all nodes to {dest_file}") +``` + +### Export csv files +And hence the calls are made as such to extract first the velocities then the positions, in the z-direction, as below. + +``` python +x_data = sol0.data.dynamicsystem_s1.t + +export_data( + x_data, + sol0.data.dynamicsystem_s1.X1, + 2, + route_export, + "vel_z" +) + +export_data( + x_data, + sol0.data.dynamicsystem_s1.ra, + 2, + route_export, + "pos_z" +) +``` diff --git a/docs/documentation/examples/SailPlane/sailplane_nb.md b/docs/documentation/examples/SailPlane/sailplane_nb.md index 7fcaf762..fafaff1e 100644 --- a/docs/documentation/examples/SailPlane/sailplane_nb.md +++ b/docs/documentation/examples/SailPlane/sailplane_nb.md @@ -46,9 +46,9 @@ from tabulate import tabulate ``` ## Run cases +Here we define functions for running FENIAX and recording runtimes. ``` python - import time TIMES_DICT = dict() @@ -72,6 +72,11 @@ def save_times(): pd_times.to_csv("./run_times.csv") ``` + + + + -### SP1 -``` {#SP1 .python} +### Case SP1 +Solving for static equilibrium of the sailplane model with two follower force point loads applied at both wing tips, using 5 active modes with 7 ramping load cases. +``` {#SP1 .python} +name = "SP1" SP_folder = feniax.PATH / "../examples/SailPlane" inp = Inputs() inp.engine = "intrinsicmodal" @@ -196,10 +204,11 @@ inp.driver.sol_path = pathlib.Path( run(inp, label=name) ``` -### SP2 +### Case SP2 +Same as Case SP1, but with 15 retained modes. ``` {#SP2 .python} - +name = "SP2" SP_folder = feniax.PATH / "../examples/SailPlane" inp = Inputs() inp.engine = "intrinsicmodal" @@ -262,10 +271,11 @@ inp.driver.sol_path = pathlib.Path( run(inp, label=name) ``` -### SP3 +### Case SP3 +Same as Case SP1, but with 30 retained modes. ``` {#SP3 .python} - +name = "SP3" SP_folder = feniax.PATH / "../examples/SailPlane" inp = Inputs() inp.engine = "intrinsicmodal" @@ -328,10 +338,11 @@ inp.driver.sol_path = pathlib.Path( run(inp, label=name) ``` -### SP4 +### Case SP4 +Same as Case SP1, but with 50 retained modes. ``` {#SP4 .python} - +name = "SP4" SP_folder = feniax.PATH / "../examples/SailPlane" inp = Inputs() inp.engine = "intrinsicmodal" @@ -394,10 +405,11 @@ inp.driver.sol_path = pathlib.Path( run(inp, label=name) ``` -### SP5 +### Case SP5 +Same as Case SP1, but with 100 retained modes. ``` {#SP5 .python} - +name = "SP5" SP_folder = feniax.PATH / "../examples/SailPlane" inp = Inputs() inp.engine = "intrinsicmodal" @@ -460,6 +472,7 @@ inp.driver.sol_path = pathlib.Path( run(inp, label=name) ``` +### Saving runtimes ``` python save_times() ``` diff --git a/docs/documentation/examples/wingSP/wingSP_nb.md b/docs/documentation/examples/wingSP/wingSP_nb.md index 54d40018..0b60caeb 100644 --- a/docs/documentation/examples/wingSP/wingSP_nb.md +++ b/docs/documentation/examples/wingSP/wingSP_nb.md @@ -46,6 +46,7 @@ from tabulate import tabulate ``` ## Run cases +Here we define functions for running FENIAX and recording runtimes. ``` python @@ -72,8 +73,11 @@ def save_times(): pd_times.to_csv("./run_times.csv") ``` -``` {#wingSP .python} +### Case WSP1 + -WSP1 - -``` {#WSP1 .python} +Exciting the wing with two linearly increasing follower force point load at the tip for 4 seconds, and simulate the transient response over 15 seconds with 5 active modes while the dynamics decay. +``` {#WSP1 .python} +name = "WSP1" wingSP_folder = feniax.PATH / "../examples/wingSP" inp = Inputs() inp.engine = "intrinsicmodal" @@ -166,11 +171,11 @@ inp.driver.sol_path = pathlib.Path( run(inp, label=name) ``` -WSP2 +### Case WSP2 +Same as Case WSP1, but with 15 retained modes. ``` {#WSP2 .python} - - +name = "WSP2" wingSP_folder = feniax.PATH / "../examples/wingSP" inp = Inputs() inp.engine = "intrinsicmodal" @@ -203,11 +208,11 @@ inp.driver.sol_path = pathlib.Path( run(inp, label=name) ``` -WSP3 +### Case WSP3 +Same as Case WSP1, but with 30 retained modes. ``` {#WSP3 .python} - - +name = "WSP3" wingSP_folder = feniax.PATH / "../examples/wingSP" inp = Inputs() inp.engine = "intrinsicmodal" @@ -240,11 +245,11 @@ inp.driver.sol_path = pathlib.Path( run(inp, label=name) ``` -WSP4 +### Case WSP4 +Same as WSP1, but with 50 retained modes. ``` {#WSP4 .python} - - +name = "WSP4" wingSP_folder = feniax.PATH / "../examples/wingSP" inp = Inputs() inp.engine = "intrinsicmodal" @@ -277,11 +282,11 @@ inp.driver.sol_path = pathlib.Path( run(inp, label=name) ``` -WSP4alpha05 +### Case WSP4alpha05 +Same as WSP4, but with peak load scaled by 50%. ``` {#WSP4alpha05 .python} - - +name = "WSP4alpha05" wingSP_folder = feniax.PATH / "../examples/wingSP" inp = Inputs() inp.engine = "intrinsicmodal" @@ -317,10 +322,11 @@ inp.driver.sol_path = pathlib.Path( run(inp, label=name) ``` -WSP4alpha15 +### Case WSP4alpha15 +Same as WSP4, but with peak load scaled by 150%. ``` {#WSP4alpha15 .python} - +name = "WSP4alpha15" wingSP_folder = feniax.PATH / "../examples/wingSP" inp = Inputs() inp.engine = "intrinsicmodal" @@ -355,11 +361,11 @@ inp.driver.sol_path = pathlib.Path( run(inp, label=name) ``` -WSP5 +### Case WSP5 +Same as WSP1, but with 100 retained modes. ``` {#WSP5 .python} - - +name = "WSP5" wingSP_folder = feniax.PATH / "../examples/wingSP" inp = Inputs() inp.engine = "intrinsicmodal" diff --git a/examples/CrossBeam/FEM/Ka_m1.npy b/examples/CrossBeam/FEM/Ka_m1.npy new file mode 100644 index 00000000..4c0d7dd9 Binary files /dev/null and b/examples/CrossBeam/FEM/Ka_m1.npy differ diff --git a/examples/CrossBeam/FEM/Ma_m1.npy b/examples/CrossBeam/FEM/Ma_m1.npy new file mode 100644 index 00000000..da2ed10d Binary files /dev/null and b/examples/CrossBeam/FEM/Ma_m1.npy differ diff --git a/examples/CrossBeam/FEM/eigenvals_m1.npy b/examples/CrossBeam/FEM/eigenvals_m1.npy new file mode 100644 index 00000000..2e5ce214 Binary files /dev/null and b/examples/CrossBeam/FEM/eigenvals_m1.npy differ diff --git a/examples/CrossBeam/FEM/eigenvecs_m1.npy b/examples/CrossBeam/FEM/eigenvecs_m1.npy new file mode 100644 index 00000000..53df1abe Binary files /dev/null and b/examples/CrossBeam/FEM/eigenvecs_m1.npy differ diff --git a/examples/CrossBeam/FEM/structuralGrid_m1 b/examples/CrossBeam/FEM/structuralGrid_m1 new file mode 100644 index 00000000..0c2ed4c1 --- /dev/null +++ b/examples/CrossBeam/FEM/structuralGrid_m1 @@ -0,0 +1,28 @@ +# VARIABLES = x y z fe_order components +0.0 0.0 0.0 0 rbeam +0.1 0.0 0.0 1 rbeam +0.2 0.0 0.0 2 rbeam +0.3 0.0 0.0 3 rbeam +0.4 0.0 0.0 4 rbeam +0.5 0.0 0.0 5 rbeam +0.6 0.0 0.0 6 rbeam +0.7 0.0 0.0 7 rbeam +0.8 0.0 0.0 8 rbeam +0.9 0.0 0.0 9 rbeam +1.0 0.0 0.0 10 rbeam +-0.1 0.0 0.0 11 lbeam +-0.2 0.0 0.0 12 lbeam +-0.3 0.0 0.0 13 lbeam +-0.4 0.0 0.0 14 lbeam +-0.5 0.0 0.0 15 lbeam +-0.6 0.0 0.0 16 lbeam +-0.7 0.0 0.0 17 lbeam +-0.8 0.0 0.0 18 lbeam +-0.9 0.0 0.0 19 lbeam +-1.0 0.0 0.0 20 lbeam +0.0 0.1 0.0 21 ubeam +0.0 0.2 0.0 22 ubeam +0.0 0.3 0.0 23 ubeam +0.0 -0.1 0.0 24 dbeam +0.0 -0.2 0.0 25 dbeam +0.0 -0.3 0.0 26 dbeam diff --git a/examples/CrossBeam/settings.py b/examples/CrossBeam/settings.py new file mode 100644 index 00000000..90796c6a --- /dev/null +++ b/examples/CrossBeam/settings.py @@ -0,0 +1,48 @@ +import feniax.preprocessor.configuration as configuration +from feniax.preprocessor.inputs import Inputs +import feniax.feniax_main +import jax.numpy as jnp +import pathlib +import numpy as np + +gravity_forces = False +gravity_label = "g" if gravity_forces else "" +label = 'm1' +label_name = label + gravity_label + +inp = Inputs() +inp.engine = "intrinsicmodal" +inp.fem.connectivity = {'rbeam': None, 'lbeam': None, 'ubeam': None, 'dbeam': None} +inp.fem.Ka_name = f"./FEM/Ka_{label}.npy" +inp.fem.Ma_name = f"./FEM/Ma_{label}.npy" +inp.fem.eig_names = [f"./FEM/eigenvals_{label}.npy", + f"./FEM/eigenvecs_{label}.npy"] +inp.fem.grid = f"./FEM/structuralGrid_{label}" +inp.fem.num_modes = 60 +inp.fem.eig_type = "inputs" +inp.driver.typeof = "intrinsic" +inp.driver.sol_path= pathlib.Path( + f"./results_{label_name}") + +inp.simulation.typeof = "single" +inp.systems.sett.s1.solution = "dynamic" +inp.systems.sett.s1.bc1 = 'free' +inp.systems.sett.s1.xloads.gravity_forces = gravity_forces +inp.systems.sett.s1.t1 = 0.1*10 +inp.systems.sett.s1.dt = 0.0001 +inp.systems.sett.s1.solver_library = "diffrax" +inp.systems.sett.s1.solver_function = "ode" +inp.systems.sett.s1.solver_settings = dict(solver_name="Dopri5") +inp.systems.sett.s1.xloads.dead_forces = True +inp.systems.sett.s1.xloads.dead_points = [[0, 2]] + +t = np.linspace(0.0, 1.0, 10001) +freq = 2 +amp = 0.1 +f = amp * np.sin(2*np.pi*freq*t) +inp.systems.sett.s1.xloads.x = t.tolist() +inp.systems.sett.s1.xloads.dead_interpolation = [f.tolist()] + +config = configuration.Config(inp) +sol = feniax.feniax_main.main(input_obj=config) + diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Couplings/alpha1.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Couplings/alpha1.npy new file mode 100644 index 00000000..a41c4bdc Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Couplings/alpha1.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Couplings/alpha2.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Couplings/alpha2.npy new file mode 100644 index 00000000..fd0a89e3 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Couplings/alpha2.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Couplings/gamma1.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Couplings/gamma1.npy new file mode 100644 index 00000000..d6ffc9a1 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Couplings/gamma1.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Couplings/gamma2.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Couplings/gamma2.npy new file mode 100644 index 00000000..83c98cac Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Couplings/gamma2.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/Cab.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/Cab.npy new file mode 100644 index 00000000..904f7489 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/Cab.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/X1.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/X1.npy new file mode 100644 index 00000000..af4d8df1 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/X1.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/X2.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/X2.npy new file mode 100644 index 00000000..26ff8094 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/X2.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/X3.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/X3.npy new file mode 100644 index 00000000..65f2e3a8 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/X3.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/q.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/q.npy new file mode 100644 index 00000000..2eea6803 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/q.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/ra.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/ra.npy new file mode 100644 index 00000000..92f09b4a Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/ra.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/t.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/t.npy new file mode 100644 index 00000000..246a0f2b Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/DynamicSystem_s1/t.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/C06ab.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/C06ab.npy new file mode 100644 index 00000000..201587e5 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/C06ab.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/C0ab.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/C0ab.npy new file mode 100644 index 00000000..dd8d49ef Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/C0ab.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/X_xdelta.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/X_xdelta.npy new file mode 100644 index 00000000..057fc1e9 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/X_xdelta.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/omega.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/omega.npy new file mode 100644 index 00000000..efa83ff4 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/omega.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi1.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi1.npy new file mode 100644 index 00000000..163a93c3 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi1.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi1l.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi1l.npy new file mode 100644 index 00000000..7d49f964 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi1l.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi1ml.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi1ml.npy new file mode 100644 index 00000000..0f42fd17 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi1ml.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi2.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi2.npy new file mode 100644 index 00000000..085c2204 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi2.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi2l.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi2l.npy new file mode 100644 index 00000000..be0a6944 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/phi2l.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/psi1.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/psi1.npy new file mode 100644 index 00000000..ac1868fe Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/psi1.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/psi1l.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/psi1l.npy new file mode 100644 index 00000000..8a258fdd Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/psi1l.npy differ diff --git a/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/psi2l.npy b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/psi2l.npy new file mode 100644 index 00000000..d3223606 Binary files /dev/null and b/tests/intrinsic/structural_dynamic/data/CrossBeam/Modes/psi2l.npy differ diff --git a/tests/intrinsic/structural_dynamic/test_cross.py b/tests/intrinsic/structural_dynamic/test_cross.py new file mode 100644 index 00000000..9e641f63 --- /dev/null +++ b/tests/intrinsic/structural_dynamic/test_cross.py @@ -0,0 +1,127 @@ +import feniax.preprocessor.configuration as configuration # import Config, dump_to_yaml +from feniax.preprocessor import solution +from feniax.preprocessor.inputs import Inputs +import feniax.feniax_main +import numpy as np +import jax.numpy as jnp +import pytest +import pathlib + +file_path = pathlib.Path(__file__).parent + +@pytest.mark.slow +class TestCrossBeam: + + @pytest.fixture(scope="class") + def sol(self): + + inp = Inputs() + inp.engine = "intrinsicmodal" + inp.fem.connectivity = {'rbeam': None, 'lbeam': None, 'ubeam': None, 'dbeam': None} + label = 'm1' + inp.fem.Ka_name = file_path / f"../../../examples/CrossBeam/FEM/Ka_{label}.npy" + inp.fem.Ma_name = file_path / f"../../../examples/CrossBeam/FEM/Ma_{label}.npy" + inp.fem.eig_names = [file_path / f"../../../examples/CrossBeam/FEM/eigenvals_{label}.npy", + file_path / f"../../../examples/CrossBeam/FEM/eigenvecs_{label}.npy"] + inp.fem.grid = file_path / f"../../../examples/CrossBeam/FEM/structuralGrid_{label}" + inp.fem.num_modes = 60 + inp.fem.eig_type = "inputs" + inp.driver.typeof = "intrinsic" + inp.driver.save_fem = False + inp.driver.sol_path=None + inp.simulation.typeof = "single" + inp.systems.sett.s1.solution = "dynamic" + inp.systems.sett.s1.bc1 = 'free' + inp.systems.sett.s1.xloads.gravity_forces = False + inp.systems.sett.s1.save = False + inp.systems.sett.s1.t1 = 1.0 + inp.systems.sett.s1.tn = 10001 + inp.systems.sett.s1.solver_library = "diffrax" + inp.systems.sett.s1.solver_function = "ode" + inp.systems.sett.s1.solver_settings = dict(solver_name="Dopri5") + inp.systems.sett.s1.xloads.dead_forces = True + inp.systems.sett.s1.xloads.dead_points = [[0, 2]] + t = np.linspace(0.0, 1.0, 10001) + freq = 2 + amp = 0.1 + f = amp * np.sin(2*np.pi*freq*t) + inp.systems.sett.s1.xloads.x = t.tolist() + inp.systems.sett.s1.xloads.dead_interpolation = [f.tolist()] + config = configuration.Config(inp) + obj_sol = feniax.feniax_main.main(input_obj=config) + return obj_sol + + @pytest.fixture + def data(self): + sol_path = file_path / "data/CrossBeam" + sol = solution.IntrinsicSolution(sol_path) + sol.load_container("Modes") + sol.load_container("Couplings") + sol.load_container("DynamicSystem", label="_s1") + + return sol.data + + def test_phi1(self, sol, data): + + assert jnp.allclose(sol.modes.phi1, data.modes.phi1) + + def test_phi2(self, sol, data): + + assert jnp.allclose(sol.modes.phi2, data.modes.phi2) + + def test_psi1(self, sol, data): + + assert jnp.allclose(sol.modes.psi1, data.modes.psi1) + + def test_phi1l(self, sol, data): + + assert jnp.allclose(sol.modes.phi1l, data.modes.phi1l) + + def test_phi2l(self, sol, data): + + assert jnp.allclose(sol.modes.phi2l, data.modes.phi2l) + + def test_psi1l(self, sol, data): + + assert jnp.allclose(sol.modes.psi1l, data.modes.psi1l) + + def test_psi2l(self, sol, data): + + assert jnp.allclose(sol.modes.psi2l, data.modes.psi2l) + + def test_phi1ml(self, sol, data): + + assert jnp.allclose(sol.modes.phi1ml, data.modes.phi1ml) + + def test_gamma1(self, sol, data): + + assert jnp.allclose(sol.couplings.gamma1, + data.couplings.gamma1) + + def test_gamma2(self, sol, data): + + assert jnp.allclose(sol.couplings.gamma2, + data.couplings.gamma2) + + def test_qs(self, sol, data): + + assert jnp.allclose(sol.dynamicsystem_s1.q, + data.dynamicsystem_s1.q) + + def test_Xs(self, sol, data): + + assert jnp.allclose(sol.dynamicsystem_s1.X2, + data.dynamicsystem_s1.X2, + atol=1e-5) + assert jnp.allclose(sol.dynamicsystem_s1.X3, + data.dynamicsystem_s1.X3) + + def test_ra(self, sol, data): + + assert jnp.allclose(sol.dynamicsystem_s1.ra, + data.dynamicsystem_s1.ra) + + def test_Cab(self, sol, data): + + assert jnp.allclose(sol.dynamicsystem_s1.Cab, + data.dynamicsystem_s1.Cab)