| 3845 | x: string, |
| 3846 | allowed: Set<string> |
| 3847 | ): boolean { |
| 3848 | if (!u.ops || u.ops.length === 0) return u.symbol !== x; // atom: not x |
| 3849 | if (CALCULUS_FNS.has(u.operator)) return false; |
| 3850 | if (activeTrigHeadQ(u) && u.ops[0].isSame(v)) return allowed.has(u.operator); |
| 3851 | return u.ops.every((o) => pureFunctionOfTrigQ(o, v, x, allowed)); |
| 3852 | } |
| 3853 | |
| 3854 | /** The substitution target trig node of `v` (the rule passes `trig(arg)/d`); |
| 3855 | * returns the bare trig node after dropping x-free factors, or null. */ |
| 3856 | function substTrigNode( |
| 3857 | v: Expression, |
| 3858 | x: string, |
| 3859 | ce: ComputeEngine |
| 3860 | ): Expression | null { |
| 3861 | let vt = v; |
| 3862 | if (v.operator === 'Multiply' || v.operator === 'Divide') |
| 3863 | vt = selectFactors(v, x, ce, false); // NonfreeFactors[v, x] |
| 3864 | return activeTrigHeadQ(vt) && vt.ops ? vt : null; |
| 3865 | } |
| 3866 | |
| 3867 | /** FunctionOfQ[v, u, x, pure] — restricted to the pure trig-substitution |
| 3868 | * targets this slice handles; false (decline) otherwise. */ |
| 3869 | function functionOfQ( |
| 3870 | ce: ComputeEngine, |
| 3871 | v: Expression, |
| 3872 | u: Expression, |
| 3873 | x: string, |
| 3874 | pure: boolean |
| 3875 | ): boolean { |
| 3876 | if (!u.has(x)) return false; // FreeQ[u, x] |
| 3877 | if (!v.ops || v.ops.length === 0) return true; // AtomQ[v] |
| 3878 | if (!pure) return false; // non-pure dispatch not yet ported |
| 3879 | const vt = substTrigNode(v, x, ce); |
| 3880 | if (vt === null) return false; |
| 3881 | const arg = vt.ops![0]; |
| 3882 | const au = activateTrig(ce, u); |
| 3883 | const head = vt.operator; |
| 3884 | if (SIN_HEADS.has(head)) return pureFunctionOfTrigQ(au, arg, x, SIN_HEADS); |
| 3885 | if (COS_HEADS.has(head)) return pureFunctionOfTrigQ(au, arg, x, COS_HEADS); |
| 3886 | if (TAN_HEADS.has(head)) return pureFunctionOfTrigQ(au, arg, x, TAN_HEADS); |
| 3887 | return false; |
| 3888 | } |
| 3889 | |