Skip to content

visualdynamics.compatibility

compatibility

Do the objects in a test belong together?

Everything carrying DOFs — mode shapes, data records, channel table rows — names nodes. Those nodes have to exist in the geometry the test is being interpreted against, or the object describes a different structure: airplane mode shapes beside a plate geometry are not a test, they are a mistake.

Compatibility is judged against the geometry each object answers to: its link group's geometry when it has one — a FEM shape set beside its own FEM mesh is consistent, whichever geometry is active — and the one active geometry otherwise, so a test holding two models gives a definite answer instead of "compatible with something". Objects with no DOFs (a geometry itself) are never flagged, and with no active geometry nothing is checked — importing data before its geometry must not paint everything red.

A data object whose DOFs are modal coordinates (M1Mn, validate.modal_coordinate) names no node at all: it answers to a shape set, the one it was transformed through, and fits wherever a set with at least that many modes is — its link group's set when it has one, any set in the project otherwise (Brandon, 2026-09-04: the modal responses belong in the group with the shapes and the record they came from).

Deliberately not part of this:

  • units, which have their own indicator
  • direction validity: a geometry does not restrict directions, so 101RX+ is fine wherever node 101 exists
  • whether two data objects can be compared with each other

Classes:

Name Description
Issue

Why one object does not fit the active geometry.

Report

Compatibility of every object in a test.

Functions:

Name Description
modal_object

The highest mode a data object's coordinates name, when every

check_modal

The Issue for a modal object against the shape sets among its

check_object

The Issue for one object against a geometry, or None if it fits.

check_compatibility

Check every object in a test against the geometry it answers to.

Classes

Issue dataclass

Issue(
    name: str,
    kind: str,
    message: str,
    missing_dofs: list = list(),
    sub_items: list = list(),
)

Why one object does not fit the active geometry.

Report dataclass

Report(
    geometry_name: str | None = None, issues: dict = dict()
)

Compatibility of every object in a test.

Methods:

Name Description
is_compatible

Whether one object fits the geometry it is linked to.

issue_for

What is wrong with one object, or None if nothing is.

sub_item_flagged

Whether one record within an object is incompatible.

Attributes:

Name Type Description
incompatible_names list[str]

The objects that do not fit the geometry, by name — what

Attributes
incompatible_names property
incompatible_names: list[str]

The objects that do not fit the geometry, by name — what the tree marks in red and a link refuses over.

Methods:
is_compatible
is_compatible(name: str) -> bool

Whether one object fits the geometry it is linked to.

Parameters:

Name Type Description Default
name str

The object to ask about.

required

Returns:

Type Description
bool

Whether it fits the geometry it is linked to.

Source code in src/visualdynamics/compatibility.py
def is_compatible(self, name: str) -> bool:
    """Whether one object fits the geometry it is linked to.

    Parameters
    ----------
    name : str
        The object to ask about.

    Returns
    -------
    bool
        Whether it fits the geometry it is linked to.
    """
    return name not in self.issues
issue_for
issue_for(name: str) -> Issue | None

What is wrong with one object, or None if nothing is.

Parameters:

Name Type Description Default
name str

The object to ask about.

required

Returns:

Type Description
Issue or None

What is wrong with it, or None if nothing is.

Source code in src/visualdynamics/compatibility.py
def issue_for(self, name: str) -> Issue | None:
    """What is wrong with one object, or None if nothing is.

    Parameters
    ----------
    name : str
        The object to ask about.

    Returns
    -------
    Issue or None
        What is wrong with it, or None if nothing is.
    """
    return self.issues.get(name)
sub_item_flagged
sub_item_flagged(name: str, index: int) -> bool

Whether one record within an object is incompatible.

Parameters:

Name Type Description Default
name str

The object to ask about.

required
index int

Which record within it.

required

Returns:

Type Description
bool

Whether that record is one of the incompatible ones.

Source code in src/visualdynamics/compatibility.py
def sub_item_flagged(self, name: str, index: int) -> bool:
    """Whether one record within an object is incompatible.

    Parameters
    ----------
    name : str
        The object to ask about.
    index : int
        Which record within it.

    Returns
    -------
    bool
        Whether that record is one of the incompatible ones.
    """
    issue = self.issues.get(name)
    return bool(issue) and index in issue.sub_items

Functions:

modal_object

modal_object(obj: Any) -> int | None

The highest mode a data object's coordinates name, when every one of its DOFs is a modal coordinate — else None.

Source code in src/visualdynamics/compatibility.py
def modal_object(obj: Any) -> int | None:
    """The highest mode a data object's coordinates name, when every
    one of its DOFs is a modal coordinate — else None."""
    from .core.data import DataArray
    from .core.validate import modal_coordinate

    if not isinstance(obj, DataArray) or not obj.num_records:
        return None
    indices = [modal_coordinate(dof) for dof in obj.response_dof]
    if any(index is None for index in indices):
        return None
    return max(indices)

check_modal

check_modal(
    name: str, obj: Any, companions: Mapping[str, Any]
) -> Issue | None

The Issue for a modal object against the shape sets among its companions, or None when one of them has every mode it names.

Source code in src/visualdynamics/compatibility.py
def check_modal(name: str, obj: Any, companions: Mapping[str, Any]
                ) -> Issue | None:
    """The Issue for a modal object against the shape sets among its
    companions, or None when one of them has every mode it names."""
    from .core.shapes import ShapeSet

    highest = modal_object(obj)
    sets = {other: value for other, value in companions.items()
            if isinstance(value, ShapeSet) and other != name}
    if any(value.num_shapes >= highest for value in sets.values()):
        return None
    beyond = [i for i, dof in enumerate(obj.response_dof)
              if _index(dof) > max((v.num_shapes for v in sets.values()),
                                   default=0)]
    if not sets:
        message = (f'modal coordinates up to M{highest}, and no shape set '
                   'to answer to — link the set they were transformed '
                   'through')
    else:
        most = max(sets.items(), key=lambda item: item[1].num_shapes)
        message = (f'modal coordinates up to M{highest}, but shape set '
                   f'{most[0]!r} has {most[1].num_shapes} modes')
    return Issue(name=name, kind='modes-not-in-set', message=message,
                 missing_dofs=[obj.response_dof[i] for i in beyond],
                 sub_items=beyond)

check_object

check_object(
    name: str,
    obj: Any,
    geometry: Geometry | None,
    geometry_name: str = "the active geometry",
    companions: Mapping[str, Any] | None = None,
) -> Issue | None

The Issue for one object against a geometry, or None if it fits.

companions are the objects it is grouped with, for a modal object, which answers to a shape set among them rather than to the geometry.

Source code in src/visualdynamics/compatibility.py
def check_object(name: str, obj: Any, geometry: Geometry | None,
                 geometry_name: str = 'the active geometry',
                 companions: Mapping[str, Any] | None = None) -> Issue | None:
    """The Issue for one object against a geometry, or None if it fits.

    `companions` are the objects it is grouped with, for a modal
    object, which answers to a shape set among them rather than to
    the geometry.
    """
    from .core.channel_table import ChannelTable
    from .core.data import DataArray
    from .core.geometry import Geometry
    from .core.shapes import ShapeSet

    if isinstance(obj, Geometry):
        return None
    if modal_object(obj) is not None:
        return check_modal(name, obj, companions or {})
    if geometry is None:
        return None

    if isinstance(obj, ShapeSet):
        missing = geometry.missing_dofs(obj.coordinate)
        if not missing:
            return None
        return Issue(
            name=name, kind='dofs-not-in-geometry',
            message=(f'{len(missing)} of {obj.num_dofs} shape DOFs are not in '
                     f'geometry {geometry_name!r}: {_listed(missing)}'),
            missing_dofs=missing,
            # every mode spans the same DOFs, so all of them are affected
            sub_items=list(range(obj.num_shapes)))

    if isinstance(obj, DataArray):
        missing, flagged = [], []
        for i in range(obj.num_records):
            dofs = [obj.response_dof[i]]
            if obj.reference_dof is not None:
                dofs.append(obj.reference_dof[i])
            gone = geometry.missing_dofs(dofs)
            if gone:
                flagged.append(i)
                missing.extend(d for d in gone if d not in missing)
        if not flagged:
            return None
        return Issue(
            name=name, kind='dofs-not-in-geometry',
            message=(f'{len(flagged)} of {obj.num_records} records reference '
                     f'DOFs not in geometry {geometry_name!r}: '
                     f'{_listed(missing)}'),
            missing_dofs=missing, sub_items=flagged)

    if isinstance(obj, ChannelTable):
        dofs = obj.dof_strings()
        flagged = [i for i, dof in enumerate(dofs)
                   if geometry.missing_dofs([dof])]
        if not flagged:
            return None
        missing = [dofs[i] for i in flagged]
        return Issue(
            name=name, kind='dofs-not-in-geometry',
            message=(f'{len(flagged)} of {len(dofs)} channels are not in '
                     f'geometry {geometry_name!r}: {_listed(missing)}'),
            missing_dofs=missing, sub_items=flagged)

    return None

check_compatibility

check_compatibility(
    objects: Mapping[str, Any],
    geometry_name: str | None = None,
    links: Sequence[Mapping[str, Any]] | None = None,
) -> Report

Check every object in a test against the geometry it answers to.

objects maps name -> object. An object linked into a group that holds a geometry is judged against that geometry — a FEM shape set beside its own FEM mesh is consistent, whichever geometry is active. Everything else is judged against geometry_name (the active geometry; without it the first geometry found). Returns a Report.

Source code in src/visualdynamics/compatibility.py
def check_compatibility(objects: Mapping[str, Any],
                        geometry_name: str | None = None,
                        links: Sequence[Mapping[str, Any]] | None = None
                        ) -> Report:
    """Check every object in a test against the geometry it answers to.

    `objects` maps name -> object. An object linked into a group that
    holds a geometry is judged against *that* geometry — a FEM shape
    set beside its own FEM mesh is consistent, whichever geometry is
    active. Everything else is judged against `geometry_name` (the
    active geometry; without it the first geometry found). Returns a
    Report.
    """
    from .core.geometry import Geometry

    geometries = {name: obj for name, obj in objects.items()
                  if isinstance(obj, Geometry)}
    if geometry_name not in geometries:
        geometry_name = next(iter(geometries), None)

    def home(name: str) -> str | None:
        for group in links or []:
            if name in group.get('members', ()):
                linked = next((member for member in group['members']
                               if member in geometries), None)
                if linked is not None:
                    return linked
                break
        return geometry_name

    def companions(name: str) -> Mapping[str, Any]:
        # a modal object answers to its group's shape sets, or to any
        # in the project while it is not yet linked
        for group in links or []:
            if name in group.get('members', ()):
                return {member: objects[member] for member in group['members']
                        if member in objects}
        return objects

    report = Report(geometry_name=geometry_name)
    for name, obj in objects.items():
        if modal_object(obj) is not None:
            issue = check_modal(name, obj, companions(name))
        elif geometry_name is None:
            continue
        else:
            against = home(name)
            issue = check_object(name, obj, geometries.get(against), against)
        if issue is not None:
            report.issues[name] = issue
    return report