Skip to content

Commit d24a5db

Browse files
authored
Merge pull request #3473 from luwang00/f/ADI_CBind
Add an AeroDyn C-binding subroutine that exports the instantaneous per-node blade added-mass matrices and py_ad_MHK_RM1_Fixed regression test
2 parents 0ab1150 + 37edfa6 commit d24a5db

4 files changed

Lines changed: 285 additions & 4 deletions

File tree

‎glue-codes/python/pyOpenFAST/aerodyn_inflow.py‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,64 @@ def adi_calcOutput(self, time: float, output_channel_values: npt.NDArray[np.floa
586586
# Copy results back to numpy array
587587
output_channel_values[:] = np.reshape(output_channel_values_c, (self.num_channels))
588588

589+
def adi_calcOutput_and_addedMass(
590+
self,
591+
time: float,
592+
output_channel_values: npt.NDArray[np.float32],
593+
node_added_mass: npt.NDArray[np.float32]
594+
) -> None:
595+
"""Calculate output values and the instantaneous blade added-mass matrices at the given time.
596+
597+
On return, the internal AeroDyn output holds the remainder loads (loads computed with zero
598+
structural acceleration), so a subsequent call to adi_getrotorloads for each turbine returns
599+
the loads on the blades excluding the added-mass terms.
600+
601+
Note:
602+
AeroDyn added mass is only nonzero for MHK turbines; otherwise the matrices are zero.
603+
Each per-node 6x6 block is a diagonal (block) approximation of the true (banded)
604+
structural added-mass matrix (all nodes perturbed simultaneously -> row-sum).
605+
606+
Args:
607+
time: Current simulation time
608+
output_channel_values: Array to store calculated output values
609+
node_added_mass: Array of shape (num_turbines*num_mesh_pts, 6, 6) to store the per-node
610+
added-mass blocks (global frame), ordered by turbine then mesh point.
611+
612+
Raises:
613+
ValueError: If output_channel_values or node_added_mass array has wrong size
614+
RuntimeError: If calculation fails
615+
"""
616+
if output_channel_values.size != self.num_channels:
617+
raise ValueError(
618+
f"Output array must have size {self.num_channels}, "
619+
f"got {output_channel_values.size}"
620+
)
621+
622+
total_mesh_pts = self.num_turbines * self.num_mesh_pts
623+
if node_added_mass.size != 36 * total_mesh_pts:
624+
raise ValueError(
625+
f"node_added_mass array must have size {36 * total_mesh_pts} "
626+
f"(({total_mesh_pts}, 6, 6)), got {node_added_mass.size}"
627+
)
628+
629+
output_channel_values_c = (c_float * self.num_channels)(0.)
630+
node_added_mass_c = (c_float * (36 * total_mesh_pts))(0.)
631+
632+
self.ADI_C_CalcOutput_and_AddedMass(
633+
byref(c_double(time)), # IN -> current simulation time
634+
output_channel_values_c, # OUT <- calculated output channel values
635+
node_added_mass_c, # OUT <- per-node 6x6 added-mass blocks (column-major per block)
636+
byref(self.error_status_c), # OUT <- error status
637+
self.error_message_c # OUT <- error message
638+
)
639+
self.check_error()
640+
641+
# Copy results back to numpy arrays
642+
output_channel_values[:] = np.reshape(output_channel_values_c, (self.num_channels))
643+
# Each node's 6x6 block is stored column-major -> reshape to (node, col, row) then swap to (node, row, col)
644+
flat = np.frombuffer(node_added_mass_c, dtype=np.float32)
645+
node_added_mass[:, :, :] = flat.reshape((total_mesh_pts, 6, 6)).transpose(0, 2, 1)
646+
589647
def adi_updateStates(self, time: float, time_next: float) -> None:
590648
"""Update states from current time to next time step.
591649
@@ -811,6 +869,18 @@ def _initialize_routines(self) -> None:
811869
]
812870
self.ADI_C_CalcOutput.restype = c_int
813871

872+
#--------------------------------------
873+
# ADI_C_CalcOutput_and_AddedMass
874+
#--------------------------------------
875+
self.ADI_C_CalcOutput_and_AddedMass.argtypes = [
876+
POINTER(c_double), # Time_C
877+
POINTER(c_float), # Output Channel Values
878+
POINTER(c_float), # NodeAdm -- per-node 6x6 added-mass blocks, flat 36*sum(numMeshPts)
879+
POINTER(c_int), # ErrStat_C
880+
POINTER(c_char) # ErrMsg_C
881+
]
882+
self.ADI_C_CalcOutput_and_AddedMass.restype = c_int
883+
814884
#--------------------------------------
815885
# ADI_C_UpdateStates
816886
#--------------------------------------

‎modules/aerodyn/src/AeroDyn_Inflow_C_Binding.f90‎

Lines changed: 213 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ MODULE AeroDyn_Inflow_C_BINDING
3535

3636
PUBLIC :: ADI_C_Init
3737
PUBLIC :: ADI_C_CalcOutput
38+
PUBLIC :: ADI_C_CalcOutput_and_AddedMass ! CalcOutput plus instantaneous blade added-mass matrices (MHK)
3839
PUBLIC :: ADI_C_UpdateStates
3940
PUBLIC :: ADI_C_End
4041
PUBLIC :: ADI_C_PreInit ! Initial call to setup number of turbines
@@ -1175,6 +1176,214 @@ end function Failed
11751176
!FIXME: add a showpassed/return routine
11761177
END SUBROUTINE ADI_C_CalcOutput
11771178

1179+
!===============================================================================================================
1180+
!------------------------------------ AeroDyn CalcOutput and AddedMass -----------------------------------------
1181+
!===============================================================================================================
1182+
!> This routine calculates the outputs at Time_C and the instantaneous added-mass matrices for the blade
1183+
!! structural mesh points of all rotors. On return, ADI%y holds the remainder loads (loads computed with zero
1184+
!! structural acceleration), so a subsequent call to ADI_C_GetRotorLoads for each rotor returns those remainder
1185+
!! loads (i.e. the loads on the blades excluding the added-mass terms).
1186+
!! NOTE: make sure to call ADI_C_SetRotorMotion for every rotor before calling this routine.
1187+
!! NOTE: AeroDyn added mass is only nonzero for MHK turbines (p%MHK /= MHK_None); otherwise the matrix is zero.
1188+
!! NOTE: the returned per-node 6x6 blocks are a diagonal (block) approximation of the true (banded) structural
1189+
!! added-mass matrix -- all structural nodes are perturbed simultaneously, so each block is the row-sum of
1190+
!! the corresponding matrix rows. This conserves rigid-body totals and mirrors the strip-theory approach
1191+
!! used in HydroDyn_C_CalcOutput_and_AddedMass.
1192+
SUBROUTINE ADI_C_CalcOutput_and_AddedMass(Time_C, &
1193+
OutputChannelValues_C, NodeAdm_C, ErrStat_C, ErrMsg_C) BIND (C, NAME='ADI_C_CalcOutput_and_AddedMass')
1194+
implicit none
1195+
#ifndef IMPLICIT_DLLEXPORT
1196+
!DEC$ ATTRIBUTES DLLEXPORT :: ADI_C_CalcOutput_and_AddedMass
1197+
!GCC$ ATTRIBUTES DLLEXPORT :: ADI_C_CalcOutput_and_AddedMass
1198+
#endif
1199+
real(c_double), intent(in ) :: Time_C
1200+
real(c_float), intent( out) :: OutputChannelValues_C(ADI%p%NumOuts)
1201+
real(c_float), intent( out) :: NodeAdm_C(*) !< 6x6 added-mass block per structural mesh point, concatenated by rotor then mesh point, each block column-major (global frame). Sized 36*sum(NumMeshPts).
1202+
integer(c_int), intent( out) :: ErrStat_C
1203+
character(kind=c_char), intent( out) :: ErrMsg_C(ErrMsgLen_C)
1204+
1205+
! Local variables
1206+
real(DbKi) :: Time
1207+
integer(IntKi) :: iWT !< current rotor/turbine
1208+
integer(IntKi) :: i,j,k,g,pt !< generic index variables
1209+
integer(IntKi) :: nWT !< number of turbines
1210+
integer(IntKi) :: NT !< total number of structural mesh points across all rotors
1211+
integer(IntKi), allocatable :: globOffset(:) !< global mesh-point offset for each rotor
1212+
real(ReKi), allocatable :: NodeAdm(:,:,:) !< (6,6,NT) added-mass blocks
1213+
real(ReKi), allocatable :: baseLoad(:,:) !< (6,NT) remainder loads at zero structural acceleration
1214+
integer(IntKi) :: ErrStat_F !< aggregated error status
1215+
character(ErrMsgLen) :: ErrMsg_F !< aggregated error message
1216+
integer(IntKi) :: ErrStat_F2 !< temporary error status from a call
1217+
character(ErrMsgLen) :: ErrMsg_F2 !< temporary error message from a call
1218+
character(*), parameter :: RoutineName = 'ADI_C_CalcOutput_and_AddedMass' !< for error handling
1219+
1220+
! Initialize error handling
1221+
ErrStat_F = ErrID_None
1222+
ErrMsg_F = ""
1223+
1224+
! Convert the inputs from C to Fortran
1225+
Time = REAL(Time_C,DbKi)
1226+
1227+
! Global mesh-point bookkeeping (rotors in order, within a rotor by mesh-point index)
1228+
nWT = Sim%NumTurbines
1229+
call AllocAry(globOffset, nWT, "globOffset", ErrStat_F2, ErrMsg_F2); if (Failed()) return
1230+
NT = 0
1231+
do iWT=1,nWT
1232+
globOffset(iWT) = NT
1233+
NT = NT + NumMeshPts(iWT)
1234+
enddo
1235+
call AllocAry(NodeAdm, 6, 6, NT, "NodeAdm", ErrStat_F2, ErrMsg_F2); if (Failed()) return
1236+
call AllocAry(baseLoad, 6, NT, "baseLoad", ErrStat_F2, ErrMsg_F2); if (Failed()) return
1237+
NodeAdm = 0.0_ReKi
1238+
baseLoad = 0.0_ReKi
1239+
1240+
! Copy new inputs over once; probes/baseline only override the blade structural acceleration
1241+
call ADI_CopyInput (ADI_u, ADI%u(1), MESH_UPDATECOPY, ErrStat_F2, ErrMsg_F2)
1242+
if (Failed()) return
1243+
1244+
!-------------------------------------------------------
1245+
! Probe the added-mass response: perturb all blade structural nodes with a unit negative acceleration in
1246+
! each of the 6 DOFs. Perturbing all nodes at once gives the row-sum (diagonal-block) approximation.
1247+
!-------------------------------------------------------
1248+
do k=1,6
1249+
call SetBladeStrAccel(k, -1.0_ReKi) ! DOF k acceleration = -1 on all blade structural nodes
1250+
do iWT=1,nWT
1251+
call AD_SetBladeMotion(iWT, ErrStat_F2, ErrMsg_F2); if (Failed()) return
1252+
enddo
1253+
CALL ADI_CalcOutput( Time, ADI%u(1), ADI%p, ADI%x(STATE_CURR), ADI%xd(STATE_CURR), ADI%z(STATE_CURR), ADI%OtherState(STATE_CURR), ADI%y, ADI%m, ErrStat_F2, ErrMsg_F2 )
1254+
if (Failed()) return
1255+
do iWT=1,nWT
1256+
call AD_TransferLoads( iWT, ADI%u(1), ADI%y, ErrStat_F2, ErrMsg_F2 ); if (Failed()) return
1257+
call Set_OutputLoadArray(iWT)
1258+
do i=1,Sim%WT(iWT)%NumBlades
1259+
do j=1,StrucPts_2_Bld_Map(iWT)%NumMeshPtsPerBlade(i)
1260+
pt = StrucPts_2_Bld_Map(iWT)%BladeNode_2_MeshPt(i)%BladeNodeToMeshPoint(j)
1261+
g = globOffset(iWT) + pt
1262+
NodeAdm(1:6,k,g) = StrucPts_2_Bld_Map(iWT)%BladeStrMeshCoords(i)%Force(1:6,j)
1263+
enddo
1264+
enddo
1265+
enddo
1266+
enddo
1267+
1268+
!-------------------------------------------------------
1269+
! Baseline (zero structural acceleration). Done last so that ADI%y holds the remainder loads on return,
1270+
! ready for subsequent ADI_C_GetRotorLoads calls.
1271+
!-------------------------------------------------------
1272+
call SetBladeStrAccel(0, 0.0_ReKi) ! zero acceleration on all blade structural nodes
1273+
do iWT=1,nWT
1274+
call AD_SetBladeMotion(iWT, ErrStat_F2, ErrMsg_F2); if (Failed()) return
1275+
enddo
1276+
CALL ADI_CalcOutput( Time, ADI%u(1), ADI%p, ADI%x(STATE_CURR), ADI%xd(STATE_CURR), ADI%z(STATE_CURR), ADI%OtherState(STATE_CURR), ADI%y, ADI%m, ErrStat_F2, ErrMsg_F2 )
1277+
if (Failed()) return
1278+
do iWT=1,nWT
1279+
call AD_TransferLoads( iWT, ADI%u(1), ADI%y, ErrStat_F2, ErrMsg_F2 ); if (Failed()) return
1280+
call Set_OutputLoadArray(iWT)
1281+
do i=1,Sim%WT(iWT)%NumBlades
1282+
do j=1,StrucPts_2_Bld_Map(iWT)%NumMeshPtsPerBlade(i)
1283+
pt = StrucPts_2_Bld_Map(iWT)%BladeNode_2_MeshPt(i)%BladeNodeToMeshPoint(j)
1284+
g = globOffset(iWT) + pt
1285+
baseLoad(1:6,g) = StrucPts_2_Bld_Map(iWT)%BladeStrMeshCoords(i)%Force(1:6,j)
1286+
enddo
1287+
enddo
1288+
enddo
1289+
1290+
! Subtract baseline: each block is F(-1) - F(0). Added-mass load is F = -M*accel, so probing with
1291+
! accel = -1 gives the added-mass matrix M directly.
1292+
do g=1,NT
1293+
do k=1,6
1294+
NodeAdm(1:6,k,g) = NodeAdm(1:6,k,g) - baseLoad(1:6,g)
1295+
enddo
1296+
enddo
1297+
1298+
! Pack into the C return array (per node: 6x6 block in column-major order)
1299+
do g=1,NT
1300+
NodeAdm_C(36*(g-1)+1:36*g) = reshape( real(NodeAdm(1:6,1:6,g), c_float), (/36/) )
1301+
enddo
1302+
1303+
! Get the output channel info out of y (reflects the remainder / zero-acceleration state)
1304+
OutputChannelValues_C = REAL(ADI%y%WriteOutput, C_FLOAT)
1305+
1306+
!-------------------------------------------------------
1307+
! write outputs (mirror ADI_C_CalcOutput)
1308+
!-------------------------------------------------------
1309+
if (WrOutputsData%WrVTK > 1_IntKi) then
1310+
VTKn_Global = nint(Time_C / WrOutputsData%VTK_dt )
1311+
if (VTKn_Global /= VTKn_last) then
1312+
VTKn_last = VTKn_Global
1313+
call WrVTK_Meshes(ADI%u(1)%AD%rotors(:),(/0.0_SiKi,0.0_SiKi,0.0_SiKi/),ErrStat_F2,ErrMsg_F2)
1314+
if (Failed()) return
1315+
endif
1316+
endif
1317+
1318+
if (WrOutputsData%fileFmt > idFmtNone) then
1319+
call Dvr_WriteOutputs(n_Global+1, ADI%InputTimes(INPUT_CURR), Sim, WrOutputsData, ADI%y, SeaSt, ErrStat_F2, ErrMsg_F2); if(Failed()) return
1320+
endif
1321+
1322+
! Set error status
1323+
call SetErrStat_F2C(ErrStat_F,ErrMsg_F,ErrStat_C,ErrMsg_C)
1324+
1325+
! Store info what time we just ran calcs for
1326+
InputTimePrev_Calc = Time
1327+
1328+
if (allocated(globOffset)) deallocate(globOffset)
1329+
if (allocated(NodeAdm)) deallocate(NodeAdm)
1330+
if (allocated(baseLoad)) deallocate(baseLoad)
1331+
1332+
CONTAINS
1333+
!> Set the acceleration on all blade structural motion mesh nodes (all rotors, all blades).
1334+
!! dof = 0 zeroes all 6 DOFs; dof = 1..3 sets that translational DOF; dof = 4..6 sets that rotational DOF.
1335+
subroutine SetBladeStrAccel(dof, val)
1336+
integer(IntKi), intent(in) :: dof
1337+
real(ReKi), intent(in) :: val
1338+
integer(IntKi) :: iWTl, iBl, jn
1339+
do iWTl=1,Sim%NumTurbines
1340+
do iBl=1,Sim%WT(iWTl)%NumBlades
1341+
do jn=1,StrucPts_2_Bld_Map(iWTl)%NumMeshPtsPerBlade(iBl)
1342+
BldStrMotionMesh(iWTl)%BldMesh(iBl)%TranslationAcc(1:3,jn) = 0.0_ReKi
1343+
BldStrMotionMesh(iWTl)%BldMesh(iBl)%RotationAcc( 1:3,jn) = 0.0_ReKi
1344+
if (dof >= 1 .and. dof <= 3) then
1345+
BldStrMotionMesh(iWTl)%BldMesh(iBl)%TranslationAcc(dof,jn) = val
1346+
elseif (dof >= 4 .and. dof <= 6) then
1347+
BldStrMotionMesh(iWTl)%BldMesh(iBl)%RotationAcc(dof-3,jn) = val
1348+
endif
1349+
enddo
1350+
enddo
1351+
enddo
1352+
end subroutine SetBladeStrAccel
1353+
1354+
!> Transfer the blade structural motion mesh to the AD blade motion mesh for a single rotor. Only the blade
1355+
!! motion is (re)transferred; hub/nacelle/root motions already present in ADI%u(1) are left unchanged.
1356+
subroutine AD_SetBladeMotion(iWTl, ErrStat3, ErrMsg3)
1357+
integer(IntKi), intent(in ) :: iWTl
1358+
integer(IntKi), intent( out) :: ErrStat3
1359+
character(ErrMsgLen), intent( out) :: ErrMsg3
1360+
integer(IntKi) :: iBl
1361+
integer(IntKi) :: n_elems
1362+
ErrStat3 = 0_IntKi
1363+
ErrMsg3 = ''
1364+
do iBl=1,Sim%WT(iWTl)%NumBlades
1365+
n_elems = size(BldStrMotionMesh(iWTl)%BldMesh(iBl)%Position, 2)
1366+
if ( ADI%u(1)%AD%rotors(iWTl)%BladeMotion(iBl)%Committed .and. (n_elems > 0)) then
1367+
if (PointLoadOutput) then
1368+
call Transfer_Point_to_Line2(BldStrMotionMesh(iWTl)%BldMesh(iBl), ADI%u(1)%AD%rotors(iWTl)%BladeMotion(iBl), Map_BldStrMotion_2_AD_Blade(iBl,iWTl), ErrStat3, ErrMsg3)
1369+
else
1370+
call Transfer_Line2_to_Line2(BldStrMotionMesh(iWTl)%BldMesh(iBl), ADI%u(1)%AD%rotors(iWTl)%BladeMotion(iBl), Map_BldStrMotion_2_AD_Blade(iBl,iWTl), ErrStat3, ErrMsg3)
1371+
endif
1372+
if (ErrStat3 >= AbortErrLev) return
1373+
endif
1374+
enddo
1375+
end subroutine AD_SetBladeMotion
1376+
1377+
logical function Failed()
1378+
CALL SetErrStat( ErrStat_F2, ErrMsg_F2, ErrStat_F, ErrMsg_F, RoutineName )
1379+
Failed = ErrStat_F >= AbortErrLev
1380+
if (Failed) then
1381+
call ClearTmpStorage()
1382+
call SetErrStat_F2C(ErrStat_F,ErrMsg_F,ErrStat_C,ErrMsg_C)
1383+
endif
1384+
end function Failed
1385+
END SUBROUTINE ADI_C_CalcOutput_and_AddedMass
1386+
11781387
!===============================================================================================================
11791388
!--------------------------------------------- AeroDyn UpdateStates -------------------------------------------
11801389
!===============================================================================================================
@@ -1687,7 +1896,7 @@ subroutine SetupMotionMesh()
16871896
ErrMess = ErrMsg_F2 , &
16881897
TranslationDisp = .TRUE., Orientation = .TRUE. , &
16891898
TranslationVel = .TRUE., RotationVel = .TRUE. , &
1690-
TranslationAcc = .TRUE., RotationAcc = .FALSE. )
1899+
TranslationAcc = .TRUE., RotationAcc = .TRUE. )
16911900
if(Failed()) return
16921901
enddo
16931902

@@ -2126,6 +2335,7 @@ subroutine Set_MotionMesh(iWT, ErrStat3, ErrMsg3)
21262335
BldStrMotionMesh(iWT)%BldMesh(iBlade)%TranslationVel( 1:3,j) = StrucPts_2_Bld_Map(iWT)%BladeStrMeshCoords(iBlade)%Velocity(1:3,j)
21272336
BldStrMotionMesh(iWT)%BldMesh(iBlade)%RotationVel( 1:3,j) = StrucPts_2_Bld_Map(iWT)%BladeStrMeshCoords(iBlade)%Velocity(4:6,j)
21282337
BldStrMotionMesh(iWT)%BldMesh(iBlade)%TranslationAcc( 1:3,j) = StrucPts_2_Bld_Map(iWT)%BladeStrMeshCoords(iBlade)%Accln(1:3,j)
2338+
BldStrMotionMesh(iWT)%BldMesh(iBlade)%RotationAcc( 1:3,j) = StrucPts_2_Bld_Map(iWT)%BladeStrMeshCoords(iBlade)%Accln(4:6,j)
21292339
call OrientRemap(BldStrMotionMesh(iWT)%BldMesh(iBlade)%Orientation(1:3,1:3,j))
21302340
if (TransposeDCM) then
21312341
BldStrMotionMesh(iWT)%BldMesh(iBlade)%Orientation(1:3,1:3,j) = transpose(BldStrMotionMesh(iWT)%BldMesh(iBlade)%Orientation(1:3,1:3,j))
@@ -2210,9 +2420,9 @@ subroutine AD_SetInputMotion( iWT, u_local, &
22102420
n_elems = size(BldStrMotionMesh(iWT)%BldMesh(iBlade)%Position, 2)
22112421
if (( u_local%AD%rotors(iWT)%BladeMotion(iBlade)%Committed ) .and. (n_elems > 0)) then
22122422
if (PointLoadOutput) then
2213-
call Transfer_Point_to_Line2(BldStrMotionMesh(iWT)%BldMesh(iBlade), u_local%AD%rotors(iWT)%BladeMotion(iBlade), Map_BldStrMotion_2_AD_Blade(i,iWT), ErrStat, ErrMsg)
2423+
call Transfer_Point_to_Line2(BldStrMotionMesh(iWT)%BldMesh(iBlade), u_local%AD%rotors(iWT)%BladeMotion(iBlade), Map_BldStrMotion_2_AD_Blade(iBlade,iWT), ErrStat, ErrMsg)
22142424
else
2215-
call Transfer_Line2_to_Line2(BldStrMotionMesh(iWT)%BldMesh(iBlade), u_local%AD%rotors(iWT)%BladeMotion(iBlade), Map_BldStrMotion_2_AD_Blade(i,iWT), ErrStat, ErrMsg)
2425+
call Transfer_Line2_to_Line2(BldStrMotionMesh(iWT)%BldMesh(iBlade), u_local%AD%rotors(iWT)%BladeMotion(iBlade), Map_BldStrMotion_2_AD_Blade(iBlade,iWT), ErrStat, ErrMsg)
22162426
u_local%AD%rotors(iWT)%BladeMotion(iBlade)%RemapFlag = .false.
22172427
end if
22182428
if (ErrStat >= AbortErrLev) return

‎reg_tests/CTestList.cmake‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ ad_regression("ad_B1n2_OLAF" "aerodyn;OLAF")
468468
ad_regression("ad_Sphere_OLAF" "aerodyn;OLAF")
469469
py_ad_regression("py_ad_5MW_OC4Semi_WSt_WavesWN" "aerodyn;bem;python")
470470
py_ad_regression("py_ad_B1n2_OLAF" "aerodyn;OLAF;python")
471+
py_ad_regression("py_ad_MHK_RM1_Fixed" "aerodyn;bem;python;mhk")
471472

472473
# UnsteadyAero
473474
ua_regression("ua_redfreq" "unsteadyaero")

‎reg_tests/r-test‎

Submodule r-test updated 157 files

0 commit comments

Comments
 (0)