Skip to content

Commit

Permalink
actions
Browse files Browse the repository at this point in the history
  • Loading branch information
sunnivin committed Oct 21, 2024
1 parent 622db20 commit fe7d6c9
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 7 deletions.
11 changes: 4 additions & 7 deletions README.md
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)
83 changes: 83 additions & 0 deletions demo/demo/example_with_bounds.py
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")
61 changes: 61 additions & 0 deletions demo/demo/parameters_values_lmfit.py
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")
46 changes: 46 additions & 0 deletions demo/demo/parameters_values_scipy_no_bounds.py
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")
74 changes: 74 additions & 0 deletions demo/demo/rosenbrock.py
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")
50 changes: 50 additions & 0 deletions talk/.github/workflows/build-sldes.yaml
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 }}

0 comments on commit fe7d6c9

Please sign in to comment.