70 void backward (
MF const& inmf,
MF& outmf,
IntVect const& ngout,
72 void backward (
cMF const& inmf,
MF& outmf,
IntVect const& ngout,
81 Plan<T> m_fft_fwd_y{};
82 Plan<T> m_fft_bwd_y{};
83 Plan<T> m_fft_fwd_z{};
84 Plan<T> m_fft_bwd_z{};
86 std::unique_ptr<MultiBlockCommMetaData> m_cmd_cx2cy;
87 std::unique_ptr<MultiBlockCommMetaData> m_cmd_rx2ry;
88 std::unique_ptr<MultiBlockCommMetaData> m_cmd_cy2cz;
89 std::unique_ptr<MultiBlockCommMetaData> m_cmd_ry2rz;
91 std::unique_ptr<MultiBlockCommMetaData> m_cmd_cy2cx;
92 std::unique_ptr<MultiBlockCommMetaData> m_cmd_ry2rx;
93 std::unique_ptr<MultiBlockCommMetaData> m_cmd_cz2cy;
94 std::unique_ptr<MultiBlockCommMetaData> m_cmd_rz2ry;
108 std::unique_ptr<char,DataDeleter> m_data_1;
109 std::unique_ptr<char,DataDeleter> m_data_2;
118 std::unique_ptr<R2X<T>> m_r2x_sub;
119 detail::SubHelper m_sub_helper;
126 Array<std::pair<Boundary,Boundary>,AMREX_SPACEDIM>
const& bc,
130 m_sub_helper(domain),
135 static_assert(std::is_same_v<float,T> || std::is_same_v<double,T>);
138#if (AMREX_SPACEDIM == 2)
143 int(domain.
length(1) > 1) +
144 int(domain.
length(2) > 1)) >= 2);
148 for (
int idim = 0; idim < AMREX_SPACEDIM; ++idim) {
156 Box subbox = m_sub_helper.make_box(m_dom_0);
157 if (subbox.
size() != m_dom_0.
size()) {
158 m_r2x_sub = std::make_unique<R2X<T>>
159 (subbox, m_sub_helper.make_array(bc), info);
174 m_rx.define(bax, dmx, 1, 0,
MFInfo().SetAlloc(
false));
183 for (
auto & b : bl) {
184 b.setBig(0, m_dom_cx.
bigEnd(0));
190#if (AMREX_SPACEDIM >= 2)
192 if (! m_cx.
empty()) {
199 if (ba.size() == m_cx.
size()) {
202 dm = detail::make_iota_distromap(ba.size());
216 if (ba.size() == m_rx.size()) {
217 dm = m_rx.DistributionMap();
219 dm = detail::make_iota_distromap(ba.size());
221 m_ry.define(ba, dm, 1, 0,
MFInfo().SetAlloc(
false));
231 for (
auto & b : bl) {
232 b.setBig(0, m_dom_cy.
bigEnd(0));
241#if (AMREX_SPACEDIM == 3)
243 if (! m_cy.
empty()) {
250 if (ba.size() == m_cy.
size()) {
253 dm = detail::make_iota_distromap(ba.size());
267 if (ba.size() == m_ry.size()) {
268 dm = m_ry.DistributionMap();
270 dm = detail::make_iota_distromap(ba.size());
272 m_rz.define(ba, dm, 1, 0,
MFInfo().SetAlloc(
false));
282 for (
auto & b : bl) {
283 b.setBig(0, m_dom_cz.
bigEnd(0));
314 if (! m_cx.
empty()) {
315 m_data_1 = detail::make_mfs_share(m_rx, m_cy);
316 m_data_2 = detail::make_mfs_share(m_cx, m_cz);
317 }
else if (! m_cy.
empty()) {
318 m_data_1 = detail::make_mfs_share(m_rx, m_cy);
319 m_data_2 = detail::make_mfs_share(m_ry, m_cz);
320 }
else if (! m_cz.
empty()) {
321 m_data_1 = detail::make_mfs_share(m_rx, m_rz);
322 m_data_2 = detail::make_mfs_share(m_ry, m_cz);
324 m_data_1 = detail::make_mfs_share(m_rx, m_rz);
325 m_data_2 = detail::make_mfs_share(m_ry, m_cz);
332#if (AMREX_SPACEDIM >= 2)
333 if (!m_cy.
empty() || !m_ry.empty()) {
334 if (! m_cx.
empty()) {
336 m_cmd_cx2cy = std::make_unique<MultiBlockCommMetaData>
337 (m_cy, m_dom_cy, m_cx,
IntVect(0), m_dtos_x2y);
338 m_cmd_cy2cx = std::make_unique<MultiBlockCommMetaData>
339 (m_cx, m_dom_cx, m_cy,
IntVect(0), m_dtos_y2x);
342 m_cmd_rx2ry = std::make_unique<MultiBlockCommMetaData>
343 (m_ry, m_dom_ry, m_rx,
IntVect(0), m_dtos_x2y);
344 m_cmd_ry2rx = std::make_unique<MultiBlockCommMetaData>
345 (m_rx, m_dom_rx, m_ry,
IntVect(0), m_dtos_y2x);
350#if (AMREX_SPACEDIM == 3)
351 if (!m_cz.
empty() || !m_rz.empty()) {
352 if (! m_cy.
empty()) {
354 m_cmd_cy2cz = std::make_unique<MultiBlockCommMetaData>
355 (m_cz, m_dom_cz, m_cy,
IntVect(0), m_dtos_y2z);
356 m_cmd_cz2cy = std::make_unique<MultiBlockCommMetaData>
357 (m_cy, m_dom_cy, m_cz,
IntVect(0), m_dtos_z2y);
360 m_cmd_ry2rz = std::make_unique<MultiBlockCommMetaData>
361 (m_rz, m_dom_rz, m_ry,
IntVect(0), m_dtos_y2z);
362 m_cmd_rz2ry = std::make_unique<MultiBlockCommMetaData>
363 (m_ry, m_dom_ry, m_rz,
IntVect(0), m_dtos_z2y);
374 if (myproc < m_rx.size())
376 Box const& box = m_rx.box(myproc);
377 auto* pf = m_rx[myproc].dataPtr();
379 auto* pb = (VendorComplex*) m_cx[myproc].dataPtr();
380 m_fft_fwd_x.template init_r2c<Direction::forward>(box, pf, pb);
381#if defined(AMREX_USE_SYCL)
382 m_fft_bwd_x = m_fft_fwd_x;
384 m_fft_bwd_x.template init_r2c<Direction::backward>(box, pf, pb);
387 m_fft_fwd_x.template init_r2r<Direction::forward>(box, pf, bc[0]);
388#if defined(AMREX_USE_GPU)
391 m_fft_bwd_x = m_fft_fwd_x;
395 m_fft_bwd_x.template init_r2r<Direction::backward>(box, pf, bc[0]);
400#if (AMREX_SPACEDIM >= 2)
402 if (myproc < m_cy.
size()) {
403 Box const& box = m_cy.
box(myproc);
404 auto* p = (VendorComplex *)m_cy[myproc].dataPtr();
405 m_fft_fwd_y.template init_c2c<Direction::forward>(box, p);
406#if defined(AMREX_USE_SYCL)
407 m_fft_bwd_y = m_fft_fwd_y;
409 m_fft_bwd_y.template init_c2c<Direction::backward>(box, p);
413 if (myproc < m_ry.size()) {
414 Box const& box = m_ry.box(myproc);
415 auto* pr = m_ry[myproc].dataPtr();
416 auto* pc = (VendorComplex*)m_cy[myproc].dataPtr();
417 m_fft_fwd_y.template init_r2c<Direction::forward>(box, pr, pc);
418#if defined(AMREX_USE_SYCL)
419 m_fft_bwd_y = m_fft_fwd_y;
421 m_fft_bwd_y.template init_r2c<Direction::backward>(box, pr, pc);
424 }
else if (!m_cy.
empty()) {
425 if (myproc < m_cy.
size()) {
426 Box const& box = m_cy.
box(myproc);
427 auto* p = (VendorComplex*) m_cy[myproc].dataPtr();
428 m_fft_fwd_y.template init_r2r<Direction::forward>(box, p, bc[1]);
429#if defined(AMREX_USE_GPU)
432 m_fft_bwd_y = m_fft_fwd_y;
436 m_fft_bwd_y.template init_r2r<Direction::backward>(box, p, bc[1]);
440 if (myproc < m_ry.size()) {
441 Box const& box = m_ry.box(myproc);
442 auto* p = m_ry[myproc].dataPtr();
443 m_fft_fwd_y.template init_r2r<Direction::forward>(box, p, bc[1]);
444#if defined(AMREX_USE_GPU)
447 m_fft_bwd_y = m_fft_fwd_y;
451 m_fft_bwd_y.template init_r2r<Direction::backward>(box, p, bc[1]);
457#if (AMREX_SPACEDIM == 3)
459 if (myproc < m_cz.
size()) {
460 Box const& box = m_cz.
box(myproc);
461 auto* p = (VendorComplex*)m_cz[myproc].dataPtr();
462 m_fft_fwd_z.template init_c2c<Direction::forward>(box, p);
463#if defined(AMREX_USE_SYCL)
464 m_fft_bwd_z = m_fft_fwd_z;
466 m_fft_bwd_z.template init_c2c<Direction::backward>(box, p);
470 if (myproc < m_rz.size()) {
471 Box const& box = m_rz.box(myproc);
472 auto* pr = m_rz[myproc].dataPtr();
473 auto* pc = (VendorComplex*)m_cz[myproc].dataPtr();
474 m_fft_fwd_z.template init_r2c<Direction::forward>(box, pr, pc);
475#if defined(AMREX_USE_SYCL)
476 m_fft_bwd_z = m_fft_fwd_z;
478 m_fft_bwd_z.template init_r2c<Direction::backward>(box, pr, pc);
481 }
else if (!m_cz.
empty()) {
482 if (myproc < m_cz.
size()) {
483 Box const& box = m_cz.
box(myproc);
484 auto* p = (VendorComplex*) m_cz[myproc].dataPtr();
485 m_fft_fwd_z.template init_r2r<Direction::forward>(box, p, bc[2]);
486#if defined(AMREX_USE_GPU)
489 m_fft_bwd_z = m_fft_fwd_z;
493 m_fft_bwd_z.template init_r2r<Direction::backward>(box, p, bc[2]);
497 if (myproc < m_rz.size()) {
498 Box const& box = m_rz.box(myproc);
499 auto* p = m_rz[myproc].dataPtr();
500 m_fft_fwd_z.template init_r2r<Direction::forward>(box, p, bc[2]);
501#if defined(AMREX_USE_GPU)
504 m_fft_bwd_z = m_fft_fwd_z;
508 m_fft_bwd_z.template init_r2r<Direction::backward>(box, p, bc[2]);
518 if (m_fft_bwd_x.plan != m_fft_fwd_x.plan) {
519 m_fft_bwd_x.destroy();
521 if (m_fft_bwd_y.plan != m_fft_fwd_y.plan) {
522 m_fft_bwd_y.destroy();
524 if (m_fft_bwd_z.plan != m_fft_fwd_z.plan) {
525 m_fft_bwd_z.destroy();
527 m_fft_fwd_x.destroy();
528 m_fft_fwd_y.destroy();
529 m_fft_fwd_z.destroy();
536 int ndims = m_info.twod_mode ? AMREX_SPACEDIM-1 : AMREX_SPACEDIM;
537#if (AMREX_SPACEDIM == 3)
538 if (m_info.twod_mode && m_dom_0.length(2) == 1) { ndims = 1; };
540 for (
int idim = 0; idim < ndims; ++idim) {
541 r *= m_dom_0.length(idim);
553 forwardThenBackward_doit_0(inmf, outmf, post_forward);
559 F const& post_forward,
566 bool inmf_safe = m_sub_helper.ghost_safe(inmf.nGrowVect());
567 MF inmf_sub, inmf_tmp;
569 inmf_sub = m_sub_helper.make_alias_mf(inmf);
571 inmf_tmp.define(inmf.boxArray(), inmf.DistributionMap(), 1, 0);
572 inmf_tmp.LocalCopy(inmf, 0, 0, 1,
IntVect(0));
573 inmf_sub = m_sub_helper.make_alias_mf(inmf_tmp);
576 bool outmf_safe = m_sub_helper.ghost_safe(outmf.nGrowVect());
577 MF outmf_sub, outmf_tmp;
579 outmf_sub = m_sub_helper.make_alias_mf(outmf);
581 IntVect const& ngtmp = m_sub_helper.make_safe_ghost(outmf.nGrowVect());
582 outmf_tmp.define(outmf.boxArray(), outmf.DistributionMap(), 1, ngtmp);
583 outmf_sub = m_sub_helper.make_alias_mf(outmf_tmp);
586 IntVect const& subngout = m_sub_helper.make_iv(ngout);
587 Periodicity const& subperiod = m_sub_helper.make_periodicity(period);
589 m_r2x_sub->forwardThenBackward_doit_1
590 (inmf_sub, outmf_sub,
594 post_forward(idx[order[0]], idx[order[1]], idx[order[2]], sp);
596 subngout, subperiod);
599 outmf.LocalCopy(outmf_tmp, 0, 0, 1, outmf_tmp.nGrowVect());
604 this->forwardThenBackward_doit_1(inmf, outmf, post_forward, ngout, period);
611 F const& post_forward,
618 amrex::Abort(
"R2X::forwardThenBackward_doit_1: How did this happen?");
626 int actual_dim = AMREX_SPACEDIM;
627#if (AMREX_SPACEDIM >= 2)
628 if (m_dom_0.length(1) == 1) { actual_dim = 1; }
630#if (AMREX_SPACEDIM == 3)
631 if ((m_dom_0.length(2) == 1) && (m_dom_0.length(1) > 1)) { actual_dim = 2; }
634 if (actual_dim == 1) {
636 post_forward_doit<0>(detail::get_fab(m_rx), post_forward);
638 post_forward_doit<0>(detail::get_fab(m_cx), post_forward);
641#if (AMREX_SPACEDIM >= 2)
642 else if (actual_dim == 2) {
644 post_forward_doit<1>(detail::get_fab(m_ry), post_forward);
646 post_forward_doit<1>(detail::get_fab(m_cy), post_forward);
650#if (AMREX_SPACEDIM == 3)
651 else if (actual_dim == 3) {
653 post_forward_doit<2>(detail::get_fab(m_rz), post_forward);
655 post_forward_doit<2>(detail::get_fab(m_cz), post_forward);
662 outmf.ParallelCopy(m_rx, 0, 0, 1,
IntVect(0),
673 if (m_sub_helper.ghost_safe(inmf.nGrowVect())) {
674 m_r2x_sub->forward(m_sub_helper.make_alias_mf(inmf));
676 MF tmp(inmf.boxArray(), inmf.DistributionMap(), 1, 0);
677 tmp.LocalCopy(inmf, 0, 0, 1,
IntVect(0));
678 m_r2x_sub->forward(m_sub_helper.make_alias_mf(tmp));
683 m_rx.ParallelCopy(inmf, 0, 0, 1);
685 m_fft_fwd_x.template compute_r2c<Direction::forward>();
687 m_fft_fwd_x.template compute_r2r<Direction::forward>();
690#if (AMREX_SPACEDIM >= 2)
692 ParallelCopy(m_cy, m_cx, *m_cmd_cx2cy, 0, 0, 1, m_dtos_x2y);
693 }
else if ( m_cmd_rx2ry) {
694 ParallelCopy(m_ry, m_rx, *m_cmd_rx2ry, 0, 0, 1, m_dtos_x2y);
698 m_fft_fwd_y.template compute_r2r<Direction::forward>();
702 m_fft_fwd_y.template compute_c2c<Direction::forward>();
706 m_fft_fwd_y.template compute_r2c<Direction::forward>();
710#if (AMREX_SPACEDIM == 3)
712 ParallelCopy(m_cz, m_cy, *m_cmd_cy2cz, 0, 0, 1, m_dtos_y2z);
713 }
else if ( m_cmd_ry2rz) {
714 ParallelCopy(m_rz, m_ry, *m_cmd_ry2rz, 0, 0, 1, m_dtos_y2z);
718 m_fft_fwd_z.template compute_r2r<Direction::forward>();
723 m_fft_fwd_z.template compute_c2c<Direction::forward>();
727 m_fft_fwd_z.template compute_r2c<Direction::forward>();
733void R2X<T>::forward (MF
const& inmf, MF& outmf)
737 bool inmf_safe = m_sub_helper.ghost_safe(inmf.nGrowVect());
738 MF inmf_sub, inmf_tmp;
740 inmf_sub = m_sub_helper.make_alias_mf(inmf);
742 inmf_tmp.define(inmf.boxArray(), inmf.DistributionMap(), 1, 0);
743 inmf_tmp.LocalCopy(inmf, 0, 0, 1,
IntVect(0));
744 inmf_sub = m_sub_helper.make_alias_mf(inmf_tmp);
747 bool outmf_safe = m_sub_helper.ghost_safe(outmf.nGrowVect());
748 MF outmf_sub, outmf_tmp;
750 outmf_sub = m_sub_helper.make_alias_mf(outmf);
752 outmf_tmp.define(outmf.boxArray(), outmf.DistributionMap(), 1, 0);
753 outmf_sub = m_sub_helper.make_alias_mf(outmf_tmp);
756 m_r2x_sub->forward(inmf_sub, outmf_sub);
759 outmf.LocalCopy(outmf_tmp, 0, 0, 1,
IntVect(0));
766#if (AMREX_SPACEDIM == 3)
767 if (m_info.twod_mode) {
768 if (m_cy.empty() && !m_ry.empty()) {
770 }
else if (m_ry.empty() && m_cy.empty() && m_cx.empty()) {
771 outmf.ParallelCopy(m_rx, 0, 0, 1);
773 amrex::Abort(
"R2X::forward(MF,MF): How did this happen?");
785void R2X<T>::forward (MF
const& inmf, cMF& outmf)
789 bool inmf_safe = m_sub_helper.ghost_safe(inmf.nGrowVect());
790 MF inmf_sub, inmf_tmp;
792 inmf_sub = m_sub_helper.make_alias_mf(inmf);
794 inmf_tmp.define(inmf.boxArray(), inmf.DistributionMap(), 1, 0);
795 inmf_tmp.LocalCopy(inmf, 0, 0, 1,
IntVect(0));
796 inmf_sub = m_sub_helper.make_alias_mf(inmf_tmp);
799 bool outmf_safe = m_sub_helper.ghost_safe(outmf.nGrowVect());
800 cMF outmf_sub, outmf_tmp;
802 outmf_sub = m_sub_helper.make_alias_mf(outmf);
804 outmf_tmp.define(outmf.boxArray(), outmf.DistributionMap(), 1, 0);
805 outmf_sub = m_sub_helper.make_alias_mf(outmf_tmp);
808 m_r2x_sub->forward(inmf_sub, outmf_sub);
811 outmf.LocalCopy(outmf_tmp, 0, 0, 1,
IntVect(0));
818#if (AMREX_SPACEDIM == 3)
819 if (m_info.twod_mode) {
821 auto lo = m_dom_cy.smallEnd();
822 auto hi = m_dom_cy.bigEnd();
823 std::swap(lo[0],lo[1]);
824 std::swap(hi[0],hi[1]);
827 }
else if (m_ry.empty() && m_cy.empty() && !m_cx.empty()) {
828 outmf.ParallelCopy(m_cx, 0, 0, 1);
830 amrex::Abort(
"R2X::forward(MF,cMF): How did this happen?");
842void R2X<T>::backward ()
848#if (AMREX_SPACEDIM == 3)
851 m_fft_bwd_z.template compute_r2r<Direction::backward>();
856 m_fft_bwd_z.template compute_c2c<Direction::backward>();
860 m_fft_bwd_z.template compute_r2c<Direction::backward>();
863 ParallelCopy(m_cy, m_cz, *m_cmd_cz2cy, 0, 0, 1, m_dtos_z2y);
864 }
else if ( m_cmd_rz2ry) {
865 ParallelCopy(m_ry, m_rz, *m_cmd_rz2ry, 0, 0, 1, m_dtos_z2y);
869#if (AMREX_SPACEDIM >= 2)
872 m_fft_bwd_y.template compute_r2r<Direction::backward>();
876 m_fft_bwd_y.template compute_c2c<Direction::backward>();
880 m_fft_bwd_y.template compute_r2c<Direction::backward>();
883 ParallelCopy(m_cx, m_cy, *m_cmd_cy2cx, 0, 0, 1, m_dtos_y2x);
884 }
else if ( m_cmd_ry2rx) {
885 ParallelCopy(m_rx, m_ry, *m_cmd_ry2rx, 0, 0, 1, m_dtos_y2x);
890 m_fft_bwd_x.template compute_r2c<Direction::backward>();
892 m_fft_bwd_x.template compute_r2r<Direction::backward>();
897void R2X<T>::backward (MF
const& inmf, MF& outmf,
IntVect const& ngout,
898 Periodicity
const& period)
902 bool inmf_safe = m_sub_helper.ghost_safe(inmf.nGrowVect());
903 MF inmf_sub, inmf_tmp;
905 inmf_sub = m_sub_helper.make_alias_mf(inmf);
907 inmf_tmp.define(inmf.boxArray(), inmf.DistributionMap(), 1, 0);
908 inmf_tmp.LocalCopy(inmf, 0, 0, 1,
IntVect(0));
909 inmf_sub = m_sub_helper.make_alias_mf(inmf_tmp);
912 bool outmf_safe = m_sub_helper.ghost_safe(outmf.nGrowVect());
913 MF outmf_sub, outmf_tmp;
915 outmf_sub = m_sub_helper.make_alias_mf(outmf);
917 IntVect const& ngtmp = m_sub_helper.make_safe_ghost(outmf.nGrowVect());
918 outmf_tmp.define(outmf.boxArray(), outmf.DistributionMap(), 1, ngtmp);
919 outmf_sub = m_sub_helper.make_alias_mf(outmf_tmp);
922 IntVect const& subngout = m_sub_helper.make_iv(ngout);
923 Periodicity
const& subperiod = m_sub_helper.make_periodicity(period);
924 m_r2x_sub->backward(inmf_sub, outmf_sub, subngout, subperiod);
927 outmf.LocalCopy(outmf_tmp, 0, 0, 1, outmf_tmp.nGrowVect());
932#if (AMREX_SPACEDIM == 3)
933 if (m_info.twod_mode) {
934 if (m_cy.empty() && !m_ry.empty()) {
936 }
else if (m_ry.empty() && m_cy.empty() && m_cx.empty()) {
937 m_rx.ParallelCopy(inmf, 0, 0, 1);
939 amrex::Abort(
"R2X::backward(MF,MF): How did this happen?");
950 outmf.ParallelCopy(m_rx, 0, 0, 1,
IntVect(0),
956void R2X<T>::backward (cMF
const& inmf, MF& outmf,
IntVect const& ngout,
957 Periodicity
const& period)
961 bool inmf_safe = m_sub_helper.ghost_safe(inmf.nGrowVect());
962 cMF inmf_sub, inmf_tmp;
964 inmf_sub = m_sub_helper.make_alias_mf(inmf);
966 inmf_tmp.define(inmf.boxArray(), inmf.DistributionMap(), 1, 0);
967 inmf_tmp.LocalCopy(inmf, 0, 0, 1,
IntVect(0));
968 inmf_sub = m_sub_helper.make_alias_mf(inmf_tmp);
971 bool outmf_safe = m_sub_helper.ghost_safe(outmf.nGrowVect());
972 MF outmf_sub, outmf_tmp;
974 outmf_sub = m_sub_helper.make_alias_mf(outmf);
976 IntVect const& ngtmp = m_sub_helper.make_safe_ghost(outmf.nGrowVect());
977 outmf_tmp.define(outmf.boxArray(), outmf.DistributionMap(), 1, ngtmp);
978 outmf_sub = m_sub_helper.make_alias_mf(outmf_tmp);
981 IntVect const& subngout = m_sub_helper.make_iv(ngout);
982 Periodicity
const& subperiod = m_sub_helper.make_periodicity(period);
983 m_r2x_sub->backward(inmf_sub, outmf_sub, subngout, subperiod);
986 outmf.LocalCopy(outmf_tmp, 0, 0, 1, outmf_tmp.nGrowVect());
991#if (AMREX_SPACEDIM == 3)
992 if (m_info.twod_mode) {
995 }
else if (m_ry.empty() && m_cy.empty() && !m_cx.empty()) {
996 m_cx.ParallelCopy(inmf, 0, 0, 1);
998 amrex::Abort(
"R2X::backward(cMF,MF): How did this happen?");
1009 outmf.ParallelCopy(m_rx, 0, 0, 1,
IntVect(0),
1014template <
typename T>
1015template <
int dim,
typename FAB,
typename F>
1018 if (m_info.twod_mode) {
1022 auto const& a = fab->array();
1026 if constexpr (dim == 0) {
1028 }
else if constexpr (dim == 1) {