Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
bd3565a
Investigations into cahcing equivalent variables lookup.
hsorby May 19, 2026
ea06360
Investigations into cahcing equivalent variables lookup.
hsorby May 19, 2026
b354b8e
Merge branch 'cache-equiv-vars' into issue1396
hsorby May 19, 2026
db5ea70
Progress in improving caching for model analysis.
hsorby May 20, 2026
89e46df
Fix code formatting issues.
hsorby Jun 15, 2026
0e7345d
Merge branch 'main' into issue1396
hsorby Jun 15, 2026
b590367
Merge branch 'main' into issue1396
hsorby Jun 17, 2026
fa252bb
Add search optional parameter to equivalenceVariableId variable API f…
hsorby Jun 18, 2026
fdc2e32
Make use of search parameter in equivalenceVariableId to reduce time …
hsorby Jun 18, 2026
f16c9ab
Implement a group staged cache for the analyzer equivalent variables.
hsorby Jun 18, 2026
204c5c4
Merge in changes from origin/issue1396 fixing conflicts.
hsorby Jun 18, 2026
f5e6df8
Remove deleted unionfind.h file from list of headers.
hsorby Jun 18, 2026
92a18de
Tidy up use of raw component ptr pair type.
hsorby Jun 18, 2026
530e44c
Merge in changes from prime/main fixing conflicts.
hsorby Jun 18, 2026
7d16ceb
Fix code formatting.
hsorby Jun 18, 2026
bd3f06f
Fix spelling errors.
hsorby Jun 18, 2026
6403793
Remove function connectionIdMap that is no longer required.
hsorby Jun 18, 2026
cb2ddb9
Rework detection of equivalence connection id.
hsorby Jun 19, 2026
557ce74
Add test to check that connection id is fully cleared when equivalenc…
hsorby Jun 20, 2026
55be869
Remove investigations tests.
hsorby Jun 20, 2026
654cfba
Fix code formatting.
hsorby Jun 20, 2026
4758562
Tidy the code changes.
hsorby Jun 20, 2026
1b27b2d
Remove unused type definition.
hsorby Jun 20, 2026
745af54
Fix code formatting errors.
hsorby Jun 20, 2026
298fb51
Variable: slight update to an API description.
agarny Jun 21, 2026
797d696
Analyser: some minor cleaning up.
agarny Jun 21, 2026
ed52008
Analyser: slight improvement (one lookup instead of two).
agarny Jun 21, 2026
cd80609
Test utils: reverted changes made to fileContents().
agarny Jun 21, 2026
5713d32
AnalyserModel: various speed improvements.
agarny Jun 21, 2026
4906bb8
AnalyserModel: replaced `equivalentVariableGroups` with `groupCount`.
agarny Jun 21, 2026
2ee4d24
AnalyserModel: replaced some double hash lookups with one.
agarny Jun 21, 2026
65b8902
AnalyserModel: slight improvements to `typeAsString()` and `hasExtern…
agarny Jun 21, 2026
7fdb63a
Validator: make gatherComponents() iterative rather than recursive.
agarny Jun 22, 2026
5e34db3
Validator: improved the populating of connectionIds.
agarny Jun 22, 2026
9abadbc
Variable: some minor cleaning up.
agarny Jun 22, 2026
ac17c72
Merge branch 'main' into issue1396
agarny Jun 22, 2026
1cbd582
AnalyserModel: slight improvements to `typeAsString()` and `hasExtern…
agarny Jun 21, 2026
5bf9973
Validator: make gatherComponents() iterative rather than recursive.
agarny Jun 22, 2026
bebf51d
Validator: improved the populating of connectionIds.
agarny Jun 22, 2026
60a44e4
Variable: some minor cleaning up.
agarny Jun 22, 2026
2a81832
Merge branch 'issue1396' of https://github.com/hsorby/libcellml into …
agarny Jun 22, 2026
59a5a0b
Addressed a coverage issue.
agarny Jun 22, 2026
d3e759d
Remove some redundant code.
hsorby Jun 22, 2026
eb5add2
Merge branch 'main' into issue1396
hsorby Jun 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,17 @@ AnalyserInternalVariablePtr Analyser::AnalyserImpl::internalVariable(const Varia
// Find and return, if there is one, the internal variable associated with
// the given variable.

auto rawPtr = reinterpret_cast<uintptr_t>(variable.get());
auto rawPtrIt = mInternalVariableMap.find(rawPtr);

if (rawPtrIt != mInternalVariableMap.end()) {
return rawPtrIt->second;
}

for (const auto &internalVariable : mInternalVariables) {
if (mAnalyserModel->areEquivalentVariables(variable, internalVariable->mVariable)) {
mInternalVariableMap[rawPtr] = internalVariable;

return internalVariable;
}
}
Expand All @@ -416,6 +425,8 @@ AnalyserInternalVariablePtr Analyser::AnalyserImpl::internalVariable(const Varia

mInternalVariables.push_back(res);

mInternalVariableMap[rawPtr] = res;

return res;
}

Expand Down Expand Up @@ -2321,6 +2332,7 @@ void Analyser::AnalyserImpl::analyseModel(const ModelPtr &model)
mAnalyserModel = AnalyserModel::AnalyserModelImpl::create(model);

mInternalVariables.clear();
mInternalVariableMap.clear();
mInternalEquations.clear();

mCiCnUnits.clear();
Expand Down
1 change: 1 addition & 0 deletions src/analyser_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ class Analyser::AnalyserImpl: public Logger::LoggerImpl
AnalyserExternalVariablePtrs mExternalVariables;

AnalyserInternalVariablePtrs mInternalVariables;
std::unordered_map<std::uintptr_t, AnalyserInternalVariablePtr> mInternalVariableMap;
AnalyserInternalEquationPtrs mInternalEquations;

GeneratorProfilePtr mGeneratorProfile = GeneratorProfile::create();
Expand Down
122 changes: 89 additions & 33 deletions src/analysermodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,35 +48,50 @@ AnalyserModel::~AnalyserModel()
delete mPimpl;
}

void AnalyserModel::AnalyserModelImpl::buildEquivalentVariablesCache(const ComponentPtr &component)
void exploreEquivalentVariables(const VariablePtr &variable, std::unordered_set<uintptr_t> &equivalentGroup, std::unordered_set<uintptr_t> &visited)
{
for (size_t i = 0; i < component->variableCount(); ++i) {
auto variable = component->variable(i);

for (size_t j = 0; j < variable->equivalentVariableCount(); ++j) {
auto equivalentVariable = variable->equivalentVariable(j);
auto v1 = reinterpret_cast<uintptr_t>(variable.get());
auto v2 = reinterpret_cast<uintptr_t>(equivalentVariable.get());
auto rawPtr = reinterpret_cast<uintptr_t>(variable.get());

if (v2 < v1) {
std::swap(v1, v2);
}
if (visited.insert(rawPtr).second) {
equivalentGroup.insert(rawPtr);

uniteEquivalentVariableAddresses(v1, v2);
for (size_t i = 0; i < variable->equivalentVariableCount(); ++i) {
exploreEquivalentVariables(variable->equivalentVariable(i), equivalentGroup, visited);
}
}

for (size_t i = 0; i < component->componentCount(); ++i) {
buildEquivalentVariablesCache(component->component(i));
}
}

void AnalyserModel::AnalyserModelImpl::buildEquivalentVariablesCache()
{
std::unordered_set<uintptr_t> visited;
size_t groupCount = 0;
mEquivalentVariableCache.clear();

for (size_t i = 0; i < mModel->componentCount(); ++i) {
buildEquivalentVariablesCache(mModel->component(i));
buildEquivalentVariablesCache(mModel->component(i), visited, groupCount);
}
}

void AnalyserModel::AnalyserModelImpl::buildEquivalentVariablesCache(const ComponentPtr &component, std::unordered_set<uintptr_t> &visited, size_t &groupCount)
{
for (size_t i = 0; i < component->variableCount(); ++i) {
auto variable = component->variable(i);
auto rawPtr = reinterpret_cast<uintptr_t>(variable.get());

if (visited.count(rawPtr) == 0) {
std::unordered_set<uintptr_t> equivalentGroup;
exploreEquivalentVariables(variable, equivalentGroup, visited);

for (uintptr_t v : equivalentGroup) {
mEquivalentVariableCache[v] = groupCount;
}

++groupCount;
}
}

for (size_t i = 0; i < component->componentCount(); ++i) {
buildEquivalentVariablesCache(component->component(i), visited, groupCount);
}
}

Expand All @@ -98,20 +113,21 @@ AnalyserModel::Type AnalyserModel::type() const
return mPimpl->mType;
}

static const std::map<AnalyserModel::Type, std::string> typeToString = {
{AnalyserModel::Type::UNKNOWN, "unknown"},
{AnalyserModel::Type::ODE, "ode"},
{AnalyserModel::Type::DAE, "dae"},
{AnalyserModel::Type::NLA, "nla"},
{AnalyserModel::Type::ALGEBRAIC, "algebraic"},
{AnalyserModel::Type::INVALID, "invalid"},
{AnalyserModel::Type::UNDERCONSTRAINED, "underconstrained"},
{AnalyserModel::Type::OVERCONSTRAINED, "overconstrained"},
{AnalyserModel::Type::UNSUITABLY_CONSTRAINED, "unsuitably_constrained"}};

std::string AnalyserModel::typeAsString(Type type)
{
return typeToString.at(type);
static constexpr const char *names[] = {
"unknown",
"algebraic",
"dae",
"invalid",
"nla",
"ode",
"overconstrained",
"underconstrained",
"unsuitably_constrained",
};

return names[static_cast<size_t>(type)];
}

bool AnalyserModel::hasExternalVariables() const
Expand Down Expand Up @@ -274,9 +290,37 @@ AnalyserVariablePtr AnalyserModel::analyserVariable(const VariablePtr &variable)
return {};
}

for (const auto &analyserVariable : analyserVariables(shared_from_this())) {
if (areEquivalentVariables(variable, analyserVariable->variable())) {
return analyserVariable;
if (mPimpl->mVoi && areEquivalentVariables(variable, mPimpl->mVoi->variable())) {
return mPimpl->mVoi;
}

for (const auto &state : mPimpl->mStates) {
if (areEquivalentVariables(variable, state->variable())) {
return state;
}
}

for (const auto &constant : mPimpl->mConstants) {
if (areEquivalentVariables(variable, constant->variable())) {
return constant;
}
}

for (const auto &computedConstant : mPimpl->mComputedConstants) {
if (areEquivalentVariables(variable, computedConstant->variable())) {
return computedConstant;
}
}

for (const auto &algebraicVariable : mPimpl->mAlgebraicVariables) {
if (areEquivalentVariables(variable, algebraicVariable->variable())) {
return algebraicVariable;
}
}

for (const auto &externalVariable : mPimpl->mExternalVariables) {
if (areEquivalentVariables(variable, externalVariable->variable())) {
return externalVariable;
}
}

Expand Down Expand Up @@ -546,7 +590,19 @@ bool AnalyserModel::areEquivalentVariables(const VariablePtr &variable1,
const auto v1 = reinterpret_cast<uintptr_t>(variable1.get());
const auto v2 = reinterpret_cast<uintptr_t>(variable2.get());

return mPimpl->findVariableAddress(v1) == mPimpl->findVariableAddress(v2);
const auto it1 = mPimpl->mEquivalentVariableCache.find(v1);

if (it1 == mPimpl->mEquivalentVariableCache.end()) {
return false;
}

const auto it2 = mPimpl->mEquivalentVariableCache.find(v2);

if (it2 == mPimpl->mEquivalentVariableCache.end()) {
return false;
}

return it1->second == it2->second;
}

} // namespace libcellml
32 changes: 3 additions & 29 deletions src/analysermodel_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ limitations under the License.

#include <cstdint>
#include <unordered_map>
#include <unordered_set>

#include "libcellml/analysermodel.h"

Expand Down Expand Up @@ -46,34 +47,7 @@ struct AnalyserModel::AnalyserModelImpl

std::vector<AnalyserEquationPtr> mAnalyserEquations;

std::unordered_map<uintptr_t, uintptr_t> mEquivalentVariableCache;

uintptr_t findVariableAddress(uintptr_t x)
{
auto it = mEquivalentVariableCache.find(x);

if (it == mEquivalentVariableCache.end()) {
mEquivalentVariableCache[x] = x;

return x;
}

if (it->second != x) {
it->second = findVariableAddress(it->second);
}

return it->second;
}

void uniteEquivalentVariableAddresses(uintptr_t x, uintptr_t y)
{
const uintptr_t &rootX = findVariableAddress(x);
const uintptr_t &rootY = findVariableAddress(y);

if (rootX != rootY) {
mEquivalentVariableCache[rootY] = rootX;
}
}
std::unordered_map<uintptr_t, size_t> mEquivalentVariableCache;

bool mNeedEqFunction = false;
bool mNeedNeqFunction = false;
Expand Down Expand Up @@ -104,7 +78,7 @@ struct AnalyserModel::AnalyserModelImpl

static AnalyserModelPtr create(const ModelPtr &model = nullptr);

void buildEquivalentVariablesCache(const ComponentPtr &component);
void buildEquivalentVariablesCache(const ComponentPtr &component, std::unordered_set<uintptr_t> &visited, size_t &groupCount);
void buildEquivalentVariablesCache();

AnalyserModelImpl(const ModelPtr &model);
Expand Down
7 changes: 1 addition & 6 deletions src/api/libcellml/analysermodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -604,10 +604,6 @@ class LIBCELLML_EXPORT AnalyserModel
* analysis phase (@ref Analyser::analyseModel). The cache may become
* out of date if the model is changed after the model has been analysed.
*
* @note This function is primarily designed for use during model analysis
* by the @ref Analyser. While external usage is not programmatically
* restricted, it is not the primary intended use case.
*
* @param variable1 The @ref Variable to test if it is equivalent to
* @p variable2.
* @param variable2 The @ref Variable that is potentially equivalent to
Expand All @@ -616,8 +612,7 @@ class LIBCELLML_EXPORT AnalyserModel
* @return @c true if @p variable1 is equivalent to @p variable2 and
* @c false otherwise.
*/
bool areEquivalentVariables(const VariablePtr &variable1,
const VariablePtr &variable2);
bool areEquivalentVariables(const VariablePtr &variable1, const VariablePtr &variable2);

private:
AnalyserModel(const ModelPtr &model); /**< Constructor, @private. */
Expand Down
5 changes: 4 additions & 1 deletion src/api/libcellml/variable.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,17 @@ class LIBCELLML_EXPORT Variable: public NamedEntity
*
* Get the connection identifier set for the equivalence defined with the given variables.
* The variables are commutative. If no connection identifier is set the empty string is returned.
* The optional parameter @p deepSearch will traverse the equivalence network to find the connection identifier for the
* equivalence defined by the two variables. By default this is true.
*
* If the two variables are not equivalent the empty string is returned.
*
* @param variable1 Variable one of the equivalence.
* @param variable2 Variable two of the equivalence.
* @param deepSearch Optional parameter to deep search the equivalence network for the connection identifier, true by default.
* @return the @c std::string connection identifier.
*/
static std::string equivalenceConnectionId(const VariablePtr &variable1, const VariablePtr &variable2);
static std::string equivalenceConnectionId(const VariablePtr &variable1, const VariablePtr &variable2, bool deepSearch = true);

/**
* @brief Clear equivalent connection identifier for this equivalence.
Expand Down
5 changes: 4 additions & 1 deletion src/internaltypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ using VariableMap = std::vector<VariablePairPtr>; /**< Type definition for vecto
using VariableMapIterator = VariableMap::const_iterator; /**< Type definition of const iterator for vector of VariablePair.*/

// ComponentMap
using ComponentPair = std::pair<ComponentPtr, ComponentPtr>; /**< Type definition for Component pointer pair.*/
using ComponentPair = std::pair<ComponentPtr, ComponentPtr>; /**< Type definition for Component pointer pair using standard library.*/
using ComponentMap = std::vector<ComponentPair>; /**< Type definition for vector of ComponentPair.*/
using ComponentMapIterator = ComponentMap::const_iterator; /**< Type definition of const iterator for vector of ComponentPair.*/

Expand Down Expand Up @@ -79,6 +79,9 @@ using UnitsConstPtr = std::shared_ptr<const Units>; /**< Type definition for sha
using ConnectionMap = std::map<VariablePtr, VariablePtr>; /**< Type definition for a connection map.*/
using NamePairList = std::vector<NamePair>; /**< Type definition for a list of a pair of names. */

using ComponentRawPtrPair = std::pair<const Component *, const Component *>; /**< Type definition for pair of raw component pointers. */
using ConnectionIdMap = std::map<ComponentRawPtrPair, std::string>; /**< Type definition for map of pair of raw component pointers to connection ID. */

/**
* @brief Class for defining an epoch in the history of a @ref Component or @ref Units.
*
Expand Down
Loading
Loading