|
|
@ -1,3 +1,4 @@ |
|
|
|
#include <array> |
|
|
|
#include <bitset> |
|
|
|
#include <iostream> |
|
|
|
#include <booluarray.hpp> |
|
|
@ -10,12 +11,13 @@ |
|
|
|
#include <vector> |
|
|
|
#include "bernstein.hpp" |
|
|
|
#include "quadrature_multipoly.hpp" |
|
|
|
#include "binomial.hpp" |
|
|
|
|
|
|
|
#include "real.hpp" |
|
|
|
#include "uvector.hpp" |
|
|
|
#include "vector" |
|
|
|
#include "xarray.hpp" |
|
|
|
#include<chrono> |
|
|
|
#include <chrono> |
|
|
|
|
|
|
|
|
|
|
|
using namespace algoim; |
|
|
@ -68,6 +70,8 @@ void qConv1(const Phi& phi, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
enum BoolOp { Union, Intersection, Difference }; |
|
|
|
|
|
|
|
// Driver method which takes two phi functors defining two polynomials in the reference
|
|
|
|
// rectangle [xmin, xmax]^N, each of of Bernstein degree P, builds a quadrature scheme with the
|
|
|
|
// given q, and outputs it for visualisation in a set of VTP XML files
|
|
|
@ -79,13 +83,13 @@ void qConvMultiPloy(const F1& fphi1, |
|
|
|
const uvector<int, N>& P, |
|
|
|
const F& integrand, |
|
|
|
int q, |
|
|
|
std::string qfile) |
|
|
|
BoolOp op) |
|
|
|
{ |
|
|
|
// Construct phi by mapping [0,1] onto bounding box [xmin,xmax]
|
|
|
|
xarray<real, N> phi1(nullptr, P), phi2(nullptr, P); |
|
|
|
algoim_spark_alloc(real, phi1, phi2); |
|
|
|
bernstein::bernsteinInterpolate<N>([&](const uvector<real, N>& x) { return fphi1(xmin + x * (xmax - xmin)); }, phi1); |
|
|
|
// bernstein::bernsteinInterpolate<N>([&](const uvector<real, N>& x) { return fphi2(xmin + x * (xmax - xmin)); }, phi2);
|
|
|
|
bernstein::bernsteinInterpolate<N>([&](const uvector<real, N>& x) { return fphi2(xmin + x * (xmax - xmin)); }, phi2); |
|
|
|
|
|
|
|
// Build quadrature hierarchy
|
|
|
|
ImplicitPolyQuadrature<N> ipquad(phi1, phi2); |
|
|
@ -98,7 +102,16 @@ void qConvMultiPloy(const F1& fphi1, |
|
|
|
surf = 0.0; |
|
|
|
// compute volume integral over {phi < 0} using AutoMixed strategy
|
|
|
|
ipquad.integrate(AutoMixed, q, [&](const uvector<real, N>& x, real w) { |
|
|
|
if (bernstein::evalBernsteinPoly(phi1, x) < 0 && bernstein::evalBernsteinPoly(phi2, x) < 0) volume += w * integrand(xmin + x * (xmax - xmin)); |
|
|
|
if (op == Union) { |
|
|
|
if (bernstein::evalBernsteinPoly(phi1, x) < 0 || bernstein::evalBernsteinPoly(phi2, x) < 0) |
|
|
|
volume += w * integrand(xmin + x * (xmax - xmin)); |
|
|
|
} else if (op == Intersection) { |
|
|
|
if (bernstein::evalBernsteinPoly(phi1, x) < 0 && bernstein::evalBernsteinPoly(phi2, x) < 0) |
|
|
|
volume += w * integrand(xmin + x * (xmax - xmin)); |
|
|
|
} else if (op == Difference) { |
|
|
|
if (bernstein::evalBernsteinPoly(phi1, x) < 0 && bernstein::evalBernsteinPoly(phi2, x) > 0) |
|
|
|
volume += w * integrand(xmin + x * (xmax - xmin)); |
|
|
|
} |
|
|
|
// if (bernstein::evalBernsteinPoly(phi1, x) < 0) volume += w * integrand(xmin + x * (xmax - xmin));
|
|
|
|
}); |
|
|
|
// compute surface integral over {phi == 0} using AutoMixed strategy
|
|
|
@ -114,37 +127,30 @@ void qConvMultiPloy(const F1& fphi1, |
|
|
|
} |
|
|
|
|
|
|
|
template <int N, typename F> |
|
|
|
void qConvMultiPloy2( |
|
|
|
real xmin, |
|
|
|
real xmax, |
|
|
|
const uvector<int, N>& P, |
|
|
|
const F& integrand, |
|
|
|
int q, |
|
|
|
std::string qfile) |
|
|
|
void qConvMultiPloy2(real xmin, real xmax, const uvector<int, N>& P, const F& integrand, int q, std::string qfile) |
|
|
|
{ |
|
|
|
// Construct phi by mapping [0,1] onto bounding box [xmin,xmax]
|
|
|
|
|
|
|
|
// bernstein::bernsteinInterpolate<N>([&](const uvector<real, N>& x) { return fphi1(xmin + x * (xmax - xmin)); }, phi1);
|
|
|
|
// bernstein::bernsteinInterpolate<N>([&](const uvector<real, N>& x) { return fphi2(xmin + x * (xmax - xmin)); }, phi2);
|
|
|
|
|
|
|
|
std::vector<xarray<real, N>> phis; // 大量sphere
|
|
|
|
for (int i = 0; i < 100; i++) { |
|
|
|
real centerX = 1.0 - rand() % 20 / 10.0, centerY = 1.0 - rand() % 20 / 10.0, centerZ = 1.0 - rand() % 20 / 10.0; |
|
|
|
real r = 0.1 + rand() % 9 / 10.0; |
|
|
|
auto fphi = [&](const uvector<real, 3>& xx) { |
|
|
|
real x = xx(0) - centerX; |
|
|
|
real y = xx(1) - centerY; |
|
|
|
real z = xx(2) - centerZ; |
|
|
|
return x * x + y * y + z * z - 1; |
|
|
|
}; |
|
|
|
xarray<real, N> phi(nullptr, P); |
|
|
|
algoim_spark_alloc(real, phi); |
|
|
|
bernstein::bernsteinInterpolate<N>([&](const uvector<real, N>& x) { return fphi(xmin + x * (xmax - xmin)); }, phi); |
|
|
|
} |
|
|
|
auto t = std::chrono::system_clock::now(); |
|
|
|
|
|
|
|
ImplicitPolyQuadrature<N> ipquad(phis); |
|
|
|
std::vector<xarray<real, N>> phis; // 大量sphere
|
|
|
|
for (int i = 0; i < 100; i++) { |
|
|
|
real centerX = 1.0 - rand() % 20 / 10.0, centerY = 1.0 - rand() % 20 / 10.0, centerZ = 1.0 - rand() % 20 / 10.0; |
|
|
|
real r = 0.1 + rand() % 9 / 10.0; |
|
|
|
auto fphi = [&](const uvector<real, 3>& xx) { |
|
|
|
real x = xx(0) - centerX; |
|
|
|
real y = xx(1) - centerY; |
|
|
|
real z = xx(2) - centerZ; |
|
|
|
return x * x + y * y + z * z - r * r; |
|
|
|
}; |
|
|
|
xarray<real, N> phi(nullptr, P); |
|
|
|
algoim_spark_alloc(real, phi); |
|
|
|
bernstein::bernsteinInterpolate<N>([&](const uvector<real, N>& x) { return fphi(xmin + x * (xmax - xmin)); }, phi); |
|
|
|
} |
|
|
|
auto t = std::chrono::system_clock::now(); |
|
|
|
|
|
|
|
ImplicitPolyQuadrature<N> ipquad(phis); |
|
|
|
|
|
|
|
|
|
|
|
// Build quadrature hierarchy
|
|
|
@ -158,9 +164,10 @@ void qConvMultiPloy2( |
|
|
|
surf = 0.0; |
|
|
|
// compute volume integral over {phi < 0} using AutoMixed strategy
|
|
|
|
ipquad.integrate(AutoMixed, q, [&](const uvector<real, N>& x, real w) { |
|
|
|
// if (bernstein::evalBernsteinPoly(phi1, x) < 0 && bernstein::evalBernsteinPoly(phi2, x) < 0) volume += w * integrand(xmin + x * (xmax - xmin));
|
|
|
|
// if (bernstein::evalBernsteinPoly(phi1, x) < 0) volume += w * integrand(xmin + x * (xmax - xmin));
|
|
|
|
volume += w * integrand(xmin + x * (xmax - xmin)); |
|
|
|
// if (bernstein::evalBernsteinPoly(phi1, x) < 0 && bernstein::evalBernsteinPoly(phi2, x) < 0) volume += w *
|
|
|
|
// integrand(xmin + x * (xmax - xmin)); if (bernstein::evalBernsteinPoly(phi1, x) < 0) volume += w * integrand(xmin
|
|
|
|
// + x * (xmax - xmin));
|
|
|
|
volume += w * integrand(xmin + x * (xmax - xmin)); |
|
|
|
}); |
|
|
|
// compute surface integral over {phi == 0} using AutoMixed strategy
|
|
|
|
// ipquad.integrate_surf(AutoMixed, q, [&](const uvector<real, N>& x, real w, const uvector<real, N>& wn) {
|
|
|
@ -172,15 +179,13 @@ void qConvMultiPloy2( |
|
|
|
}; |
|
|
|
compute(q); |
|
|
|
|
|
|
|
auto tAfter = std::chrono::system_clock::now(); |
|
|
|
std::chrono::duration<double> elapsed_seconds = tAfter - t; |
|
|
|
std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n"; |
|
|
|
auto tAfter = std::chrono::system_clock::now(); |
|
|
|
std::chrono::duration<double> elapsed_seconds = tAfter - t; |
|
|
|
std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n"; |
|
|
|
std::cout << "q volume: " << volume << std::endl; |
|
|
|
} |
|
|
|
|
|
|
|
void fromCommon2Bernstein() { |
|
|
|
|
|
|
|
} |
|
|
|
void fromCommon2Bernstein() {} |
|
|
|
|
|
|
|
void testMultiPolys() |
|
|
|
{ |
|
|
@ -219,12 +224,233 @@ void testMultiPolys() |
|
|
|
// };
|
|
|
|
auto integrand = [](const uvector<real, 3>& x) { return 1.0; }; |
|
|
|
// qConvMultiPloy<3>(phi0, phi1, -1.0, 1.0, 3, integrand, 3, "exampleC");
|
|
|
|
qConvMultiPloy2<3>(-1.0, 1.0, 3, integrand, 20, "exampleC"); |
|
|
|
std::cout << "\n\nQuadrature visualisation of a 2D implicitly-defined domain involving the\n"; |
|
|
|
std::cout << "intersection of two polynomials, corresponding to the top-left example of Figure 15,\n"; |
|
|
|
std::cout << "https://doi.org/10.1016/j.jcp.2021.110720, written to exampleC-xxxx.vtp files\n"; |
|
|
|
std::cout << "(XML VTP file format).\n"; |
|
|
|
qConvMultiPloy2<3>(-1.0, 1.0, 3, integrand, 10, "exampleC"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
template <int N> |
|
|
|
void power2BernsteinTensorProduct(const xarray<real, N>& phiPower, xarray<real, N>& phiBernsetin) |
|
|
|
{ |
|
|
|
for (auto i = phiPower.loop(); ~i; ++i) { |
|
|
|
// phi.l(i) = powerFactors.l(i);
|
|
|
|
real factorBase = phiPower.l(i); |
|
|
|
if (factorBase == 0) continue; |
|
|
|
auto traverseRange = phiPower.ext() - i(); |
|
|
|
std::vector<std::vector<real>> decompFactors(N, std::vector<real>(max(traverseRange), 0.)); |
|
|
|
for (int dim = 0; dim < N; ++dim) { |
|
|
|
// Sigma
|
|
|
|
size_t nDim = phiPower.ext()(dim) - 1; |
|
|
|
const real* binomNDim = Binomial::row(nDim); |
|
|
|
for (int j = i(dim); j <= nDim; ++j) { |
|
|
|
const real* binomJ = Binomial::row(j); |
|
|
|
decompFactors[dim][j - i(dim)] = binomJ[i(dim)] / binomNDim[i(dim)]; |
|
|
|
} |
|
|
|
} |
|
|
|
xarray<real, N> subgrid(nullptr, traverseRange); |
|
|
|
// algoim_spark_alloc(real, subgrid);
|
|
|
|
|
|
|
|
for (auto ii = subgrid.loop(); ~ii; ++ii) { |
|
|
|
real factor = factorBase; |
|
|
|
for (int dim = 0; dim < N; ++dim) { factor *= decompFactors[dim][ii(dim)]; } |
|
|
|
phiBernsetin.m(i() + ii()) += factor; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
template <int N> |
|
|
|
real powerEvaluation(const xarray<real, N>& phi, const uvector<real, N>& x) |
|
|
|
{ |
|
|
|
real res = 0; |
|
|
|
for (auto i = phi.loop(); ~i; ++i) { |
|
|
|
real item = phi.l(i); |
|
|
|
auto exps = i(); |
|
|
|
for (int dim = 0; dim < N; ++dim) { item *= pow(x(dim), exps(dim)); } |
|
|
|
res += item; |
|
|
|
} |
|
|
|
return res; |
|
|
|
} |
|
|
|
|
|
|
|
auto xarray2StdVector(const xarray<real, 3>& array) |
|
|
|
{ |
|
|
|
auto ext = array.ext(); |
|
|
|
std::vector<std::vector<std::vector<real>>> v(ext(0), std::vector<std::vector<real>>(ext(1), std::vector<real>(ext(2), 0))); |
|
|
|
for (int i = 0; i < ext(0); ++i) { |
|
|
|
for (int j = 0; j < ext(1); ++j) { |
|
|
|
for (int k = 0; k < ext(2); ++k) { v[i][j][k] = array.m(uvector<int, 3>(i, j, k)); } |
|
|
|
} |
|
|
|
} |
|
|
|
return v; |
|
|
|
} |
|
|
|
|
|
|
|
template <int N, typename F> |
|
|
|
void qConvBernstein(const xarray<real, N>& phi, |
|
|
|
real xmin, |
|
|
|
real xmax, |
|
|
|
const F& integrand, |
|
|
|
int q, |
|
|
|
const std::vector<std::array<real, 4>>& halfFaces) |
|
|
|
{ |
|
|
|
uvector<real, 3> testX(0., 0., 0.5); |
|
|
|
real testEvalBernstein = bernstein::evalBernsteinPoly(phi, testX); |
|
|
|
auto vec1 = xarray2StdVector(phi); |
|
|
|
std::cout << "eval bernstein without interpolation:" << testEvalBernstein << std::endl; |
|
|
|
|
|
|
|
// Build quadrature hierarchy
|
|
|
|
ImplicitPolyQuadrature<N> ipquad(phi); |
|
|
|
// ImplicitPolyQuadrature<N> ipquad(phi1);
|
|
|
|
|
|
|
|
// Functional to evaluate volume and surface integrals of given integrand
|
|
|
|
real volume; |
|
|
|
auto compute = [&](int q) { |
|
|
|
volume = 0.0; |
|
|
|
// compute volume integral over {phi < 0} using AutoMixed strategy
|
|
|
|
if (!halfFaces.empty()) { |
|
|
|
ipquad.integrate(AutoMixed, q, [&](const uvector<real, N>& x, real w) { |
|
|
|
if (bernstein::evalBernsteinPoly(phi, x) >= 0) return; |
|
|
|
bool in = true; |
|
|
|
for (int i = 0; i < halfFaces.size(); ++i) { |
|
|
|
uvector<real, 3> factors(halfFaces[i][0], halfFaces[i][1], halfFaces[i][2]); |
|
|
|
if (prod(factors * x) + halfFaces[i][3] < 0) { |
|
|
|
in = false; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
if (in) volume += w * integrand(xmin + x * (xmax - xmin)); |
|
|
|
}); |
|
|
|
|
|
|
|
} else |
|
|
|
ipquad.integrate(AutoMixed, q, [&](const uvector<real, N>& x, real w) { |
|
|
|
if (bernstein::evalBernsteinPoly(phi, x) < 0) volume += w * integrand(xmin + x * (xmax - xmin)); |
|
|
|
}); |
|
|
|
// scale appropriately
|
|
|
|
volume *= pow(xmax - xmin, N); |
|
|
|
}; |
|
|
|
compute(q); |
|
|
|
std::cout << "q volume: " << volume << std::endl; |
|
|
|
} |
|
|
|
|
|
|
|
template <typename T, int N> |
|
|
|
void xarrayInit(xarray<T, N>& arr) |
|
|
|
{ |
|
|
|
for (auto i = arr.loop(); ~i; ++i) { arr.l(i) = 0; } |
|
|
|
} |
|
|
|
|
|
|
|
void testSpherePowerDirectly() |
|
|
|
{ |
|
|
|
// a_x(x-c_x)^2 + a_y(y-c_y)^2 + a_z(x-c_z)^2 - r^2
|
|
|
|
uvector<real, 3> xmin = -1, xmax = 1; |
|
|
|
uvector<real, 3> range = xmax - xmin; |
|
|
|
assert(all(range != 0)); |
|
|
|
// uvector<real, 3> k = 1 / range;
|
|
|
|
// uvector<real, 3> bias = -k * xmin;
|
|
|
|
uvector<real, 3> k = range; |
|
|
|
uvector<real, 3> bias = xmin; |
|
|
|
real a[3] = {1, 1, 1}; |
|
|
|
real c[3] = {0, 0, 0}; |
|
|
|
real r = 1; |
|
|
|
uvector<int, 3> ext = 3; |
|
|
|
xarray<real, 3> phiPower(nullptr, ext), phiBernstein(nullptr, ext); |
|
|
|
algoim_spark_alloc(real, phiPower, phiBernstein); |
|
|
|
xarrayInit(phiPower); |
|
|
|
xarrayInit(phiBernstein); |
|
|
|
auto v = xarray2StdVector(phiPower); |
|
|
|
auto vv = xarray2StdVector(phiBernstein); |
|
|
|
for (int dim = 0; dim < 3; ++dim) { |
|
|
|
uvector<int, 3> idx = 0; |
|
|
|
phiPower.m(idx) += a[dim] * (bias(dim) - c[dim]) * (bias(dim) - c[dim]); |
|
|
|
idx(dim) = 1; |
|
|
|
phiPower.m(idx) = 2 * a[dim] * k(dim) * (bias(dim) - c[dim]); |
|
|
|
idx(dim) = 2; |
|
|
|
phiPower.m(idx) = a[dim] * k(dim) * k(dim); |
|
|
|
} |
|
|
|
phiPower.m(0) -= r * r; |
|
|
|
auto integrand = [](const uvector<real, 3>& x) { return 1.0; }; |
|
|
|
power2BernsteinTensorProduct(phiPower, phiBernstein); |
|
|
|
|
|
|
|
v = xarray2StdVector(phiPower); |
|
|
|
uvector<real, 3> testX(0., 0., 0.5); |
|
|
|
real testEval = powerEvaluation(phiPower, testX); |
|
|
|
std::cout << "eval power:" << testEval << std::endl; |
|
|
|
|
|
|
|
real testEvalBernstein = bernstein::evalBernsteinPoly(phiBernstein, testX); |
|
|
|
auto vec = xarray2StdVector(phiBernstein); |
|
|
|
std::cout << "eval bernstein:" << testEval << std::endl; |
|
|
|
qConvBernstein(phiBernstein, -1, 1, integrand, 10, {}); |
|
|
|
} |
|
|
|
|
|
|
|
void testCylinderPowerDirectly() |
|
|
|
{ |
|
|
|
// a_x(x-c_x)^2 + a_y(y-c_y)^2 + a_z(x-c_z)^2 - r^2
|
|
|
|
uvector<real, 3> xmin = -1, xmax = 1; |
|
|
|
uvector<real, 3> range = xmax - xmin; |
|
|
|
assert(all(range != 0)); |
|
|
|
// uvector<real, 3> k = 1 / range;
|
|
|
|
// uvector<real, 3> bias = -k * xmin;
|
|
|
|
uvector<real, 3> k = range; |
|
|
|
uvector<real, 3> bias = xmin; |
|
|
|
real c[3] = {0, 0, 0}; |
|
|
|
real r = 1, h = 1; |
|
|
|
uvector<int, 3> ext = 3; |
|
|
|
xarray<real, 3> phiPower(nullptr, ext), phiBernstein(nullptr, ext); |
|
|
|
algoim_spark_alloc(real, phiPower, phiBernstein); |
|
|
|
xarrayInit(phiPower); |
|
|
|
xarrayInit(phiBernstein); |
|
|
|
auto v = xarray2StdVector(phiPower); |
|
|
|
auto vv = xarray2StdVector(phiBernstein); |
|
|
|
real top = c[2] + h * 0.5, bottom = c[2] - h * 0.5; |
|
|
|
phiPower.m(uvector<int, 3>(2, 0, 2)) = -1; |
|
|
|
phiPower.m(uvector<int, 3>(0, 2, 2)) = -1; |
|
|
|
phiPower.m(uvector<int, 3>(0, 0, 2)) = r * r; |
|
|
|
phiPower.m(uvector<int, 3>(2, 0, 1)) = top + bottom; |
|
|
|
phiPower.m(uvector<int, 3>(0, 2, 1)) = top + bottom; |
|
|
|
phiPower.m(uvector<int, 3>(0, 0, 1)) = -(bottom + top) * r * r; |
|
|
|
phiPower.m(uvector<int, 3>(1, 0, 0)) = -bottom * top; |
|
|
|
phiPower.m(uvector<int, 3>(0, 1, 0)) = -bottom * top; |
|
|
|
phiPower.m(uvector<int, 3>(0, 0, 0)) = bottom * top * r * r; |
|
|
|
auto integrand = [](const uvector<real, 3>& x) { return 1.0; }; |
|
|
|
power2BernsteinTensorProduct(phiPower, phiBernstein); |
|
|
|
|
|
|
|
v = xarray2StdVector(phiPower); |
|
|
|
uvector<real, 3> testX(0., 0., 0.5); |
|
|
|
real testEval = powerEvaluation(phiPower, testX); |
|
|
|
std::cout << "eval power:" << testEval << std::endl; |
|
|
|
|
|
|
|
real testEvalBernstein = bernstein::evalBernsteinPoly(phiBernstein, testX); |
|
|
|
auto vec = xarray2StdVector(phiBernstein); |
|
|
|
std::cout << "eval bernstein:" << testEval << std::endl; |
|
|
|
qConvBernstein(phiBernstein, |
|
|
|
-1, |
|
|
|
1, |
|
|
|
integrand, |
|
|
|
50, |
|
|
|
{ |
|
|
|
{0, 0, 1, -bottom}, |
|
|
|
{0, 0, -1, top } |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
void testMultiScale() |
|
|
|
{ |
|
|
|
auto phi0 = [](const uvector<real, 3>& xx) { |
|
|
|
real x = xx(0) + 100000.; |
|
|
|
real y = xx(1); |
|
|
|
real z = xx(2) - 100000.; |
|
|
|
real r = 800000.; |
|
|
|
return x * x + y * y + z * z - r * r; |
|
|
|
}; |
|
|
|
auto phi1 = [](const uvector<real, 3>& xx) { |
|
|
|
// real x = xx(0);
|
|
|
|
// real y = xx(1);
|
|
|
|
// real z = xx(2);
|
|
|
|
// return x * x + y * y + z * z - 1;
|
|
|
|
real x = xx(0) + 100000.; |
|
|
|
real y = xx(1) - 800000.; |
|
|
|
real z = xx(2) - 100000.; |
|
|
|
real r = 1000.; |
|
|
|
return x * x + y * y + z * z - r * r; |
|
|
|
}; |
|
|
|
auto integrand = [](const uvector<real, 3>& x) { return 1.0; }; |
|
|
|
qConvMultiPloy<3>(phi0, phi1, -1000000., 1000000., 3, integrand, 20, Difference); |
|
|
|
} |
|
|
|
|
|
|
|
void testBitSet() |
|
|
@ -247,4 +473,7 @@ void testMain() |
|
|
|
{ |
|
|
|
testBooluarray(); |
|
|
|
testMultiPolys(); |
|
|
|
testMultiScale(); |
|
|
|
testSpherePowerDirectly(); |
|
|
|
// testCylinderPowerDirectly();
|
|
|
|
} |
|
|
|