# Research & Design Decisions

---
**Purpose**: Capture discovery findings and rationale for the semi-major axis normalization feature.
---

## Summary
- **Feature**: `3d-efa-normalization-semi-major-axis`
- **Discovery Scope**: Extension
- **Key Findings**:
  - Scale factor is the only difference between area-based and semi-major-axis-based normalization; all other steps (reorientation, phase shift, direction correction) are shared
  - The `__init__` parameter approach (Option C from gap analysis) aligns best with sklearn conventions and enables `GridSearchCV` / `Pipeline` integration
  - No new dependencies or external research required; the mathematical foundation is identical

## Research Log

### Scale Factor Conventions in EFA Literature
- **Context**: Understanding why 2D and 3D use different scale factors
- **Sources Consulted**: Kuhl & Giardina (1982), Godefroy et al. (2012)
- **Findings**:
  - Kuhl & Giardina (1982) 2D normalization uses semi-major axis length as scale — the standard in 2D morphometrics
  - Godefroy et al. (2012) chose ellipse area for 3D, likely for robustness to eccentricity variation
  - Both are mathematically valid; the choice affects only the magnitude of normalized coefficients
- **Implications**: Supporting both methods provides researchers with flexibility to match their domain conventions

### sklearn Parameter Convention
- **Context**: How to expose the normalization method choice
- **Sources Consulted**: sklearn documentation (BaseEstimator, get_params/set_params contract)
- **Findings**:
  - sklearn convention: hyperparameters in `__init__`, data in `fit`/`transform`
  - `BaseEstimator.get_params()` introspects `__init__` signature
  - Adding `norm_method` to `__init__` makes it searchable via `GridSearchCV`
  - `norm` stays as a `transform`-level parameter (controls whether to normalize at all)
- **Implications**: `norm_method` belongs in `__init__`, not in `transform`

## Architecture Pattern Evaluation

| Option | Description | Strengths | Risks / Limitations | Notes |
|--------|-------------|-----------|---------------------|-------|
| A: Method param on `_normalize_3d` | Pass `norm_method` only to internal method | Minimal change | Requires threading through call chain | Simple but not sklearn-native |
| B: Extend `norm` to `bool\|str` | Overload `norm` parameter | No new parameter | Mixed types, less clear API | Type hint complexity |
| C: `__init__` parameter | Add `norm_method` to constructor | sklearn-native, clean types, searchable | One more ctor param | **Selected** |

## Design Decisions

### Decision: Parameter Placement — `__init__` vs `transform`
- **Context**: Where to expose the normalization method selection
- **Alternatives Considered**:
  1. Add `norm_method` to `transform()` — analogous to how `norm` works
  2. Add `norm_method` to `__init__()` — as a hyperparameter
- **Selected Approach**: `__init__` parameter `norm_method: str = "area"`
- **Rationale**: The normalization method is a model-level configuration choice, not a per-call toggle. It affects the interpretation of all subsequent `transform`/`inverse_transform` calls. sklearn convention places such choices in `__init__`.
- **Trade-offs**: Slightly more complex constructor, but enables `GridSearchCV` and `Pipeline` integration
- **Follow-up**: Verify `get_params()`/`set_params()` work correctly with the new parameter

### Decision: Parameter Naming — `norm_method`
- **Context**: Choosing between `norm_method`, `normalization_method`, `scale_type`
- **Selected Approach**: `norm_method`
- **Rationale**: Concise, descriptive, consistent with existing short parameter names (`n_dim`, `n_jobs`). The prefix `norm_` ties it to the `norm` parameter semantically.
- **Trade-offs**: Less verbose than `normalization_method`, but more readable than `scale_type` which is too narrow (normalization involves more than just scaling)

### Decision: Valid Values — `"area"` and `"semi_major_axis"`
- **Context**: Naming the normalization method options
- **Selected Approach**: `"area"` (default) and `"semi_major_axis"`
- **Rationale**: `"area"` is concise and accurately describes `sqrt(π·a·b)`. `"semi_major_axis"` is explicit and matches the mathematical term. No aliases for simplicity.
- **Trade-offs**: Could add `"major_axis"` alias later if needed

### Decision: 2D Behavior Unchanged
- **Context**: Should `norm_method` affect 2D normalization?
- **Selected Approach**: No — `norm_method` is only meaningful for `n_dim=3`
- **Rationale**: 2D already uses semi-major axis (the only implemented method). Adding area-based 2D normalization is a separate feature (noted in `_normalize_2d` TODO). Introducing `norm_method` for 2D now would create scope creep.
- **Trade-offs**: `norm_method` is silently ignored for 2D. Could add a warning, but this matches how other parameters (e.g., `verbose`) are silently handled in various contexts.

## Risks & Mitigations
- **Near-degenerate ellipse (a ≈ b)**: Semi-major axis normalization is more sensitive than area-based when `a ≈ b` because the "semi-major" direction is numerically unstable. Mitigation: this is inherent to the method and already handled by `_compute_ellipse_geometry_3d`.
- **User confusion about method names**: Mitigated by clear docstring documentation with mathematical definitions.

## References
- Kuhl, F.P., Giardina, C.R. (1982). Elliptic Fourier features of a closed contour. Comput. Graph. Image Process. 18: 236-258.
- Godefroy, J.E., et al. (2012). Elliptical Fourier descriptors for contours in three dimensions. C. R. Biol. 335, 205-213.
- scikit-learn: Developing scikit-learn estimators — https://scikit-learn.org/stable/developers/develop.html
