diff --git a/CHANGELOG b/CHANGELOG index 81750882..f00453a2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,13 @@ Unlike previous GLVis releases, this version requires a C++17 compiler. - Added non-persistent mode of the server, when the server terminates after all visualization windows are closed. +- Added visualization of complex function data originating from + '(Par)ComplexGridFunction' class in MFEM. The complex values are represented + by their magnitude, phase, real part or imaginary part, which can be switched + by 'Q' key. Additionally, the complex function can be animated by an added + harmonic phase. To increase or decrease the phase frequency, use ';' or ':', + respectively. + - Implemented DOF numbering in 2D scalar mode, pressing the `n` or `N` key cycles through: None → Elements → Edges → Vertices → DOFs. Parallel numbering is now by default 'local' to each rank; 'global' vs. 'local' numbering can be toggled with diff --git a/README.md b/README.md index afb0016c..ec95b1ea 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,11 @@ Key commands - piece-wise constant refined (LOR) - L2 element dof collocation (interpolation) - L2 element projection (L2 projection) +- Q – Cycle between representations of the visualized *complex data*. The options are: + - magnitude + - phase + - real part + - imaginary part - q – Exit ## Advanced @@ -196,6 +201,7 @@ Key commands - `on` (default): recompute both the bounding box and the value range - `value`: recompute only the value range - `mesh`: recompute only the bounding box +- Alt + . – Start/stop harmonic complex phase animation (speed can be controlled with Alt + 0 / Alt + Enter) ## 2D scalar data diff --git a/lib/aux_vis.cpp b/lib/aux_vis.cpp index 204ad80d..60f77a0b 100644 --- a/lib/aux_vis.cpp +++ b/lib/aux_vis.cpp @@ -204,8 +204,8 @@ GLWindow* InitVisualization(const char name[], int x, int y, int w, int h, wnd->setOnKeyDown (SDLK_s, KeyS); wnd->setOnKeyDown ('S', KeyS); - wnd->setOnKeyDown (SDLK_q, KeyQPressed); - // wnd->setOnKeyDown (SDLK_Q, KeyQPressed); + wnd->setOnKeyDown (SDLK_q, KeyqPressed); + wnd->setOnKeyDown ('Q', KeyQPressed); wnd->setOnKeyDown (SDLK_LEFT, KeyLeftPressed); wnd->setOnKeyDown (SDLK_RIGHT, KeyRightPressed); @@ -609,12 +609,28 @@ void RemoveIdleFunc(void (*Func)(void)) thread_local double xang = 0., yang = 0.; +const double xang_step = 0.2; // angle in degrees thread_local gl3::GlMatrix srot; thread_local double sph_t, sph_u; thread_local GLint oldx, oldy, startx, starty; thread_local int constrained_spinning = 0; +thread_local double phase_rate = 0.; +thread_local int phase_anim = 0; +const double phase_step = 0.001; + +void CheckMainIdleFunc() +{ + if (locscene->spinning || phase_anim) + { + AddIdleFunc(MainLoop); + } + if (!locscene->spinning && !phase_anim) + { + RemoveIdleFunc(MainLoop); + } +} void MainLoop() { @@ -624,13 +640,19 @@ void MainLoop() if (!constrained_spinning) { locscene->Rotate(xang, yang); - SendExposeEvent(); } else { locscene->PreRotate(xang, 0.0, 0.0, 1.0); - SendExposeEvent(); } + } + if (phase_anim) + { + locscene->UpdateComplexPhase(phase_rate); + } + if (locscene->spinning || phase_anim) + { + SendExposeEvent(); std::this_thread::sleep_for(std::chrono::milliseconds{10}); // sleep for 0.01 seconds } if (locscene->movie) @@ -674,7 +696,7 @@ inline void ComputeSphereAngles(int &newx, int &newy, void LeftButtonDown(GLWindow::MouseEventInfo *event) { locscene -> spinning = 0; - RemoveIdleFunc(MainLoop); + CheckMainIdleFunc(); oldx = event->mouse_x; oldy = event->mouse_y; @@ -1334,12 +1356,17 @@ void KeyCtrlP() #endif } -void KeyQPressed() +void KeyqPressed() { wnd->signalQuit(); visualize = 0; } +void KeyQPressed() +{ + Window::SwitchSolution(); +} + void ToggleThreads() { static const char *state[] = { "running", "stopped" }; @@ -1350,7 +1377,7 @@ void ToggleThreads() } } -void ThreadsPauseFunc(GLenum state) +void ThreadsPauseFunc(SDL_Keymod state) { if (state & KMOD_CTRL) { @@ -1386,57 +1413,96 @@ void CheckSpin() { xang = 0.; } - if (xang != 0. || yang != 0.) - { - locscene->spinning = 1; - AddIdleFunc(MainLoop); - } - else - { - locscene->spinning = 0; - RemoveIdleFunc(MainLoop); - } + locscene->spinning = (xang != 0. || yang != 0.); + CheckMainIdleFunc(); cout << "Spin angle: " << xang << " degrees / frame" << endl; } -const double xang_step = 0.2; // angle in degrees +void CheckPhaseAnim() +{ + if (fabs(phase_rate) < phase_step / 2.) + { + phase_rate = 0.; + } + phase_anim = (phase_rate != 0.); + CheckMainIdleFunc(); + cout << "Phase rate: " << phase_rate << " period / frame" << endl; +} -void Key0Pressed() +void Key0Pressed(SDL_Keymod mod) { - if (!locscene -> spinning) + if (mod & KMOD_ALT) + { + if (!phase_anim) + { + phase_rate = 0.; + } + phase_rate -= phase_step; + CheckPhaseAnim(); + } + else { - xang = 0; + if (!locscene -> spinning) + { + xang = 0; + } + xang -= xang_step; + CheckSpin(); } - xang -= xang_step; - CheckSpin(); } -void KeyDeletePressed() +void KeyDeletePressed(SDL_Keymod mod) { - if (locscene -> spinning) + if (mod & KMOD_ALT) { - xang = yang = 0.; - locscene -> spinning = 0; - RemoveIdleFunc(MainLoop); - constrained_spinning = 1; + if (phase_anim) + { + phase_rate = 0.; + phase_anim = 0; + } + else + { + phase_rate = phase_step; + phase_anim = 1; + } } else { - xang = xang_step; - locscene -> spinning = 1; - AddIdleFunc(MainLoop); + if (locscene -> spinning) + { + xang = yang = 0.; + locscene -> spinning = 0; + } + else + { + xang = xang_step; + locscene -> spinning = 1; + } constrained_spinning = 1; } + CheckMainIdleFunc(); } -void KeyEnterPressed() +void KeyEnterPressed(SDL_Keymod mod) { - if (!locscene -> spinning) + if (mod & KMOD_ALT) { - xang = 0; + if (!phase_anim) + { + phase_rate = 0.; + } + phase_rate += phase_step; + CheckPhaseAnim(); + } + else + { + if (!locscene -> spinning) + { + xang = 0; + } + xang += xang_step; + CheckSpin(); } - xang += xang_step; - CheckSpin(); } void Key7Pressed() @@ -1515,7 +1581,7 @@ void ShiftView(double dx, double dy) locscene->ViewCenterY += dy/scale; } -void KeyLeftPressed(GLenum state) +void KeyLeftPressed(SDL_Keymod state) { if (state & KMOD_CTRL) { @@ -1528,7 +1594,7 @@ void KeyLeftPressed(GLenum state) SendExposeEvent(); } -void KeyRightPressed(GLenum state) +void KeyRightPressed(SDL_Keymod state) { if (state & KMOD_CTRL) { @@ -1541,7 +1607,7 @@ void KeyRightPressed(GLenum state) SendExposeEvent(); } -void KeyUpPressed(GLenum state) +void KeyUpPressed(SDL_Keymod state) { if (state & KMOD_CTRL) { @@ -1554,7 +1620,7 @@ void KeyUpPressed(GLenum state) SendExposeEvent(); } -void KeyDownPressed(GLenum state) +void KeyDownPressed(SDL_Keymod state) { if (state & KMOD_CTRL) { diff --git a/lib/aux_vis.hpp b/lib/aux_vis.hpp index f64e1734..ce7ee31e 100644 --- a/lib/aux_vis.hpp +++ b/lib/aux_vis.hpp @@ -52,6 +52,7 @@ void SetLegacyGLOnly(bool status); void AddIdleFunc(void (*Func)(void)); void RemoveIdleFunc(void (*Func)(void)); +void CheckMainIdleFunc(); void LeftButtonDown (GLWindow::MouseEventInfo *event); void LeftButtonLoc (GLWindow::MouseEventInfo *event); @@ -67,9 +68,10 @@ void TouchPinch(SDL_MultiGestureEvent & e); void KeyCtrlP(); void KeyS(); +void KeyqPressed(); void KeyQPressed(); void ToggleThreads(); -void ThreadsPauseFunc(GLenum); +void ThreadsPauseFunc(SDL_Keymod); void ThreadsStop(); void ThreadsRun(); @@ -83,14 +85,14 @@ void Key7Pressed(); void Key8Pressed(); void Key9Pressed(); -void Key0Pressed(); -void KeyDeletePressed(); -void KeyEnterPressed(); +void Key0Pressed(SDL_Keymod); +void KeyDeletePressed(SDL_Keymod); +void KeyEnterPressed(SDL_Keymod); -void KeyLeftPressed(GLenum); -void KeyRightPressed(GLenum); -void KeyUpPressed(GLenum); -void KeyDownPressed(GLenum); +void KeyLeftPressed(SDL_Keymod); +void KeyRightPressed(SDL_Keymod); +void KeyUpPressed(SDL_Keymod); +void KeyDownPressed(SDL_Keymod); void KeyJPressed(); void KeyMinusPressed(); void KeyPlusPressed(); diff --git a/lib/data_state.cpp b/lib/data_state.cpp index 6fbf1af8..36d2fedd 100644 --- a/lib/data_state.cpp +++ b/lib/data_state.cpp @@ -41,12 +41,14 @@ DataState &DataState::operator=(DataState &&ss) internal = std::move(ss.internal); type = ss.type; + cmplx_sol = ss.cmplx_sol; quad_sol = ss.quad_sol; keys = std::move(ss.keys); fix_elem_orient = ss.fix_elem_orient; save_coloring = ss.save_coloring; keep_attr = ss.keep_attr; + cmplx_phase = ss.cmplx_phase; return *this; } @@ -62,6 +64,7 @@ void DataState::SetMesh(std::unique_ptr &&pmesh) internal.mesh = std::move(pmesh); internal.mesh_quad.reset(); if (grid_f && grid_f->FESpace()->GetMesh() != mesh.get()) { SetGridFunction(NULL); } + if (cgrid_f && cgrid_f->FESpace()->GetMesh() != mesh.get()) { SetCmplxGridFunction(NULL); } if (quad_f && quad_f->GetSpace()->GetMesh() != mesh.get()) { SetQuadFunction(NULL); } } @@ -146,6 +149,8 @@ void DataState::SetGridFunction(std::unique_ptr &&pgf, int component) { internal.grid_f = std::move(pgf); + internal.cgrid_f.reset(); + cmplx_sol = ComplexSolution::NONE; internal.quad_f.reset(); quad_sol = QuadSolution::NONE; SetGridFunctionSolution(component); @@ -156,7 +161,75 @@ void DataState::SetGridFunction(std::vector &gf_array, { SetGridFunction(new GridFunction(mesh.get(), gf_array.data(), num_pieces), component); - if (!keep_attr) { ComputeDofsOffsets(gf_array); } + if (!keep_attr) + { + std::vector fespaces(gf_array.size()); + for (size_t i = 0; i < gf_array.size(); i++) + { + fespaces[i] = gf_array[i]->FESpace(); + } + ComputeDofsOffsets(fespaces); + } +} + +void DataState::SetCmplxGridFunction(ComplexGridFunction *gf, + int component) +{ + if (cgrid_f.get() != gf) + { + internal.grid_f.reset(); + cmplx_sol = ComplexSolution::NONE; + } + internal.cgrid_f.reset(gf); + internal.quad_f.reset(); + quad_sol = QuadSolution::NONE; + SetComplexFunctionSolution(component); +} + +void DataState::SetCmplxGridFunction( + std::unique_ptr &&pgf, int component) +{ + if (cgrid_f.get() != pgf.get()) + { + internal.grid_f.reset(); + cmplx_sol = ComplexSolution::NONE; + } + internal.cgrid_f = std::move(pgf); + internal.quad_f.reset(); + quad_sol = QuadSolution::NONE; + SetComplexFunctionSolution(component); +} + +void DataState::SetCmplxGridFunction( + const std::vector &cgf_array, int component) +{ + const int np = cgf_array.size(); + std::vector r_array(np), i_array(np); + for (int p = 0; p < np; p++) + { + r_array[p] = &(cgf_array[p]->real()); + i_array[p] = &(cgf_array[p]->imag()); + } + GridFunction *rgf = new GridFunction(mesh.get(), r_array.data(), np); + GridFunction *igf = new GridFunction(mesh.get(), i_array.data(), np); + ComplexGridFunction *cgf = new ComplexGridFunction(rgf->FESpace()); + // transfer ownership of the FES + cgf->MakeOwner(rgf->OwnFEC()); + rgf->MakeOwner(NULL); + cgf->real() = *rgf; + cgf->imag() = *igf; + delete rgf; + delete igf; + SetCmplxGridFunction(cgf, component); + if (!keep_attr) + { + std::vector fespaces(cgf_array.size()); + for (size_t i = 0; i < cgf_array.size(); i++) + { + fespaces[i] = cgf_array[i]->FESpace(); + } + ComputeDofsOffsets(fespaces); + } } void DataState::SetQuadFunction(QuadratureFunction *qf, int component) @@ -167,6 +240,7 @@ void DataState::SetQuadFunction(QuadratureFunction *qf, int component) quad_sol = QuadSolution::NONE; } internal.quad_f.reset(qf); + internal.cgrid_f.reset(); SetQuadFunctionSolution(component); } @@ -179,6 +253,7 @@ void DataState::SetQuadFunction(std::unique_ptr &&pqf, quad_sol = QuadSolution::NONE; } internal.quad_f = std::move(pqf); + internal.cgrid_f.reset(); SetQuadFunctionSolution(component); } @@ -275,7 +350,29 @@ void DataState::Extrude1DMeshAndSolution() Mesh *mesh2d = Extrude1D(mesh.get(), 1, 0.1*(xmax - xmin)); - if (grid_f) + if (cgrid_f) + { + if (cgrid_f->VectorDim() > 1) + { + ProjectVectorFEGridFunction(); + } + + FiniteElementCollection *fec2d = new L2_FECollection( + cgrid_f->FESpace()->FEColl()->GetOrder(), 2); + FiniteElementSpace *fes2d = new FiniteElementSpace(mesh2d, fec2d, + cgrid_f->FESpace()->GetVDim()); + ComplexGridFunction *cgrid_f_2d = new ComplexGridFunction(fes2d); + cgrid_f_2d->MakeOwner(fec2d); + VectorGridFunctionCoefficient vcsol_r(&cgrid_f->real()); + VectorGridFunctionCoefficient vcsol_i(&cgrid_f->imag()); + ::VectorExtrudeCoefficient vc2d_r(mesh.get(), vcsol_r, 1); + ::VectorExtrudeCoefficient vc2d_i(mesh.get(), vcsol_i, 1); + cgrid_f_2d->real().ProjectCoefficient(vc2d_r); + cgrid_f_2d->imag().ProjectCoefficient(vc2d_i); + + internal.cgrid_f.reset(cgrid_f_2d); + } + else if (grid_f) { if (grid_f->VectorDim() > 1) { @@ -319,7 +416,9 @@ void DataState::Extrude1DMeshAndSolution() void DataState::Extrude2D3VMeshAndSolution() { - if (mesh->SpaceDimension() == 3 || !grid_f || grid_f->VectorDim() < 3) { return; } + if (mesh->SpaceDimension() == 3) { return; } + if (cgrid_f && cgrid_f->VectorDim() < 3) { return; } + if (!grid_f || grid_f->VectorDim() < 3) { return; } // not all vector elements can be embedded in 3D, so conversion to L2 elements // is performed already here @@ -339,14 +438,25 @@ void DataState::Extrude2D3VMeshAndSolution() mesh3d->SetCurvature(1, false, 3); } - FiniteElementSpace *fes2d = grid_f->FESpace(); + FiniteElementSpace *fes2d = (cgrid_f)?(cgrid_f->FESpace()):(grid_f->FESpace()); FiniteElementSpace *fes3d = new FiniteElementSpace(*fes2d, mesh3d); - GridFunction *gf3d = new GridFunction(fes3d); - *gf3d = *grid_f; - grid_f->MakeOwner(NULL); - gf3d->MakeOwner(const_cast(fes2d->FEColl())); + if (cgrid_f) + { + ComplexGridFunction *cgf3d = new ComplexGridFunction(fes3d); + (Vector&)*cgf3d = (const Vector&)*cgrid_f; + cgrid_f->MakeOwner(NULL); + cgf3d->MakeOwner(const_cast(fes2d->FEColl())); + SetCmplxGridFunction(cgf3d); + } + else + { + GridFunction *gf3d = new GridFunction(fes3d); + *gf3d = *grid_f; + grid_f->MakeOwner(NULL); + gf3d->MakeOwner(const_cast(fes2d->FEColl())); + SetGridFunction(gf3d); + } - SetGridFunction(gf3d); delete fes2d; SetMesh(mesh3d); } @@ -434,6 +544,48 @@ void DataState::SetGridFunctionSolution(int gf_component) type = (grid_f->VectorDim() == 1) ? FieldType::SCALAR : FieldType::VECTOR; } +void DataState::SetComplexFunctionSolution(int gf_component) +{ + if (!cgrid_f) + { + type = (mesh)?(FieldType::MESH):(FieldType::UNKNOWN); + return; + } + + if (gf_component != -1) + { + if (gf_component < 0 || gf_component >= cgrid_f->FESpace()->GetVDim()) + { + cerr << "Invalid component " << gf_component << '.' << endl; + exit(1); + } + FiniteElementSpace *ofes = cgrid_f->FESpace(); + FiniteElementCollection *fec = + FiniteElementCollection::New(ofes->FEColl()->Name()); + FiniteElementSpace *fes = new FiniteElementSpace(mesh.get(), fec); + ComplexGridFunction *new_gf = new ComplexGridFunction(fes); + new_gf->MakeOwner(fec); + for (int i = 0; i < new_gf->real().Size(); i++) + { + (new_gf->real())(i) = (cgrid_f->real())(ofes->DofToVDof(i, gf_component)); + (new_gf->imag())(i) = (cgrid_f->imag())(ofes->DofToVDof(i, gf_component)); + } + SetCmplxGridFunction(new_gf); + return; + } + + if (cgrid_f->VectorDim() == 1) + { + type = FieldType::SCALAR; + } + else + { + type = FieldType::VECTOR; + } + + SetComplexSolution(); +} + void DataState::SetQuadFunctionSolution(int qf_component) { if (!quad_f) @@ -474,6 +626,200 @@ void DataState::SetQuadFunctionSolution(int qf_component) SetQuadSolution(); } +void DataState::SetComplexSolution(ComplexSolution cmplx_type, bool print) +{ + double (*cmplx_2_scalar)(double, double); + const char *str_cmplx_2_scalar; + bool linear; + + switch (cmplx_type) + { + case ComplexSolution::Magnitude: + cmplx_2_scalar = [](double r, double i) { return hypot(r,i); }; + str_cmplx_2_scalar = "magnitude"; + linear = false; + break; + case ComplexSolution::Phase: + cmplx_2_scalar = [](double r, double i) { return atan2(r,i); }; + str_cmplx_2_scalar = "phase"; + linear = false; + break; + case ComplexSolution::Real: + cmplx_2_scalar = [](double r, double i) { return r; }; + str_cmplx_2_scalar = "real part"; + linear = true; + break; + case ComplexSolution::Imag: + cmplx_2_scalar = [](double r, double i) { return i; }; + str_cmplx_2_scalar = "imaginary part"; + linear = true; + break; + default: + cout << "Unknown complex data representation" << endl; + return; + } + + if (print) + { + cout << "Representing complex function by: " << str_cmplx_2_scalar; + if (cmplx_type != ComplexSolution::Magnitude) { cout << " (+ animated harmonic phase)"; } + cout << endl; + } + + FiniteElementSpace *fes = cgrid_f->FESpace(); + const int dim = mesh->Dimension(); + const int vdim = fes->GetVectorDim(); + + const bool scal_fes = (fes->FEColl()->GetMapType(dim) == FiniteElement::VALUE + && fes->FEColl()->GetRangeType(dim) == FiniteElement::SCALAR); + + const double cos_ph = cos(2. * M_PI * cmplx_phase); + const double sin_ph = sin(2. * M_PI * cmplx_phase); + auto rot_ph = [cos_ph, sin_ph](double &r, double &i) + { + double rph = +r * cos_ph - i * sin_ph; + double iph = +r * sin_ph + i * cos_ph; + r = rph, i = iph; + }; + + GridFunction *gf{}; + if (scal_fes || linear) + { + gf = new GridFunction(fes); + } + else + { + FiniteElementCollection *l2_fec = new L2_FECollection(fes->FEColl()->GetOrder(), + dim); + FiniteElementSpace *l2_fes = new FiniteElementSpace(mesh.get(), l2_fec, vdim); + gf = new GridFunction(l2_fes); + gf->MakeOwner(l2_fec); + } + + if (scal_fes) + { + for (int i = 0; i < fes->GetVSize(); i++) + { + double rval = cgrid_f->real()(i); + double ival = cgrid_f->imag()(i); + rot_ph(rval, ival); + (*gf)(i) = cmplx_2_scalar(rval, ival); + } + } + else + { + Array vdofs; + Vector r_data, i_data, z_data; + DenseMatrix r_vals, i_vals, z_vals; + + if (linear) + { + // pointwise projection at DOFs + DenseMatrix vshape; + Vector shape; + + auto kernel = [&](double rdof, double idof, double w) + { + double rval = rdof * w; + double ival = idof * w; + rot_ph(rval, ival); + const double zval = cmplx_2_scalar(rval, ival); + return (w != 0.)?(zval / w):(0.); + }; + + for (int z = 0; z < mesh->GetNE(); z++) + { + fes->GetElementVDofs(z, vdofs); + cgrid_f->real().GetSubVector(vdofs, r_data); + cgrid_f->imag().GetSubVector(vdofs, i_data); + z_data.SetSize(vdofs.Size()); + + ElementTransformation *Tr = fes->GetElementTransformation(z); + const FiniteElement *fe = fes->GetFE(z); + const IntegrationRule &ir = fe->GetNodes(); + const int nnp = ir.GetNPoints(); + + if (fe->GetRangeType() == FiniteElement::SCALAR) + { + shape.SetSize(nnp); + r_vals.Reset(r_data.GetData(), nnp, vdim); + i_vals.Reset(i_data.GetData(), nnp, vdim); + z_vals.Reset(z_data.GetData(), nnp, vdim); + + for (int n = 0; n < nnp; n++) + { + const IntegrationPoint &ip = ir.IntPoint(n); + Tr->SetIntPoint(&ip); + fe->CalcPhysShape(*Tr, shape); + const double w = shape(n); + for (int d = 0; d < vdim; d++) + { + z_vals(n,d) = kernel(r_vals(n,d), i_vals(n,d), w); + } + } + } + else + { + vshape.SetSize(nnp, vdim); + + for (int n = 0; n < nnp; n++) + { + const IntegrationPoint &ip = ir.IntPoint(n); + Tr->SetIntPoint(&ip); + fe->CalcPhysVShape(*Tr, vshape); + Vector vec(vdim); + vshape.GetRow(n, vec); + const double w = vec.Norml2(); + z_data(n) = kernel(r_data(n), i_data(n), w); + } + } + + gf->SetSubVector(vdofs, z_data); + } + } + else + { + // pointwise projection to L2 + + const FiniteElementSpace *l2_fes = gf->FESpace(); + Vector r_vec, i_vec; + + for (int z = 0; z < mesh->GetNE(); z++) + { + l2_fes->GetElementVDofs(z, vdofs); + + ElementTransformation *Tr = fes->GetElementTransformation(z); + const FiniteElement *fe = l2_fes->GetFE(z); + const IntegrationRule &ir = fe->GetNodes(); + const int nnp = ir.GetNPoints(); + cgrid_f->real().GetVectorValues(*Tr, ir, r_vals); + cgrid_f->imag().GetVectorValues(*Tr, ir, i_vals); + z_data.SetSize(nnp * vdim); + z_vals.Reset(z_data.GetData(), nnp, vdim); + + for (int n = 0; n < nnp; n++) + { + r_vals.GetColumnReference(n, r_vec); + i_vals.GetColumnReference(n, i_vec); + + for (int d = 0; d < vdim; d++) + { + double rval = r_vec(d); + double ival = i_vec(d); + rot_ph(rval, ival); + z_vals(n,d) = cmplx_2_scalar(rval, ival); + } + } + + gf->SetSubVector(vdofs, z_data); + } + } + } + internal.grid_f.reset(gf); + + cmplx_sol = cmplx_type; +} + void DataState::SetQuadSolution(QuadSolution quad_type) { // assume identical order @@ -626,13 +972,313 @@ DataState::ProjectVectorFEGridFunction(std::unique_ptr gf) return gf; } -void DataState::ComputeDofsOffsets(std::vector &gf_array) +std::unique_ptr +DataState::ProjectVectorFEGridFunction(std::unique_ptr gf) +{ + if ((gf->VectorDim() == 3) && (gf->FESpace()->GetVDim() == 1)) + { + int p = gf->FESpace()->GetOrder(0); + cout << "Switching to order " << p + << " discontinuous complex vector grid function..." << endl; + int dim = gf->FESpace()->GetMesh()->Dimension(); + FiniteElementCollection *d_fec = + (p == 1 && dim == 3) ? + (FiniteElementCollection*)new LinearDiscont3DFECollection : + (FiniteElementCollection*)new L2_FECollection(p, dim, 1); + FiniteElementSpace *d_fespace = + new FiniteElementSpace(gf->FESpace()->GetMesh(), d_fec, 3); + ComplexGridFunction *d_gf = new ComplexGridFunction(d_fespace); + d_gf->MakeOwner(d_fec); + gf->real().ProjectVectorFieldOn(d_gf->real()); + gf->imag().ProjectVectorFieldOn(d_gf->imag()); + gf.reset(d_gf); + } + return gf; +} + +void DataState::ProjectVectorFEGridFunction() +{ + if (cgrid_f) + { + internal.cgrid_f = ProjectVectorFEGridFunction(std::move(internal.cgrid_f)); + SetComplexSolution(GetComplexSolution(), false); + } + else + { + internal.grid_f = ProjectVectorFEGridFunction(std::move(internal.grid_f)); + } +} + +void DataState::FindComplexValueRange(double &minv, double &maxv, + std::function scale) const +{ + if (!cgrid_f) + { + std::cerr << "Not a complex function" << std::endl; + return; + } + + if (cmplx_sol == ComplexSolution::Magnitude) + { + // pass to the default routine + minv = maxv = 0.; + return; + } + + if (cmplx_sol == ComplexSolution::Phase) + { + minv = -M_PI; + maxv = +M_PI; + return; + } + + double cmplx_mag_max = 0.; + + const FiniteElementSpace *fes = cgrid_f->FESpace(); + const Mesh *msh = fes->GetMesh(); + const int dim = msh->Dimension(); + MFEM_VERIFY(fes->GetVectorDim() == 1, "Not a scalar function!"); + + if (fes->FEColl()->GetMapType(dim) == FiniteElement::VALUE) + { + for (int i = 0; i < fes->GetNDofs(); i++) + { + double rval = cgrid_f->real()(i); + double ival = cgrid_f->imag()(i); + double mag = hypot(rval, ival); + if (scale) { mag = scale(mag); } + cmplx_mag_max = max(cmplx_mag_max, mag); + } + } + else + { + Array vdofs; + ElementTransformation *Tr; + Vector r_data, i_data; + Vector shape; + for (int z = 0; z < msh->GetNE(); z++) + { + fes->GetElementVDofs(z, vdofs); + cgrid_f->real().GetSubVector(vdofs, r_data); + cgrid_f->imag().GetSubVector(vdofs, i_data); + + Tr = fes->GetElementTransformation(z); + const FiniteElement *fe = fes->GetFE(z); + const IntegrationRule &ir = fe->GetNodes(); + const int nnp = ir.GetNPoints(); + shape.SetSize(nnp); + + for (int n = 0; n < nnp; n++) + { + const IntegrationPoint &ip = ir.IntPoint(n); + Tr->SetIntPoint(&ip); + fe->CalcPhysShape(*Tr, shape); + double w = shape(n); + double rval = r_data(n) * w; + double ival = i_data(n) * w; + double mag = hypot(rval, ival); + if (scale) { mag = scale(mag); } + cmplx_mag_max = max(cmplx_mag_max, mag); + } + } + } + + switch (cmplx_sol) + { + case ComplexSolution::Real: + case ComplexSolution::Imag: + minv = -cmplx_mag_max; + maxv = +cmplx_mag_max; + break; + default: + std::cerr << "Unkown complex representation" << std::endl; + break; + } +} + +void DataState::FindComplexValueRange(double &minv, double &maxv, + std::function vec2scalar, + std::function scale) const +{ + if (!cgrid_f) + { + std::cerr << "Not a complex function" << std::endl; + return; + } + + if (cmplx_sol == ComplexSolution::Magnitude) + { + // pass to the default routine + minv = maxv = 0.; + return; + } + + const int dim = mesh->Dimension(); + const int vdim = cgrid_f->VectorDim(); + + // detect common symmetries + + enum class Symmetry { Unknown, Norm, Phase, Linear }; + + Symmetry sym = Symmetry::Unknown; + + { + double a, b; + Vector va(vdim), vb(vdim); + va = -1.; + vb = +1.; + a = vec2scalar(va); + b = vec2scalar(vb); + if (a != 0. && b != 0.) + { + if (a == b) { sym = Symmetry::Norm; } + else if (a == -b) { sym = Symmetry::Linear; } + else { sym = Symmetry::Phase; } // most likely + } + } + + if (sym == Symmetry::Unknown) + { + // pass to the default routine + minv = maxv = 0.; + return; + } + + // output phase + if (sym == Symmetry::Phase) + { + minv = -M_PI; + maxv = +M_PI; + return; + } + + // complex phase + if (cmplx_sol == ComplexSolution::Phase) + { + Vector va(vdim), vb(vdim); + va = -M_PI; + vb = +M_PI; + minv = (sym == Symmetry::Norm)?(0.):(vec2scalar(va)); + maxv = vec2scalar(vb); + return; + } + + // real or imag components + + function kernel; + if (sym == Symmetry::Norm) + { + kernel = [&](const Vector &rd, const Vector &id) + { + Vector md(vdim); + for (int d = 0; d < vdim; d++) { md(d) = hypot(rd(d), id(d)); } + double scal = vec2scalar(md); + if (scale) { scal = scale(scal); } + minv = min(minv, scal); + maxv = max(maxv, scal); + }; + } + else + { + kernel = [&](const Vector &rd, const Vector &id) + { + Vector md(vdim); + for (int d = 0; d < vdim; d++) { md(d) = hypot(rd(d), id(d)); } + double scal = vec2scalar(md); + if (scale) { scal = scale(scal); } + maxv = max(maxv, scal); + }; + } + + minv = INFINITY; + maxv = 0.; + + const FiniteElementSpace *fes = cgrid_f->FESpace(); + + if (fes->FEColl()->GetMapType(dim) == FiniteElement::VALUE + && fes->FEColl()->GetRangeType(dim) == FiniteElement::SCALAR) + { + const int ndofs = fes->GetNDofs(); + Array vdofs; + Vector rd, id; + for (int i = 0; i < ndofs; i++) + { + vdofs.SetSize(1); + vdofs[0] = i; + fes->DofsToVDofs(vdofs); + cgrid_f->real().GetSubVector(vdofs, rd); + cgrid_f->imag().GetSubVector(vdofs, id); + kernel(rd, id); + } + } + else + { + Array vdofs; + ElementTransformation *Tr; + Vector r_data, i_data; + Vector r_vec(vdim), i_vec(vdim); + DenseMatrix vshape; + Vector shape; + + for (int z = 0; z < mesh->GetNE(); z++) + { + fes->GetElementVDofs(z, vdofs); + cgrid_f->real().GetSubVector(vdofs, r_data); + cgrid_f->imag().GetSubVector(vdofs, i_data); + + Tr = fes->GetElementTransformation(z); + const FiniteElement *fe = fes->GetFE(z); + const IntegrationRule &ir = fe->GetNodes(); + const int nnp = ir.GetNPoints(); + + if (fe->GetRangeType() == FiniteElement::SCALAR) + { + shape.SetSize(nnp); + + for (int n = 0; n < nnp; n++) + { + const IntegrationPoint &ip = ir.IntPoint(n); + Tr->SetIntPoint(&ip); + fe->CalcPhysShape(*Tr, shape); + double w = shape(n); + for (int d = 0; d < vdim; d++) + { + r_vec(d) = r_data(n+d*nnp) * w; + i_vec(d) = i_data(n+d*nnp) * w; + } + kernel(r_vec, i_vec); + } + } + else + { + vshape.SetSize(nnp, vdim); + for (int n = 0; n < nnp; n++) + { + const IntegrationPoint &ip = ir.IntPoint(n); + Tr->SetIntPoint(&ip); + fe->CalcPhysVShape(*Tr, vshape); + for (int d = 0; d < vdim; d++) + { + r_vec(d) = r_data(n) * vshape(n,d); + i_vec(d) = i_data(n) * vshape(n,d); + } + kernel(r_vec, i_vec); + } + } + } + } + + if (sym != Symmetry::Norm) { minv = -maxv; } +} + +void DataState::ComputeDofsOffsets(std::vector + &fespaces) { - const int nprocs = static_cast(gf_array.size()); - MFEM_VERIFY(!gf_array.empty(), "No grid functions provided for offsets"); + const int nprocs = static_cast(fespaces.size()); + MFEM_VERIFY(!fespaces.empty(), "No grid functions provided for offsets"); // only 2D meshes are supported for dofs offsets computation - if (gf_array[0]->FESpace()->GetMesh()->Dimension() != 2) { return; } + if (fespaces[0]->GetMesh()->Dimension() != 2) { return; } internal.offsets = std::make_unique(nprocs); @@ -640,8 +1286,7 @@ void DataState::ComputeDofsOffsets(std::vector &gf_array) Array dofs, vertices; for (int rank = 0, g_e = 0; rank < nprocs; rank++) { - const GridFunction *gf = gf_array[rank]; - const FiniteElementSpace *l_fes = gf->FESpace(); + const FiniteElementSpace *l_fes = fespaces[rank]; Mesh *l_mesh = l_fes->GetMesh(); // store the dofs numbers as they are fespace dependent auto &offset = (*offsets)[rank]; diff --git a/lib/data_state.hpp b/lib/data_state.hpp index a5d192c8..47a0d5bc 100644 --- a/lib/data_state.hpp +++ b/lib/data_state.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -48,6 +49,19 @@ struct DataState MAX }; + enum class ComplexSolution + { + NONE = -1, + MIN = -1, + //---------- + Magnitude, + Phase, + Real, + Imag, + //---------- + MAX + }; + // Class used for storing offsets and map of DOFs for each rank class Offset { @@ -76,19 +90,35 @@ struct DataState std::unique_ptr mesh; std::unique_ptr mesh_quad; std::unique_ptr grid_f; + std::unique_ptr cgrid_f; std::unique_ptr quad_f; std::unique_ptr data_coll; std::unique_ptr offsets; } internal; FieldType type {FieldType::UNKNOWN}; + ComplexSolution cmplx_sol {ComplexSolution::NONE}; QuadSolution quad_sol {QuadSolution::NONE}; void SetGridFunctionSolution(int component = -1); + void SetComplexFunctionSolution(int component = -1); void SetQuadFunctionSolution(int component = -1); + static std::unique_ptr + ProjectVectorFEGridFunction(std::unique_ptr gf); + + static std::unique_ptr + ProjectVectorFEGridFunction(std::unique_ptr gf); + + void FindComplexValueRange(double &minv, double &maxv, + std::function = {}) const; + + void FindComplexValueRange(double &minv, double &maxv, + std::function, + std::function = {}) const; + /// Compute the dofs offsets from the grid function vector - void ComputeDofsOffsets(std::vector &gf_array); + void ComputeDofsOffsets(std::vector &fespaces); public: const std::unique_ptr &sol{internal.sol}; @@ -99,6 +129,7 @@ struct DataState const std::unique_ptr &mesh{internal.mesh}; const std::unique_ptr &mesh_quad{internal.mesh_quad}; const std::unique_ptr &grid_f{internal.grid_f}; + const std::unique_ptr &cgrid_f{internal.cgrid_f}; const std::unique_ptr &quad_f{internal.quad_f}; const std::unique_ptr &data_coll{internal.data_coll}; const std::unique_ptr &offsets{internal.offsets}; @@ -107,6 +138,7 @@ struct DataState bool fix_elem_orient{false}; bool save_coloring{false}; bool keep_attr{false}; + double cmplx_phase{0.}; DataState() = default; DataState(DataState &&ss) { *this = std::move(ss); } @@ -153,6 +185,23 @@ struct DataState void SetGridFunction(std::vector &gf_array, int num_pieces, int component = -1); + /// Set a complex grid function (plain pointer version) + /** Note that ownership is passed from the caller. + @see SetCmplxGridFunction(std::unique_ptr &&, int ) */ + void SetCmplxGridFunction(mfem::ComplexGridFunction *gf, int component = -1); + + /// Set a complex grid function (unique pointer version) + /** Sets the complex grid function or its component (-1 means all + components). */ + void SetCmplxGridFunction(std::unique_ptr &&pgf, + int component = -1); + + /// Set a complex grid function from pieces + /** Serializes the pieces of a complex grid function and sets it or its + component (-1 means all components) */ + void SetCmplxGridFunction(const std::vector + &cgf_array, int component = -1); + /// Set a quadrature function (plain pointer version) /** Note that ownership is passed from the caller. @see SetQuadFunction(std::unique_ptr &&, int ) */ @@ -192,6 +241,13 @@ struct DataState /// Set a (checkerboard) solution when only the mesh is given void SetMeshSolution(); + /// Set the complex function representation producing a proxy grid function + void SetComplexSolution(ComplexSolution type = ComplexSolution::Magnitude, + bool print = true); + + /// Get the current representation of complex solution + inline ComplexSolution GetComplexSolution() const { return cmplx_sol; } + /// Set the quadrature function representation producing a proxy grid function void SetQuadSolution(QuadSolution type = QuadSolution::LOR_ClosedGL); @@ -204,11 +260,22 @@ struct DataState // Replace a given VectorFiniteElement-based grid function (e.g. from a Nedelec // or Raviart-Thomas space) with a discontinuous piece-wise polynomial Cartesian // product vector grid function of the same order. - static std::unique_ptr - ProjectVectorFEGridFunction(std::unique_ptr gf); - - void ProjectVectorFEGridFunction() - { internal.grid_f = ProjectVectorFEGridFunction(std::move(internal.grid_f)); } + void ProjectVectorFEGridFunction(); + + /// Find value range of the scalar data for an intermediate representation + /** Returns @p minv = @p maxv = 0 if the range should be normally determined + from the grid function representation. */ + void FindValueRange(double &minv, double &maxv, + std::function scale = {}) const + { if (cgrid_f) { FindComplexValueRange(minv, maxv, scale); } else { minv = maxv = 0.; } } + + /// Find value range of the vector data for an intermediate representation + /** Returns @p minv = @p maxv = 0 if the range should be normally determined + from the grid function representation. */ + void FindValueRange(double &minv, double &maxv, + std::function vec2scal, + std::function scale = {}) const + { if (cgrid_f) { FindComplexValueRange(minv, maxv, vec2scal, scale); } else { minv = maxv = 0.; } } }; #endif // GLVIS_DATA_STATE_HPP diff --git a/lib/file_reader.cpp b/lib/file_reader.cpp index 7e8b3a04..b4844a01 100644 --- a/lib/file_reader.cpp +++ b/lib/file_reader.cpp @@ -9,10 +9,11 @@ // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. -#include - #include "file_reader.hpp" +#include +#include + using namespace std; using namespace mfem; @@ -54,8 +55,18 @@ int FileReader::ReadSerial(FileReader::FileType ft, const char *mesh_file, switch (ft) { case FileType::GRID_FUNC: - data.SetGridFunction(new GridFunction(data.mesh.get(), *solin), component); - break; + { + if (CheckStreamIsComplex(*solin)) + { + data.SetCmplxGridFunction(new ComplexGridFunction(data.mesh.get(), *solin), + component); + } + else + { + data.SetGridFunction(new GridFunction(data.mesh.get(), *solin), component); + } + } + break; case FileType::QUAD_FUNC: data.SetQuadFunction(new QuadratureFunction(data.mesh.get(), *solin), component); @@ -125,6 +136,19 @@ int FileReader::ReadParallel(int np, FileType ft, const char *mesh_file, return read_err; } +bool FileReader::CheckStreamIsComplex(std::istream &solin, bool parallel) +{ + string buff; + auto pos = solin.tellg(); + solin >> ws; + getline(solin, buff); + solin.seekg(pos); + mfem::filter_dos(buff); + const char *header = (parallel)?("ParComplexGridFunction"): + ("ComplexGridFunction"); + return (buff == header); +} + int FileReader::ReadParMeshAndGridFunction(int np, const char *mesh_prefix, const char *sol_prefix, int component) { @@ -140,6 +164,10 @@ int FileReader::ReadParMeshAndGridFunction(int np, const char *mesh_prefix, std::vector mesh_array(np); std::vector gf_array(np); + std::vector cgf_array(np); + + int gf_count = 0; + int cgf_count = 0; int read_err = 0; for (int p = 0; p < np; p++) @@ -185,22 +213,59 @@ int FileReader::ReadParMeshAndGridFunction(int np, const char *mesh_prefix, break; } - gf_array[p] = new GridFunction(mesh_array[p], solfile); + if (CheckStreamIsComplex(solfile, true)) + { + solfile >> ws; + solfile.ignore(3);// ignore 'Par' prefix to load as serial + cgf_array[p] = new ComplexGridFunction(mesh_array[p], solfile); + cgf_count++; + } + else + { + gf_array[p] = new GridFunction(mesh_array[p], solfile); + gf_count++; + } } else // mesh and solution in the same file { - gf_array[p] = new GridFunction(mesh_array[p], meshfile); + if (CheckStreamIsComplex(meshfile)) + { + cgf_array[p] = new ComplexGridFunction(mesh_array[p], meshfile); + cgf_count++; + } + else + { + gf_array[p] = new GridFunction(mesh_array[p], meshfile); + gf_count++; + } } } } + if (!read_err) + { + if ((gf_count > 0 && gf_count != np) + || (cgf_count > 0 && cgf_count != np)) + { + cerr << "Input files contain a mixture of data types!" << endl; + read_err = 3; + } + } + if (!read_err) { // create the combined mesh and gf data.SetMesh(new Mesh(mesh_array.data(), np)); if (sol_prefix) { - data.SetGridFunction(gf_array, np, component); + if (cgf_array[0]) + { + data.SetCmplxGridFunction(cgf_array, component); + } + else + { + data.SetGridFunction(gf_array, np, component); + } } else { @@ -211,6 +276,7 @@ int FileReader::ReadParMeshAndGridFunction(int np, const char *mesh_prefix, for (int p = 0; p < np; p++) { delete gf_array[np-1-p]; + delete cgf_array[np-1-p]; delete mesh_array[np-1-p]; } diff --git a/lib/file_reader.hpp b/lib/file_reader.hpp index 87eed714..ce176df0 100644 --- a/lib/file_reader.hpp +++ b/lib/file_reader.hpp @@ -19,6 +19,8 @@ class FileReader DataState &data; int pad_digits; + static bool CheckStreamIsComplex(std::istream &sol, bool parallel = false); + int ReadParMeshAndGridFunction(int np, const char *mesh_prefix, const char *sol_prefix, int component = -1); int ReadParMeshAndQuadFunction(int np, const char *mesh_prefix, diff --git a/lib/openglvis.hpp b/lib/openglvis.hpp index 0311d13e..1fe544e5 100644 --- a/lib/openglvis.hpp +++ b/lib/openglvis.hpp @@ -209,6 +209,8 @@ class VisualizationScene void Scale(double s); void Scale(double s1, double s2, double s3); + virtual void UpdateComplexPhase(double phase) { } + void CenterObject(); void CenterObject2D(); diff --git a/lib/script_controller.cpp b/lib/script_controller.cpp index 6ee814bd..7a30778a 100644 --- a/lib/script_controller.cpp +++ b/lib/script_controller.cpp @@ -645,19 +645,19 @@ bool ScriptController::ExecuteScriptCommand() cout << "Script: autoscale: " << word; if (word == "off") { - win.vs->SetAutoscale(0); + win.vs->SetAutoscale(VisualizationSceneScalarData::Autoscale::None); } else if (word == "on") { - win.vs->SetAutoscale(1); + win.vs->SetAutoscale(VisualizationSceneScalarData::Autoscale::MeshAndValue); } else if (word == "value") { - win.vs->SetAutoscale(2); + win.vs->SetAutoscale(VisualizationSceneScalarData::Autoscale::Value); } else if (word == "mesh") { - win.vs->SetAutoscale(3); + win.vs->SetAutoscale(VisualizationSceneScalarData::Autoscale::Mesh); } else { diff --git a/lib/stream_reader.cpp b/lib/stream_reader.cpp index c9207b92..ebab0451 100644 --- a/lib/stream_reader.cpp +++ b/lib/stream_reader.cpp @@ -13,6 +13,7 @@ #include #include +#include using namespace std; using namespace mfem; @@ -102,6 +103,18 @@ bool StreamReader::SupportsDataType(const string &data_type) return it != commands.end(); } +bool StreamReader::CheckStreamIsComplex(std::istream &solin, + bool parallel) +{ + const char *header = (parallel)?("ParComplexGridFunction"): + ("ComplexGridFunction"); + solin >> ws; + // Returning of the characters to stream(buffer) does not work reliably, + // so compare only the initial character, which fortunately does not + // conincide with the header of FiniteElementSpace. + return (solin.peek() == header[0]); +} + int StreamReader::ReadStream( istream &is, const string &data_type) { @@ -166,7 +179,14 @@ int StreamReader::ReadStream( case Command::VFem3D_GF_keys: case Command::Solution: data.SetMesh(new Mesh(is, 1, 0, data.fix_elem_orient)); - data.SetGridFunction(new GridFunction(data.mesh.get(), is)); + if (CheckStreamIsComplex(is)) + { + data.SetCmplxGridFunction(new ComplexGridFunction(data.mesh.get(), is)); + } + else + { + data.SetGridFunction(new GridFunction(data.mesh.get(), is)); + } break; case Command::Quadrature: data.SetMesh(new Mesh(is, 1, 0, data.fix_elem_orient)); @@ -311,11 +331,13 @@ int StreamReader::ReadStreams(const StreamCollection &input_streams) const int nproc = input_streams.size(); std::vector mesh_array(nproc); std::vector gf_array(nproc); + std::vector cgf_array(nproc); std::vector qf_array(nproc); std::string data_type; int gf_count = 0; + int cgf_count = 0; int qf_count = 0; for (int p = 0; p < nproc; p++) @@ -350,8 +372,17 @@ int StreamReader::ReadStreams(const StreamCollection &input_streams) } else if (data_type != "mesh") { - gf_array[p] = new GridFunction(mesh_array[p], isock); - gf_count++; + if (CheckStreamIsComplex(isock, true)) + { + isock.ignore(3);// ignore 'Par' prefix to load as serial + cgf_array[p] = new ComplexGridFunction(mesh_array[p], isock); + cgf_count++; + } + else + { + gf_array[p] = new GridFunction(mesh_array[p], isock); + gf_count++; + } } #ifdef GLVIS_DEBUG cout << "done." << endl; @@ -359,6 +390,7 @@ int StreamReader::ReadStreams(const StreamCollection &input_streams) } if ((gf_count > 0 && gf_count != nproc) + || (cgf_count > 0 && cgf_count != nproc) || (qf_count > 0 && qf_count != nproc)) { mfem_error("Input streams contain a mixture of data types!"); @@ -369,6 +401,10 @@ int StreamReader::ReadStreams(const StreamCollection &input_streams) { data.SetGridFunction(gf_array, nproc); } + else if (cgf_count > 0) + { + data.SetCmplxGridFunction(cgf_array); + } else if (qf_count > 0) { data.SetQuadFunction(qf_array); @@ -405,6 +441,12 @@ void StreamReader::WriteStream(std::ostream &os) } data.quad_f->Save(os); } + else if (data.cgrid_f) + { + os << "solution\n"; + data.mesh->Print(os); + data.cgrid_f->Save(os); + } else if (data.grid_f) { os << "solution\n"; diff --git a/lib/stream_reader.hpp b/lib/stream_reader.hpp index b62211af..b4ac669c 100644 --- a/lib/stream_reader.hpp +++ b/lib/stream_reader.hpp @@ -24,6 +24,8 @@ class StreamReader { DataState &data; + static bool CheckStreamIsComplex(std::istream &sol, bool parallel = false); + public: StreamReader(DataState &data_): data(data_) { } diff --git a/lib/threads.cpp b/lib/threads.cpp index 67b14f92..1680a840 100644 --- a/lib/threads.cpp +++ b/lib/threads.cpp @@ -493,6 +493,19 @@ int GLVisCommand::Execute() } new_state.ExtrudeMeshAndSolution(); } + else if (new_state.cgrid_f) + { + auto cs = win.data_state.GetComplexSolution(); + if (cs != DataState::ComplexSolution::NONE) + { + new_state.SetComplexSolution(cs); + } + else + { + new_state.SetComplexSolution(); + } + new_state.ExtrudeMeshAndSolution(); + } break; default: cerr << "Unknown field type" << endl; @@ -693,19 +706,19 @@ int GLVisCommand::Execute() cout << "Command: autoscale: " << autoscale_mode; if (autoscale_mode == "off") { - win.vs->SetAutoscale(0); + win.vs->SetAutoscale(VisualizationSceneScalarData::Autoscale::None); } else if (autoscale_mode == "on") { - win.vs->SetAutoscale(1); + win.vs->SetAutoscale(VisualizationSceneScalarData::Autoscale::MeshAndValue); } else if (autoscale_mode == "value") { - win.vs->SetAutoscale(2); + win.vs->SetAutoscale(VisualizationSceneScalarData::Autoscale::Value); } else if (autoscale_mode == "mesh") { - win.vs->SetAutoscale(3); + win.vs->SetAutoscale(VisualizationSceneScalarData::Autoscale::Mesh); } else { diff --git a/lib/vsdata.cpp b/lib/vsdata.cpp index c811e5ac..8f7f8e53 100644 --- a/lib/vsdata.cpp +++ b/lib/vsdata.cpp @@ -103,15 +103,15 @@ int VisualizationSceneScalarData::GetAutoRefineFactor() void VisualizationSceneScalarData::DoAutoscale(bool prepare) { - if (autoscale == 1) + if (autoscale == Autoscale::MeshAndValue) { FindNewBoxAndValueRange(prepare); } - else if (autoscale == 2) + else if (autoscale == Autoscale::Value) { FindNewValueRange(prepare); } - else if (autoscale == 3) + else if (autoscale == Autoscale::Mesh) { FindMeshBox(prepare); } @@ -119,7 +119,7 @@ void VisualizationSceneScalarData::DoAutoscale(bool prepare) void VisualizationSceneScalarData::DoAutoscaleValue(bool prepare) { - if (autoscale == 1 || autoscale == 3) + if (autoscale == Autoscale::MeshAndValue || autoscale == Autoscale::Mesh) { FindNewBoxAndValueRange(prepare); } @@ -664,11 +664,12 @@ void Key_Mod_a_Pressed(GLenum state) { if (state & KMOD_CTRL) { - static const char *autoscale_modes[] = { "off", "on", "value", "mesh" }; - int autoscale = vsdata->GetAutoscale(); - autoscale = (autoscale + 1)%4; + constexpr int nmodes = (int) VisualizationSceneScalarData::Autoscale::Max; + static const char *autoscale_modes[nmodes] = { "off", "on", "value", "mesh" }; + int autoscale = (int)vsdata->GetAutoscale(); + autoscale = (autoscale + 1) % (nmodes); cout << "Autoscale: " << flush; - vsdata->SetAutoscale(autoscale); + vsdata->SetAutoscale((VisualizationSceneScalarData::Autoscale) autoscale); cout << autoscale_modes[autoscale] << endl; SendExposeEvent(); } @@ -1344,12 +1345,16 @@ void VisualizationSceneScalarData::ToggleTexture() } } -void VisualizationSceneScalarData::SetAutoscale(int _autoscale) +void VisualizationSceneScalarData::SetAutoscale(Autoscale _autoscale, + bool update) { if (autoscale != _autoscale) { autoscale = _autoscale; - DoAutoscale(true); + if (update) + { + DoAutoscale(true); + } } } @@ -1469,7 +1474,7 @@ void VisualizationSceneScalarData::Init() PrepareRuler(); - autoscale = 1; + autoscale = VisualizationSceneScalarData::Autoscale::MeshAndValue; } VisualizationSceneScalarData::~VisualizationSceneScalarData() diff --git a/lib/vsdata.hpp b/lib/vsdata.hpp index f7ad8481..3c55d3e7 100644 --- a/lib/vsdata.hpp +++ b/lib/vsdata.hpp @@ -62,6 +62,20 @@ class VisualizationSceneScalarData : public VisualizationScene Max }; + // autoscale controls the behavior when the mesh/solution are updated + enum class Autoscale + { + Invalid = -1, + Min = -1, + //--------- + None, // do not change the bounding box and the value range + MeshAndValue, // recompute both the bounding box and the value range (default) + Value, // recompute only the value range + Mesh, // recompute only the bounding box + //--------- + Max + }; + protected: mfem::Mesh *mesh{}, *mesh_coarse{}; mfem::Vector *sol{}; @@ -102,12 +116,7 @@ class VisualizationSceneScalarData : public VisualizationScene int ruler_on; double ruler_x, ruler_y, ruler_z; - // autoscale controls the behavior when the mesh/solution are updated: - // 0 - do not change the bounding box and the value range - // 1 - recompute both the bounding box and the value range (default) - // 2 - recompute only the value range - // 3 - recompute only the bounding box - int autoscale; + Autoscale autoscale; bool logscale; @@ -188,10 +197,10 @@ class VisualizationSceneScalarData : public VisualizationScene virtual void FindMeshBox(bool prepare) { FindNewBox(prepare); } // Perform autoscaling depending on the value of 'autoscale': - // 0 - do nothing - // 1 - call FindNewBoxAndValueRange - // 2 - call FindNewValueRange - // 3 - call FindMeshBox + // None - do nothing + // MeshAndValue - call FindNewBoxAndValueRange + // Value - call FindNewValueRange + // Mesh - call FindMeshBox void DoAutoscale(bool prepare); // Similar to the above but force recomputation of the value range void DoAutoscaleValue(bool prepare); @@ -224,6 +233,8 @@ class VisualizationSceneScalarData : public VisualizationScene gl3::SceneInfo GetSceneObjs() override; + void UpdateComplexPhase(double ph) override { win.UpdateComplexPhase(ph); } + void ProcessUpdatedBufs(gl3::SceneInfo& scene); void glTF_ExportBox(glTF_Builder &bld, @@ -315,8 +326,8 @@ class VisualizationSceneScalarData : public VisualizationScene void Toggle2DView(); - void SetAutoscale(int _autoscale); - int GetAutoscale() const { return autoscale; } + void SetAutoscale(Autoscale _autoscale, bool update = true); + Autoscale GetAutoscale() const { return autoscale; } /// Shrink the set of points towards attributes centers of gravity void ShrinkPoints(mfem::DenseMatrix &pointmat, int i, int fn, int di); diff --git a/lib/vssolution.cpp b/lib/vssolution.cpp index 1dd31287..dfbe6341 100644 --- a/lib/vssolution.cpp +++ b/lib/vssolution.cpp @@ -65,9 +65,16 @@ std::string VisualizationSceneSolution::GetHelpString() const << "| o - (De)refine elem. (NC shading) |" << endl << "| O - Switch 'o' func. (NC shading) |" << endl << "| p/P Cycle through color palettes |" << endl - << "| q - Quits |" << endl - << "| Q - Cycle quadrature data mode |" << endl - << "| r - Reset the plot to 3D view |" << endl + << "| q - Quits |" << endl; + if (win.data_state.quad_f) + { + os << "| Q - Cycle quadrature data mode |" << endl; + } + else if (win.data_state.cgrid_f) + { + os << "| Q - Cycle complex data mode |" << endl; + } + os << "| r - Reset the plot to 3D view |" << endl << "| R - Reset the plot to 2D view |" << endl << "| s - Turn on/off unit cube scaling |" << endl << "| S - Take snapshot/Record a movie |" << endl @@ -99,8 +106,13 @@ std::string VisualizationSceneSolution::GetHelpString() const << "| *,/ Scale up/down |" << endl << "| +/- Change z-scaling |" << endl << "| . - Start/stop spinning |" << endl - << "| 0/Enter - Spinning speed and dir. |" << endl - << "+------------------------------------+" << endl + << "| 0/Enter - Spinning speed and dir. |" << endl; + if (win.data_state.cgrid_f) + { + os << "| Alt+. - Start/stop phase anim. |" << endl + << "| Alt+0/Enter - Phase anim. speed |" << endl; + } + os << "+------------------------------------+" << endl << "| Mouse |" << endl << "+------------------------------------+" << endl << "| left btn - Rotation |" << endl @@ -1150,8 +1162,14 @@ void VisualizationSceneSolution::FindNewBox(bool prepare) { FindNewBox(bb.x, bb.y, bb.z); - minv = bb.z[0]; - maxv = bb.z[1]; + win.data_state.FindValueRange(minv, maxv, + [this](double v) { return LogVal(v); }); + + if (minv == 0. && maxv == 0.) + { + minv = bb.z[0]; + maxv = bb.z[1]; + } FixValueRange(); @@ -1164,11 +1182,16 @@ void VisualizationSceneSolution::FindNewBox(bool prepare) void VisualizationSceneSolution::FindNewValueRange(bool prepare) { - double rx[2], ry[2], rv[2]; + win.data_state.FindValueRange(minv, maxv, + [this](double v) { return LogVal(v); }); - FindNewBox(rx, ry, rv); - minv = rv[0]; - maxv = rv[1]; + if (minv == 0. && maxv == 0.) + { + double rx[2], ry[2], rv[2]; + FindNewBox(rx, ry, rv); + minv = rv[0]; + maxv = rv[1]; + } FixValueRange(); diff --git a/lib/vssolution3d.cpp b/lib/vssolution3d.cpp index 8d81247a..8e50ab73 100644 --- a/lib/vssolution3d.cpp +++ b/lib/vssolution3d.cpp @@ -64,9 +64,16 @@ std::string VisualizationSceneSolution3d::GetHelpString() const << "| M - Toggle the mesh in the CP |" << endl << "| o/O (De)refine elem, disc shading |" << endl << "| p/P Cycle through color palettes |" << endl - << "| q - Quits |" << endl - << "| Q - Cycle quadrature data mode |" << endl - << "| r - Reset the plot to 3D view |" << endl + << "| q - Quits |" << endl; + if (win.data_state.quad_f) + { + os << "| Q - Cycle quadrature data mode |" << endl; + } + else if (win.data_state.cgrid_f) + { + os << "| Q - Cycle complex data mode |" << endl; + } + os << "| r - Reset the plot to 3D view |" << endl << "| R - Reset the plot to 2D view |" << endl << "| s - Turn on/off unit cube scaling |" << endl << "| S - Take snapshot/Record a movie |" << endl @@ -102,8 +109,13 @@ std::string VisualizationSceneSolution3d::GetHelpString() const << "| *,/ Scale up/down |" << endl << "| +/- Change z-scaling |" << endl << "| . - Start/stop spinning |" << endl - << "| 0/Enter - Spinning speed and dir. |" << endl - << "+------------------------------------+" << endl + << "| 0/Enter - Spinning speed and dir. |" << endl; + if (win.data_state.cgrid_f) + { + os << "| Alt+. - Start/stop phase anim. |" << endl + << "| Alt+0/Enter - Phase anim. speed |" << endl; + } + os << "+------------------------------------+" << endl << "| Mouse |" << endl << "+------------------------------------+" << endl << "| left btn - Rotation |" << endl @@ -1058,19 +1070,24 @@ void VisualizationSceneSolution3d::FindNewBox(bool prepare) void VisualizationSceneSolution3d::FindNewValueRange(bool prepare) { - int map_type = (GridF) ? - GridF->FESpace()->FEColl()->GetMapType(mesh->Dimension()) : - FiniteElement::VALUE; + win.data_state.FindValueRange(minv, maxv); - if (shading < Shading::Noncomforming || map_type != (int)FiniteElement::VALUE) - { - minv = sol->Min(); - maxv = sol->Max(); - } - else + if (minv == 0. && maxv == 0.) { - minv = GridF->Min(); - maxv = GridF->Max(); + int map_type = (GridF) ? + GridF->FESpace()->FEColl()->GetMapType(mesh->Dimension()) : + FiniteElement::VALUE; + + if (shading < Shading::Noncomforming || map_type != (int)FiniteElement::VALUE) + { + minv = sol->Min(); + maxv = sol->Max(); + } + else + { + minv = GridF->Min(); + maxv = GridF->Max(); + } } FixValueRange(); UpdateValueRange(prepare); diff --git a/lib/vsvector.cpp b/lib/vsvector.cpp index acc7bc45..20cc1e31 100644 --- a/lib/vsvector.cpp +++ b/lib/vsvector.cpp @@ -50,9 +50,16 @@ std::string VisualizationSceneVector::GetHelpString() const << "| o - (De)refine elem. (NC shading) |" << endl << "| O - Switch 'o' func. (NC shading) |" << endl << "| p/P Cycle through color palettes |" << endl - << "| q - Quits |" << endl - << "| Q - Cycle quadrature data mode |" << endl - << "| r - Reset the plot to 3D view |" << endl + << "| q - Quits |" << endl; + if (win.data_state.quad_f) + { + os << "| Q - Cycle quadrature data mode |" << endl; + } + else if (win.data_state.cgrid_f) + { + os << "| Q - Cycle complex data mode |" << endl; + } + os << "| r - Reset the plot to 3D view |" << endl << "| R - Reset the plot to 2D view |" << endl << "| s - Turn on/off unit cube scaling |" << endl << "| S - Take snapshot/Record a movie |" << endl @@ -84,8 +91,13 @@ std::string VisualizationSceneVector::GetHelpString() const << "| *,/ Scale up/down |" << endl << "| +/- Change z-scaling |" << endl << "| . - Start/stop spinning |" << endl - << "| 0/Enter - Spinning speed and dir. |" << endl - << "+------------------------------------+" << endl + << "| 0/Enter - Spinning speed and dir. |" << endl; + if (win.data_state.cgrid_f) + { + os << "| Alt+. - Start/stop phase anim. |" << endl + << "| Alt+0/Enter - Phase anim. speed |" << endl; + } + os << "+------------------------------------+" << endl << "| Mouse |" << endl << "+------------------------------------+" << endl << "| left btn - Rotation |" << endl @@ -478,7 +490,7 @@ void VisualizationSceneVector::NewMeshAndSolution( VisualizationSceneSolution::NewMeshAndSolution(mesh, mesh_coarse, sol, vgf); - if (autoscale) + if (autoscale != VisualizationSceneScalarData::Autoscale::None) { if (Vec2Scalar == VecLength) { @@ -909,6 +921,48 @@ int VisualizationSceneVector::GetFunctionAutoRefineFactor() return VisualizationSceneScalarData::GetFunctionAutoRefineFactor(*VecGridF); } +void VisualizationSceneVector::FindNewBox(bool prepare) +{ + VisualizationSceneSolution::FindNewBox(bb.x, bb.y, bb.z); + + win.data_state.FindValueRange(minv, maxv, + [this](const Vector &x) { return Vec2Scalar(x(0), x(1)); }, + [this](double v) { return LogVal(v); }); + + if (minv == 0. && maxv == 0.) + { + minv = bb.z[0]; + maxv = bb.z[1]; + } + + FixValueRange(); + + bb.z[0] = minv; + bb.z[1] = maxv; + + SetNewScalingFromBox(); // UpdateBoundingBox minus PrepareAxes + UpdateValueRange(prepare); +} + +void VisualizationSceneVector::FindNewValueRange(bool prepare) +{ + win.data_state.FindValueRange(minv, maxv, + [this](const Vector &x) { return Vec2Scalar(x(0), x(1)); }, + [this](double v) { return LogVal(v); }); + + if (minv == 0. && maxv == 0.) + { + double rx[2], ry[2], rv[2]; + VisualizationSceneSolution::FindNewBox(rx, ry, rv); + minv = rv[0]; + maxv = rv[1]; + } + + FixValueRange(); + + UpdateValueRange(prepare); +} + void VisualizationSceneVector::PrepareVectorField() { int rerun; diff --git a/lib/vsvector.hpp b/lib/vsvector.hpp index e019fa24..6e734b54 100644 --- a/lib/vsvector.hpp +++ b/lib/vsvector.hpp @@ -56,6 +56,9 @@ class VisualizationSceneVector : public VisualizationSceneSolution void NewMeshAndSolution(const DataState &s) override; + void FindNewBox(bool prepare) override; + void FindNewValueRange(bool prepare) override; + virtual ~VisualizationSceneVector(); std::string GetHelpString() const override; diff --git a/lib/vsvector3d.cpp b/lib/vsvector3d.cpp index 57cd4315..f3014567 100644 --- a/lib/vsvector3d.cpp +++ b/lib/vsvector3d.cpp @@ -59,9 +59,16 @@ std::string VisualizationSceneVector3d::GetHelpString() const << "| n - Displacements step forward |" << endl << "| o/O (De)refine elem, disc shading |" << endl << "| p/P Cycle through color palettes |" << endl - << "| q - Quits |" << endl - << "| Q - Cycle quadrature data mode |" << endl - << "| r - Reset the plot to 3D view |" << endl + << "| q - Quits |" << endl; + if (win.data_state.quad_f) + { + os << "| Q - Cycle quadrature data mode |" << endl; + } + else if (win.data_state.cgrid_f) + { + os << "| Q - Cycle complex data mode |" << endl; + } + os << "| r - Reset the plot to 3D view |" << endl << "| R - Reset the plot to 2D view |" << endl << "| s - Turn on/off unit cube scaling |" << endl << "| S - Take snapshot/Record a movie |" << endl @@ -95,8 +102,13 @@ std::string VisualizationSceneVector3d::GetHelpString() const << "| *,/ Scale up/down |" << endl << "| +/- Change z-scaling |" << endl << "| . - Start/stop spinning |" << endl - << "| 0/Enter - Spinning speed and dir. |" << endl - << "+------------------------------------+" << endl + << "| 0/Enter - Spinning speed and dir. |" << endl; + if (win.data_state.cgrid_f) + { + os << "| Alt+. - Start/stop phase anim. |" << endl + << "| Alt+0/Enter - Phase anim. speed |" << endl; + } + os << "+------------------------------------+" << endl << "| Mouse |" << endl << "+------------------------------------+" << endl << "| left btn - Rotation |" << endl @@ -274,8 +286,8 @@ void VisualizationSceneVector3d::ToggleVectorField(int i) PrepareVectorField(); } -static const char *scal_func_name[] = -{"magnitude", "x-component", "y-component", "z-component"}; +const char *VisualizationSceneVector3d::scal_func_name[(int)ScalarFunction::Max] + = {"magnitude", "x-component", "y-component", "z-component"}; void VisualizationSceneVector3d::SetScalarFunction() { @@ -283,7 +295,7 @@ void VisualizationSceneVector3d::SetScalarFunction() switch (scal_func) { - case 0: // magnitude + case ScalarFunction::Magnitude: for (int i = 0; i < sol->Size(); i++) (*sol)(i) = sqrt((*solx)(i) * (*solx)(i) + (*soly)(i) * (*soly)(i) + @@ -304,7 +316,7 @@ void VisualizationSceneVector3d::SetScalarFunction() } } break; - case 1: // x-component + case ScalarFunction::Component_X: *sol = *solx; if (GridF) for (int i = 0; i < GridF->Size(); i++) @@ -312,7 +324,7 @@ void VisualizationSceneVector3d::SetScalarFunction() (*GridF)(i) = (*VecGridF)(fes->DofToVDof(i, 0)); } break; - case 2: // y-component + case ScalarFunction::Component_Y: *sol = *soly; if (GridF) for (int i = 0; i < GridF->Size(); i++) @@ -320,7 +332,7 @@ void VisualizationSceneVector3d::SetScalarFunction() (*GridF)(i) = (*VecGridF)(fes->DofToVDof(i, 1)); } break; - case 3: // z-component + case ScalarFunction::Component_Z: *sol = *solz; if (GridF) for (int i = 0; i < GridF->Size(); i++) @@ -328,14 +340,17 @@ void VisualizationSceneVector3d::SetScalarFunction() (*GridF)(i) = (*VecGridF)(fes->DofToVDof(i, 2)); } break; + default: + cerr << "Unknown scalar representation" << endl; + return; } - win.extra_caption = scal_func_name[scal_func]; + win.extra_caption = scal_func_name[(int)scal_func]; } void VisualizationSceneVector3d::ToggleScalarFunction() { - scal_func = (scal_func + 1) % 4; - cout << "Displaying " << scal_func_name[scal_func] << endl; + scal_func = (ScalarFunction)(((int)scal_func + 1) % ((int)ScalarFunction::Max)); + cout << "Displaying " << scal_func_name[(int)scal_func] << endl; SetScalarFunction(); FindNewValueRange(true); } @@ -384,7 +399,7 @@ void VisualizationSceneVector3d::Init() drawdisp = 0; drawvector = 0; - scal_func = 0; + scal_func = ScalarFunction::Magnitude; ianim = ianimd = 0; ianimmax = 10; @@ -554,6 +569,52 @@ void VisualizationSceneVector3d::NewMeshAndSolution( PrepareDisplacedMesh(); } +void VisualizationSceneVector3d::FindNewValueRange(bool prepare) +{ + function vec2scal; + switch (scal_func) + { + case ScalarFunction::Magnitude: + vec2scal = [](const Vector &v) { return v.Norml2(); }; + break; + case ScalarFunction::Component_X: + vec2scal = [](const Vector &v) { return v(0); }; + break; + case ScalarFunction::Component_Y: + vec2scal = [](const Vector &v) { return v(1); }; + break; + case ScalarFunction::Component_Z: + vec2scal = [](const Vector &v) { return v(2); }; + break; + default: + cerr << "Unknown scalar representation" << endl; + return; + } + + win.data_state.FindValueRange(minv, maxv, vec2scal); + + if (minv == 0. && maxv == 0.) + { + int map_type = (GridF) ? + GridF->FESpace()->FEColl()->GetMapType(mesh->Dimension()) : + FiniteElement::VALUE; + + if (shading < Shading::Noncomforming || map_type != (int)FiniteElement::VALUE) + { + minv = sol->Min(); + maxv = sol->Max(); + } + else + { + minv = GridF->Min(); + maxv = GridF->Max(); + } + return; + } + FixValueRange(); + UpdateValueRange(prepare); +} + void VisualizationSceneVector3d::PrepareFlat() { int i, j; diff --git a/lib/vsvector3d.hpp b/lib/vsvector3d.hpp index 9e2e83a2..346f117b 100644 --- a/lib/vsvector3d.hpp +++ b/lib/vsvector3d.hpp @@ -21,7 +21,22 @@ class VisualizationSceneVector3d : public VisualizationSceneSolution3d protected: mfem::Vector *solx, *soly, *solz; - int drawvector, scal_func; + int drawvector; + + enum class ScalarFunction + { + Invalid = -1, + Min = -1, + //---------- + Magnitude, + Component_X, + Component_Y, + Component_Z, + //---------- + Max + } scal_func; + static const char *scal_func_name[]; + double mesh_volume; gl3::GlDrawable vector_buf; gl3::GlDrawable displine_buf; @@ -47,6 +62,8 @@ class VisualizationSceneVector3d : public VisualizationSceneSolution3d void NewMeshAndSolution(const DataState &s) override; + void FindNewValueRange(bool prepare) override; + virtual ~VisualizationSceneVector3d(); std::string GetHelpString() const override; diff --git a/lib/window.cpp b/lib/window.cpp index 7ee6899e..fe5fe435 100644 --- a/lib/window.cpp +++ b/lib/window.cpp @@ -73,11 +73,6 @@ bool Window::GLVisInitVis(StreamCollection input_streams) locwin = this; - if (data_state.quad_f) - { - wnd->setOnKeyDown('Q', SwitchQuadSolution); - } - double mesh_range = -1.0; if (field_type == DataState::FieldType::SCALAR || field_type == DataState::FieldType::MESH) @@ -159,7 +154,7 @@ bool Window::GLVisInitVis(StreamCollection input_streams) if (mesh_range > 0.0) { vs->SetValueRange(-mesh_range, mesh_range); - vs->SetAutoscale(0); + vs->SetAutoscale(VisualizationSceneScalarData::Autoscale::None); } if (data_state.mesh->SpaceDimension() == 2 && field_type == DataState::FieldType::MESH) @@ -190,12 +185,33 @@ void Window::GLVisStartVis() std::cout << "GLVis window closed." << std::endl; } +void Window::SwitchComplexSolution(DataState::ComplexSolution cmplx_type) +{ + data_state.SetComplexSolution(cmplx_type); + ResetMeshAndSolution(data_state); +} + void Window::SwitchQuadSolution(DataState::QuadSolution quad_type) { data_state.SwitchQuadSolution(quad_type); ResetMeshAndSolution(data_state); } +void Window::UpdateComplexPhase(double ph) +{ + data_state.cmplx_phase += ph; + data_state.cmplx_phase -= floor(data_state.cmplx_phase); + DataState::ComplexSolution cs = data_state.GetComplexSolution(); + // check if magnitude is viewed, which remains the same + if (cs == DataState::ComplexSolution::Magnitude) { return; } + data_state.SetComplexSolution(cs, false); + // do not autoscale for animation + auto as = vs->GetAutoscale(); + vs->SetAutoscale(VisualizationSceneScalarData::Autoscale::None); + ResetMeshAndSolution(data_state); + vs->SetAutoscale(as, false); +} + bool Window::SetNewMeshAndSolution(DataState new_state) { if (new_state.mesh->SpaceDimension() == data_state.mesh->SpaceDimension() && @@ -227,9 +243,28 @@ void Window::ResetMeshAndSolution(DataState &ss) vs->NewMeshAndSolution(ss); } - thread_local Window *Window::locwin = NULL; +void Window::SwitchSolution() +{ + if (locwin->data_state.cgrid_f) + { + SwitchComplexSolution(); + } + else if (locwin->data_state.quad_f) + { + SwitchQuadSolution(); + } +} + +void Window::SwitchComplexSolution() +{ + int ics = ((int)locwin->data_state.GetComplexSolution()+1) + % ((int)DataState::ComplexSolution::MAX); + locwin->SwitchComplexSolution((DataState::ComplexSolution)ics); + SendExposeEvent(); +} + void Window::SwitchQuadSolution() { int iqs = ((int)locwin->data_state.GetQuadSolution()+1) diff --git a/lib/window.hpp b/lib/window.hpp index eeb90ce1..3391a2aa 100644 --- a/lib/window.hpp +++ b/lib/window.hpp @@ -61,9 +61,15 @@ struct Window bool GLVisInitVis(StreamCollection input_streams); void GLVisStartVis(); + /// Switch the complex function representation and update the visualization + void SwitchComplexSolution(DataState::ComplexSolution type); + /// Switch the quadrature function representation and update the visualization void SwitchQuadSolution(DataState::QuadSolution type); + /// Update complex phase of the solution and update the visualization + void UpdateComplexPhase(double ph); + /// Sets a new mesh and solution from another DataState object, and /// updates the VisualizationScene with the new data. /// @@ -73,10 +79,16 @@ struct Window /// updated. bool SetNewMeshAndSolution(DataState new_state); + /// Switch representation of the solution + static void SwitchSolution(); + private: /// Thread-local singleton for key handlers static thread_local Window *locwin; + /// Switch representation of the complex function + static void SwitchComplexSolution(); + /// Switch representation of the quadrature function static void SwitchQuadSolution(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1e9cf3fb..339f6f7e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -33,6 +33,8 @@ set(stream_tests "ex5" "ex6-dofs-numbering" "ex9" + "ex22-scalar" + "ex22-vector" "klein-bottle" "laghos" "mesh-explorer" diff --git a/tests/data b/tests/data index bd2a11d9..f1d01ff0 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit bd2a11d9eabf7dcf2d83cffd906b313e56ab7ef0 +Subproject commit f1d01ff03a5e1d2149e15adef506a803100b45ec