1 #ifndef AMREX_FFT_R2X_H_
2 #define AMREX_FFT_R2X_H_
3 #include <AMReX_Config.H>
14 template <
typename T>
class Poisson;
15 template <
typename T>
class PoissonHybrid;
23 template <
typename T = Real>
27 using MF = std::conditional_t<std::is_same_v<T,Real>,
31 template <
typename U>
friend class Poisson;
35 Array<std::pair<Boundary,Boundary>,AMREX_SPACEDIM>
const& bc,
51 template <
int dim,
typename FAB,
typename F>
115 template <
typename T>
117 Array<std::pair<Boundary,Boundary>,AMREX_SPACEDIM>
const& bc,
125 static_assert(std::is_same_v<float,T> || std::is_same_v<double,T>);
129 #if (AMREX_SPACEDIM == 3)
134 for (
int idim = 0; idim < AMREX_SPACEDIM; ++idim) {
136 if (bc[idim].first == Boundary::periodic ||
137 bc[idim].
second == Boundary::periodic) {
152 m_rx.define(bax, dmx, 1, 0,
MFInfo().SetAlloc(
false));
155 if (bc[0].first == Boundary::periodic) {
161 for (
auto &
b : bl) {
168 #if (AMREX_SPACEDIM >= 2)
169 if (domain.
length(1) > 1) {
194 if (ba.size() ==
m_rx.size()) {
195 dm =
m_rx.DistributionMap();
199 m_ry.define(ba, dm, 1, 0,
MFInfo().SetAlloc(
false));
204 if (bc[1].first == Boundary::periodic) {
209 for (
auto &
b : bl) {
219 #if (AMREX_SPACEDIM == 3)
245 if (ba.size() ==
m_ry.size()) {
246 dm =
m_ry.DistributionMap();
250 m_rz.define(ba, dm, 1, 0,
MFInfo().SetAlloc(
false));
255 if (bc[2].first == Boundary::periodic) {
260 for (
auto &
b : bl) {
310 #if (AMREX_SPACEDIM >= 2)
311 if (domain.
length(1) > 1) {
314 m_cmd_cx2cy = std::make_unique<MultiBlockCommMetaData>
316 m_cmd_cy2cx = std::make_unique<MultiBlockCommMetaData>
320 m_cmd_rx2ry = std::make_unique<MultiBlockCommMetaData>
322 m_cmd_ry2rx = std::make_unique<MultiBlockCommMetaData>
328 #if (AMREX_SPACEDIM == 3)
332 m_cmd_cy2cz = std::make_unique<MultiBlockCommMetaData>
334 m_cmd_cz2cy = std::make_unique<MultiBlockCommMetaData>
338 m_cmd_ry2rz = std::make_unique<MultiBlockCommMetaData>
340 m_cmd_rz2ry = std::make_unique<MultiBlockCommMetaData>
352 if (myproc <
m_rx.size())
354 Box const& box =
m_rx.box(myproc);
355 auto* pf =
m_rx[myproc].dataPtr();
356 if (bc[0].first == Boundary::periodic) {
357 auto* pb = (VendorComplex*)
m_cx[myproc].dataPtr();
358 m_fft_fwd_x.template init_r2c<Direction::forward>(box, pf, pb);
359 #if defined(AMREX_USE_SYCL)
362 m_fft_bwd_x.template init_r2c<Direction::backward>(box, pf, pb);
365 m_fft_fwd_x.template init_r2r<Direction::forward>(box, pf, bc[0]);
366 #if defined(AMREX_USE_GPU)
367 if ((bc[0].first == Boundary::even && bc[0].
second == Boundary::odd) ||
368 (bc[0].first == Boundary::odd && bc[0].
second == Boundary::even)) {
373 m_fft_bwd_x.template init_r2r<Direction::backward>(box, pf, bc[0]);
378 #if (AMREX_SPACEDIM >= 2)
379 if (
m_ry.empty() &&
m_bc[1].first == Boundary::periodic) {
382 auto* p = (VendorComplex *)
m_cy[myproc].dataPtr();
383 m_fft_fwd_y.template init_c2c<Direction::forward>(box, p);
384 #if defined(AMREX_USE_SYCL)
387 m_fft_bwd_y.template init_c2c<Direction::backward>(box, p);
390 }
else if (!
m_ry.empty() &&
m_bc[1].first == Boundary::periodic) {
391 if (myproc <
m_ry.size()) {
392 Box const& box =
m_ry.box(myproc);
393 auto* pr =
m_ry[myproc].dataPtr();
394 auto* pc = (VendorComplex*)
m_cy[myproc].dataPtr();
395 m_fft_fwd_y.template init_r2c<Direction::forward>(box, pr, pc);
396 #if defined(AMREX_USE_SYCL)
399 m_fft_bwd_y.template init_r2c<Direction::backward>(box, pr, pc);
405 auto* p = (VendorComplex*)
m_cy[myproc].dataPtr();
406 m_fft_fwd_y.template init_r2r<Direction::forward>(box, p, bc[1]);
407 #if defined(AMREX_USE_GPU)
408 if ((bc[1].first == Boundary::even && bc[1].
second == Boundary::odd) ||
409 (bc[1].first == Boundary::odd && bc[1].
second == Boundary::even)) {
414 m_fft_bwd_y.template init_r2r<Direction::backward>(box, p, bc[1]);
418 if (myproc <
m_ry.size()) {
419 Box const& box =
m_ry.box(myproc);
420 auto* p =
m_ry[myproc].dataPtr();
421 m_fft_fwd_y.template init_r2r<Direction::forward>(box, p, bc[1]);
422 #if defined(AMREX_USE_GPU)
423 if ((bc[1].first == Boundary::even && bc[1].
second == Boundary::odd) ||
424 (bc[1].first == Boundary::odd && bc[1].
second == Boundary::even)) {
429 m_fft_bwd_y.template init_r2r<Direction::backward>(box, p, bc[1]);
435 #if (AMREX_SPACEDIM == 3)
436 if (
m_rz.empty() &&
m_bc[2].first == Boundary::periodic) {
439 auto* p = (VendorComplex*)
m_cz[myproc].dataPtr();
440 m_fft_fwd_z.template init_c2c<Direction::forward>(box, p);
441 #if defined(AMREX_USE_SYCL)
444 m_fft_bwd_z.template init_c2c<Direction::backward>(box, p);
447 }
else if (!
m_rz.empty() &&
m_bc[2].first == Boundary::periodic) {
448 if (myproc <
m_rz.size()) {
449 Box const& box =
m_rz.box(myproc);
450 auto* pr =
m_rz[myproc].dataPtr();
451 auto* pc = (VendorComplex*)
m_cz[myproc].dataPtr();
452 m_fft_fwd_z.template init_r2c<Direction::forward>(box, pr, pc);
453 #if defined(AMREX_USE_SYCL)
456 m_fft_bwd_z.template init_r2c<Direction::backward>(box, pr, pc);
462 auto* p = (VendorComplex*)
m_cz[myproc].dataPtr();
463 m_fft_fwd_z.template init_r2r<Direction::forward>(box, p, bc[2]);
464 #if defined(AMREX_USE_GPU)
465 if ((bc[2].first == Boundary::even && bc[2].
second == Boundary::odd) ||
466 (bc[2].first == Boundary::odd && bc[2].
second == Boundary::even)) {
471 m_fft_bwd_z.template init_r2r<Direction::backward>(box, p, bc[2]);
475 if (myproc <
m_rz.size()) {
476 Box const& box =
m_rz.box(myproc);
477 auto* p =
m_rz[myproc].dataPtr();
478 m_fft_fwd_z.template init_r2r<Direction::forward>(box, p, bc[2]);
479 #if defined(AMREX_USE_GPU)
480 if ((bc[2].first == Boundary::even && bc[2].
second == Boundary::odd) ||
481 (bc[2].first == Boundary::odd && bc[2].
second == Boundary::even)) {
486 m_fft_bwd_z.template init_r2r<Direction::backward>(box, p, bc[2]);
493 template <
typename T>
496 if (m_fft_bwd_x.plan != m_fft_fwd_x.plan) {
497 m_fft_bwd_x.destroy();
499 if (m_fft_bwd_y.plan != m_fft_fwd_y.plan) {
500 m_fft_bwd_y.destroy();
502 if (m_fft_bwd_z.plan != m_fft_fwd_z.plan) {
503 m_fft_bwd_z.destroy();
505 m_fft_fwd_x.destroy();
506 m_fft_fwd_y.destroy();
507 m_fft_fwd_z.destroy();
510 template <
typename T>
514 int ndims = m_info.batch_mode ? AMREX_SPACEDIM-1 : AMREX_SPACEDIM;
515 for (
int idim = 0; idim < ndims; ++idim) {
516 r *= m_dom_0.length(idim);
517 if (m_bc[idim].first != Boundary::periodic && (m_dom_0.length(idim) > 1)) {
524 template <
typename T>
525 template <
typename F>
528 forwardThenBackward_doit(inmf, outmf, post_forward);
531 template <
typename T>
532 template <
typename F>
534 F
const& post_forward,
544 int actual_dim = AMREX_SPACEDIM;
545 #if (AMREX_SPACEDIM >= 2)
546 if (m_dom_0.length(1) == 1) { actual_dim = 1; }
548 #if (AMREX_SPACEDIM == 3)
549 if ((m_dom_0.length(2) == 1) && (m_dom_0.length(1) > 1)) { actual_dim = 2; }
552 if (actual_dim == 1) {
559 #if (AMREX_SPACEDIM >= 2)
560 else if (actual_dim == 2) {
568 #if (AMREX_SPACEDIM == 3)
569 else if (actual_dim == 3) {
580 outmf.ParallelCopy(m_rx, 0, 0, 1,
IntVect(0), ngout, period);
583 template <
typename T>
588 m_rx.ParallelCopy(inmf, 0, 0, 1);
589 if (m_bc[0].first == Boundary::periodic) {
590 m_fft_fwd_x.template compute_r2c<Direction::forward>();
592 m_fft_fwd_x.template compute_r2r<Direction::forward>();
595 #if (AMREX_SPACEDIM >= 2)
597 ParallelCopy(m_cy, m_cx, *m_cmd_cx2cy, 0, 0, 1, m_dtos_x2y);
598 }
else if ( m_cmd_rx2ry) {
599 ParallelCopy(m_ry, m_rx, *m_cmd_rx2ry, 0, 0, 1, m_dtos_x2y);
601 if (m_bc[1].first != Boundary::periodic)
603 m_fft_fwd_y.template compute_r2r<Direction::forward>();
605 else if (m_bc[0].first == Boundary::periodic)
607 m_fft_fwd_y.template compute_c2c<Direction::forward>();
611 m_fft_fwd_y.template compute_r2c<Direction::forward>();
615 #if (AMREX_SPACEDIM == 3)
617 ParallelCopy(m_cz, m_cy, *m_cmd_cy2cz, 0, 0, 1, m_dtos_y2z);
618 }
else if ( m_cmd_ry2rz) {
619 ParallelCopy(m_rz, m_ry, *m_cmd_ry2rz, 0, 0, 1, m_dtos_y2z);
621 if (m_bc[2].first != Boundary::periodic)
623 m_fft_fwd_z.template compute_r2r<Direction::forward>();
625 else if (m_bc[0].first == Boundary::periodic ||
626 m_bc[1].first == Boundary::periodic)
628 m_fft_fwd_z.template compute_c2c<Direction::forward>();
632 m_fft_fwd_z.template compute_r2c<Direction::forward>();
637 template <
typename T>
642 #if (AMREX_SPACEDIM == 3)
643 if (m_info.batch_mode) {
647 amrex::Abort(
"R2X::forward(MF,MF): How did this happen?");
657 template <
typename T>
662 #if (AMREX_SPACEDIM == 3)
663 if (m_info.batch_mode) {
665 auto lo = m_dom_cy.smallEnd();
666 auto hi = m_dom_cy.bigEnd();
672 amrex::Abort(
"R2X::forward(MF,cMF): How did this happen?");
682 template <
typename T>
687 #if (AMREX_SPACEDIM == 3)
688 if (m_bc[2].first != Boundary::periodic)
690 m_fft_bwd_z.template compute_r2r<Direction::backward>();
692 else if (m_bc[0].first == Boundary::periodic ||
693 m_bc[1].first == Boundary::periodic)
695 m_fft_bwd_z.template compute_c2c<Direction::backward>();
699 m_fft_bwd_z.template compute_r2c<Direction::backward>();
702 ParallelCopy(m_cy, m_cz, *m_cmd_cz2cy, 0, 0, 1, m_dtos_z2y);
703 }
else if ( m_cmd_rz2ry) {
704 ParallelCopy(m_ry, m_rz, *m_cmd_rz2ry, 0, 0, 1, m_dtos_z2y);
708 #if (AMREX_SPACEDIM >= 2)
709 if (m_bc[1].first != Boundary::periodic)
711 m_fft_bwd_y.template compute_r2r<Direction::backward>();
713 else if (m_bc[0].first == Boundary::periodic)
715 m_fft_bwd_y.template compute_c2c<Direction::backward>();
719 m_fft_bwd_y.template compute_r2c<Direction::backward>();
722 ParallelCopy(m_cx, m_cy, *m_cmd_cy2cx, 0, 0, 1, m_dtos_y2x);
723 }
else if ( m_cmd_ry2rx) {
724 ParallelCopy(m_rx, m_ry, *m_cmd_ry2rx, 0, 0, 1, m_dtos_y2x);
728 if (m_bc[0].first == Boundary::periodic) {
729 m_fft_bwd_x.template compute_r2c<Direction::backward>();
731 m_fft_bwd_x.template compute_r2r<Direction::backward>();
735 template <
typename T>
739 #if (AMREX_SPACEDIM == 3)
740 if (m_info.batch_mode) {
744 amrex::Abort(
"R2X::backward(MF,MF): How did this happen?");
755 outmf.ParallelCopy(m_rx, 0, 0, 1,
IntVect(0), ngout, period);
758 template <
typename T>
762 #if (AMREX_SPACEDIM == 3)
763 if (m_info.batch_mode) {
767 amrex::Abort(
"R2X::backward(cMF,MF): How did this happen?");
778 outmf.ParallelCopy(m_rx, 0, 0, 1,
IntVect(0), ngout, period);
781 template <
typename T>
782 template <
int dim,
typename FAB,
typename F>
786 auto const& a = fab->array();
790 if constexpr (dim == 0) {
792 }
else if constexpr (dim == 1) {
#define BL_PROFILE(a)
Definition: AMReX_BLProfiler.H:551
#define AMREX_ALWAYS_ASSERT(EX)
Definition: AMReX_BLassert.H:50
#define AMREX_GPU_DEVICE
Definition: AMReX_GpuQualifiers.H:18
#define AMREX_D_DECL(a, b, c)
Definition: AMReX_SPACE.H:104
A collection of Boxes stored in an Array.
Definition: AMReX_BoxArray.H:550
A class for managing a List of Boxes that share a common IndexType. This class implements operations ...
Definition: AMReX_BoxList.H:52
AMREX_GPU_HOST_DEVICE const IntVectND< dim > & smallEnd() const &noexcept
Get the smallend of the BoxND.
Definition: AMReX_Box.H:105
AMREX_GPU_HOST_DEVICE const IntVectND< dim > & bigEnd() const &noexcept
Get the bigend.
Definition: AMReX_Box.H:116
AMREX_GPU_HOST_DEVICE IntVectND< dim > length() const noexcept
Return the length of the BoxND.
Definition: AMReX_Box.H:146
AMREX_GPU_HOST_DEVICE bool cellCentered() const noexcept
Returns true if BoxND is cell-centered in all indexing directions.
Definition: AMReX_Box.H:319
Calculates the distribution of FABs to MPI processes.
Definition: AMReX_DistributionMapping.H:41
3D Poisson solver for periodic, Dirichlet & Neumann boundaries in the first two dimensions,...
Definition: AMReX_FFT_Poisson.H:106
Poisson solver for periodic, Dirichlet & Neumann boundaries using FFT.
Definition: AMReX_FFT_Poisson.H:22
Discrete Fourier Transform.
Definition: AMReX_FFT_R2X.H:25
MF m_rz
Definition: AMReX_FFT_R2X.H:97
Array< std::pair< Boundary, Boundary >, AMREX_SPACEDIM > m_bc
Definition: AMReX_FFT_R2X.H:71
void post_forward_doit(FAB *fab, F const &f)
Definition: AMReX_FFT_R2X.H:783
Swap01 m_dtos_x2y
Definition: AMReX_FFT_R2X.H:90
std::conditional_t< std::is_same_v< T, Real >, MultiFab, FabArray< BaseFab< T > > > MF
Definition: AMReX_FFT_R2X.H:28
std::unique_ptr< MultiBlockCommMetaData > m_cmd_rz2ry
Definition: AMReX_FFT_R2X.H:88
MF m_ry
Definition: AMReX_FFT_R2X.H:96
Box m_dom_cz
Definition: AMReX_FFT_R2X.H:110
Swap02 m_dtos_y2z
Definition: AMReX_FFT_R2X.H:92
~R2X()
Definition: AMReX_FFT_R2X.H:494
void forward(MF const &inmf, MF &outmf)
Definition: AMReX_FFT_R2X.H:638
void forwardThenBackward(MF const &inmf, MF &outmf, F const &post_forward)
Definition: AMReX_FFT_R2X.H:526
std::unique_ptr< MultiBlockCommMetaData > m_cmd_ry2rx
Definition: AMReX_FFT_R2X.H:86
Info m_info
Definition: AMReX_FFT_R2X.H:112
std::unique_ptr< MultiBlockCommMetaData > m_cmd_cz2cy
Definition: AMReX_FFT_R2X.H:87
std::unique_ptr< MultiBlockCommMetaData > m_cmd_cy2cz
Definition: AMReX_FFT_R2X.H:82
Plan< T > m_fft_fwd_y
Definition: AMReX_FFT_R2X.H:75
MF m_rx
Definition: AMReX_FFT_R2X.H:95
std::unique_ptr< MultiBlockCommMetaData > m_cmd_ry2rz
Definition: AMReX_FFT_R2X.H:83
Box m_dom_rx
Definition: AMReX_FFT_R2X.H:105
std::unique_ptr< MultiBlockCommMetaData > m_cmd_rx2ry
Definition: AMReX_FFT_R2X.H:81
Swap01 m_dtos_y2x
Definition: AMReX_FFT_R2X.H:91
Box m_dom_cy
Definition: AMReX_FFT_R2X.H:109
T scalingFactor() const
Definition: AMReX_FFT_R2X.H:511
Box m_dom_ry
Definition: AMReX_FFT_R2X.H:106
void backward()
Definition: AMReX_FFT_R2X.H:683
Plan< T > m_fft_bwd_z
Definition: AMReX_FFT_R2X.H:78
void forwardThenBackward_doit(MF const &inmf, MF &outmf, F const &post_forward, IntVect const &ngout=IntVect(0), Periodicity const &period=Periodicity::NonPeriodic())
Definition: AMReX_FFT_R2X.H:533
std::unique_ptr< char, DataDeleter > m_data_1
Definition: AMReX_FFT_R2X.H:102
Plan< T > m_fft_fwd_x
Definition: AMReX_FFT_R2X.H:73
R2X(Box const &domain, Array< std::pair< Boundary, Boundary >, AMREX_SPACEDIM > const &bc, Info const &info=Info{})
Definition: AMReX_FFT_R2X.H:116
std::unique_ptr< MultiBlockCommMetaData > m_cmd_cy2cx
Definition: AMReX_FFT_R2X.H:85
Box m_dom_0
Definition: AMReX_FFT_R2X.H:70
std::unique_ptr< MultiBlockCommMetaData > m_cmd_cx2cy
Definition: AMReX_FFT_R2X.H:80
std::unique_ptr< char, DataDeleter > m_data_2
Definition: AMReX_FFT_R2X.H:103
Plan< T > m_fft_fwd_z
Definition: AMReX_FFT_R2X.H:77
Plan< T > m_fft_bwd_y
Definition: AMReX_FFT_R2X.H:76
cMF m_cx
Definition: AMReX_FFT_R2X.H:98
cMF m_cz
Definition: AMReX_FFT_R2X.H:100
R2X & operator=(R2X const &)=delete
Swap02 m_dtos_z2y
Definition: AMReX_FFT_R2X.H:93
cMF m_cy
Definition: AMReX_FFT_R2X.H:99
Box m_dom_cx
Definition: AMReX_FFT_R2X.H:108
Plan< T > m_fft_bwd_x
Definition: AMReX_FFT_R2X.H:74
Box m_dom_rz
Definition: AMReX_FFT_R2X.H:107
int size() const noexcept
Return the number of FABs in the FabArray.
Definition: AMReX_FabArrayBase.H:109
bool empty() const noexcept
Definition: AMReX_FabArrayBase.H:88
const DistributionMapping & DistributionMap() const noexcept
Return constant reference to associated DistributionMapping.
Definition: AMReX_FabArrayBase.H:130
Box box(int K) const noexcept
Return the Kth Box in the BoxArray. That is, the valid region of the Kth grid.
Definition: AMReX_FabArrayBase.H:100
An Array of FortranArrayBox(FAB)-like Objects.
Definition: AMReX_FabArray.H:344
void define(const BoxArray &bxs, const DistributionMapping &dm, int nvar, int ngrow, const MFInfo &info=MFInfo(), const FabFactory< FAB > &factory=DefaultFabFactory< FAB >())
Define this FabArray identically to that performed by the constructor having an analogous function si...
Definition: AMReX_FabArray.H:2027
A collection (stored as an array) of FArrayBox objects.
Definition: AMReX_MultiFab.H:38
This provides length of period for periodic domains. 0 means it is not periodic in that direction....
Definition: AMReX_Periodicity.H:17
static const Periodicity & NonPeriodic() noexcept
Definition: AMReX_Periodicity.cpp:52
@ FAB
Definition: AMReX_AmrvisConstants.H:86
FA::FABType::value_type * get_fab(FA &fa)
Definition: AMReX_FFT_Helper.H:1303
std::unique_ptr< char, DataDeleter > make_mfs_share(FA1 &fa1, FA2 &fa2)
Definition: AMReX_FFT_Helper.H:1314
DistributionMapping make_iota_distromap(Long n)
Definition: AMReX_FFT.cpp:88
Definition: AMReX_FFT.cpp:7
int MyProcSub() noexcept
my sub-rank in current frame
Definition: AMReX_ParallelContext.H:76
int NProcsSub() noexcept
number of ranks in current frame
Definition: AMReX_ParallelContext.H:74
static int f(amrex::Real t, N_Vector y_data, N_Vector y_rhs, void *user_data)
Definition: AMReX_SundialsIntegrator.H:44
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void swap(T &a, T &b) noexcept
Definition: AMReX_algoim_K.H:113
@ min
Definition: AMReX_ParallelReduce.H:18
std::enable_if_t< std::is_integral_v< T > > ParallelFor(TypeList< CTOs... > ctos, std::array< int, sizeof...(CTOs)> const &runtime_options, T N, F &&f)
Definition: AMReX_CTOParallelForImpl.H:200
BoxND< AMREX_SPACEDIM > Box
Definition: AMReX_BaseFwd.H:27
double second() noexcept
Definition: AMReX_Utility.cpp:922
IntVectND< AMREX_SPACEDIM > IntVect
Definition: AMReX_BaseFwd.H:30
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void ignore_unused(const Ts &...)
This shuts up the compiler about unused variables.
Definition: AMReX.H:111
BoxArray decompose(Box const &domain, int nboxes, Array< bool, AMREX_SPACEDIM > const &decomp={AMREX_D_DECL(true, true, true)}, bool no_overlap=false)
Decompose domain box into BoxArray.
void Abort(const std::string &msg)
Print out message to cerr and exit via abort().
Definition: AMReX.cpp:225
void ParallelCopy(MF &dst, MF const &src, int scomp, int dcomp, int ncomp, IntVect const &ng_src=IntVect(0), IntVect const &ng_dst=IntVect(0), Periodicity const &period=Periodicity::NonPeriodic())
dst = src w/ MPI communication
Definition: AMReX_FabArrayUtility.H:1672
std::array< T, N > Array
Definition: AMReX_Array.H:24
Definition: AMReX_FFT_Helper.H:56
bool batch_mode
Definition: AMReX_FFT_Helper.H:60
int nprocs
Max number of processes to use.
Definition: AMReX_FFT_Helper.H:63
Definition: AMReX_FFT_Helper.H:111
std::conditional_t< std::is_same_v< float, T >, cuComplex, cuDoubleComplex > VendorComplex
Definition: AMReX_FFT_Helper.H:115
Definition: AMReX_FFT_Helper.H:1355
Definition: AMReX_FFT_Helper.H:1378
FabArray memory allocation information.
Definition: AMReX_FabArray.H:66