-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
318 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,14 @@ | ||
# Testing pydantic v2 | ||
|
||
This repository contains a [talk](https://sunnivin.github.io/a-new-core/talk/slides.html) and some demos (`demo\v1\`, `demo\v2\`) for exploring the new features of Pydantic v2. | ||
This repository contains a [talk](https://sunnivin.github.io/a-new-core/talk/slides.html) and a demo (`demo`)for exploring optimization in Python presented at the Oslo Python MeetUp 24.10.2024. | ||
|
||
Compare the how to use v2 by looking at the different scripts with the same names in folder `demo/v1` and `demo/v2`. | ||
|
||
|
||
# Credit | ||
|
||
The material in the slides and demo are based on the content from the following sources: | ||
|
||
- [Curve fitting with python](https://machinelearningmastery.com/curve-fitting-with-python/) (accessed 21.10.2024) | ||
- Terrence Dorsey and Samuel Colvin [Pydantic v2 Pre Release](https://docs.pydantic.dev/latest/blog/pydantic-v2-alpha/) (accessed 22.08.2023) | ||
- Prashanth Rao, The Data Quarry, [Obtain a 5x speedup for free by upgrading to Pydantic v2](https://thedataquarry.com/posts/why-pydantic-v2-matters/) (accessed 22.08.2023) | ||
- Yaakov Bressler in [Medium](https://blog.det.life/dont-write-another-line-of-code-until-you-see-these-pydantic-v2-breakthrough-features-5cdc65e6b448) (accessed 22.08.2023) | ||
- Samuel Colvin at [PyCon US 2023](https://www.youtube.com/watch?v=pWZw7hYoRVU) (accessed 22.08.2023) | ||
- Configuring [mypy with Pydantic](https://docs.pydantic.dev/latest/integrations/mypy/#configuring-the-plugin) (accessed 22.08.2023) | ||
- H.-P. Halvorsen [Optimization in python](https://www.halvorsen.blog/documents/programming/python/resources/powerpoints/Optimization%20in%20Python.pdf) (accessed 21.10.2024) | ||
- Documentation of [SciPy.Optimize](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.least_squares.html) (accessed 21.10.2024) | ||
- Documentation of [Lmfit](https://lmfit.github.io/lmfit-py/index.html) (accessed 21.10.2024) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
""" | ||
Fit Using Bounds | ||
================ | ||
A major advantage of using lmfit is that one can specify boundaries on fitting | ||
parameters, even if the underlying algorithm in SciPy does not support this. | ||
For more information on how this is implemented, please refer to: | ||
https://lmfit.github.io/lmfit-py/bounds.html | ||
The example below shows how to set boundaries using the ``min`` and ``max`` | ||
attributes to fitting parameters. | ||
""" | ||
|
||
import matplotlib.pyplot as plt | ||
from numpy import exp, linspace, pi, random, sign, sin | ||
|
||
from lmfit import create_params, minimize | ||
from lmfit.printfuncs import report_fit | ||
|
||
import matplotlib | ||
|
||
matplotlib.use("Agg") # Use a non-interactive backend | ||
|
||
############################################################################### | ||
# create the 'true' Parameter values and residual function: | ||
p_true = create_params(amp=14.0, period=5.4321, shift=0.12345, decay=0.010) | ||
|
||
|
||
def residual(pars, x, data=None): | ||
argu = (x * pars["decay"]) ** 2 | ||
shift = pars["shift"] | ||
if abs(shift) > pi / 2: | ||
shift = shift - sign(shift) * pi | ||
model = pars["amp"] * sin(shift + x / pars["period"]) * exp(-argu) | ||
if data is None: | ||
return model | ||
return model - data | ||
|
||
|
||
############################################################################### | ||
# Generate synthetic data and initialize fitting Parameters: | ||
random.seed(0) | ||
x = linspace(0, 250, 1500) | ||
noise = random.normal(scale=0.8, size=x.size) | ||
data = residual(p_true, x) + noise | ||
|
||
fit_params = create_params( | ||
amp=dict(value=13, max=20, min=0), | ||
period=dict(value=2, max=10), | ||
shift=dict(value=0, max=pi / 2.0, min=-pi / 2.0), | ||
decay=dict(value=0.02, max=0.1, min=0), | ||
) | ||
|
||
|
||
fit_params = create_params( | ||
amp=dict( | ||
value=13, | ||
), | ||
period=dict( | ||
value=2, | ||
), | ||
shift=dict( | ||
value=0, | ||
), | ||
decay=dict(value=0.02), | ||
) | ||
|
||
############################################################################### | ||
# Perform the fit and show the results: | ||
out = minimize(residual, fit_params, args=(x,), kws={"data": data}) | ||
fit = residual(out.params, x) | ||
|
||
############################################################################### | ||
report_fit(out, modelpars=p_true, correl_mode="table") | ||
|
||
############################################################################### | ||
plt.plot(x, data, "o", label="data") | ||
plt.plot(x, fit, label="best fit") | ||
plt.plot(x, p_true, label="initial guess") | ||
plt.legend() | ||
|
||
plt.savefig("with_bounds.png") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import numpy as np | ||
|
||
from lmfit import Minimizer, create_params, report_fit, minimize | ||
|
||
import matplotlib.pyplot as plt | ||
import matplotlib | ||
|
||
matplotlib.use("Agg") # Use a non-interactive backend | ||
|
||
|
||
# create data to be fitted | ||
x = np.linspace(0, 15, 301) | ||
np.random.seed(2021) | ||
amp = 5.0 | ||
omega = 2.0 | ||
shift = -0.1 | ||
decay = 0.025 | ||
data = amp * np.sin(omega * x + shift) * np.exp(-x * x * decay) + np.random.normal( | ||
size=x.size, scale=0.2 | ||
) | ||
|
||
|
||
# define objective function: returns the array to be minimized | ||
def fcn2min(params, x, data): | ||
"""Model a decaying sine wave and subtract data.""" | ||
v = params.valuesdict() | ||
|
||
model = v["amp"] * np.sin(x * v["omega"] + v["shift"]) * np.exp(-x * x * v["decay"]) | ||
return model - data | ||
|
||
|
||
# create a set of Parameters | ||
params = create_params( | ||
amp=dict(value=10, min=0), | ||
decay=0.1, | ||
omega=3.0, | ||
shift=dict(value=0.0, min=-np.pi / 2.0, max=np.pi / 2), | ||
) | ||
|
||
initial_model = ( | ||
params["amp"].value | ||
* np.sin(x * params["omega"].value + params["shift"].value) | ||
* np.exp(-x * x * params["decay"].value) | ||
) | ||
# do fit, here with the default leastsq algorithm | ||
minner = Minimizer(fcn2min, params, fcn_args=(x, data)) | ||
result = minner.minimize(method="least_squares") | ||
|
||
# calculate final result | ||
final = data + result.residual | ||
|
||
# write error report | ||
report_fit(result) | ||
|
||
# try to plot results | ||
|
||
plt.plot(x, data, "+", label="data", color="red") | ||
plt.plot(x, initial_model, label="initial", linestyle="--", color="green") | ||
plt.plot(x, final, label="final", color="blue") | ||
plt.legend() | ||
plt.savefig("parameters_values_lmfit.png") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import numpy as np | ||
import matplotlib.pyplot as plt | ||
from scipy.optimize import curve_fit | ||
import matplotlib | ||
|
||
matplotlib.use("Agg") # Use a non-interactive backend | ||
|
||
# Create data to be fitted | ||
x = np.linspace(0, 15, 301) | ||
np.random.seed(2021) | ||
amp = 5.0 | ||
omega = 2.0 | ||
shift = -0.1 | ||
decay = 0.025 | ||
data = amp * np.sin(omega * x + shift) * np.exp(-x * x * decay) + np.random.normal( | ||
size=x.size, scale=0.2 | ||
) | ||
|
||
|
||
# Define the model function | ||
def model_func(x, amp, omega, shift, decay): | ||
return amp * np.sin(omega * x + shift) * np.exp(-x * x * decay) | ||
|
||
|
||
# Initial guess for the parameters | ||
initial_guess = [10, 3.0, 0.0, 0.1] | ||
|
||
# Perform the curve fitting | ||
popt, pcov = curve_fit(model_func, x, data, p0=initial_guess) | ||
|
||
# Calculate the fitted data | ||
fitted_data = model_func(x, *popt) | ||
|
||
# Plot the results | ||
plt.figure(figsize=(10, 6)) | ||
plt.plot(x, data, "+", label="Data", color="red") | ||
plt.plot(x, fitted_data, label="Fitted Curve", color="blue") | ||
plt.axhline( | ||
0, color="gray", lw=0.5, ls="--" | ||
) # Add a horizontal line at y=0 for reference | ||
plt.title("Curve Fitting with scipy.optimize.curve_fit") | ||
plt.xlabel("x") | ||
plt.ylabel("Data and Fitted Curve") | ||
plt.legend() | ||
plt.grid() | ||
plt.savefig("parameters_values_scipy.png") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import numpy as np | ||
import lmfit | ||
import matplotlib.pyplot as plt | ||
import matplotlib | ||
|
||
matplotlib.use("Agg") # Use a non-interactive backend | ||
|
||
|
||
# Define Rosenbrock's Banana Function | ||
def rosenbrock(x, y, a=1, b=100): | ||
return (a - x) ** 2 + b * (y - x**2) ** 2 | ||
|
||
|
||
# Generate synthetic data points | ||
def generate_data(num_points=100, noise_level=0.1): | ||
x_true = np.linspace(-2, 2, num_points) | ||
y_true = x_true**2 # We expect y = x^2 for Rosenbrock | ||
noise = np.random.normal(0, noise_level, size=y_true.shape) # Add noise | ||
y_observed = y_true + noise | ||
return x_true, y_observed | ||
|
||
|
||
# Create a dataset | ||
x_data, y_data = generate_data(num_points=100, noise_level=1.0) | ||
|
||
|
||
# Define the objective function | ||
def objective(params): | ||
# Get the parameters | ||
a = params["a"] | ||
b = params["b"] | ||
|
||
# Model predictions | ||
y_pred = a - x_data**2 # Compute y values based on Rosenbrock's function model | ||
return y_data - y_pred # Return residuals | ||
|
||
|
||
# Initial guess for the parameters | ||
initial_params = lmfit.Parameters() | ||
initial_params.add("a", value=1.0) # Initial guess for parameter a | ||
initial_params.add("b", value=100.0) # Initial guess for parameter b | ||
|
||
# Perform the optimization | ||
result = lmfit.minimize(objective, initial_params) | ||
|
||
# Print the results | ||
print(result.message) | ||
print( | ||
f"Optimized parameters: a = {result.params['a'].value}, b = {result.params['b'].value}" | ||
) | ||
print( | ||
f"Minimum value of Rosenbrock's function (sum of squares of residuals): {result.chisqr}" | ||
) | ||
|
||
# Optional: Plot the data and the fitted curve | ||
plt.figure(figsize=(10, 6)) | ||
plt.scatter(x_data, y_data, label="Observed Data", color="red", alpha=0.6) | ||
plt.plot( | ||
x_data, | ||
rosenbrock( | ||
result.params["a"].value, | ||
x_data**2, | ||
a=result.params["a"].value, | ||
b=result.params["b"].value, | ||
), | ||
label="Fitted Curve", | ||
color="blue", | ||
) | ||
plt.title("Rosenbrock's Banana Function Optimization") | ||
plt.xlabel("x") | ||
plt.ylabel("y") | ||
plt.legend() | ||
plt.grid() | ||
plt.savefig("rosenbrock.png") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
name: Build and Deploy Slides | ||
|
||
on: | ||
push: | ||
branches: | ||
- main # Triggers on pushes to the main branch | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Check out the repository | ||
uses: actions/checkout@v2 | ||
|
||
- name: Set up Docker | ||
run: | | ||
echo "Docker is already available on GitHub-hosted runners." | ||
- name: Run Marp to generate HTML slides | ||
run: | | ||
docker run --rm --init \ | ||
-v ${{ github.workspace }}:/home/marp/app \ | ||
-e LANG=${{ secrets.LANG }} \ | ||
-p 8080:8080 -p 37717:37717 \ | ||
marpteam/marp-cli:v3.2.0 --theme ngi-theme.css --html . | ||
- name: Commit and Push HTML slides to gh-pages | ||
run: | | ||
# Install Git if it's not already available | ||
sudo apt-get update | ||
sudo apt-get install -y git | ||
# Configure git user | ||
git config --global user.name "GitHub Action" | ||
git config --global user.email "[email protected]" | ||
# Create gh-pages branch if it doesn't exist | ||
git checkout -b gh-pages | ||
# Remove old slides and add new ones | ||
rm -rf ./* | ||
cp -r /home/marp/app/*.html . | ||
# Add, commit, and push the changes | ||
git add . | ||
git commit -m "Update slides" | ||
git push -f origin gh-pages | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |