From 82e471e045325c4c40a29985df4119539ba1fc40 Mon Sep 17 00:00:00 2001 From: Andrew Heather <> Date: Sun, 15 Dec 2024 16:32:22 +0000 Subject: [PATCH 1/4] ENH: Added new graphFunctionObject function object Accumulates function object result values and renders into a graph in SVG format Minimal example by using system/controlDict.functions to plot the residuals from the solverInfo function Object: residualGraph { type graphFunctionObject; libs (utilityFunctionObjects); writeControl writeTime; logScaleX no; logScaleY yes; xlabel "Iteration"; ylabel "log10(Initial residual)"; functions { line1 { object solverInfo1; entry Ux_initial; } line2 { object solverInfo1; entry Uy_initial; } line3 { object solverInfo1; entry Uz_initial; } line4 { object solverInfo1; entry p_initial; } } } --- src/functionObjects/utilities/Make/files | 2 + .../utilities/graphFunctionObject/SVGTools.H | 238 +++++++ .../graphFunctionObject/graphFunctionObject.C | 654 ++++++++++++++++++ .../graphFunctionObject/graphFunctionObject.H | 300 ++++++++ 4 files changed, 1194 insertions(+) create mode 100644 src/functionObjects/utilities/graphFunctionObject/SVGTools.H create mode 100644 src/functionObjects/utilities/graphFunctionObject/graphFunctionObject.C create mode 100644 src/functionObjects/utilities/graphFunctionObject/graphFunctionObject.H diff --git a/src/functionObjects/utilities/Make/files b/src/functionObjects/utilities/Make/files index aed6a6fa70..bf81b19565 100644 --- a/src/functionObjects/utilities/Make/files +++ b/src/functionObjects/utilities/Make/files @@ -9,6 +9,8 @@ areaWrite/areaWrite.C ensightWrite/ensightWrite.C ensightWrite/ensightWriteUpdate.C +graphFunctionObject/graphFunctionObject.C + vtkWrite/vtkWrite.C vtkWrite/vtkWriteUpdate.C diff --git a/src/functionObjects/utilities/graphFunctionObject/SVGTools.H b/src/functionObjects/utilities/graphFunctionObject/SVGTools.H new file mode 100644 index 0000000000..18ec80537b --- /dev/null +++ b/src/functionObjects/utilities/graphFunctionObject/SVGTools.H @@ -0,0 +1,238 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \ / F ield | OpenFOAM: The Open Source CFD Toolbox + \ / O peration | + \ / A nd | www.openfoam.com + \/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2024 OpenCFD Ltd. +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see . + +Namespace + Foam::SVG + +Description + Collection of tools to generate SVG strings + +SourceFiles + SVGTools.H + +\*---------------------------------------------------------------------------*/ + +#ifndef Foam_SVGTools_H +#define Foam_SVGTools_H + +#include "Ostream.H" +#include "OStringStream.H" +#include "List.H" + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +namespace Foam +{ + +/*---------------------------------------------------------------------------*\ + Namespace SVG Declaration +\*---------------------------------------------------------------------------*/ + +namespace SVG +{ + typedef std::pair entryType; + + struct element; + Ostream& operator<<(Ostream& os, const element& e); + + // Base SVG element + struct element + { + const word key_; + DynamicList styles_; + DynamicList elems_; + + element + ( + const word& key, + const std::initializer_list& styles = {}, + const std::initializer_list& elems = {} + ) + : + key_(key), + styles_(styles), + elems_(elems) + {} + + template + void addAttr(const char* key, const Type& value) + { + OStringStream oss; + oss << value; + elems_.push_back(entryType(key, oss.str().c_str())); + } + + void addAttrStr(const char* key, const string& str) + { + elems_.push_back(entryType(key, str.c_str())); + } + + friend Ostream& operator<<(Ostream& os, const element& ele) + { + os << "<" << ele.key_; + + for (const auto& e : ele.elems_) + { + os << " " << e.first << "=" << e.second; + } + + os << " style=\""; + for (const auto& s : ele.styles_) + { + os << s.first << ":" << s.second.c_str() << ";"; + } + + os << "\">"; + + return os; + } + + const word end = ""; + }; + + + struct text; + Ostream& operator<<(Ostream& os, const text& t); + + // Text + struct text + : + element + { + const string text_; + + text + ( + const string text, + const label left, + const label top, + const std::initializer_list& styles = {}, + const word anchor = "middle", + const std::initializer_list& elems = {} + ) + : + element("text", styles, elems), + text_(text) + { + elems_.push_back(entryType("x", Foam::name(left))); + elems_.push_back(entryType("y", Foam::name(top))); + elems_.push_back(entryType("text-anchor", anchor)); + elems_.push_back + ( + entryType("font-family", "Arial, Helvetica, sans-serif") + ); + } + + + friend Ostream& operator<<(Ostream& os, const text& t) + { + // element::operator<<(os, t); + os << static_cast(t); + + os << t.text_.c_str(); + + os << t.end; + + return os; + } + }; + + + struct line; + Ostream& operator<<(Ostream& os, const line& l); + + // Line + struct line + : + element + { + line + ( + const label x1, + const label y1, + const label x2, + const label y2, + const std::initializer_list& styles = {}, + const std::initializer_list& elems = {} + ) + : + element("line", styles, elems) + { + elems_.push_back(entryType("x1", Foam::name(x1))); + elems_.push_back(entryType("y1", Foam::name(y1))); + elems_.push_back(entryType("x2", Foam::name(x2))); + elems_.push_back(entryType("y2", Foam::name(y2))); + } + + + friend Ostream& operator<<(Ostream& os, const line& l) + { + // element::operator<<(os, l); + os << static_cast(l); + os << l.end; + + return os; + } + }; + + struct header; + Ostream& operator<<(Ostream& os, const header& h); + + // Header + struct header + { + label width_; + label height_; + + header(const label width, const label height) + : + width_(width), + height_(height) + {} + + friend Ostream& operator<<(Ostream& os, const header& h) + { + os << ""; + + return os; + } + }; + + // Close SVG element + const char* end = ""; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +} // End namespace SVG +} // End namespace Foam + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#endif + +// ************************************************************************* // \ No newline at end of file diff --git a/src/functionObjects/utilities/graphFunctionObject/graphFunctionObject.C b/src/functionObjects/utilities/graphFunctionObject/graphFunctionObject.C new file mode 100644 index 0000000000..1627dbf6f2 --- /dev/null +++ b/src/functionObjects/utilities/graphFunctionObject/graphFunctionObject.C @@ -0,0 +1,654 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \ / F ield | OpenFOAM: The Open Source CFD Toolbox + \ / O peration | + \ / A nd | www.openfoam.com + \/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2024 OpenCFD Ltd. +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see . + +\*---------------------------------------------------------------------------*/ + +#include "graphFunctionObject.H" +#include "addToRunTimeSelectionTable.H" +#include "OFstream.H" +#include "labelVector.H" +#include "FlatOutput.H" +#include "SVGTools.H" + +// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // + +namespace Foam +{ +namespace functionObjects +{ + defineTypeNameAndDebug(graphFunctionObject, 0); + addToRunTimeSelectionTable + ( + functionObject, + graphFunctionObject, + dictionary + ); +} +} + +// 'Muted' colour scheme from https://personal.sron.nl/~pault/ (12.07.24) +Foam::wordList Foam::functionObjects::graphFunctionObject::defaultColours +({ + "#CC6677", + "#332288", + "#DDCC77", + "#117733", + "#88CCEE", + "#882255", + "#44AA99", + "#999933", + "#AA4499" +}); + + +// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * // + +template +bool Foam::functionObjects::graphFunctionObject::getValue +( + const label objecti, + label& valuei +) +{ + const word& object = objects_[objecti]; + const word& entry = entries_[objecti]; + + Type result; + if (!this->getObjectResult(object, entry, result)) + { + return false; + } + + auto& cols = objectToCol_[objecti]; + if (cols.empty()) + { + for (direction d = 0; d < pTraits::nComponents; ++d) + { + cols.push_back(valuei++); + values_.push_back(DynamicList()); + } + } + + for (direction d = 0; d < pTraits::nComponents; ++d) + { + scalar v = component(result, d); + + if (logScaleY_) + { + v = (v < SMALL) ? 1 : log10(v); + } + + values_[cols[d]].push_back(v); + } + + return true; +} + + +Foam::label Foam::functionObjects::graphFunctionObject::setAxisProps +( + const bool logScale, + scalar& xmin, + scalar& xmax, + scalar& xtick +) const +{ + DebugInfo + << "1 -- xmin:" << xmin << " xmax:" << xmax + << " xtick:" << xtick << endl; + + /* + Divisions Based on (12.07.24): + https://peltiertech.com/calculate-nice-axis-scales-in-your-excel-worksheet + */ + + const scalar range = xmax - xmin; + const scalar eps = 0.01*range; + + // Extend xmin and xmax by eps + if (mag(xmin) < SMALL) + { + xmin = 0; + } + else + { + xmin = (xmin > 0) ? max(0, xmin - eps) : xmin - eps; + } + + if (mag(xmax) < SMALL) + { + xmax = mag(xmin) < SMALL ? 1 : 0; + } + else + { + xmax = (xmax < 0) ? min(0, xmax + eps) : xmax + eps; + } + + DebugInfo + << "2 -- xmin:" << xmin << " xmax:" << xmax + << " xtick:" << xtick << endl; + + auto lookup = [](const scalar x) -> scalar + { + if (x < 2.5) { return 0.2; } + if (x < 5.0) { return 0.5; } + if (x < 10.0) { return 2.0; } + return 10.0; + }; + + const scalar power = log10(range); + const scalar factor = pow(10, power - floor(power)); + + xtick = lookup(factor)*pow(10, floor(power)); + xmin = xtick*floor(xmin/xtick); + xmax = xtick*(floor(xmax/xtick) + 1); + + // Convert ticks to integer powers of 10 for log scales + if (logScale) + { + xmin = floor(xmin); + xmax = ceil(xmax); + xtick = 1; + } + + DebugInfo + << "power:" << power << " factor:" << factor + << " xmin:" << xmin << " xmax:" << xmax + << " xtick:" << xtick << endl; + + return round((xmax - xmin)/xtick); +} + + +// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // + +Foam::functionObjects::graphFunctionObject::graphFunctionObject +( + const word& name, + const Time& runTime, + const dictionary& dict +) +: + stateFunctionObject(name, runTime), + writeFile(runTime, name, typeName, dict, true, ".svg"), + objects_(), + entries_(), + titles_(), + colours_(), + dashes_(), + times_(), + values_(), + objectToCol_(), + xMin_(dict.getOrDefault("xMin", GREAT)), + xMax_(dict.getOrDefault("xMax", GREAT)), + yMin_(dict.getOrDefault("yMin", GREAT)), + yMax_(dict.getOrDefault("yMax", GREAT)), + xlabel_(dict.getOrDefault("xlabel", "Iteration/Time")), + ylabel_(dict.getOrDefault("ylabel", "Property")), + width_(dict.getOrDefault