|
| 1 | +"""Gear physics provider: gearbox fault verification via gear-mesh signatures. |
| 2 | +
|
| 3 | +Implements the PhysicsProvider interface for gearboxes. Verifies a predicted gear |
| 4 | +fault against the gear-mesh frequency (GMF) and its shaft-rate sidebands: |
| 5 | +localized tooth faults (chip, root crack, missing tooth) raise the sidebands; |
| 6 | +distributed surface wear raises the mesh harmonics. The SEU gearbox taxonomy |
| 7 | +(Health, Chipped, Miss, Root, Surface) maps onto these two evidence channels. |
| 8 | +""" |
| 9 | +from cnsd.physics.providers.base import PhysicsProvider |
| 10 | +from cnsd.physics.gear import gear_fault_evidence |
| 11 | + |
| 12 | +# SEU gearbox fault families -> which evidence channel signifies them. |
| 13 | +# 'localized' faults show shaft-rate sidebands; 'distributed' wear shows mesh. |
| 14 | +_FAMILY_CHANNEL = { |
| 15 | + 'Chipped Tooth': 'sideband', |
| 16 | + 'Missing Tooth': 'sideband', |
| 17 | + 'Root Crack': 'sideband', |
| 18 | + 'Surface Wear': 'mesh', |
| 19 | +} |
| 20 | + |
| 21 | +_ROOT_CAUSE = { |
| 22 | + 'Chipped Tooth': ('gear tooth', 'GMF sidebands', |
| 23 | + 'a chipped tooth striking once per revolution, producing ' |
| 24 | + 'shaft-rate sidebands around the gear-mesh frequency'), |
| 25 | + 'Missing Tooth': ('gear tooth', 'GMF sidebands', |
| 26 | + 'a missing tooth producing a strong once-per-revolution ' |
| 27 | + 'impact and shaft-rate sidebands'), |
| 28 | + 'Root Crack': ('gear tooth root', 'GMF sidebands', |
| 29 | + 'a root crack reducing tooth stiffness, modulating the mesh at ' |
| 30 | + 'the shaft rate'), |
| 31 | + 'Surface Wear': ('gear flank', 'GMF harmonics', |
| 32 | + 'distributed surface wear raising the gear-mesh harmonics'), |
| 33 | + 'Health': (None, None, 'no gear fault; mesh spectrum is clean'), |
| 34 | +} |
| 35 | + |
| 36 | + |
| 37 | +class GearProvider(PhysicsProvider): |
| 38 | + families = ['Chipped Tooth', 'Missing Tooth', 'Root Crack', 'Surface Wear'] |
| 39 | + |
| 40 | + def __init__(self, n_teeth_input, n_teeth_output=None, cond_to_rpm=None, |
| 41 | + fs=5120): |
| 42 | + self.n_teeth_input = n_teeth_input |
| 43 | + self.n_teeth_output = n_teeth_output |
| 44 | + self.cond_to_rpm = cond_to_rpm or {0: 1800} |
| 45 | + self.fs = fs |
| 46 | + |
| 47 | + def evidence(self, signal, condition): |
| 48 | + rpm = self.cond_to_rpm.get(int(condition), |
| 49 | + next(iter(self.cond_to_rpm.values()), 1800)) |
| 50 | + ev = gear_fault_evidence(signal, rpm, self.n_teeth_input, |
| 51 | + self.n_teeth_output, fs=self.fs) |
| 52 | + # localized fault families share the sideband channel; surface wear the mesh |
| 53 | + side, mesh = ev['sideband_strength'], ev['mesh_strength'] |
| 54 | + return { |
| 55 | + 'family_strength': {'Chipped Tooth': side, 'Missing Tooth': side, |
| 56 | + 'Root Crack': side, 'Surface Wear': mesh}, |
| 57 | + 'frequencies_hz': ev['freqs_hz'], |
| 58 | + '_channels': {'sideband': side, 'mesh': mesh}, |
| 59 | + } |
| 60 | + |
| 61 | + def root_cause(self, family, evidence): |
| 62 | + comp, freq_name, mechanism = _ROOT_CAUSE.get(family, (None, None, '')) |
| 63 | + channel = _FAMILY_CHANNEL.get(family) |
| 64 | + strength = evidence.get('_channels', {}).get(channel, 0.0) if channel else 0.0 |
| 65 | + gmf = evidence.get('frequencies_hz', {}).get('GMF') |
| 66 | + if family in ('Health', None): |
| 67 | + statement = 'No gear fault detected; the mesh spectrum is clean.' |
| 68 | + elif gmf is not None: |
| 69 | + statement = (f'Defect on the {comp} ({family}), evidenced by ' |
| 70 | + f'{freq_name} around GMF={gmf:.1f} Hz ' |
| 71 | + f'(strength {strength:.1f}). {mechanism}.') |
| 72 | + else: |
| 73 | + statement = f'{family} on the {comp}.' |
| 74 | + return {'component': comp, 'fault_type': family, 'mechanism': mechanism, |
| 75 | + 'evidence_frequency': freq_name, |
| 76 | + 'evidence_frequency_hz': gmf, |
| 77 | + 'evidence_strength': float(strength), 'statement': statement} |
0 commit comments