From ab930481391c3f1263e3cac0c69998f9b93ef58d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 9 Jul 2026 02:43:02 +0000 Subject: [PATCH 1/3] Changes to update the release branch to 0.7.0. --- .github/scripts/generate_changelog.py | 49 ++++++---- .github/workflows/release-changelog.yml | 6 +- .github/workflows/release-prepare.yml | 27 +++--- CMakeLists.txt | 2 +- src/analyser.cpp | 12 +++ src/analyser_p.h | 1 + src/analysermodel.cpp | 122 +++++++++++++++++------- src/analysermodel_p.h | 32 +------ src/api/libcellml/analysermodel.h | 7 +- src/api/libcellml/variable.h | 5 +- src/generator.cpp | 4 + src/internaltypes.h | 5 +- src/validator.cpp | 60 +++++++++++- src/variable.cpp | 42 ++++++-- src/variable_p.h | 9 ++ tests/connection/connection.cpp | 115 ++++++++++++++++++++-- tests/coverage/coverage.cpp | 2 + tests/generator/generator.cpp | 23 +++++ tests/variable/variable.cpp | 22 ----- 19 files changed, 397 insertions(+), 148 deletions(-) diff --git a/.github/scripts/generate_changelog.py b/.github/scripts/generate_changelog.py index 77b4b1bf3d..4ccd5c9225 100644 --- a/.github/scripts/generate_changelog.py +++ b/.github/scripts/generate_changelog.py @@ -14,7 +14,7 @@ "Authorization": f"Bearer {os.environ['GH_TOKEN']}" } -TAG_PATTERN = re.compile(r"^source-v(\d+\.\d+\.\d+)$") +TAG_PATTERN = re.compile(r"^v(\d+\.\d+\.\d+)$") LABEL_PRIORITY = [] @@ -53,25 +53,29 @@ def find_previous_source_tag(end_tag): m = None if end_tag == "HEAD" else TAG_PATTERN.match(end_tag) end_version = semver.parse(m.group(1)) if m else None - print(f"Finding previous source tag before {end_tag} (version {end_version})") + print(f"Finding previous version tag before {end_tag} (version: {end_version})") for t in tags: m = TAG_PATTERN.match(t) if not m: continue v = semver.parse(m.group(1)) - if end_version and v >= semver.parse(end_version): + if end_version and v >= end_version: continue valid.append((v, t)) print(valid) if not valid: - raise RuntimeError("No valid source-vX.Y.Z tags found") + raise RuntimeError("No valid vX.Y.Z tags found") valid.sort(reverse=True) - print(f"Previous source tag found: {valid[0][1]} with version {valid[0][0]}") - return valid[0][1] + previous_source_tag = f"source-{valid[0][1]}" + if not previous_source_tag in tags: + raise RuntimeError(f"Previous source tag {previous_source_tag} for {valid[0][1]} not found in git tags") + + print(f"Previous source tag found: {previous_source_tag} with version {valid[0][0]}") + return previous_source_tag def get_merge_commits(start, end="HEAD"): @@ -80,19 +84,23 @@ def get_merge_commits(start, end="HEAD"): f"{start}..{end}", "--merges", "--first-parent", - "--pretty=%s" + "--pretty=%h" ]) print(f"Git log between {start} and {end}:\n{log}") return log.splitlines() -def extract_pr_numbers(messages): - prs = [] - for msg in messages: - m = re.search(r"#(\d+)", msg) - if m: - prs.append(int(m.group(1))) - return prs +def fetch_associated_pr_number(commit): + url = f"{GITHUB_API}/repos/cellml/libcellml/commits/{commit}/pulls" + r = requests.get(url, headers=HEADERS) + r.raise_for_status() + prs = r.json() + if not prs: + print(f"No associated PR found for commit {commit}.") + return None + pr_number = prs[0]["number"] + print(f"Associated PR #{pr_number} found for commit {commit}.") + return pr_number def fetch_pr(org_repo, pr_number): @@ -184,9 +192,14 @@ def process_arguments(): if __name__ == "__main__": args = process_arguments() previous_source_tag = find_previous_source_tag(args.tag_end) if args.tag_start == "PREV" else args.tag_start - messages = get_merge_commits(previous_source_tag, 'source-v' + args.tag_end if args.tag_end != "HEAD" else "HEAD") - print(f"Found {len(messages)} merge commits between {previous_source_tag} and {args.tag_end}.") - pr_numbers = extract_pr_numbers(messages) + commits = get_merge_commits(previous_source_tag, 'source-' + args.tag_end if args.tag_end != "HEAD" else "HEAD") + print(f"Found {len(commits)} merge commits between {previous_source_tag} and {args.tag_end}.") + + pr_numbers = [] + for commit in commits: + pr_number = fetch_associated_pr_number(commit) + if pr_number is not None: + pr_numbers.append(pr_number) print(f"Extracted {len(pr_numbers)} PR numbers from merge commits.") summaries = [] @@ -203,6 +216,6 @@ def process_arguments(): print(f"Extracted summaries for {len(sorted_summaries)} merged PRs.") print(sorted_summaries) - tag_end_label = "latest" if args.tag_end == "HEAD" else f"v{args.tag_end}" + tag_end_label = "latest" if args.tag_end == "HEAD" else f"{args.tag_end}" tag_end_label = tag_end_label if args.tag_end_display_name is None else args.tag_end_display_name write_out_to_changelog_file(sorted_summaries, tag_end=tag_end_label) diff --git a/.github/workflows/release-changelog.yml b/.github/workflows/release-changelog.yml index a997cd4d1d..47625ac9dc 100644 --- a/.github/workflows/release-changelog.yml +++ b/.github/workflows/release-changelog.yml @@ -34,7 +34,7 @@ jobs: # Extract release tag by removing prefix tag="${branch#${{ vars.RELEASE_STAGING_BRANCH_STEM }}}" - echo "RELEASE_TAG=$tag" >> "$GITHUB_ENV" + echo "RELEASE_VERSION=$tag" >> "$GITHUB_ENV" - name: Checkout release staging branch run: | @@ -50,7 +50,7 @@ jobs: GH_TOKEN: ${{ secrets.GH_TOKEN }} run: | pip install requests packaging - python .github/scripts/generate_changelog.py -p cellml/libcellml PREV ${{ env.RELEASE_TAG }} + python .github/scripts/generate_changelog.py -p cellml/libcellml PREV v${{ env.RELEASE_VERSION }} mv changelog_*.rst docs/changelogs/ python .github/scripts/clean_and_reindex_changelogs.py . @@ -60,5 +60,5 @@ jobs: git config user.email "github-actions[bot]@users.noreply.github.com" git add docs/changelogs/changelog_*.rst docs/changelogs/index.rst docs/index.rst - git commit -m "Add changelog for release ${RELEASE_TAG}" || echo "No changes to commit" + git commit -m "Add changelog for release ${RELEASE_VERSION}" || echo "No changes to commit" git push diff --git a/.github/workflows/release-prepare.yml b/.github/workflows/release-prepare.yml index 08a1111aa5..6bf61a2d33 100644 --- a/.github/workflows/release-prepare.yml +++ b/.github/workflows/release-prepare.yml @@ -25,6 +25,11 @@ jobs: ref: release path: release-repo + - uses: actions/checkout@v6 + with: + ref: ${{ inputs.source_branch }} + path: source-repo + - name: Detect active release staging branches (PR-backed) id: detect_branch env: @@ -65,32 +70,31 @@ jobs: #branch="${active[0]}" #echo "release_branch=$branch" >> "$GITHUB_OUTPUT" - - name: Set GitHub Actions bot identity + - name: Set GitHub Actions bot identity (both repos) run: | cd release-repo git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - uses: actions/checkout@v6 - with: - ref: ${{ inputs.source_branch }} - path: source-repo + cd ../source-repo - - name: Create staging branch - run: | - cd release-repo - git checkout -b ${{ vars.RELEASE_STAGING_BRANCH_STEM }}${{ inputs.version }} + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" - name: Tag source of release run: | cd source-repo - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" git tag -a "source-v${{ inputs.version }}" -m "Source tag for release v${{ inputs.version }}" git push origin "source-v${{ inputs.version }}" + - name: Create staging branch + run: | + cd release-repo + + git checkout -b ${{ vars.RELEASE_STAGING_BRANCH_STEM }}${{ inputs.version }} + - name: Copy release changes to release repo run: | rsync -avW --delete --exclude='.git' source-repo/ release-repo/ @@ -105,6 +109,7 @@ jobs: - name: Push branch run: | cd release-repo + git push origin ${{ vars.RELEASE_STAGING_BRANCH_STEM }}${{ inputs.version }} generate_changelog: diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bea276909..f077ab2502 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ endif() set(PROJECT_NAME libCellML) set(PROJECT_URL https://libcellml.org) set(_PROJECT_VERSION 0.7.0) -set(PROJECT_DEVELOPER_VERSION -rc.4) +set(PROJECT_DEVELOPER_VERSION -rc.4.post) project(${PROJECT_NAME} VERSION ${_PROJECT_VERSION} LANGUAGES CXX) # Set policies that affect the build. diff --git a/src/analyser.cpp b/src/analyser.cpp index 00c89e83a8..401936d6f0 100644 --- a/src/analyser.cpp +++ b/src/analyser.cpp @@ -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(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; } } @@ -416,6 +425,8 @@ AnalyserInternalVariablePtr Analyser::AnalyserImpl::internalVariable(const Varia mInternalVariables.push_back(res); + mInternalVariableMap[rawPtr] = res; + return res; } @@ -2321,6 +2332,7 @@ void Analyser::AnalyserImpl::analyseModel(const ModelPtr &model) mAnalyserModel = AnalyserModel::AnalyserModelImpl::create(model); mInternalVariables.clear(); + mInternalVariableMap.clear(); mInternalEquations.clear(); mCiCnUnits.clear(); diff --git a/src/analyser_p.h b/src/analyser_p.h index 43ea5d9a1d..237e5c3b4a 100644 --- a/src/analyser_p.h +++ b/src/analyser_p.h @@ -161,6 +161,7 @@ class Analyser::AnalyserImpl: public Logger::LoggerImpl AnalyserExternalVariablePtrs mExternalVariables; AnalyserInternalVariablePtrs mInternalVariables; + std::unordered_map mInternalVariableMap; AnalyserInternalEquationPtrs mInternalEquations; GeneratorProfilePtr mGeneratorProfile = GeneratorProfile::create(); diff --git a/src/analysermodel.cpp b/src/analysermodel.cpp index 3cddd3ea39..9e86c66c4f 100644 --- a/src/analysermodel.cpp +++ b/src/analysermodel.cpp @@ -48,35 +48,50 @@ AnalyserModel::~AnalyserModel() delete mPimpl; } -void AnalyserModel::AnalyserModelImpl::buildEquivalentVariablesCache(const ComponentPtr &component) +void exploreEquivalentVariables(const VariablePtr &variable, std::unordered_set &equivalentGroup, std::unordered_set &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(variable.get()); - auto v2 = reinterpret_cast(equivalentVariable.get()); + auto rawPtr = reinterpret_cast(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 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 &visited, size_t &groupCount) +{ + for (size_t i = 0; i < component->variableCount(); ++i) { + auto variable = component->variable(i); + auto rawPtr = reinterpret_cast(variable.get()); + + if (visited.count(rawPtr) == 0) { + std::unordered_set 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); } } @@ -98,20 +113,21 @@ AnalyserModel::Type AnalyserModel::type() const return mPimpl->mType; } -static const std::map 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(type)]; } bool AnalyserModel::hasExternalVariables() const @@ -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; } } @@ -546,7 +590,19 @@ bool AnalyserModel::areEquivalentVariables(const VariablePtr &variable1, const auto v1 = reinterpret_cast(variable1.get()); const auto v2 = reinterpret_cast(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 diff --git a/src/analysermodel_p.h b/src/analysermodel_p.h index adee6853e2..89f9dd4319 100644 --- a/src/analysermodel_p.h +++ b/src/analysermodel_p.h @@ -18,6 +18,7 @@ limitations under the License. #include #include +#include #include "libcellml/analysermodel.h" @@ -46,34 +47,7 @@ struct AnalyserModel::AnalyserModelImpl std::vector mAnalyserEquations; - std::unordered_map 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 mEquivalentVariableCache; bool mNeedEqFunction = false; bool mNeedNeqFunction = false; @@ -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 &visited, size_t &groupCount); void buildEquivalentVariablesCache(); AnalyserModelImpl(const ModelPtr &model); diff --git a/src/api/libcellml/analysermodel.h b/src/api/libcellml/analysermodel.h index 688a143e03..b264be7729 100644 --- a/src/api/libcellml/analysermodel.h +++ b/src/api/libcellml/analysermodel.h @@ -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 @@ -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. */ diff --git a/src/api/libcellml/variable.h b/src/api/libcellml/variable.h index 2d9e6292dc..5bfa4630c9 100644 --- a/src/api/libcellml/variable.h +++ b/src/api/libcellml/variable.h @@ -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. diff --git a/src/generator.cpp b/src/generator.cpp index be3bd6096e..4a4ecc2ec4 100644 --- a/src/generator.cpp +++ b/src/generator.cpp @@ -2282,6 +2282,8 @@ std::string Generator::interfaceCode(const AnalyserModelPtr &analyserModel, cons pFunc()->addInterfaceComputeModelMethodsCode(); + pFunc()->mAnalyserModel = nullptr; + return pFunc()->mCode; } @@ -2414,6 +2416,8 @@ std::string Generator::implementationCode(const AnalyserModelPtr &analyserModel, pFunc()->addImplementationComputeVariablesMethodCode(remainingAnalyserEquations); + pFunc()->mAnalyserModel = nullptr; + return pFunc()->mCode; } diff --git a/src/internaltypes.h b/src/internaltypes.h index e21e35ed67..77d278877b 100644 --- a/src/internaltypes.h +++ b/src/internaltypes.h @@ -46,7 +46,7 @@ using VariableMap = std::vector; /**< Type definition for vecto using VariableMapIterator = VariableMap::const_iterator; /**< Type definition of const iterator for vector of VariablePair.*/ // ComponentMap -using ComponentPair = std::pair; /**< Type definition for Component pointer pair.*/ +using ComponentPair = std::pair; /**< Type definition for Component pointer pair using standard library.*/ using ComponentMap = std::vector; /**< Type definition for vector of ComponentPair.*/ using ComponentMapIterator = ComponentMap::const_iterator; /**< Type definition of const iterator for vector of ComponentPair.*/ @@ -79,6 +79,9 @@ using UnitsConstPtr = std::shared_ptr; /**< Type definition for sha using ConnectionMap = std::map; /**< Type definition for a connection map.*/ using NamePairList = std::vector; /**< Type definition for a list of a pair of names. */ +using ComponentRawPtrPair = std::pair; /**< Type definition for pair of raw component pointers. */ +using ConnectionIdMap = std::map; /**< 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. * diff --git a/src/validator.cpp b/src/validator.cpp index 55a1db1186..f0d17bb972 100644 --- a/src/validator.cpp +++ b/src/validator.cpp @@ -639,8 +639,9 @@ class Validator::ValidatorImpl: public LoggerImpl * @param component The component to check. * @param idMap The IdMap object to construct. * @param reportedConnections A set of connection identifiers to prevent duplicate reporting. + * @param connectionIds A map to speed up lookups for component to component connection identifiers. */ - void buildComponentIdMap(const ComponentPtr &component, IdMap &idMap, std::set &reportedConnections); + void buildComponentIdMap(const ComponentPtr &component, IdMap &idMap, std::set &reportedConnections, const ConnectionIdMap &connectionIds); /** @brief Utility function to add an item to the idMap. * @@ -2692,11 +2693,58 @@ void Validator::ValidatorImpl::addIdMapItem(const std::string &id, const std::st } } +void gatherComponents(const ComponentPtr &component, std::vector &allComponents) +{ + std::vector stack; + + stack.push_back(component); + + while (!stack.empty()) { + allComponents.emplace_back(std::move(stack.back())); + stack.pop_back(); + + auto *current = allComponents.back().get(); + const auto childCount = current->componentCount(); + + for (size_t i = 0; i < childCount; ++i) { + stack.emplace_back(current->component(i)); + } + } +} + IdMap Validator::ValidatorImpl::buildModelIdMap(const ModelPtr &model) { IdMap idMap; std::string info; std::set reportedConnections; + std::vector allComponents; + + for (size_t c = 0; c < model->componentCount(); ++c) { + gatherComponents(model->component(c), allComponents); + } + + ConnectionIdMap connectionIds; + + for (const auto &comp : allComponents) { + auto rawPtr = comp.get(); + const size_t varCount = comp->variableCount(); + + for (size_t i = 0; i < varCount; ++i) { + auto currentVariable = comp->variable(i); + + for (size_t e = 0; e < currentVariable->equivalentVariableCount(); ++e) { + auto equiv = currentVariable->equivalentVariable(e); + auto equivParent = owningComponent(equiv); + + if (equivParent != nullptr) { + auto equivRawPtr = equivParent.get(); + auto key = (rawPtr < equivRawPtr) ? ComponentRawPtrPair {rawPtr, equivRawPtr} : ComponentRawPtrPair {equivRawPtr, rawPtr}; + connectionIds.try_emplace(key, Variable::equivalenceConnectionId(currentVariable, equiv, false)); + } + } + } + } + // Model. if (!model->id().empty()) { info = " - model '" + model->name() + "'"; @@ -2748,12 +2796,12 @@ IdMap Validator::ValidatorImpl::buildModelIdMap(const ModelPtr &model) // Start recursion through encapsulation hierarchy. for (size_t c = 0; c < model->componentCount(); ++c) { - buildComponentIdMap(model->component(c), idMap, reportedConnections); + buildComponentIdMap(model->component(c), idMap, reportedConnections, connectionIds); } return idMap; } -void Validator::ValidatorImpl::buildComponentIdMap(const ComponentPtr &component, IdMap &idMap, std::set &reportedConnections) +void Validator::ValidatorImpl::buildComponentIdMap(const ComponentPtr &component, IdMap &idMap, std::set &reportedConnections, const ConnectionIdMap &connectionIds) { std::string info; @@ -2807,8 +2855,10 @@ void Validator::ValidatorImpl::buildComponentIdMap(const ComponentPtr &component addIdMapItem(mappingId, info, idMap); } // Connections. - auto connectionId = Variable::equivalenceConnectionId(item, equiv); + auto key = component.get() < equivParent.get() ? ComponentRawPtrPair {component.get(), equivParent.get()} : ComponentRawPtrPair {equivParent.get(), component.get()}; + auto connectionId = connectionIds.at(key); std::string connection = component->name() < equivParent->name() ? component->name() + equivParent->name() : equivParent->name() + component->name(); + if ((s1 < s2) && !connectionId.empty() && (reportedConnections.count(connection) == 0)) { std::string connectionDescription = "between components '" + component->name() + "' and '" + equivParent->name() @@ -2879,7 +2929,7 @@ void Validator::ValidatorImpl::buildComponentIdMap(const ComponentPtr &component // Child components. for (size_t c = 0; c < component->componentCount(); ++c) { - buildComponentIdMap(component->component(c), idMap, reportedConnections); + buildComponentIdMap(component->component(c), idMap, reportedConnections, connectionIds); } } diff --git a/src/variable.cpp b/src/variable.cpp index 5644b63219..06983ee978 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -124,7 +124,11 @@ bool Variable::removeEquivalence(const VariablePtr &variable1, const VariablePtr { if ((variable1 != nullptr) && (variable2 != nullptr)) { if (variable1->pFunc()->unsetEquivalentTo(variable2)) { - return variable2->pFunc()->unsetEquivalentTo(variable1); + variable2->pFunc()->unsetEquivalentTo(variable1); + variable1->pFunc()->unsafeResetEquivalenceIds(variable2); + variable2->pFunc()->unsafeResetEquivalenceIds(variable1); + + return true; } } @@ -140,7 +144,10 @@ void Variable::removeAllEquivalences() equivalentVariable->pFunc()->unsetEquivalentTo(thisVariable); } } + pFunc()->mEquivalentVariables.clear(); + pFunc()->mConnectionIdMap.clear(); + pFunc()->mMappingIdMap.clear(); } VariablePtr Variable::equivalentVariable(size_t index) const @@ -181,6 +188,12 @@ void Variable::VariableImpl::cleanExpiredVariables() mEquivalentVariables.erase(std::remove_if(mEquivalentVariables.begin(), mEquivalentVariables.end(), [=](const VariableWeakPtr &variableWeak) -> bool { return variableWeak.expired(); }), mEquivalentVariables.end()); } +void Variable::VariableImpl::unsafeResetEquivalenceIds(const VariablePtr &equivalentVariable) +{ + setEquivalentMappingId(equivalentVariable, ""); + setEquivalentConnectionId(equivalentVariable, ""); +} + bool Variable::VariableImpl::hasEquivalentVariable(const VariablePtr &equivalentVariable, bool considerIndirectEquivalences) const { bool equivalent = false; @@ -431,18 +444,26 @@ std::string Variable::equivalenceMappingId(const VariablePtr &variable1, const V return id; } -std::string Variable::equivalenceConnectionId(const VariablePtr &variable1, const VariablePtr &variable2) +std::string Variable::equivalenceConnectionId(const VariablePtr &variable1, const VariablePtr &variable2, bool deepSearch) { std::string id; if ((variable1 != nullptr) && (variable2 != nullptr)) { - if (variable1->hasEquivalentVariable(variable2, true)) { - auto map = createConnectionMap(variable1, variable2); - for (auto &it : map) { - id = it.first->pFunc()->equivalentConnectionId(it.second); - } - if (id.empty()) { + if (deepSearch) { + if (variable1->hasEquivalentVariable(variable2, true)) { + auto map = createConnectionMap(variable1, variable2); + + for (auto &it : map) { + id = it.first->pFunc()->equivalentConnectionId(it.second); + + if (!id.empty()) { + return id; + } + } + id = variable1->pFunc()->equivalentConnectionId(variable2); } + } else { + id = variable1->pFunc()->equivalentConnectionId(variable2); } } return id; @@ -452,6 +473,11 @@ void Variable::removeEquivalenceConnectionId(const VariablePtr &variable1, const { if ((variable1 != nullptr) && (variable2 != nullptr)) { if (variable1->hasEquivalentVariable(variable2, true)) { + for (auto &it : createConnectionMap(variable1, variable2)) { + it.first->pFunc()->setEquivalentConnectionId(it.second, ""); + it.second->pFunc()->setEquivalentConnectionId(it.first, ""); + } + variable1->pFunc()->setEquivalentConnectionId(variable2, ""); variable2->pFunc()->setEquivalentConnectionId(variable1, ""); } diff --git a/src/variable_p.h b/src/variable_p.h index 2b1e6fc56b..23507aa4e0 100644 --- a/src/variable_p.h +++ b/src/variable_p.h @@ -173,6 +173,15 @@ class Variable::VariableImpl: public NamedEntityImpl */ std::string equivalentConnectionId(const VariablePtr &equivalentVariable) const; + /** + * @brief Reset the connection and mapping ids associated with this variable and equivalent variable. + * + * This method will reset the connection id and the mapping id to empty. + * It will not check that the @p equivalentVariable is valid, this method expects the equivalent variable + * to be safe before using. + */ + void unsafeResetEquivalenceIds(const VariablePtr &equivalentVariable); + std::vector::iterator findEquivalentVariable(const VariablePtr &equivalentVariable); std::vector::const_iterator findEquivalentVariable(const VariablePtr &equivalentVariable) const; }; diff --git a/tests/connection/connection.cpp b/tests/connection/connection.cpp index 64ce2ea81a..4789fe4b7f 100644 --- a/tests/connection/connection.cpp +++ b/tests/connection/connection.cpp @@ -19,7 +19,7 @@ limitations under the License. #include "test_utils.h" #include -TEST(Variable, addEquivalenceNullptrFirstParameter) +TEST(Connection, addEquivalenceNullptrFirstParameter) { libcellml::VariablePtr v1 = nullptr; libcellml::VariablePtr v2 = libcellml::Variable::create(); @@ -28,7 +28,7 @@ TEST(Variable, addEquivalenceNullptrFirstParameter) EXPECT_FALSE(v2->hasEquivalentVariable(v1)); } -TEST(Variable, addEquivalenceNullptrSecondParameter) +TEST(Connection, addEquivalenceNullptrSecondParameter) { libcellml::VariablePtr v1 = libcellml::Variable::create(); libcellml::VariablePtr v2 = nullptr; @@ -37,14 +37,14 @@ TEST(Variable, addEquivalenceNullptrSecondParameter) EXPECT_FALSE(v1->hasEquivalentVariable(v2)); } -TEST(Variable, addEquivalenceNullptrBothParameters) +TEST(Connection, addEquivalenceNullptrBothParameters) { libcellml::VariablePtr v1 = nullptr; libcellml::VariablePtr v2 = nullptr; libcellml::Variable::addEquivalence(v1, v2); } -TEST(Variable, addAndGetEquivalentVariable) +TEST(Connection, addAndGetEquivalentVariable) { libcellml::VariablePtr v1 = libcellml::Variable::create(); libcellml::VariablePtr v2 = libcellml::Variable::create(); @@ -52,7 +52,7 @@ TEST(Variable, addAndGetEquivalentVariable) EXPECT_EQ(v2, v1->equivalentVariable(0)); } -TEST(Variable, addAndGetEquivalentVariableReciprocal) +TEST(Connection, addAndGetEquivalentVariableReciprocal) { libcellml::VariablePtr v1 = libcellml::Variable::create(); libcellml::VariablePtr v2 = libcellml::Variable::create(); @@ -60,7 +60,7 @@ TEST(Variable, addAndGetEquivalentVariableReciprocal) EXPECT_EQ(v1, v2->equivalentVariable(0)); } -TEST(Variable, addTwoEquivalentVariablesAndCount) +TEST(Connection, addTwoEquivalentVariablesAndCount) { libcellml::VariablePtr v1 = libcellml::Variable::create(); libcellml::VariablePtr v2 = libcellml::Variable::create(); @@ -72,7 +72,7 @@ TEST(Variable, addTwoEquivalentVariablesAndCount) EXPECT_EQ(e, a); } -TEST(Variable, addDuplicateEquivalentVariablesAndCount) +TEST(Connection, addDuplicateEquivalentVariablesAndCount) { libcellml::VariablePtr v1 = libcellml::Variable::create(); libcellml::VariablePtr v2 = libcellml::Variable::create(); @@ -85,7 +85,7 @@ TEST(Variable, addDuplicateEquivalentVariablesAndCount) EXPECT_EQ(e, a); } -TEST(Variable, hasNoEquivalentVariable) +TEST(Connection, hasNoEquivalentVariable) { libcellml::VariablePtr v1 = libcellml::Variable::create(); libcellml::VariablePtr v2 = libcellml::Variable::create(); @@ -108,7 +108,7 @@ TEST(Variable, hasNoEquivalentVariable) EXPECT_FALSE(v1->hasEquivalentVariable(v2, true)); } -TEST(Variable, hasIndirectEquivalentVariable) +TEST(Connection, hasIndirectEquivalentVariable) { libcellml::VariablePtr v1 = libcellml::Variable::create(); libcellml::VariablePtr v2 = libcellml::Variable::create(); @@ -118,7 +118,7 @@ TEST(Variable, hasIndirectEquivalentVariable) EXPECT_TRUE(v1->hasEquivalentVariable(v3, true)); } -TEST(Variable, connectionId) +TEST(Connection, connectionId) { libcellml::VariablePtr v1 = libcellml::Variable::create(); libcellml::VariablePtr v2 = libcellml::Variable::create(); @@ -1440,3 +1440,98 @@ TEST(Connection, repeatedMapVariables) EXPECT_EQ_ISSUES(expectedIssues, p); } + +TEST(Connection, addEquivalenceReturnsFalseProperly) +{ + auto m = libcellml::Model::create("m"); + auto c1 = libcellml::Component::create("c1"); + auto c2 = libcellml::Component::create("c2"); + auto v1 = libcellml::Variable::create("v1"); + auto v2 = libcellml::Variable::create("v2"); + + EXPECT_TRUE(m->addComponent(c1)); + EXPECT_TRUE(m->addComponent(c2)); + EXPECT_TRUE(c1->addVariable(v1)); + EXPECT_TRUE(c2->addVariable(v2)); + + // Create a connection with self variable, expect no connections have been created. + EXPECT_FALSE(libcellml::Variable::addEquivalence(v1, v1)); + EXPECT_EQ(size_t(0), v1->equivalentVariableCount()); + + // Create a connection with one nullptr, expect no connections have been created. + EXPECT_FALSE(libcellml::Variable::addEquivalence(v2, nullptr)); + EXPECT_EQ(size_t(0), v2->equivalentVariableCount()); +} + +TEST(Connection, addEquivalenceConnectionIdPropagation) +{ + auto m = libcellml::Model::create("m"); + auto c1 = libcellml::Component::create("c1"); + auto c2 = libcellml::Component::create("c2"); + auto v1 = libcellml::Variable::create("v1"); + auto v2 = libcellml::Variable::create("v2"); + auto v3 = libcellml::Variable::create("v3"); + auto v4 = libcellml::Variable::create("v4"); + auto v5 = libcellml::Variable::create("v5"); + auto v6 = libcellml::Variable::create("v6"); + + m->addComponent(c1); + m->addComponent(c2); + c1->addVariable(v1); + c2->addVariable(v2); + c1->addVariable(v3); + c2->addVariable(v4); + c1->addVariable(v5); + c2->addVariable(v6); + + libcellml::Variable::addEquivalence(v1, v2); + libcellml::Variable::addEquivalence(v3, v4); + libcellml::Variable::setEquivalenceConnectionId(v1, v2, "connection_01"); + EXPECT_EQ("connection_01", libcellml::Variable::equivalenceConnectionId(v3, v4)); + + libcellml::Variable::addEquivalence(v5, v6); + EXPECT_EQ("connection_01", libcellml::Variable::equivalenceConnectionId(v5, v6)); + EXPECT_EQ("", libcellml::Variable::equivalenceConnectionId(v5, v6, false)); + + libcellml::Variable::removeEquivalenceConnectionId(v1, v2); + EXPECT_EQ("", libcellml::Variable::equivalenceConnectionId(v1, v2)); + EXPECT_EQ("", libcellml::Variable::equivalenceConnectionId(v3, v4)); + EXPECT_EQ("", libcellml::Variable::equivalenceConnectionId(v5, v6)); +} + +TEST(Connection, removeEquivalenceConnectionIdFromVariablesThatAreNotInComponents) +{ + auto v1 = libcellml::Variable::create("v1"); + auto v2 = libcellml::Variable::create("v2"); + + libcellml::Variable::addEquivalence(v1, v2); + libcellml::Variable::setEquivalenceConnectionId(v1, v2, "connection_01"); + EXPECT_EQ("connection_01", libcellml::Variable::equivalenceConnectionId(v1, v2)); + + libcellml::Variable::removeEquivalenceConnectionId(v1, v2); + EXPECT_EQ("", libcellml::Variable::equivalenceConnectionId(v1, v2)); +} + +TEST(Connection, addEquivalenceConnectionIdClearedAfterDisconnect) +{ + auto m = libcellml::Model::create("m"); + auto c1 = libcellml::Component::create("c1"); + auto c2 = libcellml::Component::create("c2"); + auto v1 = libcellml::Variable::create("v1"); + auto v2 = libcellml::Variable::create("v2"); + + m->addComponent(c1); + m->addComponent(c2); + c1->addVariable(v1); + c2->addVariable(v2); + + libcellml::Variable::addEquivalence(v1, v2); + libcellml::Variable::setEquivalenceConnectionId(v1, v2, "connection_01"); + EXPECT_EQ("connection_01", libcellml::Variable::equivalenceConnectionId(v1, v2)); + + libcellml::Variable::removeEquivalenceConnectionId(v1, v2); + EXPECT_EQ("", libcellml::Variable::equivalenceConnectionId(v1, v2)); + + libcellml::Variable::addEquivalence(v1, v2); + EXPECT_EQ("", libcellml::Variable::equivalenceConnectionId(v1, v2)); +} diff --git a/tests/coverage/coverage.cpp b/tests/coverage/coverage.cpp index 18f567abe8..3a8aa192e5 100644 --- a/tests/coverage/coverage.cpp +++ b/tests/coverage/coverage.cpp @@ -602,6 +602,8 @@ TEST(Coverage, analyserAreEquivalentVariables) auto variable = model->component("membrane")->variable("V"); EXPECT_FALSE(analyserModel->areEquivalentVariables(nullptr, variable)); EXPECT_FALSE(analyserModel->areEquivalentVariables(variable, nullptr)); + auto otherVariable = libcellml::Variable::create("other"); + EXPECT_FALSE(analyserModel->areEquivalentVariables(variable, otherVariable)); } void checkAstTypeAsString(const libcellml::AnalyserEquationAstPtr &ast) diff --git a/tests/generator/generator.cpp b/tests/generator/generator.cpp index 411080c523..13af89e313 100644 --- a/tests/generator/generator.cpp +++ b/tests/generator/generator.cpp @@ -1754,3 +1754,26 @@ TEST(Generator, generateCodeUsingProfileEnum) EXPECT_EQ_FILE_CONTENTS("generator/algebraic_eqn_computed_var_on_rhs/model.py", generator->implementationCode(analyserModel, libcellml::GeneratorProfile::Profile::PYTHON)); } + +TEST(Generator, checkGeneratorReleasesAnalyserModel) +{ + auto parser = libcellml::Parser::create(); + auto model = parser->parseModel(fileContents("generator/algebraic_eqn_computed_var_on_rhs/model.cellml")); + auto analyser = libcellml::Analyser::create(); + + analyser->analyseModel(model); + + auto analyserModel = analyser->analyserModel(); + + EXPECT_EQ(2, analyserModel.use_count()); + EXPECT_EQ("x = a", libcellml::Generator::equationCode(analyserModel->analyserEquation(0)->ast())); + EXPECT_EQ(2, analyserModel.use_count()); + + auto generator = libcellml::Generator::create(); + + EXPECT_EQ_FILE_CONTENTS("generator/algebraic_eqn_computed_var_on_rhs/model.c", generator->implementationCode(analyserModel)); + EXPECT_EQ(2, analyserModel.use_count()); + + EXPECT_EQ_FILE_CONTENTS("generator/algebraic_eqn_computed_var_on_rhs/model.h", generator->interfaceCode(analyserModel)); + EXPECT_EQ(2, analyserModel.use_count()); +} diff --git a/tests/variable/variable.cpp b/tests/variable/variable.cpp index 9da5e20701..3380024f91 100644 --- a/tests/variable/variable.cpp +++ b/tests/variable/variable.cpp @@ -1890,25 +1890,3 @@ TEST(Variable, addVariableDuplicates) EXPECT_EQ(size_t(1), apple->variableCount()); EXPECT_EQ(size_t(1), tomato->variableCount()); } - -TEST(Variable, addEquivalenceReturnsFalseProperly) -{ - auto m = libcellml::Model::create("m"); - auto c1 = libcellml::Component::create("c1"); - auto c2 = libcellml::Component::create("c2"); - auto v1 = libcellml::Variable::create("v1"); - auto v2 = libcellml::Variable::create("v2"); - - EXPECT_TRUE(m->addComponent(c1)); - EXPECT_TRUE(m->addComponent(c2)); - EXPECT_TRUE(c1->addVariable(v1)); - EXPECT_TRUE(c2->addVariable(v2)); - - // Create a connection with self variable, expect no connections have been created. - EXPECT_FALSE(libcellml::Variable::addEquivalence(v1, v1)); - EXPECT_EQ(size_t(0), v1->equivalentVariableCount()); - - // Create a connection with one nullptr, expect no connections have been created. - EXPECT_FALSE(libcellml::Variable::addEquivalence(v2, nullptr)); - EXPECT_EQ(size_t(0), v2->equivalentVariableCount()); -} From a403f9f402bf458349febd60cc161826973d23ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 9 Jul 2026 02:44:11 +0000 Subject: [PATCH 2/3] Add changelog for release 0.7.0 --- docs/changelogs/changelog_v0.7.0.rst | 106 +++++++++++++++++++++++++++ docs/changelogs/index.rst | 2 +- docs/index.rst | 2 +- 3 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 docs/changelogs/changelog_v0.7.0.rst diff --git a/docs/changelogs/changelog_v0.7.0.rst b/docs/changelogs/changelog_v0.7.0.rst new file mode 100644 index 0000000000..a63ea08a91 --- /dev/null +++ b/docs/changelogs/changelog_v0.7.0.rst @@ -0,0 +1,106 @@ +libCellML v0.7.0 Changelog +========================== + +Analyser +-------- + +* Improve caching of equivalent variables in analyser model by `@hsorby `_ [`#1389 `_]. +* Analyser: account for an underconstrained NLA system by `@agarny `_ [`#1338 `_]. + +Bug +--- + +* Fix bug with litre units by `@hsorby `_ [`#1314 `_]. +* Fix segfault with imports across multiple directories by `@hsorby `_ [`#1304 `_]. + +Documentation +------------- + +* Fix warnings when building the documentation with Doxygen by `@hsorby `_ [`#1349 `_]. +* Updating citation information for libCellML by `@nickerso `_ [`#1361 `_]. + +Generator +--------- + +* Generator class holds a reference to an analyser model by `@hsorby `_ [`#1439 `_]. +* Generator: fix a couple of issues with the Python profile by `@agarny `_ [`#1376 `_]. +* Generator profile: fix `computeComputedConstants()`'s signature by `@agarny `_ [`#1364 `_]. +* Add additional overloads to `Generator` class implementationCode and interfaceCode methods by `@hsorby `_ [`#1345 `_]. +* Add a `GeneratorVariableTracker` class to manage un/tracked variables by `@agarny `_ [`#1340 `_]. +* Allow a constant, a computed constant, or a state variable (but not an algebraic variable) to be used as an initial value by `@agarny `_ [`#1325 `_]. +* Code generator: allow for variables to be tracked/untracked by `@agarny `_ [`#1256 `_]. +* Code generator: use different arrays for constants, computed constants, and algebraic variables by `@agarny `_ [`#1247 `_]. +* Syntax error in generated Python code by `@agarny `_ [`#1334 `_]. +* API: simplify/improve the API for external variables by `@agarny `_ [`#1252 `_]. + +Infrastructure +-------------- + +* macOS: support version 14.0 and later by `@agarny `_ [`#1447 `_]. +* Output unit test failures to CI logs by `@hsorby `_ [`#1441 `_]. +* Don't push changes to main too early in finalise workflow by `@hsorby `_ [`#1433 `_]. +* Reset working directory when ccloning passed documentation repositories by `@hsorby `_ [`#1432 `_]. +* Revert to windows-2022 to avoid issues with windows-2025 by `@hsorby `_ [`#1431 `_]. +* Try a different approach to setting permissions for make release workflow by `@hsorby `_ [`#1430 `_]. +* Re-instate final fixes to release process workflow overwritten during testing by `@hsorby `_ [`#1428 `_]. +* Perform final sanity check before pushing release changes to main by `@hsorby `_ [`#1427 `_]. +* Correct conditional syntax in finalise release workflow by `@hsorby `_ [`#1426 `_]. +* Skip user documentation update if the documentation tests failed by `@hsorby `_ [`#1425 `_]. +* Fix more references to libcellml/website-src by `@hsorby `_ [`#1424 `_]. +* Fix the way commits for release are pushed to main by `@hsorby `_ [`#1423 `_]. +* Set default summary if documentation checks returns nothing by `@hsorby `_ [`#1422 `_]. +* Fix missing installation of ResolveZLIB.cmake file by `@hsorby `_ [`#1421 `_]. +* Set identity of Git user to github actions bot for tagging release source by `@hsorby `_ [`#1419 `_]. +* Correctly tag source of release by `@hsorby `_ [`#1418 `_]. +* Switch to release branch before committing changelog changes by `@hsorby `_ [`#1416 `_]. +* Remove -v from git tag -a command in release-prepare.yml by `@hsorby `_ [`#1415 `_]. +* Set a source tag and use that to determine which merge commits to consider by `@hsorby `_ [`#1414 `_]. +* Tweaks to release process, investigating changelog creation issue by `@hsorby `_ [`#1412 `_]. +* Adjust finalise release workflow by `@hsorby `_ [`#1411 `_]. +* Release process fixes by `@hsorby `_ [`#1409 `_]. +* Add finalise release workflow by `@hsorby `_ [`#1405 `_]. +* Add make release workflow by `@hsorby `_ [`#1404 `_]. +* Add check release workflow by `@hsorby `_ [`#1403 `_]. +* Add GitHub action to prepare a release by `@hsorby `_ [`#1394 `_]. +* Shift from OpenCMISS dependencies to CMLibs dependencies by `@hsorby `_ [`#1378 `_]. +* GHA: account for the fact that `macos-13` is about to be retired by `@agarny `_ [`#1329 `_]. +* CMake: cannot use `DEPENDS` with `add_custom_command(TARGET ...)` by `@agarny `_ [`#1319 `_]. +* CI by `@agarny `_ [`#1315 `_]. + +JavaScript bindings +------------------- + +* JavaScript: account for the fact that our JavaScript bindings use a 32-bit address space by `@agarny `_ [`#1384 `_]. + +Miscellaneous +------------- + +* Update C++ standard to version 20 by `@agarny `_ [`#1390 `_]. +* Remove all unnecessary headers as guided by Qt Creator on macOS by `@hsorby `_ [`#1346 `_]. +* Remove second definition of AnalyserModelWeakPtr by `@hsorby `_ [`#1343 `_]. + +Python bindings +--------------- + +* Add 3.14 to cibuildwheel builds by `@hsorby `_ [`#1444 `_]. + +Validation +---------- + +* Improve the time taken to validate and analyse large models by `@hsorby `_ [`#1397 `_]. + +Contributors +------------ + +.. image:: https://avatars.githubusercontent.com/u/778048?v=4 + :target: https://github.com/hsorby + :height: 32 + :width: 32 +.. image:: https://avatars.githubusercontent.com/u/602265?v=4 + :target: https://github.com/agarny + :height: 32 + :width: 32 +.. image:: https://avatars.githubusercontent.com/u/811244?v=4 + :target: https://github.com/nickerso + :height: 32 + :width: 32 diff --git a/docs/changelogs/index.rst b/docs/changelogs/index.rst index c6b617b97f..ac8fd64ec5 100644 --- a/docs/changelogs/index.rst +++ b/docs/changelogs/index.rst @@ -4,7 +4,7 @@ Changelogs .. toctree:: - changelog_v0.7.0-rc.4 + changelog_v0.7.0 changelog_v0.6.3 changelog_v0.6.2 changelog_v0.6.1 diff --git a/docs/index.rst b/docs/index.rst index db921f855d..72c62cabd2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -39,7 +39,7 @@ Changelogs .. toctree:: - changelogs/changelog_v0.7.0-rc.4 + changelogs/changelog_v0.7.0 changelogs/changelog_v0.6.3 changelogs/changelog_v0.6.2 changelogs/changelog_v0.6.1 From 8f40d7ea83bd9c5bc3f13da9ab25adb9337e9c2c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 9 Jul 2026 02:44:20 +0000 Subject: [PATCH 3/3] Set the version number throughout the codebase to 0.7.0. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f077ab2502..95ab404bee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ endif() set(PROJECT_NAME libCellML) set(PROJECT_URL https://libcellml.org) set(_PROJECT_VERSION 0.7.0) -set(PROJECT_DEVELOPER_VERSION -rc.4.post) +set(PROJECT_DEVELOPER_VERSION ) project(${PROJECT_NAME} VERSION ${_PROJECT_VERSION} LANGUAGES CXX) # Set policies that affect the build.