Skip to content

Handle degenerate geometry in constraints #83

@nickmccleery

Description

@nickmccleery

There are a few spots in our constraints, e.g. LineTangentToCircle, LinesEqualLength (under #58), where we check for degenerate cases and then don't really do too much with that.

In some cases, like the python implementation's LineLineAngle constraint, we actually effectively report constraint satisfaction for degenerate cases by virtue of returning zero:

    def get_residual(self, variable_values: Mapping[str, float]) -> nb.Vector:
        # Get direction vectors for both lines.
        p1 = self.line1.p1.get_state(variable_values)
        p2 = self.line1.p2.get_state(variable_values)
        p3 = self.line2.p1.get_state(variable_values)
        p4 = self.line2.p2.get_state(variable_values)

        v1 = p2 - p1
        v2 = p4 - p3

        # Calculate magnitudes.
        mag1 = nb.np.linalg.norm(v1)
        mag2 = nb.np.linalg.norm(v2)

        # Check for zero-length lines.
        is_invalid = (mag1 < EPS) | (mag2 < EPS)

        # 2D cross product and dot product.
        cross_2d = v1[0] * v2[1] - v1[1] * v2[0]
        dot_product = nb.np.dot(v1, v2)

        # Current angle using atan2.
        current_angle = nb.np.atan2(cross_2d, dot_product)

        # Compute angle difference.
        angle_residual = nb.np.array([current_angle - self.angle])

        # Return 0.0 if invalid, otherwise return residual. <----------- here
        return nb.np.where(is_invalid, nb.np.array([0.0]), angle_residual)

I think the best idea I have is probably to penalise the solver for degenerate solutions.

We don't want the solver to home in on a degenerate candidate solution then report that as satisfactory if an actual, valid, non-degenerate solution exists. Practically this means adding some residual term for degenerate cases, e.g., where line length is very short... though ideally I think we'd shape this function a little bit.

I've done a bunch of hyberbolic tangent or otherwise sigmoid shaped scaling applied to gated functions in the past, something like that might be a good idea here, though maybe a simple linear shape is better. Either way, we could slap in a residual term that's large for true degeneracy, but that smoothly fades out to zero where we have real and valid candidate solutions.

We might also want some amount of error handling as a user could request something degenerate and we need to be able to tell them that. For example, a user could draw a line $L1$, fix one endpoint, then constrain the other endpoint to be coincident with the first. This is fundamentally degenerate and we'd benefit from being able to spot and flag this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions