unitConversion: Fixed format of non-named units

Non-named unit conversions are now written in a single set of square
brackets, with colons used to separate the sets of exponents and
multipliers. So, for example, a named unit conversion of [rpm] gets
converted to a non-named [0 0 -1 0 0 0 0 : 0 1 : 0.10472] (i.e.,
[time^-1 : angle : 0.10472]).

Previously, multiple sets of square brackets were used to separate the
sections, so the above example would have been written as
[0 0 -1 0 0 0 0][0 1][0.10472]. The problem with this is that '][' is
not distinguished in the token parser from '] ['. Combined with the fact
that the later sections are optional, this makes reading pairs or lists
of these objects impossible. The new format is, by comparison,
unambiguously delimited in all contexts.

This change may break the restart of a case in which unit conversion
entries have been written out. It is unlikely to require modification to
an initial configuration as named unit conversion entries (such as [rpm]
and [MPa] and [l/min]) are unaffected.
This commit is contained in:
Will Bainbridge
2024-10-03 10:25:20 +01:00
parent cc05c1b03c
commit a00b947c1d
8 changed files with 346 additions and 195 deletions

View File

@ -227,15 +227,21 @@ public:
// I/O
//- Read, assuming the '[' has already been read
Istream& readNoBegin(Istream& is);
//- Read without the square brackets
Istream& readNoBeginOrEnd(Istream& is);
//- Read
Istream& read(Istream& is);
//- Write without the square brackets
Ostream& writeNoBeginOrEnd(Ostream& os) const;
//- Write
Ostream& write(Ostream& os) const;
//- Write info without the square brackets
Ostream& writeInfoNoBeginOrEnd(Ostream& os) const;
//- Return info proxy
inline InfoProxy<dimensionSet> info() const
{

View File

@ -30,7 +30,7 @@ License
void Foam::dimensionSet::round(const scalar tol)
{
for (int i=0; i < dimensionSet::nDimensions; ++i)
for (int i=0; i<dimensionSet::nDimensions; ++i)
{
scalar integralPart;
scalar fractionalPart = std::modf(exponents_[i], &integralPart);
@ -61,59 +61,47 @@ Foam::dimensionSet::dimensionSet(Istream& is)
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
Foam::Istream& Foam::dimensionSet::readNoBegin(Istream& is)
Foam::Istream& Foam::dimensionSet::readNoBeginOrEnd(Istream& is)
{
// Read next token
token nextToken(is);
token nextToken;
// Peek at the next token
is >> nextToken;
is.putBack(nextToken);
// If not a number, then these are named dimensions. Parse.
if (!nextToken.isNumber())
{
// Named dimensions. Parse.
is.putBack(nextToken);
reset(symbols::parseNoBegin(is, dimless, dimensions()));
reset(symbols::parseNoBeginOrEnd(is, dimless, dimensions()));
return is;
}
// Otherwise these are numbered dimensions. Read directly...
// Read first five dimensions
for (int i=0; i<dimensionSet::CURRENT; i++)
{
is >> exponents_[i];
}
// Peek at the next token
is >> nextToken;
is.putBack(nextToken);
// If next token is another number then read the last two dimensions
// and then read another token for the end of the dimensionSet
if (nextToken.isNumber())
{
is >> exponents_[dimensionSet::CURRENT];
is >> exponents_[dimensionSet::LUMINOUS_INTENSITY];
}
else
{
// Numbered dimensions. Read directly.
// Read first five dimensions
exponents_[dimensionSet::MASS] = nextToken.number();
for (int Dimension=1; Dimension<dimensionSet::CURRENT; Dimension++)
{
is >> exponents_[Dimension];
}
// Read next token
token nextToken(is);
// If next token is another number then read the last two dimensions
// and then read another token for the end of the dimensionSet
if (nextToken.isNumber())
{
exponents_[dimensionSet::CURRENT] = nextToken.number();
is >> nextToken;
exponents_[dimensionSet::LUMINOUS_INTENSITY] = nextToken.number();
is >> nextToken;
}
else
{
exponents_[dimensionSet::CURRENT] = 0;
exponents_[dimensionSet::LUMINOUS_INTENSITY] = 0;
}
// Check end of dimensionSet
if (nextToken != token::END_SQR)
{
FatalIOErrorInFunction(is)
<< "expected a " << token::END_SQR << " in dimensionSet "
<< endl << "in stream " << is.info()
<< exit(FatalIOError);
}
exponents_[dimensionSet::CURRENT] = 0;
exponents_[dimensionSet::LUMINOUS_INTENSITY] = 0;
}
// Check state of Istream
is.check("Istream& operator>>(Istream&, dimensionSet&)");
return is;
}
@ -122,7 +110,6 @@ Foam::Istream& Foam::dimensionSet::read(Istream& is)
{
// Read beginning of dimensionSet
token startToken(is);
if (startToken != token::BEGIN_SQR)
{
FatalIOErrorInFunction(is)
@ -131,7 +118,42 @@ Foam::Istream& Foam::dimensionSet::read(Istream& is)
<< exit(FatalIOError);
}
return readNoBegin(is);
readNoBeginOrEnd(is);
// Read end of dimensionSet
token endToken(is);
if (endToken != token::END_SQR)
{
FatalIOErrorInFunction(is)
<< "expected a " << token::END_SQR << " in dimensionSet "
<< endl << "in stream " << is.info()
<< exit(FatalIOError);
}
// Check state of Istream
is.check("Istream& operator>>(Istream&, dimensionSet&)");
return is;
}
Foam::Ostream& Foam::dimensionSet::writeNoBeginOrEnd(Ostream& os) const
{
if (dimensionless())
{
// Do nothing
}
else
{
for (int i=0; i<dimensionSet::nDimensions-1; i++)
{
os << exponents_[i] << token::SPACE;
}
os << exponents_[dimensionSet::nDimensions-1];
}
return os;
}
@ -139,12 +161,9 @@ Foam::Ostream& Foam::dimensionSet::write(Ostream& os) const
{
os << token::BEGIN_SQR;
for (int d=0; d<dimensionSet::nDimensions-1; d++)
{
os << exponents_[d] << token::SPACE;
}
writeNoBeginOrEnd(os);
os << exponents_[dimensionSet::nDimensions-1] << token::END_SQR;
os << token::END_SQR;
// Check state of Ostream
os.check("Ostream& operator<<(Ostream&, const dimensionSet&)");
@ -153,6 +172,33 @@ Foam::Ostream& Foam::dimensionSet::write(Ostream& os) const
}
Foam::Ostream& Foam::dimensionSet::writeInfoNoBeginOrEnd(Ostream& os) const
{
for (int first=true, i=0; i<dimensionSet::nDimensions; i++)
{
if (mag(exponents_[i]) > dimensionSet::smallExponent)
{
if (!first)
{
os << token::SPACE;
}
os << dimensionSet::dimensionTypeNames_
[static_cast<dimensionSet::dimensionType>(i)];
if (exponents_[i] != 1)
{
os << '^' << exponents_[i];
}
first = false;
}
}
return os;
}
void Foam::writeEntry(Ostream& os, const dimensionSet& value)
{
os << value;
@ -185,30 +231,9 @@ Foam::Ostream& Foam::operator<<(Ostream& os, const dimensionSet& dims)
Foam::Ostream& Foam::operator<<(Ostream& os, const InfoProxy<dimensionSet>& ip)
{
const dimensionSet& dims = ip.t_;
os << token::BEGIN_SQR;
for (int first=true, i=0; i<dimensionSet::nDimensions; i++)
{
if (mag(dims.exponents_[i]) > dimensionSet::smallExponent)
{
if (!first)
{
os << token::SPACE;
}
os << dimensionSet::dimensionTypeNames_
[static_cast<dimensionSet::dimensionType>(i)];
if (dims.exponents_[i] != 1)
{
os << '^' << dims.exponents_[i];
}
first = false;
}
}
ip.t_.writeInfoNoBeginOrEnd(os);
os << token::END_SQR;

View File

@ -72,8 +72,8 @@ bool Foam::Function1s::unitConversions::readIfPresent
ITstream& is = entryPtr->stream();
is.readBegin("unitConversions");
x.readIfPresent(keyword, dict, is);
value.readIfPresent(keyword, dict, is);
x.read(keyword, dict, is);
value.read(keyword, dict, is);
is.readEnd("unitConversions");
return true;

View File

@ -90,6 +90,7 @@ Foam::token Foam::symbols::tokeniser::nextToken()
if (size_ == 0)
{
token t(is_);
if (t.isWord())
{
splitWord(t.wordToken());
@ -175,16 +176,17 @@ bool Foam::symbols::tokeniser::valid(char c)
!isspace(c)
&& c != '"' // string quote
&& c != '\'' // string quote
&& c != '/' // div
&& c != '/' // divide
&& c != ';' // end statement
&& c != '{' // beg subdict
&& c != '}' // end subdict
&& c != '(' // beg expr
&& c != ')' // end expr
&& c != '[' // beg dim
&& c != ']' // end dim
&& c != '{' // begin sub-dictionary
&& c != '}' // end sub-dictionary
&& c != '(' // begin expression
&& c != ')' // end expression
&& c != '[' // begin dimensions/units
&& c != ']' // end dimensions/units
&& c != ':' // separate dimensions/units
&& c != '^' // power
&& c != '*' // mult
&& c != '*' // multiply
);
}

View File

@ -124,10 +124,11 @@ Type parseNoBegin
const HashTable<Type>& table
);
//- Parse a dimension set or unit conversion, assuming that the '[' has already
// been read
//- Parse tokens into a dimension set or unit conversion, assuming that the '['
// has already been read, and ending before the '[' or ':'. Note that this
// will leave the stream with a pushed back token.
template<class Type>
Type parseNoBegin
Type parseNoBeginOrEnd
(
Istream& is,
const Type& identity,

View File

@ -76,12 +76,19 @@ Type Foam::symbols::parseNoBegin
tis.putBack(nextToken);
return result;
}
else if (nextToken.pToken() == token::COLON)
{
// End the units
tis.putBack(nextToken);
return result;
}
else if (nextToken.pToken() == token::BEGIN_LIST)
{
// Parenthesis. Evaluate the sub-units and multiply.
result.reset
(
result*parseNoBegin(nextPrior, tis, identity, table)
result
*parseNoBegin(nextPrior, tis, identity, table)
);
// Check that the parentheses end
@ -108,7 +115,8 @@ Type Foam::symbols::parseNoBegin
// This has priority. Evaluate the next units and multiply.
result.reset
(
result*parseNoBegin(nextPrior, tis, identity, table)
result
*parseNoBegin(nextPrior, tis, identity, table)
);
}
else
@ -127,7 +135,8 @@ Type Foam::symbols::parseNoBegin
{
result.reset
(
result/parseNoBegin(nextPrior, tis, identity, table)
result
/parseNoBegin(nextPrior, tis, identity, table)
);
}
else
@ -177,6 +186,7 @@ Type Foam::symbols::parseNoBegin
}
nextToken = tis.nextToken();
if (nextToken.error())
{
break;
@ -195,7 +205,7 @@ Type Foam::symbols::parseNoBegin
template<class Type>
Type Foam::symbols::parseNoBegin
Type Foam::symbols::parseNoBeginOrEnd
(
Istream& is,
const Type& identity,
@ -204,7 +214,11 @@ Type Foam::symbols::parseNoBegin
{
symbols::tokeniser tis(is);
return parseNoBegin(0, tis, identity, table);
Type result = parseNoBegin(0, tis, identity, table);
is.putBack(tis.nextToken());
return result;
}

View File

@ -219,6 +219,12 @@ public:
//- Update
void read(const word& keyword, const dictionary&);
//- Update
void read(Istream& is);
//- Update
void read(const word& keyword, const dictionary&, Istream& is);
//- Update if found in the dictionary
bool readIfPresent(const word& keyword, const dictionary&);

View File

@ -47,6 +47,23 @@ void Foam::unitConversion::read(const word& keyword, const dictionary& dict)
if (!compare(*this, units, false))
{
FatalIOErrorInFunction(dict)
<< "The units " << units.info() << " of " << keyword
<< " in dictionary " << dict.name() << " do not match "
<< "the required units " << info()
<< abort(FatalIOError);
}
reset(units);
}
void Foam::unitConversion::read(Istream& is)
{
const unitConversion units(is);
if (!compare(*this, units, false))
{
FatalIOErrorInFunction(is)
<< "The units " << units.info() << " provided do not match "
<< "the required units " << info()
<< abort(FatalIOError);
@ -56,6 +73,28 @@ void Foam::unitConversion::read(const word& keyword, const dictionary& dict)
}
void Foam::unitConversion::read
(
const word& keyword,
const dictionary& dict,
Istream& is
)
{
const unitConversion units(is);
if (!compare(*this, units, false))
{
FatalIOErrorInFunction(dict)
<< "The units " << units.info() << " of " << keyword
<< " in dictionary " << dict.name() << " do not match "
<< "the required units " << info()
<< abort(FatalIOError);
}
reset(units);
}
bool Foam::unitConversion::readIfPresent
(
const word& keyword,
@ -71,7 +110,8 @@ bool Foam::unitConversion::readIfPresent
if (!compare(*this, units, false))
{
FatalIOErrorInFunction(dict)
<< "The units " << units.info() << " provided do not match "
<< "The units " << units.info() << " of " << keyword
<< " in dictionary " << dict.name() << " do not match "
<< "the required units " << info()
<< abort(FatalIOError);
}
@ -102,24 +142,24 @@ bool Foam::unitConversion::readIfPresent(Istream& is)
if (nextToken != token::BEGIN_SQR) return false;
unitConversion u(is);
const unitConversion units(is);
if (!unitConversion::compare(u, *this, false))
if (!unitConversion::compare(units, *this, false))
{
FatalIOErrorInFunction(is)
<< "The units " << u.info() << " provided do not match "
<< "The units " << units.info() << " provided do not match "
<< "the required units " << info()
<< abort(FatalIOError);
}
if (debug && (any() || !unitConversion::compare(u, *this, true)))
if (debug && (any() || !unitConversion::compare(units, *this, true)))
{
Info<< "Unit conversion at line " << is.lineNumber()
<< " of file " << is.name()
<< " with factor " << u.multiplier_ << endl;
<< " with factor " << units.multiplier_ << endl;
}
reset(u);
reset(units);
return true;
}
@ -137,25 +177,25 @@ bool Foam::unitConversion::readIfPresent
if (nextToken != token::BEGIN_SQR) return false;
unitConversion u(is);
const unitConversion units(is);
if (!unitConversion::compare(u, *this, false))
if (!unitConversion::compare(units, *this, false))
{
FatalIOErrorInFunction(is)
<< "The units " << u.info() << " of " << keyword
FatalIOErrorInFunction(dict)
<< "The units " << units.info() << " of " << keyword
<< " in dictionary " << dict.name() << " do not match "
<< "the required units " << info()
<< abort(FatalIOError);
}
if (debug && (any() || !unitConversion::compare(u, *this, true)))
if (debug && (any() || !unitConversion::compare(units, *this, true)))
{
Info<< "Unit conversion of " << keyword
<< " in dictionary " << dict.name()
<< " with factor " << u.multiplier_ << endl;
<< " with factor " << units.multiplier_ << endl;
}
reset(u);
reset(units);
return true;
}
@ -165,95 +205,133 @@ bool Foam::unitConversion::readIfPresent
Foam::Istream& Foam::operator>>(Istream& is, unitConversion& units)
{
// Read beginning of unitConversion
token startToken(is);
token nextToken;
if (startToken != token::BEGIN_SQR)
// Read the next delimiting token. This must be the start bracket.
is >> nextToken;
if (nextToken != token::BEGIN_SQR)
{
FatalIOErrorInFunction(is)
<< "expected a " << token::BEGIN_SQR << " in unitConversion"
<< endl << "in stream " << is.info()
<< exit(FatalIOError);
<< endl << "in stream " << is.info() << ", got a "
<< nextToken << exit(FatalIOError);
}
// Peek at the next token
token nextToken(is);
is >> nextToken;
is.putBack(nextToken);
if (!nextToken.isNumber())
// If not a number or separator, then these are named units. Parse.
if (!nextToken.isNumber() && nextToken != token::COLON)
{
// Named units. Parse.
units.reset(symbols::parseNoBegin(is, unitless, Foam::units()));
units.reset(symbols::parseNoBeginOrEnd(is, unitless, Foam::units()));
// Read the next delimiting token. This must be the end bracket.
is >> nextToken;
if (nextToken != token::END_SQR)
{
FatalIOErrorInFunction(is)
<< "expected a " << token::END_SQR << " in unitConversion "
<< endl << "in stream " << is.info() << ", got a "
<< nextToken << exit(FatalIOError);
}
// Check state of Istream
is.check("Istream& operator>>(Istream&, unitConversion&)");
return is;
}
else
// Otherwise these are numbered units. Read directly...
// Read the dimensions
units.dimensions_.readNoBeginOrEnd(is);
// Read the next delimiting token. If a separator, then there are
// dimensionless units and a multiplier to read. If it is an end bracket,
// then the dimensionless units are zero and the multiplier is one and the
// parsing is finished. Otherwise the parsing has failed.
is >> nextToken;
if (nextToken == token::COLON)
{
// Read the dimensions
units.dimensions_.readNoBegin(is);
// Peek at the next token
is >> nextToken;
is.putBack(nextToken);
// Read the dimensionless units if present, or set to zero
token nextToken;
if (!is.eof())
{
is >> nextToken;
}
if (nextToken == token::BEGIN_SQR)
if (!nextToken.isNumber())
{
for (int i = 0; i < unitConversion::nDimlessUnits; ++ i)
{
is >> units.exponents_[i];
}
// Check end of dimensionless units
token endToken(is);
if (endToken != token::END_SQR)
{
FatalIOErrorInFunction(is)
<< "expected a " << token::END_SQR
<< " in unitConversion " << endl << "in stream "
<< is.info() << exit(FatalIOError);
}
// Read the multiplier if present, or set to unity
token nextToken;
if (!is.eof())
{
is >> nextToken;
}
if (nextToken == token::BEGIN_SQR)
{
is >> units.multiplier_;
// Check end of multiplier
token endToken(is);
if (endToken != token::END_SQR)
{
FatalIOErrorInFunction(is)
<< "expected a " << token::END_SQR
<< " in unitConversion " << endl << "in stream "
<< is.info() << exit(FatalIOError);
}
}
else
{
units.multiplier_ = 1;
if (!nextToken.undefined())
{
is.putBack(nextToken);
}
units.exponents_[i] = 0;
}
}
else
{
for (int i = 0; i < unitConversion::nDimlessUnits; ++ i)
{
units.exponents_[i] = 0;
}
units.multiplier_ = 1;
if (!nextToken.undefined())
{
is.putBack(nextToken);
is >> units.exponents_[i];
}
}
// Read the next delimiting token. If a separator then there is a
// multiplier to read. If it is an end bracket then the multiplier is
// one and the parsing is finished. Otherwise the parsing has failed.
is >> nextToken;
if (nextToken == token::COLON)
{
// Peek at the next token
is >> nextToken;
is.putBack(nextToken);
// Read the multiplier if present, or set to unity
if (!nextToken.isNumber())
{
units.multiplier_ = 1;
}
else
{
is >> units.multiplier_;
}
// Read the next delimiting token. This must be the end bracket.
is >> nextToken;
if (nextToken != token::END_SQR)
{
FatalIOErrorInFunction(is)
<< "expected a " << token::END_SQR << " in unitConversion "
<< endl << "in stream " << is.info() << ", got a "
<< nextToken << exit(FatalIOError);
}
}
else if (nextToken == token::END_SQR)
{
units.multiplier_ = 1;
}
else
{
FatalIOErrorInFunction(is)
<< "expected a " << token::END_SQR << " or a " << token::COLON
<< " in unitConversion " << endl << "in stream " << is.info()
<< ", got a " << nextToken << exit(FatalIOError);
}
}
else if (nextToken == token::END_SQR)
{
for (int i = 0; i < unitConversion::nDimlessUnits; ++ i)
{
units.exponents_[i] = 0;
}
units.multiplier_ = 1;
}
else
{
FatalIOErrorInFunction(is)
<< "expected a " << token::END_SQR << " or a " << token::COLON
<< " in unitConversion " << endl << "in stream " << is.info()
<< ", got a " << nextToken << exit(FatalIOError);
}
// Check state of Istream
@ -265,8 +343,11 @@ Foam::Istream& Foam::operator>>(Istream& is, unitConversion& units)
Foam::Ostream& Foam::operator<<(Ostream& os, const unitConversion& units)
{
// Write the start
os << token::BEGIN_SQR;
// Write the dimensions
os << units.dimensions_;
units.dimensions_.writeNoBeginOrEnd(os);
// Determine if any dimensionless units are non-zero
bool nonZeroDimlessUnits = false;
@ -280,25 +361,36 @@ Foam::Ostream& Foam::operator<<(Ostream& os, const unitConversion& units)
// Determine if the multiplier is non-unity
bool nonUnityMultiplier = units.multiplier_ != 1;
// Write the dimensionless units if any are non-zero or we have a
// multiplier to write out afterwards
// Write a separator if there is anything to follow
if (nonZeroDimlessUnits || nonUnityMultiplier)
{
os << token::BEGIN_SQR;
os << token::SPACE << token::COLON;
}
// Write the dimensionless units if any are non-zero
if (nonZeroDimlessUnits)
{
for (int i = 0; i < unitConversion::nDimlessUnits; ++ i)
{
if (i) os << token::SPACE;
os << units.exponents_[i];
os << token::SPACE << units.exponents_[i];
}
os << token::END_SQR;
}
// Write a separator if there is anything to follow
if (nonUnityMultiplier)
{
os << token::SPACE << token::COLON;
}
// Write the multiplier if it is non-unity
if (nonUnityMultiplier)
{
os << token::BEGIN_SQR << units.multiplier_ << token::END_SQR;
os << token::SPACE << units.multiplier_;
}
// Write the end
os << token::END_SQR;
// Check state of Ostream
os.check("Ostream& operator<<(Ostream&, const unitConversion&)");
@ -324,8 +416,11 @@ Foam::Ostream& Foam::operator<<
return os << token::BEGIN_SQR << "<none>" << token::END_SQR;
}
// Write the start
os << token::BEGIN_SQR;
// Write the dimensions
os << units.dimensions_.info();
units.dimensions_.writeInfoNoBeginOrEnd(os);
// Determine if any dimensionless units are non-zero
bool nonZeroDimlessUnits = false;
@ -339,43 +434,45 @@ Foam::Ostream& Foam::operator<<
// Determine if the multiplier is non-unity
bool nonUnityMultiplier = units.multiplier_ != 1;
// Write the dimensionless units if any are non-zero or we have a
// multiplier to write out afterwards
// Write a separator if there is anything to follow
if (nonZeroDimlessUnits || nonUnityMultiplier)
{
// Write the dimensionless units
os << token::BEGIN_SQR;
os << token::SPACE << token::COLON;
}
for (int first=true, i=0; i<unitConversion::nDimlessUnits; i++)
// Write the dimensionless units if any are non-zero
if (nonZeroDimlessUnits)
{
for (int i=0; i<unitConversion::nDimlessUnits; i++)
{
if (mag(units.exponents_[i]) > unitConversion::smallExponent)
{
if (!first)
{
os << token::SPACE;
}
os << unitConversion::dimlessUnitTypeNames_
os << token::SPACE << unitConversion::dimlessUnitTypeNames_
[static_cast<unitConversion::dimlessUnitType>(i)];
if (units.exponents_[i] != 1)
{
os << '^' << units.exponents_[i];
}
first = false;
}
}
}
os << token::END_SQR;
// Write a separator if there is anything to follow
if (nonUnityMultiplier)
{
os << token::SPACE << token::COLON;
}
// Write the multiplier if it is non-unity
if (nonUnityMultiplier)
{
os << token::BEGIN_SQR << units.multiplier_ << token::END_SQR;
os << token::SPACE << units.multiplier_;
}
// Write the end
os << token::END_SQR;
// Check state of Ostream
os.check("Ostream& operator<<(Ostream&, const InfoProxy<unitConversion>&)");