Skip to content

Commit

Permalink
Merge pull request #44 from ISISComputingGroup/fixes_linear_fitting
Browse files Browse the repository at this point in the history
Fixes issues found with linear fitting guess function
  • Loading branch information
Tom-Willemsen authored Oct 29, 2024
2 parents e031857 + 33bfa13 commit 7063ad4
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 41 deletions.
22 changes: 11 additions & 11 deletions doc/fitting/fitting.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ See the following example on how to define these.

import lmfit

def model(x: float, m: float, c: float) -> float:
def model(x: float, c1: float, c0: float) -> float:

return m * x + c # y = mx + c
return c1 * x + c0 # y = mx + c

def guess(x: npt.NDArray[np.float64], y: npt.NDArray[np.float64]) -> dict[str, lmfit.Parameter]:

Expand All @@ -125,12 +125,12 @@ def guess(x: npt.NDArray[np.float64], y: npt.NDArray[np.float64]) -> dict[str, l
numerator = sum(x * y) - sum(x) * sum(y)
denominator = sum(x**2) - sum(x) ** 2

m = numerator / denominator
c = (sum(y) - m * sum(x)) / len(x)
c1 = numerator / denominator
c0 = (sum(y) - c1 * sum(x)) / len(x)

init_guess = {
"m": lmfit.Parameter("m", m), # gradient
"c": lmfit.Parameter("c", c), # y - intercept
"c1": lmfit.Parameter("c1", c1), # gradient
"c0": lmfit.Parameter("c0", c0), # y - intercept
}

return init_guess
Expand All @@ -155,9 +155,9 @@ This means that aslong as the parameters returned from the guess function match
import lmfit
from ibex_bluesky_core.callbacks.fitting.fitting_utils import Linear

def different_model(x: float, m: float, c: float) -> float:
def different_model(x: float, c1: float, c0: float) -> float:

return m * x + c ** 2 # y = mx + (c ** 2)
return c1 * x + c0 ** 2 # y = mx + (c ** 2)


fit_method = FitMethod(different_model, Linear.guess())
Expand All @@ -176,11 +176,11 @@ from ibex_bluesky_core.callbacks.fitting.fitting_utils import Linear
# This Guessing. function isn't very good because it's return values don't change on the data already collected in the Bluesky run
# It always guesses that the linear function is y = x

def different_guess(x: float, m: float, c: float) -> float:
def different_guess(x: float, c1: float, c0: float) -> float:

init_guess = {
"m": lmfit.Parameter("m", 1), # gradient
"c": lmfit.Parameter("c", 0), # y - intercept
"c1": lmfit.Parameter("c1", 1), # gradient
"c0": lmfit.Parameter("c0", 0), # y - intercept
}

return init_guess
Expand Down
10 changes: 5 additions & 5 deletions doc/fitting/standard_fits.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@

## Linear

- `m` - Gradient
- `c` - (y) Intercept
- `c1` - Gradient
- `c0` - (y) Intercept

```{math}
y = mx + c
y = c_1x + c_0
```

## Polynomial

- `a` ... `d` - Polynomial coefficients
- `cn` ... `c0` - Polynomial coefficients

For a polynomial degree `n`:
```{math}
y = ax^n + bx^n-1 + ... + cx^1 + d
y = c_{n}x^n + c_{n-1}x^n-1 + ... + c_1 * x^1 + c_0
```

## Gaussian
Expand Down
25 changes: 4 additions & 21 deletions src/ibex_bluesky_core/callbacks/fitting/fitting_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ class Linear(Fit):
def model(cls, *args: int) -> lmfit.Model:
"""Linear Model."""

def model(x: npt.NDArray[np.float64], m: float, c: float) -> npt.NDArray[np.float64]:
return m * x + c
def model(x: npt.NDArray[np.float64], c1: float, c0: float) -> npt.NDArray[np.float64]:
return c1 * x + c0

return lmfit.Model(model)

Expand All @@ -189,25 +189,8 @@ def guess(
cls, *args: int
) -> Callable[[npt.NDArray[np.float64], npt.NDArray[np.float64]], dict[str, lmfit.Parameter]]:
"""Linear Guessing."""

def guess(
x: npt.NDArray[np.float64], y: npt.NDArray[np.float64]
) -> dict[str, lmfit.Parameter]:
# Linear Regression
numerator = sum(x * y) - sum(x) * sum(y)
denominator = sum(x**2) - sum(x) ** 2

m = numerator / denominator
c = (sum(y) - m * sum(x)) / len(x)

init_guess = {
"m": lmfit.Parameter("m", m),
"c": lmfit.Parameter("c", c),
}

return init_guess

return guess
return Polynomial.guess(1)
# Uses polynomial guessing with a degree of 1


class Polynomial(Fit):
Expand Down
15 changes: 11 additions & 4 deletions tests/callbacks/fitting/test_fitting_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def test_linear_model(self):
grad = 3
y_intercept = 2

outp = Linear.model().func(x, m=grad, c=y_intercept)
outp = Linear.model().func(x, c1=grad, c0=y_intercept)

# Check for constant gradient of grad
outp_m = np.diff(outp)
Expand All @@ -234,19 +234,26 @@ def test_gradient_guess(self):
y = np.array([-1.0, 0.0, 1.0])
outp = Linear.guess()(x, y)

assert pytest.approx(outp["m"]) == 1.0
assert pytest.approx(outp["c1"]) == 1.0

y = np.array([-2.0, 0.0, 2.0])
outp1 = Linear.guess()(x, y)
# check with a graph with steeper gradient
assert outp["m"] < outp1["m"]
assert outp["c1"] < outp1["c1"]

def test_y_intercept_guess(self):
x = np.array([-1.0, 0.0, 1.0])
y = np.array([-1.0, 0.0, 1.0])
outp = Linear.guess()(x, y)

assert pytest.approx(outp["c"]) == 0.0
assert pytest.approx(outp["c0"]) == 0.0

def test_zero_gradient_guess(self):
x = np.array([-1.0, 0.0, 1.0])
y = np.array([0.0, 0.0, 0.0])
outp = Linear.guess()(x, y)

assert pytest.approx(outp["c1"]) == 0.0


class TestPolynomial:
Expand Down

0 comments on commit 7063ad4

Please sign in to comment.