Skip to content

WIP: Add RefinableParProxy to prevent in-place parameter name mutation - #78

Merged
vincefn merged 1 commit into
masterfrom
vincefn-refinablepar-name-proxy-design
Aug 24, 2026
Merged

WIP: Add RefinableParProxy to prevent in-place parameter name mutation#78
vincefn merged 1 commit into
masterfrom
vincefn-refinablepar-name-proxy-design

Conversation

@vincefn

@vincefn vincefn commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #71 by introducing a clean proxy-based approach that prevents RefinableObj::AddPar(obj, copyParam=false) from mutating the names of the original RefinablePar objects.

Problem

When LSQNumObj::PrepareRefParList() (or any code calling AddPar(obj, false)) aggregated parameters from multiple objects, duplicate parameter names (e.g. a, b, c from two crystal phases) were resolved by appending ~ suffixes in-place on the original objects. This silently corrupted the parameter names of the source objects.

Solution

RefinableParProxy — a new lightweight subclass of RefinablePar that:

  • Keeps its own hierarchy-prefixed name (ParentClass:ParentName:paramName)
  • Delegates all other operations (value, limits, flags, type) to the wrapped original RefinablePar, so attribute changes propagate correctly

RefinableObj::GetParNameHierarchy(par, max_parents=2) — new public helper that recursively follows the first entry of each object's mClientObjRegistry to build a unique, human-readable prefix. For example:

GrandParentClass:GrandParentName:ParentClass:ParentName:paramName

If the hierarchy-derived name still collides, a warning is printed to stderr and the RefinablePar address is appended as a disambiguating suffix.

Changes

File Change
ObjCryst/RefinableObj/RefinableObj.h RefinableParProxy class; all RefinablePar methods made virtual; GetParNameHierarchy declaration
ObjCryst/RefinableObj/RefinableObj.cpp RefinableParProxy implementation; recursive CollectParentPrefixes helper; updated AddPar(obj, false)
test/unit/api_refinableobj.cpp New test binary: one/two/three-level hierarchy, proxy delegation, collision fallback
test/unit/api_optimization.cpp lsqnumobj-preserve-original-parameter-names extended with two crystal phases sharing a, b, c names
ChangeLog.txt Changelog entry

Relationship to PR #77

PR #77 addresses the same issue (#71) by making the ~-suffix order deterministic (insertion-ordered vector). This PR takes a different approach: proxy objects carry the decorated name, leaving the originals untouched entirely. See comment on PR #77 for a detailed comparison.

Introduce RefinableParProxy, a lightweight subclass of RefinablePar that
keeps its own hierarchy-prefixed name while delegating all other operations
(value, limits, flags, type) to the original RefinablePar.

When RefinableObj::AddPar(obj, copyParam=false) is called (e.g. in
LSQNumObj::PrepareRefParList), parameters are now wrapped in
RefinableParProxy instances rather than having their names mutated
with '~' suffixes. Proxy names are built by GetParNameHierarchy(),
which follows the first entry of each object's client registry
recursively up to max_parents levels to produce a name in the form
'GrandParentClass:GrandParentName:ParentClass:ParentName:paramName'.

If the hierarchy-derived name still collides, a warning is printed to
stderr and the pointer address is appended as a disambiguating suffix.

New API:
  class RefinableParProxy : public RefinablePar
  string RefinableObj::GetParNameHierarchy(RefinablePar&, int max_parents=2)

Unit tests added:
  test/unit/api_refinableobj.cpp  -- proxy behaviour and hierarchy naming
    (one/two/three-level hierarchy, delegation, collision fallback)
  test/unit/api_optimization.cpp  -- lsqnumobj-preserve-original-parameter-names
    extended with two crystalline phases sharing unit-cell parameter names

Fixes #71.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@clemisch

clemisch commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Thanks, sounds good in principle. I'm not sure what the best "naming scheme" is to solve the ultimate problem of user-friendly access to parameters.

In the original issue I complained that unit cell length a would become e.g. a~~ in place, on the same Crystal object. What would it become here, when accessed from its Crystal?

And what would become the indended way to access parameters by name collected in an lsqr object?

I'm asking because I expect these two cases to be different. If I have the parent Crystal I expect to be able to use the canonical name, i.e. crystal.GetPar("a"). But on a flat list lsqr I must use more specifiers to make lookup stable, like you allude with GrandParentClass:GrandParentName:ParentClass:ParentName:paramName.

But what's the "name" of a PowderPattern? The only thing which has a sensible name for me is a phase, like "Corundum". But a phase is a PowderPatternDiffraction, and technically the name lives on its Crystal?

@clemisch

clemisch commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Sorry if this sounds like bikeshedding. I'm being so meticulous because this change isn't technically needed, but only needed for being more user-friendly. And for being user-friendly the naming has to be "expectable"?

There does exist a technical wrinkle though: the number of ~ isn't stable in current behavior. Making it stable would be more limited fix, but more well-defined 😬

@vincefn

vincefn commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

In the original issue I complained that unit cell length a would become e.g. a~~ in place, on the same Crystal object. What would it become here, when accessed from its Crystal?

With this approach, the name in the original Crystal object is unchanged - that's the main point of this approach.

And what would become the intended way to access parameters by name collected in an lsqr object?

You can choose, either use the GetParNameHierarchy() to get the lsqr parameter name, or directly alter the parameters properties in the original object. I expect the latter is the easiest approach, when you want to modify individual parameters rather than by parameter type.

But what's the "name" of a PowderPattern? The only thing which has a sensible name for me is a phase, like "Corundum". But a phase is a PowderPatternDiffraction, and technically the name lives on its Crystal?

It can be anything - the only important point, the name of a PowderPattern must be unique among all PowderPatterns. What it is is not actually relevant - usually the main phase or the dataset number.

@clemisch

Copy link
Copy Markdown
Collaborator

Ok sounds good to me, thanks. It wasn't so clear before if everything would switch to the hierarchical naming, or only lsq(r).

Are you comfortable with merging this PR?

@vincefn
vincefn marked this pull request as ready for review August 24, 2026 13:09
@vincefn
vincefn merged commit 36d89ce into master Aug 24, 2026
2 checks passed
@vincefn
vincefn deleted the vincefn-refinablepar-name-proxy-design branch August 24, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LSQ compiled parameter list mutates original parameter names by appending ~, making parameter lookup unstable

2 participants