Troubleshooting Common Issues#

This page covers common problems and their solutions when creating StructureGene from custom mesh data.

Connectivity Issues#

Error: “Element connectivity out of bounds”#

Symptoms:

IndexError: index 100 is out of bounds for axis 0 with size 5

Cause:

Connectivity array uses original node IDs instead of 0-based array indices.

Solution:

Create a mapping from original node IDs to array indices:

# ✗ WRONG: Using original IDs directly
points = np.array([
    [0, 0, 0],  # This is index 0
    [1, 0, 0],  # This is index 1
])
cells = [CellBlock(type='line', data=np.array([[100, 200]]))]  # Wrong!

# ✓ CORRECT: Map original IDs to indices
original_node_ids = [100, 200, 300, 400]
node_id_to_index = {nid: idx for idx, nid in enumerate(original_node_ids)}

# Apply mapping
original_connectivity = [100, 200]
correct_connectivity = [node_id_to_index[nid] for nid in original_connectivity]
# Result: [0, 1]

cells = [CellBlock(type='line', data=np.array([correct_connectivity]))]

Best Practice:

Always build the mapping at the start of your conversion:

# Sort node IDs to get consistent ordering
node_ids = sorted(mesh_data['nodes'].keys())
node_id_to_index = {nid: idx for idx, nid in enumerate(node_ids)}

# Create points array in the same order
points = np.array([mesh_data['nodes'][nid] for nid in node_ids])

# Apply to all connectivity
for elem in mesh_data['elements']:
    conn_indices = [node_id_to_index[nid] for nid in elem['connectivity']]
    # Use conn_indices in CellBlock

Error: “Node ID not found in mapping”#

Symptoms:

KeyError: 999

Cause:

An element references a node ID that doesn’t exist in your nodes dictionary.

Solution:

Add validation and helpful error messages:

for elem in mesh_data['elements']:
    try:
        conn = [node_id_to_index[nid] for nid in elem['connectivity']]
    except KeyError as e:
        missing_node_id = int(str(e))
        raise ValueError(
            f"Element {elem['id']} references undefined node ID {missing_node_id}. "
            f"Available node IDs: {min(node_ids)}-{max(node_ids)}"
        )

Property and Material Issues#

Error: “Property ID not found in mocombos”#

Symptoms:

KeyError: 5

Cause:

Property IDs in cell_data['property_id'] don’t match keys in sg.mocombos.

Solution:

Ensure all property IDs used by elements are defined in mocombos:

# Check which property IDs are actually used
used_prop_ids = set()
for prop_array in sg.mesh.cell_data['property_id']:
    used_prop_ids.update(prop_array)

print(f"Property IDs used: {sorted(used_prop_ids)}")
print(f"Property IDs defined: {sorted(sg.mocombos.keys())}")

# Find missing definitions
for prop_id in used_prop_ids:
    if prop_id not in sg.mocombos:
        print(f"ERROR: Missing mocombo for property_id {prop_id}")

Prevention:

When building mocombos, ensure every unique (material, angle) combination gets an entry:

mocombo_map = {}
next_property_id = 1

for elem in mesh_data['elements']:
    key = (elem['material'], elem['angle'])
    if key not in mocombo_map:
        mocombo_map[key] = next_property_id
        sg.mocombos[next_property_id] = key
        next_property_id += 1

Error: “Material name not found”#

Symptoms:

KeyError: 'steel'

Cause:

Material name in mocombos doesn’t exactly match a key in sg.materials. Often due to: - Case sensitivity ('Steel' vs 'steel') - Whitespace ('Steel' vs 'Steel ') - Typos

Solution:

Verify material names match exactly:

# Check what's defined
print("Defined materials:", list(sg.materials.keys()))

# Check what's referenced
for prop_id, (mat_name, angle) in sg.mocombos.items():
    if mat_name not in sg.materials:
        print(f"ERROR: Property {prop_id} references undefined material '{mat_name}'")
        # Show similar names
        from difflib import get_close_matches
        similar = get_close_matches(mat_name, sg.materials.keys())
        if similar:
            print(f"  Did you mean: {similar[0]}?")

Prevention:

Normalize material names during conversion:

def normalize_name(name):
    """Normalize material name (strip whitespace, consistent case)."""
    return name.strip()

# Use when adding materials
for mat_name, props in mesh_data['materials'].items():
    normalized_name = normalize_name(mat_name)
    material = CauchyContinuumModel(name=normalized_name, ...)
    sg.materials[normalized_name] = material

# Use when creating mocombos
key = (normalize_name(elem['material']), elem['angle'])

Mesh Structure Issues#

Error: “Mixed element types in one cell block”#

Symptoms:

Elements with different numbers of nodes in the same CellBlock, or validation errors.

Cause:

Trying to put different element types (e.g., triangles and quads) in one CellBlock.

Solution:

Group elements by type before creating CellBlocks:

# ✗ WRONG: Mixed types
cells = [CellBlock(type='triangle', data=np.array([
    [0, 1, 2],     # 3 nodes (triangle)
    [3, 4, 5, 6],  # 4 nodes (quad) - ERROR!
]))]

# ✓ CORRECT: Separate by type
elements_by_type = {}
for elem in mesh_data['elements']:
    elem_type = elem['type']
    if elem_type not in elements_by_type:
        elements_by_type[elem_type] = []
    elements_by_type[elem_type].append(elem)

cells = []
for elem_type, elems in elements_by_type.items():
    connectivity = [...]  # Build connectivity for this type
    cells.append(CellBlock(type=elem_type, data=np.array(connectivity)))

Error: “Unsupported element type”#

Symptoms:

ValueError: Unsupported element type: TRIA6

Cause:

Your custom format uses element type names that need mapping to meshio names.

Solution:

Create a type mapping dictionary:

TYPE_MAP = {
    # Custom format -> meshio format
    'TRIA3': 'triangle',
    'TRIA6': 'triangle6',
    'QUAD4': 'quad',
    'QUAD8': 'quad8',
    'QUAD9': 'quad9',
    'TETRA4': 'tetra',
    'TETRA10': 'tetra10',
    'HEXA8': 'hexahedron',
    'HEXA20': 'hexahedron20',
    'HEXA27': 'hexahedron27',
    'WEDGE6': 'wedge',
    'PYRA5': 'pyramid',
}

for elem in mesh_data['elements']:
    if elem['type'] not in TYPE_MAP:
        raise ValueError(
            f"Unsupported element type: {elem['type']}. "
            f"Supported types: {list(TYPE_MAP.keys())}"
        )
    meshio_type = TYPE_MAP[elem['type']]

Dimension and Configuration Issues#

Error: “Invalid model type for dimension”#

Symptoms:

ValueError: Model type 'PL1' requires sgdim=2, but got sgdim=3

Cause:

Mismatch between StructureGene dimension and model type.

Solution:

Match model type to dimension:

# Dimension 1 (Beam)
sg = StructureGene(sgdim=1, smdim=1)
sgio.write(..., model_type='BM1')  # or BM2, BM3, BM4

# Dimension 2 (Plate/Shell)
sg = StructureGene(sgdim=2, smdim=2)
sgio.write(..., model_type='PL1')  # or PL2

# Dimension 3 (Solid)
sg = StructureGene(sgdim=3, smdim=3)
sgio.write(..., model_type='SD1')  # or SD2

Auto-select model type:

MODEL_TYPES = {
    1: 'BM1',  # Default for beams
    2: 'PL1',  # Default for plates
    3: 'SD1',  # Default for solids
}

model_type = MODEL_TYPES[sg.sgdim]
sgio.write(sg=sg, fn='output.sc', model_type=model_type, ...)

Validation and Debugging#

Empty or Missing Mesh#

Symptoms:

AttributeError: 'NoneType' object has no attribute 'points'

Solution:

Check mesh exists before accessing:

if sg.mesh is None:
    raise ValueError("Mesh not created. Did you assign sg.mesh?")

if sg.nnodes == 0:
    raise ValueError("Mesh has no nodes")

if sg.nelems == 0:
    raise ValueError("Mesh has no elements")

Pre-write Validation Checklist#

Use this validation function before writing:

def validate_sg(sg):
    """Comprehensive validation with helpful messages."""
    errors = []

    # Configuration
    if sg.sgdim not in [1, 2, 3]:
        errors.append(f"Invalid sgdim: {sg.sgdim}")
    if not sg.name:
        errors.append("Name is empty")

    # Mesh
    if sg.mesh is None:
        errors.append("Mesh is None")
    elif sg.nnodes == 0:
        errors.append("Mesh has no nodes")
    elif sg.nelems == 0:
        errors.append("Mesh has no elements")

    # Materials
    if not sg.materials:
        errors.append("No materials defined")

    # Mocombos
    if not sg.mocombos:
        errors.append("No mocombos defined")

    # Property IDs
    if sg.mesh is not None:
        for i, prop_array in enumerate(sg.mesh.cell_data.get('property_id', [])):
            for prop_id in np.unique(prop_array):
                if prop_id not in sg.mocombos:
                    errors.append(
                        f"Block {i}: property_id {prop_id} not in mocombos"
                    )

    # Material references
    for prop_id, (mat_name, angle) in sg.mocombos.items():
        if mat_name not in sg.materials:
            errors.append(
                f"Mocombo {prop_id} references undefined material '{mat_name}'"
            )

    if errors:
        print("Validation errors:")
        for error in errors:
            print(f"  ✗ {error}")
        return False
    else:
        print("✓ Validation passed")
        return True

Performance Issues#

Slow conversion for large meshes#

Problem: Converting millions of nodes/elements is slow.

Solutions:

  1. Pre-allocate arrays:

# Slow: Appending to lists
connectivity = []
for elem in elems:
    connectivity.append([...])

# Fast: Pre-allocate numpy array
n_elems = len(elems)
nodes_per_elem = 4
connectivity = np.zeros((n_elems, nodes_per_elem), dtype=int)
for i, elem in enumerate(elems):
    connectivity[i] = [...]
  1. Vectorize operations:

# Slow: Loop over mapping
conn_indices = [node_id_to_index[nid] for nid in elem['connectivity']]

# Fast: Use numpy vectorized operations
conn_array = np.array(elem['connectivity'])
conn_indices = np.vectorize(node_id_to_index.get)(conn_array)
  1. Process in chunks:

CHUNK_SIZE = 10000
for i in range(0, len(elements), CHUNK_SIZE):
    chunk = elements[i:i+CHUNK_SIZE]
    # Process chunk

Getting More Help#

If you’re still having issues:

  1. Check data structure documentation: Data Structure Reference

  2. Review working examples: Quick Start: Simple Triangle Mesh and Converting from Custom Format

  3. Verify with minimal example: Strip down to smallest failing case

  4. Check sgio version: Ensure you’re using the latest version

Debug output template:

print("Debug info:")
print(f"  sgio version: {sgio.__version__}")
print(f"  sgdim: {sg.sgdim}")
print(f"  Nodes: {sg.nnodes if sg.mesh else 'No mesh'}")
print(f"  Elements: {sg.nelems if sg.mesh else 'No mesh'}")
print(f"  Materials: {list(sg.materials.keys())}")
print(f"  Mocombos: {dict(sg.mocombos)}")
if sg.mesh and 'property_id' in sg.mesh.cell_data:
    all_props = []
    for pa in sg.mesh.cell_data['property_id']:
        all_props.extend(pa)
    print(f"  Property IDs used: {sorted(set(all_props))}")