Validation¶
Lynx performs comprehensive validation before exporting diagrams to python-control. This ensures diagrams are well-formed and mathematically valid.
Validation Layers¶
Validation occurs in three layers:
System Boundary Checks: At least one InputMarker and one OutputMarker
Label Uniqueness: Warnings for duplicate block or connection labels
Port Connectivity: All non-InputMarker input ports must be connected
Automatic Validation¶
Validation runs automatically when calling export methods:
# Validation happens here
sys = diagram.get_tf('r', 'y') # Raises ValidationError if invalid
sys = diagram.get_ss('r', 'y') # Raises ValidationError if invalid
You don’t need to manually call validation.
ValidationError¶
When validation fails, Lynx raises ValidationError with detailed context:
from lynx import ValidationError
try:
sys = diagram.get_tf('r', 'y')
except ValidationError as e:
print(f"Validation failed: {e}")
print(f"Block ID: {e.block_id}")
print(f"Port ID: {e.port_id}")
Error Attributes¶
message: Human-readable error description
block_id: ID of the block causing the error (if applicable)
port_id: ID of the port causing the error (if applicable)
Common Validation Errors¶
1. Missing System Boundaries¶
Error: “Diagram must have at least one InputMarker and one OutputMarker”
Cause: No IOMarker blocks, or all markers are the same type
Fix: Add at least one input and one output marker
# Before (invalid)
diagram.add_block('gain', 'K1', K=5.0)
diagram.add_block('transfer_function', 'plant', num=[1.0], den=[1.0, 1.0])
# After (valid)
diagram.add_block('io_marker', 'r', marker_type='input', label='r')
diagram.add_block('gain', 'K1', K=5.0)
diagram.add_block('transfer_function', 'plant', num=[1.0], den=[1.0, 1.0])
diagram.add_block('io_marker', 'y', marker_type='output', label='y')
2. Unconnected Input Port¶
Error: “Port ‘in’ of block ‘plant’ is not connected”
Cause: Block has an input port with no incoming connection
Fix: Connect the port or remove the block
# Before (invalid) - plant has no input connection
diagram.add_block('io_marker', 'r', marker_type='input', label='r')
diagram.add_block('transfer_function', 'plant', num=[1.0], den=[1.0, 1.0])
# Missing connection!
# After (valid)
diagram.add_block('io_marker', 'r', marker_type='input', label='r')
diagram.add_block('transfer_function', 'plant', num=[1.0], den=[1.0, 1.0])
diagram.add_connection('c1', 'r', 'out', 'plant', 'in') # Connect input
3. Algebraic Loop¶
Error: “Algebraic loop detected in block ‘sum1’”
Cause: Feedback loop with no dynamics (no transfer function or integrator in the loop)
Fix: Add dynamics to the loop or remove the direct feedthrough
# Before (invalid) - pure gain feedback creates algebraic loop
diagram.add_block('sum', 'error', signs=['+', '-', '|'])
diagram.add_block('gain', 'K1', K=5.0)
diagram.add_block('gain', 'K2', K=2.0)
diagram.add_connection('c1', 'error', 'out', 'K1', 'in')
diagram.add_connection('c2', 'K1', 'out', 'K2', 'in')
diagram.add_connection('c3', 'K2', 'out', 'error', 'in2') # Direct feedback - algebraic loop!
# After (valid) - add dynamics
diagram.add_block('sum', 'error', signs=['+', '-', '|'])
diagram.add_block('gain', 'K1', K=5.0)
diagram.add_block('transfer_function', 'plant', # Add dynamics here
num=[1.0], den=[1.0, 1.0])
diagram.add_connection('c1', 'error', 'out', 'K1', 'in')
diagram.add_connection('c2', 'K1', 'out', 'plant', 'in')
diagram.add_connection('c3', 'plant', 'out', 'error', 'in2') # Now valid
4. Duplicate Labels¶
Warning: “Duplicate block label ‘controller’ found”
Cause: Multiple blocks or connections with the same label
Fix: Use unique labels for all blocks and connections
# Before (warning)
diagram.add_block('gain', 'K1', K=5.0, label='controller')
diagram.add_block('gain', 'K2', K=2.0, label='controller') # Duplicate!
# After (no warning)
diagram.add_block('gain', 'K1', K=5.0, label='controller_1')
diagram.add_block('gain', 'K2', K=2.0, label='controller_2')
Note: Duplicate labels issue warnings but don’t block export. However, signal references may be ambiguous.
Pre-Export Validation Workflow¶
Best practice for complex diagrams:
import lynx
from lynx import ValidationError
# Build diagram
diagram = lynx.Diagram()
# ... add blocks and connections ...
# Validate before analysis
try:
# Attempt export (triggers validation)
sys = diagram.get_tf('r', 'y')
print("✓ Diagram is valid!")
# Proceed with analysis
import control as ct
ct.bode_plot(sys)
except ValidationError as e:
print(f"✗ Validation failed: {e}")
print(f" Block: {e.block_id}, Port: {e.port_id}")
# Fix the issue...
if e.port_id:
print(f" Check connections for block '{e.block_id}' port '{e.port_id}'")
SignalNotFoundError¶
Separate from ValidationError, this exception occurs when the specified signal references don’t exist:
from lynx import SignalNotFoundError
try:
sys = diagram.get_tf('nonexistent', 'y') # 'nonexistent' not found
except SignalNotFoundError as e:
print(f"Signal not found: {e.signal_name}")
print(f"Searched: {e.searched_locations}")
Error Attributes¶
signal_name: The signal reference that wasn’t found
searched_locations: List of places searched (IOMarker labels, connection labels, block outputs)
See Also¶
Python-Control Export - Signal reference patterns and subsystem extraction
Diagram Class - Building valid diagrams
Quickstart - Quick examples