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 = "" + key_ + ">";
+ };
+
+
+ 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 << "";
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // 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