# Erweiterte Anleitung: Hinzufügen einer neuen Testfunktion (mit Templates und Properties-Liste)

Basierend auf deinem Feedback habe ich die gestraffte Version weiter verfeinert: Templates (Code-Snippets) sind nun prominent platziert und als Kopierbare Blöcke hervorgehoben, um Konsistenz zu erzwingen. Die Liste der erlaubten Eigenschaften stammt direkt aus dem Readme.md (Abschnitt "Valid Properties") und src/NonlinearOptimizationTestFunctions.jl (VALID_PROPERTIES-Set). Ich habe sie als Tabelle integriert, mit Beschreibungen und Beispielen für Klarheit. Das reduziert Abweichungen (z. B. bei Signaturen) und macht die Anleitung handlungsstärker.

Ergänzung zu Properties: "non-scalable" ist keine zulässige Property (redundant: Fehlen von "scalable" impliziert fixed Dimension). "non-separable" ist zulässig, aber prüfe auf "fully non-separable" bei Bedarf (aus Literatur wie Jamil & Yang). Passe Implementierungen an: Verwende nur VALID_PROPERTIES; bei Verstoß (z. B. ArgumentError) erweitere VALID_PROPERTIES und Readme.

**Wichtiger Hinweis zu Namenskonflikten:** In der Funktionssignatur wird `T` als generischer Typ verwendet (`where {T<:Union{Real, ForwardDiff.Dual}}`). Vermeide daher Konstantennamen wie `T` oder `Y` (die mit dem Typ kollidieren und zu MethodErrors wie `exp(::Vector{Float64})` führen können). Verwende stattdessen beschreibende Namen wie `TIMES` oder `Y_VALUES`. Dies ist in den Templates unten berücksichtigt und in den häufigen Fehlern ergänzt.

**Neuer Hinweis: Performance & Robustheit** – Verwende `@inbounds` in Loops für Speed; prüfe type-stability mit `@code_warntype`. Für ill-conditioned Functions: Teste Cond-Number der Hessian (via ForwardDiff.hessian).

**Neu: Präzise Werte in Tests vermeiden** – Berechne alle Test-Werte (f_start, f_extra, min_value) direkt aus der Implementierung via Julia-REPL/Script, um Rundungsfehler zu eliminieren. Verwende atol=1e-3 für Start/Extra-Points (Papers runden oft); speichere exakte Werte (z.B. 10-12 Digits) für :min_value. Beispiel-Script: Kopiere Funktion in REPL und evaluiere tf.f([start1,start2,...]).

**Neu: Syntax- und Code-Sauberkeit:** Vor der Integration immer Syntax prüfen mit `julia --project=. -e 'include("src/functions/<name>.jl")'`. Vermeide jegliche inline-Text-Notizen oder unvollständige Kommentare (z. B. "Wait, ..." ohne #), da diese zu ParseErrors führen. Halte den Code als reinen, ausführbaren Julia-Code; verschiebe Erklärungen in den Header-Kommentar oder separate Docs.

---

## Ziel
Beschreibe die Schritte zum Hinzufügen einer neuen Testfunktion (lowercase-Namen, z. B. biggsexp3.jl). Tests zentralisiert: Funktionsspezifisch in test/<functionname>_tests.jl; Gradienten/AD in test/runtests.jl; Optimierung in test/minima_tests.jl. Keine Redundanzen oder Debugging-Ausgaben.

Wichtiges Verbot: Keine explizite gradient!-Definition – auto-generiert als (G, x) -> copyto!(G, grad(x)).

Konstruktor-Template: TestFunction(f, grad, meta) (meta als Dict mit :name, :start, :min_position, :min_value, :properties, :lb, :ub). Keine benannten Args (MethodError).

Einbindung: include("test/<functionname>_tests.jl") in test/include_testfiles.jl. Test: julia --project=. -e 'using Pkg; Pkg.instantiate(); include("test/<functionname>_tests.jl")'.

## Recherche und Validierung
Recherchiere Minima/Werte/Schranken in Quellen (vermeide Rundungen). Validiere: tf.f(tf.meta[:min_position]()) ≈ tf.meta[:min_value]() atol=1e-6. **Neu: Prüfe ill-conditioning (z. B. cond(ForwardDiff.hessian(tf.f, min_pos)) < 1e6); normalisiere bei Bedarf. Berechne :min_value exakt (nicht gerundet aus Paper) via Script für atol=1e-8-Präzision.**

**Erweiterung: Validierung komplexer Funktionen:** Für Funktionen mit indirektem Mapping (z. B. Cola: u → (x,y)-Koordinaten), erstelle ein separates Validierungs-Script: Definiere Hilfsfunktionen (z. B. build_xy), teste mit bekannten Eingaben und vergleiche erwartete Ausgaben (z. B. x,y-Arrays). Nutze Quellen wie Adorio & Diliman (2005) für exakte Matrizen (z. B. D-Matrix); kopiere Werte präzise (z. B. 1.27 statt 1.3). Wenn Min-Position approximativ (z. B. Cola: Literatur-Pos führt zu f≈11.83 statt 11.75), suche erweiterte Quellen (z. B. MVF-Library PDF) oder optimiere exakt mit Optim.jl; passe atol im Test an (z. B. 0.1 für f_min).

Quellen-Liste (einmalig):
- Al-Roomi (2015): https://www.al-roomi.org/benchmarks/unconstrained
- Gavana (2013): http://infinity77.net/global_optimization/test_functions.html
- Hedar (2005): http://www-optima.amp.i.kyoto-u.ac.jp/member/student/hedar/Hedar_files/TestGO.htm
- Jamil & Yang (2013): https://arxiv.org/abs/1308.4008
- Molga & Smutnicki (2005): http://www.zsd.ict.pwr.wroc.pl/files/docs/functions.pdf
- Suganthan et al. (2005): https://www.lri.fr/~hansen/Tech-Report-May-30-05.pdf
- Weitere: sfu.ca/~ssurjano, geatbx.com, indusmic.com.
- **Neu: Speziell für komplexe (z. B. Cola):** Adorio & Diliman (2005): http://www.geocities.ws/eadorio/mvf.pdf (MVF-Library; extrahiere D-Matrix und Mapping genau).

Häufige Fehler (konsolidiert):
- Werte: Branin([0,0])=55.602; Bohachevsky2([1,1])=3.6; Bohachevsky3([0.01,0.01])≈0.0405.
- Minima: Six-Hump=-1.031628 bei (±0.0898,±0.7127).
- Schranken: BartelsConn=[-500,-500]~[500,500]; Bohachevsky2/3=[-100,-100]~[100,100].
- Multimodal: Alternativen in :description dokumentieren (z. B. Langermann).
- **Namenskonflikte in Konstanten:** Der generische Typ `T` in der Signatur (`where {T<:...}`) überschreibt lokale `const T = ...`. Dies führt zu MethodErrors (z. B. `exp(::Vector{Float64})` in Edge-Case-Tests). Lösung: Benenne Konstanten um (z. B. `TIMES` statt `T`), prüfe mit `julia --project=. test/runtests.jl` (Edge Cases).
- **Neu: Ill-conditioning:** Symmetry in Functions (z. B. Sphere) kann Algorithmen täuschen; teste mit random starts.
- **Neu: Ungenaue Test-Werte:** Papers runden Minima/Start-Werte (z. B. Brad: 0.00821487 statt 0.008214878782); berechne exakt in REPL und passe Tests an (atol=1e-3 für Start/Extra).
- **Neu: ParseErrors durch Code-Kommentare:** Inline-Notizen wie "Wait, u is 1-based..." ohne # oder unvollständige Blöcke (z. B. fehlendes `end`) verursachen Syntax-Fehler. Lösung: Schreibe Code sauber; teste Syntax separat mit `julia -e 'include(...)` vor Integration. Verschiebe Erklärungen in Header oder separate .md-Datei.
- **Neu: Falsches Index-Mapping in Hilfsfunktionen:** Bei Transformationen (z. B. Cola: u[1]=x[2], u[2]=x[3], u[3]=y[3], ...), verifiziere mit Test-Case: z. B. u=ones(17), erwarte spezifische x,y-Werte. Nutze 1-based Julia-Indices strikt; dokumentiere Mapping im Header.
- **Neu: Ungenaue Min-Positionen in High-Dim-Funktionen:** Approx.-Pos aus Papern (z. B. Cola: [0.651906,...] → f=11.83 statt Literatur 11.75) führen zu Inkonsistenzen. Lösung: Berechne f_min exakt via Implementierung; erhöhe atol auf 0.1 im Test; suche präzise Koordinaten in Originalquellen (z. B. MVF-PDF) oder optimiere mit Tools wie Optim.jl.

## Erlaubte Eigenschaften (aus Readme.md)
Nur Properties aus diesem Set verwenden (VALID_PROPERTIES in src/). Neue? Ergänze Set und Readme ("Valid Properties"-Abschnitt). Füge "bounded" bei definierten Schranken hinzu; "scalable" nur bei variabler n (impliziert fixed ohne "fixed" – redundant, vermeiden). "non-scalable" ungültig (nicht in Set); "non-separable" zulässig, aber prüfe "fully non-separable" für starke Kopplung. **Neu: Für separable (unabhängige Terme): "separable" (z. B. Bohachevsky1).**

| Property                  | Beschreibung                                                                 | Beispiele (Funktionen)          |
|---------------------------|-----------------------------------------------------------------------------|---------------------------------|
| bounded                  | Hat definierte Schranken (:lb, :ub).                                        | Branin, Bohachevsky2            |
| continuous               | Kontinuierlich überall.                                                     | Ackley, Rosenbrock              |
| controversial            | Minimum umstritten (z. B. mehrere Quellen).                                 | Langermann                      |
| convex                   | Konvex (globales Minimum = einziges).                                       | Sphere                          |
| deceptive                | Täuscht lokale Optima vor.                                                  | -                               |
| differentiable           | Differenzierbar überall.                                                    | Rosenbrock                      |
| finite_at_inf            | Endlicher Wert bei ±∞.                                                      | -                               |
| fully non-separable      | Vollständig nicht-separabel.                                                | -                               |
| has_constraints          | Hat Nebenbedingungen.                                                       | -                               |
| has_noise                | Stochastisch/noisy.                                                         | -                               |
| highly multimodal        | Stark multimodal (viele Local Minima).                                      | Rastrigin                       |
| multimodal               | Mehrere Local Minima.                                                       | Branin                          |
| non-convex               | Nicht-konvex.                                                               | Ackley                          |
| non-separable            | Nicht-separabel (Variablen gekoppelt).                                      | Rosenbrock                      |
| partially differentiable | Teilweise differenzierbar.                                                  | BartelsConn                     |
| partially separable      | Teilweise separabel.                                                        | -                               |
| quasi-convex             | Quasi-konvex.                                                               | -                               |
| scalable                 | Skalierbar (beliebige n).                                                   | Ackley, Rastrigin               |
| separable                | Separabel (unabhängige Variablen).                                          | Sphere, Bohachevsky1            |
| strongly convex          | Stark konvex.                                                               | Quadratic                       |
| unimodal                 | Unimodal (ein Global Minimum).                                              | Sphere                          |
| **ill-conditioned**      | **Schlecht konditioniert (hohe Cond-Number).**                              | **Rosenbrock**                  |

## Implementierung: Code-Templates
**Wichtiger Hinweis zu Code-Sauberkeit:** Kopiere Templates als reinen Code; alle Erklärungen/Tests in separaten Kommentaren mit # oder Header. Vermeide jegliche nicht-kommentierten Texte (z. B. "Adapt formula" nur als #). Nach Anpassung: Syntax-Check mit `julia -e 'include("src/functions/<name>.jl")'` – kein Output = OK.

Template für src/functions/<name>.jl (kopiere und passe an; z. B. für nicht-skalierbar, n=3 wie Biggs EXP3):

# src/functions/<name>.jl
# Purpose: Implementation of the <Full Name> test function.
# Context: <Details: scalable/non-scalable, source, properties>.
# Global minimum: f(x*)=<value> at x*=<pos>.
# Bounds: <lb> ≤ x_i ≤ <ub>.
# Last modified: <Date>.
# Wichtig: Halte Code sauber – keine Erklärungen inline ohne #; validiere Mapping/Gradient separat.

export <UPPERCASE>_FUNCTION, <name>, <name>_gradient

# Constants (if needed, e.g. for Biggs EXP3; vermeide 'T' wegen Typ-Konflikt)
const TIMES = 0.1 * collect(1:10)
const Y_VALUES = exp.(-TIMES) .- 5 * exp.(-10 * TIMES)

function <name>(x::AbstractVector{T}) where {T<:Union{Real, ForwardDiff.Dual}}
    n = length(x)
    n == 0 && throw(ArgumentError("Input vector cannot be empty"))
    n != <DIM> && throw(ArgumentError("<Name> requires exactly <DIM> dimensions"))
    any(isnan.(x)) && return T(NaN)  # Neu: Edge-Case-Handling
    any(isinf.(x)) && return T(Inf)
    
    # Extract vars: x1, x2, ... = x[1], x[2], ...
    sum_sq = zero(T)  # Type-stable
    @inbounds for i in 1:<LOOP_SIZE>  # Neu: @inbounds für Performance
        t_i = TIMES[i]
        y_i = Y_VALUES[i]
        e_i = exp(-t_i * x1) - x3 * exp(-t_i * x2) - y_i  # Adapt formula
        sum_sq += e_i^2
    end
    return sum_sq
end

function <name>_gradient(x::AbstractVector{T}) where {T<:Union{Real, ForwardDiff.Dual}}
    n = length(x)
    n == 0 && throw(ArgumentError("Input vector cannot be empty"))
    n != <DIM> && throw(ArgumentError("<Name> requires exactly <DIM> dimensions"))
    any(isnan.(x)) && return fill(T(NaN), <DIM>)  # Neu: Edge-Case
    any(isinf.(x)) && return fill(T(Inf), <DIM>)
    
    # Extract vars
    grad = zeros(T, <DIM>)
    @inbounds for i in 1:<LOOP_SIZE>  # Neu: @inbounds
        # Compute partials: grad[1] += ... (adapt analytically)
    end
    return grad
end

const <UPPERCASE>_FUNCTION = TestFunction(
    <name>,
    <name>_gradient,
    Dict(
        :name => "<name>",
        :description => "<Beschreibung: Source, Eigenschaften, Multimodal-Notes>.",
        :math => raw"""f(\mathbf{x}) = <LaTeX-Formel>. """,
        :start => () -> [<start1>, <start2>, <start3>],  # ()-> for non-scalable
        :min_position => () -> [<pos1>, <pos2>, <pos3>],
        :min_value => () -> <value>,  # ()-> for non-scalable; berechne exakt via Script!
        :properties => ["<prop1>", "<prop2>", ...],  # From table above
        :lb => () -> [<lb1>, <lb2>, <lb3>],
        :ub => () -> [<ub1>, <ub2>, <ub3>],

    )
)

Template für skalierbar (z. B. Ackley, n>=1): Ersetze () -> [...] durch (n::Int) -> (n < 1 && throw(...); fill(<val>, n)); füge "scalable" zu properties. **Neu: Für n>2, prüfe separability (z. B. Bohachevsky1: separable, da unabhängige Terme).**

Für Matrizen (skalierbar): 
A = Diagonal(fill(2.0, n))  # Positiv definit; prüfe all(eigvals(A) .> 0)

Abhängigkeiten: In Project.toml: Random = "..."; dann Pkg.resolve(); Pkg.instantiate().

Top-Level-Check: using NonlinearOptimizationTestFunctions; @test isdefined(NonlinearOptimizationTestFunctions, :<name>). **Neu: @btime tf.f(rand(<DIM>))  # Benchmark <1μs**

Cache-Clear: Nach Änderungen: rmdir /s /q ~/.julia/compiled.

**Neu: Für komplexe Funktionen (z. B. Cola):** Definiere Hilfsfunktionen (z. B. build_xy, D_MATRIX) vor den Hauptfunktionen; validiere sie in einem separaten Script (z. B. test_mapping.jl): Lade Funktion, rufe build_xy(known_u), assert(expected_x ≈ computed_x). Für Gradient: Vergleiche analytisch mit ForwardDiff.gradient(tf.f, x) atol=1e-6.

## Tests: Template für test/<name>_tests.jl
# test/<name>_tests.jl
using Test, NonlinearOptimizationTestFunctions
@testset "<name>" begin
    tf = <UPPERCASE>_FUNCTION

    @test tf.meta[:name] == "<name>"
    @test has_property(tf, "<prop1>")  # For each in :properties
    @test !has_property(tf, "scalable")  # If non-scalable
    @test length(tf.meta[:properties]) == <count>

    @test_throws ArgumentError tf.f(Float64[])

    start_point = tf.meta[:start]()  # () for non-scalable
    @test start_point ≈ [<val1>, ...] atol=1e-6
    f_start = tf.f(start_point)
    @test f_start ≈ <computed_start_val> atol=1e-3  # Neu: atol=1e-3 für Start (gerundet in Papers)

    min_pos = tf.meta[:min_position]()
    @test min_pos ≈ [<pos1>, ...] atol=1e-6
    f_min = tf.f(min_pos)
    @test f_min ≈ tf.meta[:min_value]() atol=1e-8  # Neu: Engere atol für exakt berechnete min_value; erhöhe bei Approx.-Pos (z.B. 0.1 für Cola)

    lb, ub = tf.meta[:lb](), tf.meta[:ub]()
    @test lb ≈ [<lb1>, ...]
    @test ub ≈ [<ub1>, ...]

    # Extra point
    test_pt = [<pt1>, <pt2>, <pt3>]
    @test tf.f(test_pt) ≈ <computed_val> atol=1e-3  # Neu: atol=1e-3 für Extra (Approximation)
    # Non-diff (if applicable): @test_throws DomainError <name>_gradient([0.0, 0.0])
end 

Für skalierbar: tf.meta[:start](2) (n=2 default). **Neu: Gradient-Tests zentral in gradient_tests.jl (AD/Numerisch/Minima) – keine Duplikate hier! Berechne <computed_start_val> und <computed_val> exakt via REPL-Script vor dem Test-Schreiben.**

**Erweiterung: Bei ungenauen Minima:** Wenn |f_min - literature| > 1e-3 (z. B. durch approx. Pos), füge @test f_min ≈ <computed> atol=0.1 hinzu und notiere in :description: "Computed min_value via implementation; literature approx."

## Schritte-Checkliste
1. Template kopieren/anpassen (src/functions). **Neu: Schreibe sauberen Code; vermeide inline-Notizen.**
2. Recherche/Validierung (Minima, Werte, Properties aus Tabelle). **Neu: Hessian-Cond prüfen. Berechne :min_value exakt via Script (z.B. REPL: tf.f(min_pos)). Für Mapping: Validiere Hilfsfunktionen separat.**
3. Test-Template (test/<name>_tests.jl), einbinden. **Neu: Evaluiere f_start und f_extra in REPL und kopiere Werte (10 Digits) – vermeide Schätzungen. Passe atol bei Approx.-Werten an.**
4. **Neu: Syntax-Check: julia --project=. -e 'include("src/functions/<name>.jl")'` – kein Error.**
5. Gesamttests: julia --project=. test/runtests.jl. **Neu: Benchmark mit @btime.**
6. Debug: @info "min_value: $(tf.meta[:min_value])" in minima_tests.jl bei MethodError.

Finale Validierung:
- [ ] Template-Signatur (Dispatch mit Dual).
- [ ] Properties aus Tabelle (kein "fixed", kein "non-scalable").
- [ ] :min_value konsistent (skalierbar: (n)->; nicht: ()). **Neu: Exakt berechnet, nicht gerundet.**
- [ ] Tests passieren (standalone + runtests.jl).
- [ ] Keine Namenskonflikte (z. B. prüfe Edge Cases in runtests.jl auf MethodError).
- [ ] **Neu: Syntax sauber (keine ParseErrors via include-Test).**
- **[ ] Neu: AD-Gradient matcht; Performance <1μs für n=2. Test-Werte via Script validiert. Mapping/Gradient für komplexe Funktionen verifiziert.**

Diese Version priorisiert Templates für Reproduzierbarkeit und integriert die Properties-Liste für schnelle Referenz. Für Biggs EXP4/5: Passe das Template an (n=4, ähnliche Formel). Testen?