ENH: improve 'fuzzy' matching of time instant

- add '<=' and '>=' operators that include rounding.

  This means that typical code like the following would not have
  considered any rounding:

    instantList times = ...;
    scalar val = ..;

    // No rounding!
    if (times[i].value() <= val) { ... }

    // NEW: with rounding
    if (times[i] <= val) { ... }

    // old equivalent:
    if (times[i].value() <= val || times[i].equal(val)) { ... }

ENH: provide a default stopInstance for fileOperation::findInstance

- makes it more consistent with Time::findInstance and more convenient
  to use

STYLE: minor code style changes to TimePaths etc
This commit is contained in:
Mark Olesen
2025-10-01 17:28:10 +02:00
parent 9c4d0cdeef
commit a91587e36a
16 changed files with 203 additions and 111 deletions

View File

@ -1,3 +1,3 @@
Test-instant.C
Test-instant.cxx
EXE = $(FOAM_USER_APPBIN)/Test-instant

View File

@ -79,8 +79,11 @@ int main(int argc, char *argv[])
}
Info<< nl << "times:" << times << nl;
sort(times);
labelList order(Foam::sortedOrder(times));
Foam::sort(times);
Info<< "Sorted:" << times << nl;
Info<< "order:" << flatOutput(order) << nl;
for (const scalar val : { -0.5, 5.0, 18.0, 25.0, 450.0, 480.0 })
{
@ -105,10 +108,29 @@ int main(int argc, char *argv[])
files.emplace_back(10, "ten");
Info<< nl << "files:" << files << nl;
sort(files);
Foam::sort(files);
Info<< "Sorted:" << files << nl;
{
const auto& a = times[3];
scalar b = 10;
Info<< "compare (" << a << ") <= (" << b << ") => "
<< a.less_equal(10) << nl;
Info<< "compare (" << a << ") >= (" << b << ") => "
<< a.greater_equal(10) << nl;
}
{
const auto& a = times[3];
const auto& b = files[4];
Info<< "compare (" << a << ") <= (" << b << ") => "
<< (a <= b) << nl;
Info<< "compare (" << a << ") >= (" << b << ") => "
<< (a >= b) << nl;
}
Info<< "\nEnd\n" << endl;
return 0;

View File

@ -737,15 +737,18 @@ Foam::word Foam::Time::findInstance
const bool constant_fallback
) const
{
// Note: name can empty (ie, search for directory only)
// Note: name can be empty (ie, search for directory only)
IOobject startIO(name, timeName(), directory, *this, rOpt);
// Searching starts based on the current output time
scalar startValue = timeOutputValue();
IOobject io
(
fileHandler().findInstance
(
startIO,
timeOutputValue(),
startValue,
stopInstance,
constant_fallback
)

View File

@ -6,7 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2019 OpenFOAM Foundation
Copyright (C) 2016-2024 OpenCFD Ltd.
Copyright (C) 2016-2025 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -42,14 +42,13 @@ SourceFiles
#define Foam_Time_H
#include "TimePaths.H"
#include "TimeState.H"
#include "objectRegistry.H"
#include "unwatchedIOdictionary.H"
#include "FIFOStack.H"
#include "clock.H"
#include "cpuTime.H"
#include "TimeState.H"
#include "Switch.H"
#include "instantList.H"
#include "Enum.H"
#include "typeInfo.H"
#include "dlLibraryTable.H"
@ -377,7 +376,7 @@ public:
// TimeState Functions
//- Return the current time name
//- The current time name
using TimeState::timeName;
//- Return a time name for the given scalar time value
@ -390,27 +389,20 @@ public:
//- Use name from objectRegistry, not TimePaths
using objectRegistry::name;
//- Return the rootPath
//- The root path
using TimePaths::rootPath;
//- Return global case name
//- The global case name
using TimePaths::globalCaseName;
//- Return case name
//- The case name
using TimePaths::caseName;
//- Return path = rootPath/caseName. Same as TimePaths::path()
fileName path() const
{
return TimePaths::rootPath()/TimePaths::caseName();
}
//- The path for the case = rootPath/caseName
using TimePaths::path;
//- Return global path for the case = rootPath/globalCaseName.
//- Same as TimePaths::globalPath()
fileName globalPath() const
{
return TimePaths::rootPath()/TimePaths::globalCaseName();
}
//- The global path for the case = rootPath/globalCaseName
using TimePaths::globalPath;
//- Return current time path = path/timeName
fileName timePath() const

View File

@ -6,7 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2013 OpenFOAM Foundation
Copyright (C) 2016-2024 OpenCFD Ltd.
Copyright (C) 2016-2025 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -80,7 +80,7 @@ Foam::TimePaths::TimePaths
distributed_(distributed),
rootPath_(rootPath),
globalCaseName_(globalCaseName),
case_(caseName),
caseName_(caseName),
system_(systemDirName),
constant_(constantDirName)
{
@ -195,7 +195,7 @@ Foam::label Foam::TimePaths::findClosestTimeIndex
{
if (timeDirs[timei].name() == constantDirName) continue;
const scalar diff = mag(timeDirs[timei].value() - t);
const scalar diff = Foam::mag(timeDirs[timei].value() - t);
if (diff < deltaT)
{
deltaT = diff;
@ -237,7 +237,7 @@ Foam::instant Foam::TimePaths::findClosestTime(const scalar t) const
for (label timei=1; timei < nTimes; ++timei)
{
const scalar diff = mag(timeDirs[timei].value() - t);
const scalar diff = Foam::mag(timeDirs[timei].value() - t);
if (diff < deltaT)
{
deltaT = diff;

View File

@ -6,7 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2014 OpenFOAM Foundation
Copyright (C) 2016-2023 OpenCFD Ltd.
Copyright (C) 2016-2025 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -57,13 +57,25 @@ class TimePaths
{
// Private Data
//- True if this is a processor case
bool processorCase_;
//- True running with distributed directories
bool distributed_;
//- The root path
const fileName rootPath_;
//- The global case name
fileName globalCaseName_;
fileName case_;
//- The local (processor) case name
fileName caseName_;
//- The name for the "system" directory
const word system_;
//- The name for the "constant" directory
const word constant_;

View File

@ -5,7 +5,7 @@
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2018-2023 OpenCFD Ltd.
Copyright (C) 2018-2025 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -84,13 +84,13 @@ inline const Foam::fileName& Foam::TimePaths::globalCaseName() const noexcept
inline const Foam::fileName& Foam::TimePaths::caseName() const noexcept
{
return case_;
return caseName_;
}
inline Foam::fileName& Foam::TimePaths::caseName() noexcept
{
return case_;
return caseName_;
}

View File

@ -5,7 +5,7 @@
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2018-2022 OpenCFD Ltd.
Copyright (C) 2018-2025 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -64,12 +64,17 @@ public:
// Public Classes
//- Less function for sorting
//- Less function for sorting. Compares values only
struct less
{
bool operator()(const Instant& a, const Instant& b) const noexcept
template<class T1, class T2>
bool operator()
(
const Instant<T1>& a,
const Instant<T2>& b
) const noexcept
{
return a.value() < b.value();
return (a.value() < b.value());
}
};
@ -128,23 +133,102 @@ public:
T& name() noexcept { return key_; }
//- True if values are equal (includes SMALL for rounding)
bool equal(scalar val) const noexcept
// Comparison Methods (Static)
//- Are values equal within (SMALL) rounding tolerance?
static constexpr bool equal_to(scalar a, scalar b) noexcept
{
return ((val_ > val - SMALL) && (val_ < val + SMALL));
return ((a > b - SMALL) && (a < b + SMALL));
}
//- True if values are equal (includes SMALL for rounding)
//- Are values less-equal within (SMALL) rounding tolerance?
static constexpr bool less_equal(scalar a, scalar b) noexcept
{
return (a < b + SMALL);
}
//- Are values greater_equal within (SMALL) rounding tolerance?
static constexpr bool greater_equal(scalar a, scalar b) noexcept
{
return (a > b - SMALL);
}
// Comparison Methods
//- Is instant value equal to \p val within (SMALL) rounding?
bool equal(scalar val) const noexcept
{
return equal_to(this->value(), val);
}
//- Is instant value equal to \p other within (SMALL) rounding?
template<class T2>
bool equal(const Instant<T2>& other) const noexcept
{
return (*this).equal(other.value());
return equal_to(this->value(), other.value());
}
//- Is instant less-equal than \p val within (SMALL) rounding?
bool less_equal(scalar val) const noexcept
{
return less_equal(this->value(), val);
}
//- Is instant less-equal than \p other within (SMALL) rounding?
template<class T2>
bool less_equal(const Instant<T2>& other) const noexcept
{
return less_equal(this->value(), other.value());
}
//- Is instant greater-equal than \p val within (SMALL) rounding?
bool greater_equal(scalar val) const noexcept
{
return less_equal(val, this->value());
}
//- Is instant greater-equal than \p other within (SMALL) rounding?
template<class T2>
bool greater_equal(const Instant<T2>& other) const noexcept
{
return less_equal(other.value(), this->value());
}
// Member Operators
//- Is instant less-equal than \p val within (SMALL) rounding?
bool operator<=(scalar val) const noexcept
{
return less_equal(this->value(), val);
}
//- Is instant less-equal than \p other within (SMALL) rounding?
template<class T2>
bool operator<=(const Instant<T2>& other) const noexcept
{
return less_equal(this->value(), other.value());
}
//- Is instant greater-equal than \p val within (SMALL) rounding?
bool operator>=(scalar val) const noexcept
{
return greater_equal(this->value(), val);
}
//- Is instant greater-equal than \p other within (SMALL) rounding?
template<class T2>
bool operator>=(const Instant<T2>& other) const noexcept
{
return greater_equal(this->value(), other.value());
}
};
// * * * * * * * * * * * * * * * Global Operators * * * * * * * * * * * * * //
//- True if instant values are equal within rounding tolerance
template<class T1, class T2>
inline bool operator==(const Instant<T1>& a, const Instant<T2>& b) noexcept
{
@ -152,6 +236,7 @@ inline bool operator==(const Instant<T1>& a, const Instant<T2>& b) noexcept
}
//- True if instant values are not equal within rounding tolerance
template<class T1, class T2>
inline bool operator!=(const Instant<T1>& a, const Instant<T2>& b) noexcept
{

View File

@ -1099,7 +1099,7 @@ Foam::IOobject Foam::fileOperation::findInstance
// Backward search for first time that is <= startValue
for (; instIndex >= 0; --instIndex)
{
if (ts[instIndex].value() <= startValue)
if (ts[instIndex] <= startValue)
{
break;
}

View File

@ -887,7 +887,7 @@ public:
const IOobject& io,
const scalar startValue,
//! The search stop instance
const word& stopInstance,
const word& stopInstance = "",
//! Return \c "constant" instead of \c "" if the search failed
const bool constant_fallback = true
) const;

View File

@ -187,14 +187,17 @@ Foam::fileOperations::masterUncollatedFileOperation::filePathInfo
// Check for approximately same time. E.g. if time = 1e-2 and
// directory is 0.01 (due to different time formats)
const auto pathFnd = times_.cfind(io.time().path());
if (search && pathFnd.good())
if
(
const auto* instPtr = times_.get(io.time().path());
search && instPtr
)
{
newInstancePath =
Time::findInstancePath
(
*pathFnd(),
*instPtr, // instantList (cached)
instant(io.instance())
);
@ -1506,7 +1509,7 @@ Foam::fileOperations::masterUncollatedFileOperation::findInstance
// Backward search for first time that is <= startValue
for (; instIndex >= 0; --instIndex)
{
if (ts[instIndex].value() <= startValue)
if (ts[instIndex] <= startValue)
{
break;
}
@ -2283,16 +2286,15 @@ Foam::instantList Foam::fileOperations::masterUncollatedFileOperation::findTimes
const word& constantName
) const
{
const auto iter = times_.cfind(directory);
if (iter.good())
if (const auto* instPtr = times_.get(directory); instPtr)
{
if (debug)
{
Pout<< "masterUncollatedFileOperation::findTimes :"
<< " Found " << iter.val()->size() << " cached times" << nl
<< " Found " << instPtr->size() << " cached times" << nl
<< " for directory:" << directory << endl;
}
return *(iter.val());
return *instPtr;
}
else
{
@ -2350,9 +2352,7 @@ void Foam::fileOperations::masterUncollatedFileOperation::setTime
// Mutable access to instant list for modification and sorting
// - cannot use auto type deduction here
auto iter = times_.find(tm.path());
if (iter.good())
if (auto iter = times_.find(tm.path()); iter.good())
{
DynamicList<instant>& times = *(iter.val());
@ -2372,16 +2372,9 @@ void Foam::fileOperations::masterUncollatedFileOperation::setTime
if (times.size() <= startIdx || times.last() < timeNow)
{
times.append(timeNow);
times.push_back(timeNow);
}
else if
(
findSortedIndex
(
SubList<instant>(times, times.size()-startIdx, startIdx),
timeNow
) < 0
)
else if (findSortedIndex(times, timeNow, startIdx) < 0)
{
if (debug)
{
@ -2390,12 +2383,9 @@ void Foam::fileOperations::masterUncollatedFileOperation::setTime
<< " for case:" << tm.path() << endl;
}
times.append(timeNow);
times.push_back(timeNow);
SubList<instant> realTimes
(
times, times.size()-startIdx, startIdx
);
auto realTimes = times.slice(startIdx);
Foam::stableSort(realTimes);
}
}

View File

@ -765,7 +765,7 @@ public:
const IOobject& io,
const scalar startValue,
//! The search stop instance
const word& stopInstance,
const word& stopInstance = "",
//! Return \c "constant" instead of \c "" if the search failed
const bool constant_fallback = true
) const;

View File

@ -83,11 +83,11 @@ Foam::fileName Foam::fileFormats::edgeMeshFormatsCore::findMeshInstance
// closest to and lower than current time
instantList ts = t.times();
label instanceI;
label instanceI = (ts.size()-1);
for (instanceI = ts.size()-1; instanceI >= 0; --instanceI)
for (; instanceI >= 0; --instanceI)
{
if (ts[instanceI].value() <= t.timeOutputValue())
if (ts[instanceI] <= t.timeOutputValue())
{
break;
}
@ -96,14 +96,11 @@ Foam::fileName Foam::fileFormats::edgeMeshFormatsCore::findMeshInstance
// Noting that the current directory has already been searched
// for mesh data, start searching from the previously stored time directory
if (instanceI >= 0)
for (label i = instanceI; i >= 0; --i)
{
for (label i = instanceI; i >= 0; --i)
if (isFile(t.path()/ts[i].name()/localName))
{
if (isFile(t.path()/ts[i].name()/localName))
{
return ts[i].name();
}
return ts[i].name();
}
}
@ -123,11 +120,11 @@ Foam::fileName Foam::fileFormats::edgeMeshFormatsCore::findMeshFile
// closest to and lower than current time
instantList ts = t.times();
label instanceI;
label instanceI = (ts.size()-1);
for (instanceI = ts.size()-1; instanceI >= 0; --instanceI)
for (; instanceI >= 0; --instanceI)
{
if (ts[instanceI].value() <= t.timeOutputValue())
if (ts[instanceI] <= t.timeOutputValue())
{
break;
}
@ -136,16 +133,13 @@ Foam::fileName Foam::fileFormats::edgeMeshFormatsCore::findMeshFile
// Noting that the current directory has already been searched
// for mesh data, start searching from the previously stored time directory
if (instanceI >= 0)
for (label i = instanceI; i >= 0; --i)
{
for (label i = instanceI; i >= 0; --i)
{
fileName testName(t.path()/ts[i].name()/localName);
fileName testName(t.path()/ts[i].name()/localName);
if (isFile(testName))
{
return testName;
}
if (isFile(testName))
{
return testName;
}
}

View File

@ -277,7 +277,7 @@ Foam::word Foam::distributedTriSurfaceMesh::findLocalInstance
// Backward search for first time that is <= startValue
for (; instIndex >= 0; --instIndex)
{
if (ts[instIndex].value() <= startValue)
if (ts[instIndex] <= startValue)
{
break;
}

View File

@ -102,11 +102,11 @@ Foam::fileName Foam::fileFormats::surfaceFormatsCore::findMeshInstance
// closest to and lower than current time
instantList ts = t.times();
label instanceI;
label instanceI = (ts.size()-1);
for (instanceI = ts.size()-1; instanceI >= 0; --instanceI)
for (; instanceI >= 0; --instanceI)
{
if (ts[instanceI].value() <= t.timeOutputValue())
if (ts[instanceI] <= t.timeOutputValue())
{
break;
}
@ -115,14 +115,11 @@ Foam::fileName Foam::fileFormats::surfaceFormatsCore::findMeshInstance
// Noting that the current directory has already been searched
// for mesh data, start searching from the previously stored time directory
if (instanceI >= 0)
for (label i = instanceI; i >= 0; --i)
{
for (label i = instanceI; i >= 0; --i)
if (isFile(t.path()/ts[i].name()/localName))
{
if (isFile(t.path()/ts[i].name()/localName))
{
return ts[i].name();
}
return ts[i].name();
}
}
@ -142,11 +139,11 @@ Foam::fileName Foam::fileFormats::surfaceFormatsCore::findMeshFile
// closest to and lower than current time
instantList ts = t.times();
label instanceI;
label instanceI = (ts.size()-1);
for (instanceI = ts.size()-1; instanceI >= 0; --instanceI)
for (; instanceI >= 0; --instanceI)
{
if (ts[instanceI].value() <= t.timeOutputValue())
if (ts[instanceI] <= t.timeOutputValue())
{
break;
}
@ -155,16 +152,13 @@ Foam::fileName Foam::fileFormats::surfaceFormatsCore::findMeshFile
// Noting that the current directory has already been searched
// for mesh data, start searching from the previously stored time directory
if (instanceI >= 0)
for (label i = instanceI; i >= 0; --i)
{
for (label i = instanceI; i >= 0; --i)
{
fileName testName(t.path()/ts[i].name()/localName);
fileName testName(t.path()/ts[i].name()/localName);
if (isFile(testName))
{
return testName;
}
if (isFile(testName))
{
return testName;
}
}

View File

@ -83,7 +83,7 @@ Foam::fileName Foam::triSurface::triSurfInstance(const Time& d)
for (i=ts.size()-1; i>=0; i--)
{
if (ts[i].value() <= d.timeOutputValue())
if (ts[i] <= d.timeOutputValue())
{
break;
}