Skip to content

Commit

Permalink
codeStream: Typed substitutions
Browse files Browse the repository at this point in the history
Dictionary entries constructed with #calc and #codeStream can now
conveniently access and use typed variables. This means calculations
involving vectors and tensors and list and field types are now possible.

To access a variable and construct it as a given type within a #calc
or #codeStream entry, put the type immediately after the $ symbol inside
angled brackets <>. So, $<vector>var or $<vector>{var} substitutes a
variable named var as a vector.

Examples:

- Reflect a point in a plane defined by a normal

    p       (1 2 3);
    n       (1 1 0);
    pStar   #calc "$<vector>p - (2*sqr($<vector>n)/magSqr($<vector>n)&$<vector>p)";

- Rotate a list of points around an axis by a given angle

    points  ((3 0 0) (2 1 1) (1 2 2) (0 3 3));
    rotation
    {
        axis    (0 1 1);
        angle   45;
    }

    #codeStream
    {
        codeInclude
        #{
            #include "pointField.H"
            #include "transform.H"
        #};

        code
        #{
            const pointField points($<List<point>>points);
            const vector axis = $<vector>!rotation/axis;
            const scalar angle = degToRad($!rotation/angle);
            os << "pointsRotated" << nl << (Ra(axis, angle) & points)() << ";";
        #};
    };

- Compute the centre and trianglation of a polygon

   polygon  ((0 0 0) (1 0 0) (2 1 0) (0 2 0) (-1 1 0));

   #codeStream
   {
       codeInclude
       #{
           #include "polygonTriangulate.H"
       #};

       code
       #{
           const List<point> polygon($<List<point>>polygon);
           writeEntry(os, "polygonCentre", face::centre(polygon));

           polygonTriangulate triEngine;
           triEngine.triangulate(polygon);
           os << "polygonTris" << ' ' << triEngine.triPoints() << ";";
       #};
    };

- Generate a single block blockMeshDict for use with snappyHexMesh with no redundant information

    min         (-2.5 -1.2 -3.0);   // Minimum coordinates of the block
    max         (2.5 1.2 3.0);      // Maximum coordinates of the block
    nCellsByL   33.3333;            // Number of cells per unit length

    // Calculate the number of cells in each block direction
    nCells      #calc "Vector<label>($nCellsByL*($<vector>max - $<vector>min) + vector::one/2)";

    // Generate the vertices using a boundBox
    vertices    #codeStream
    {
        codeInclude
        #{
            #include "boundBox.H"
        #};

        code
        #{
            os << boundBox($<vector>min, $<vector>max).points();
        #};
    };

    blocks
    (
        hex (0 1 2 3 4 5 6 7) $nCells simpleGrading (1 1 1)
    );

    defaultPatch
    {
        type patch;
    }

    boundary
    ();
  • Loading branch information
Will Bainbridge committed Jun 22, 2023
1 parent 0927fd4 commit b2d4f25
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 22 deletions.
2 changes: 2 additions & 0 deletions etc/codeTemplates/dynamicCode/codeStreamTemplate.C
Expand Up @@ -27,8 +27,10 @@ Description
\*---------------------------------------------------------------------------*/

#include "dictionary.H"
#include "fieldTypes.H"
#include "Ostream.H"
#include "Pstream.H"
#include "read.H"
#include "unitConversion.H"

//{{{ begin codeInclude
Expand Down
21 changes: 18 additions & 3 deletions src/OpenFOAM/db/dictionary/dictionary.H
Expand Up @@ -513,16 +513,31 @@ public:
bool patternMatch=true
) const;

//- Find and return an entry data stream pointer if present
// otherwise return nullptr. Allows scoping using '/' with
// special handling for '!' and '..'.
//- Find and return an entry data stream pointer if present,
// otherwise return nullptr.
// If recursive, search parent dictionaries.
// If patternMatch, use regular expressions.
// Allows scoping using '/' with special handling for '!' and '..'.
const entry* lookupScopedEntryPtr
(
const word&,
bool recursive,
bool patternMatch
) const;

//- Find and return a T,
// if not found throw a fatal error.
// If recursive, search parent dictionaries.
// If patternMatch, use regular expressions.
// Allows scoping using '/' with special handling for '!' and '..'.
template<class T>
T lookupScoped
(
const word&,
bool recursive=false,
bool patternMatch=true
) const;

//- Check if entry is a sub-dictionary
bool isDict(const word&) const;

Expand Down
25 changes: 25 additions & 0 deletions src/OpenFOAM/db/dictionary/dictionaryTemplates.C
Expand Up @@ -223,6 +223,31 @@ bool Foam::dictionary::readIfPresent
}


template<class T>
T Foam::dictionary::lookupScoped
(
const word& keyword,
bool recursive,
bool patternMatch
) const
{
const entry* entryPtr =
lookupScopedEntryPtr(keyword, recursive, patternMatch);

if (entryPtr == nullptr)
{
FatalIOErrorInFunction
(
*this
) << "keyword " << keyword << " is undefined in dictionary "
<< name()
<< exit(FatalIOError);
}

return pTraits<T>(entryPtr->stream());
}


template<class T>
void Foam::dictionary::add(const keyType& k, const T& t, bool overwrite)
{
Expand Down
22 changes: 22 additions & 0 deletions src/OpenFOAM/primitives/strings/string/string.C
Expand Up @@ -295,4 +295,26 @@ void Foam::string::strip(const string& str)
}


Foam::string::size_type Foam::string::findClosing
(
const char c,
string::size_type i0 = 0
) const
{
size_t level = 1;

string::size_type i = i0 + 1;

while (level > 0 && i < size())
{
if (operator[](i) == operator[](i0)) ++level;
if (operator[](i) == c) --level;

++i;
}

return level == 0 ? i - 1 : string::npos;
}


// ************************************************************************* //
18 changes: 18 additions & 0 deletions src/OpenFOAM/primitives/strings/string/string.H
Expand Up @@ -244,6 +244,24 @@ public:
//- Strip characters from the start and end of the string
void strip(const string&);

//- Find the closing character. Brackets counting algorithm. The
// opening bracket character is taken to be the one at the starting
// position. The closing character is provided by argument.
//
// Examples:
//
// [char #1] [char #15]
// | |
// V V
// string("0(2(4,6)8,a(c)e)g").findClosing(')',1) == 15
//
// [char #3] [char #7]
// | |
// V V
// string("0(2(4,6)8,a(c)e)g").findClosing(')',3) == 7
//
size_type findClosing(const char, const size_type) const;


// Member Operators

Expand Down
80 changes: 61 additions & 19 deletions src/OpenFOAM/primitives/strings/stringOps/stringOps.C
Expand Up @@ -410,6 +410,7 @@ Foam::string& Foam::stringOps::inplaceExpandCodeString
(
string& s,
const dictionary& dict,
const word& dictVar,
const char sigil
)
{
Expand All @@ -427,16 +428,28 @@ Foam::string& Foam::stringOps::inplaceExpandCodeString
{
// Find end of first occurrence
string::size_type endVar = begVar;
string::size_type delim = 0;
string::size_type begDelim = 0, endDelim = 0;

if (s[begVar+1] == '{')
// Get the type, if any
word varType;
if (s[begVar+1] == '<')
{
endVar = s.find('}', begVar);
delim = 1;
begDelim += s.findClosing('>', begVar+1) - begVar;
varType = s.substr(begVar+2, begDelim-2);
}

// Parse any braces and find the end of the variable
if (s[begVar+begDelim+1] == '{')
{
endVar = s.find('}', begVar+begDelim);
begDelim += 1;
endDelim += 1;
}
else
{
string::iterator iter = s.begin() + begVar + 1;
endVar = begVar + begDelim;

string::iterator iter = s.begin() + begVar + begDelim + 1;

// Accept all dictionary and environment variable characters
while
Expand Down Expand Up @@ -474,8 +487,8 @@ Foam::string& Foam::stringOps::inplaceExpandCodeString
(
s.substr
(
begVar + 1 + delim,
endVar - begVar - 2*delim
begVar + begDelim + 1,
(endVar - endDelim) - (begVar + begDelim)
),
false
);
Expand All @@ -491,25 +504,54 @@ Foam::string& Foam::stringOps::inplaceExpandCodeString
// Substitute if found
if (ePtr)
{
// Write to string buffer. Force floating point numbers to
// be printed with at least some decimal digits.
OStringStream buf;
buf << scientific;
buf.precision(IOstream::defaultPrecision());

if (ePtr->isDict())
if (!dictVar.empty() && !varType.empty())
{
ePtr->dict().write(buf, false);
// If the dictionary is accessible and the type is
// known, then lookup the variable in the code, rather
// than substituting its value. That way we don't need
// to recompile this string if the value changes.
buf << dictVar
<< ".lookupScoped<" << varType << ">"
<< "(\"" << varName << "\", true, false)";
}
else if (isA<primitiveEntry>(*ePtr))

if (dictVar.empty() && !varType.empty())
{
dynamicCast<const primitiveEntry>(*ePtr)
.write(buf, true);
// If the dictionary is not accessible but the type is
// known, then read the substituted value from a string
buf << "read<" << varType << ">(\"";
}
else

if (dictVar.empty() || varType.empty())
{
// If the dictionary is not accessible and/or the type
// is not known, then we need to substitute the
// variable's value

// Make sure floating point values print with at least
// some decimal points
buf << scientific;
buf.precision(IOstream::defaultPrecision());

// Write the dictionary or primitive entry. Fail if
// anything else.
if (ePtr->isDict())
{
ePtr->dict().write(buf, false);
}
else
{
dynamicCast<const primitiveEntry>(*ePtr)
.write(buf, true);
}
}

if (dictVar.empty() && !varType.empty())
{
// Can't do any other types. Fail.
dynamicCast<const primitiveEntry>(*ePtr);
// Close the string and read function as necessary
buf << "\")";
}

s.std::string::replace
Expand Down
1 change: 1 addition & 0 deletions src/OpenFOAM/primitives/strings/stringOps/stringOps.H
Expand Up @@ -110,6 +110,7 @@ namespace stringOps
(
string&,
const dictionary& dict,
const word& dictVar = "dict",
const char sigil = '$'
);

Expand Down

0 comments on commit b2d4f25

Please sign in to comment.