warwick-sciml 2026.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- warwick_sciml-2026.1.0/.github/workflows/ci.yml +31 -0
- warwick_sciml-2026.1.0/.gitignore +6 -0
- warwick_sciml-2026.1.0/LICENSE +21 -0
- warwick_sciml-2026.1.0/PKG-INFO +70 -0
- warwick_sciml-2026.1.0/README.md +39 -0
- warwick_sciml-2026.1.0/pyproject.toml +37 -0
- warwick_sciml-2026.1.0/src/sciml/__init__.py +16 -0
- warwick_sciml-2026.1.0/src/sciml/atomistic.py +88 -0
- warwick_sciml-2026.1.0/src/sciml/blr.py +133 -0
- warwick_sciml-2026.1.0/src/sciml/femsolvers.py +118 -0
- warwick_sciml-2026.1.0/src/sciml/fluidsolvers.py +94 -0
- warwick_sciml-2026.1.0/src/sciml/gputils.py +244 -0
- warwick_sciml-2026.1.0/src/sciml/progress.py +47 -0
- warwick_sciml-2026.1.0/src/sciml/sensitivity.py +206 -0
- warwick_sciml-2026.1.0/src/sciml/tersoff_jax.py +171 -0
- warwick_sciml-2026.1.0/tests/test_modules.py +75 -0
- warwick_sciml-2026.1.0/tests/test_tersoff_jax.py +131 -0
- warwick_sciml-2026.1.0/uv.lock +1461 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v6
|
|
15
|
+
- run: uv run --python 3.12 --extra test pytest -q
|
|
16
|
+
|
|
17
|
+
publish:
|
|
18
|
+
# Publish to PyPI when a version tag (e.g. v2026.1.0) is pushed, via trusted publishing
|
|
19
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
20
|
+
needs: test
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
environment: pypi
|
|
23
|
+
permissions:
|
|
24
|
+
id-token: write
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: astral-sh/setup-uv@v6
|
|
28
|
+
- name: Check tag matches package version
|
|
29
|
+
run: test "v$(uv version --short)" = "${GITHUB_REF_NAME}"
|
|
30
|
+
- run: uv build
|
|
31
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 James Kermode
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: warwick-sciml
|
|
3
|
+
Version: 2026.1.0
|
|
4
|
+
Summary: Support modules for the Warwick PX914/ES98E Predictive Modelling, UQ and Scientific Machine Learning module
|
|
5
|
+
Project-URL: Repository, https://github.com/HetSys/warwick-sciml
|
|
6
|
+
Author: James Kermode
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Requires-Dist: matplotlib
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Requires-Dist: pandas
|
|
13
|
+
Requires-Dist: scipy
|
|
14
|
+
Requires-Dist: tqdm
|
|
15
|
+
Provides-Extra: atomistic
|
|
16
|
+
Requires-Dist: ase; extra == 'atomistic'
|
|
17
|
+
Requires-Dist: atomistica; extra == 'atomistic'
|
|
18
|
+
Requires-Dist: jax; extra == 'atomistic'
|
|
19
|
+
Requires-Dist: matscipy; extra == 'atomistic'
|
|
20
|
+
Provides-Extra: gp
|
|
21
|
+
Requires-Dist: jax; extra == 'gp'
|
|
22
|
+
Requires-Dist: tinygp; extra == 'gp'
|
|
23
|
+
Provides-Extra: test
|
|
24
|
+
Requires-Dist: ase; extra == 'test'
|
|
25
|
+
Requires-Dist: atomistica; extra == 'test'
|
|
26
|
+
Requires-Dist: jax; extra == 'test'
|
|
27
|
+
Requires-Dist: matscipy; extra == 'test'
|
|
28
|
+
Requires-Dist: pytest; extra == 'test'
|
|
29
|
+
Requires-Dist: tinygp; extra == 'test'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# warwick-sciml
|
|
33
|
+
|
|
34
|
+
Support modules for the University of Warwick module on Predictive Modelling,
|
|
35
|
+
Uncertainty Quantification and Scientific Machine Learning (PX914 / ES98E).
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install warwick-sciml # core: BLR, sensitivity analysis, fluid/FE solvers
|
|
39
|
+
pip install "warwick-sciml[gp]" # + Gaussian process utilities (JAX, tinygp)
|
|
40
|
+
pip install "warwick-sciml[atomistic]" # + atomistic examples (ASE, atomistica, matscipy, JAX)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
In a marimo notebook, add it to the PEP 723 header instead, e.g.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
# /// script
|
|
47
|
+
# dependencies = ["warwick-sciml[atomistic]>=2026.1", "marimo>=0.25"]
|
|
48
|
+
# ///
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
then `from sciml.blr import design_matrix`, `from sciml import tersoff_jax`, etc.
|
|
52
|
+
|
|
53
|
+
| Module | Contents |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `sciml.blr` | Bayesian linear regression: basis functions, design matrices, posteriors |
|
|
56
|
+
| `sciml.sensitivity` | finite-difference sensitivities and Taylor expansions |
|
|
57
|
+
| `sciml.fluidsolvers` | Poiseuille flow solvers |
|
|
58
|
+
| `sciml.femsolvers` | 2D plane-stress finite element solver |
|
|
59
|
+
| `sciml.gputils` | Gaussian process fitting and plotting (extra `gp`) |
|
|
60
|
+
| `sciml.atomistic` | Tersoff calculators, vacancy formation energies (extra `atomistic`) |
|
|
61
|
+
| `sciml.tersoff_jax` | differentiable Tersoff potential in JAX (extra `atomistic`) |
|
|
62
|
+
| `sciml.progress` | tqdm wrappers for lecture notebooks |
|
|
63
|
+
|
|
64
|
+
Versions follow the academic year (`2026.x` for 2026/27).
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
uv run --extra test pytest
|
|
70
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# warwick-sciml
|
|
2
|
+
|
|
3
|
+
Support modules for the University of Warwick module on Predictive Modelling,
|
|
4
|
+
Uncertainty Quantification and Scientific Machine Learning (PX914 / ES98E).
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install warwick-sciml # core: BLR, sensitivity analysis, fluid/FE solvers
|
|
8
|
+
pip install "warwick-sciml[gp]" # + Gaussian process utilities (JAX, tinygp)
|
|
9
|
+
pip install "warwick-sciml[atomistic]" # + atomistic examples (ASE, atomistica, matscipy, JAX)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
In a marimo notebook, add it to the PEP 723 header instead, e.g.
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
# /// script
|
|
16
|
+
# dependencies = ["warwick-sciml[atomistic]>=2026.1", "marimo>=0.25"]
|
|
17
|
+
# ///
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
then `from sciml.blr import design_matrix`, `from sciml import tersoff_jax`, etc.
|
|
21
|
+
|
|
22
|
+
| Module | Contents |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `sciml.blr` | Bayesian linear regression: basis functions, design matrices, posteriors |
|
|
25
|
+
| `sciml.sensitivity` | finite-difference sensitivities and Taylor expansions |
|
|
26
|
+
| `sciml.fluidsolvers` | Poiseuille flow solvers |
|
|
27
|
+
| `sciml.femsolvers` | 2D plane-stress finite element solver |
|
|
28
|
+
| `sciml.gputils` | Gaussian process fitting and plotting (extra `gp`) |
|
|
29
|
+
| `sciml.atomistic` | Tersoff calculators, vacancy formation energies (extra `atomistic`) |
|
|
30
|
+
| `sciml.tersoff_jax` | differentiable Tersoff potential in JAX (extra `atomistic`) |
|
|
31
|
+
| `sciml.progress` | tqdm wrappers for lecture notebooks |
|
|
32
|
+
|
|
33
|
+
Versions follow the academic year (`2026.x` for 2026/27).
|
|
34
|
+
|
|
35
|
+
## Development
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv run --extra test pytest
|
|
39
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "warwick-sciml"
|
|
7
|
+
version = "2026.1.0"
|
|
8
|
+
description = "Support modules for the Warwick PX914/ES98E Predictive Modelling, UQ and Scientific Machine Learning module"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "James Kermode" }]
|
|
14
|
+
|
|
15
|
+
dependencies = [
|
|
16
|
+
"numpy",
|
|
17
|
+
"scipy",
|
|
18
|
+
"matplotlib",
|
|
19
|
+
"pandas",
|
|
20
|
+
"tqdm",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Repository = "https://github.com/HetSys/warwick-sciml"
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
# Gaussian process utilities (sciml.gputils)
|
|
28
|
+
gp = ["jax", "tinygp"]
|
|
29
|
+
# Atomistic examples (sciml.atomistic, sciml.tersoff_jax)
|
|
30
|
+
atomistic = ["ase", "atomistica", "matscipy", "jax"]
|
|
31
|
+
test = ["pytest", "warwick-sciml[gp,atomistic]"]
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/sciml"]
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
filterwarnings = ["ignore::DeprecationWarning"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Support modules for the Warwick PX914/ES98E module on Predictive Modelling,
|
|
2
|
+
Uncertainty Quantification and Scientific Machine Learning.
|
|
3
|
+
|
|
4
|
+
Submodules (import them explicitly, e.g. ``from sciml.blr import design_matrix``):
|
|
5
|
+
|
|
6
|
+
- ``sciml.blr`` Bayesian linear regression: basis functions, design matrices, posteriors
|
|
7
|
+
- ``sciml.sensitivity`` finite-difference sensitivities and Taylor expansions
|
|
8
|
+
- ``sciml.fluidsolvers`` Poiseuille flow solvers
|
|
9
|
+
- ``sciml.femsolvers`` 2D plane-stress finite element solver
|
|
10
|
+
- ``sciml.gputils`` Gaussian process fitting and plotting with JAX/tinygp [extra: gp]
|
|
11
|
+
- ``sciml.atomistic`` Tersoff calculators and vacancy formation energies [extra: atomistic]
|
|
12
|
+
- ``sciml.tersoff_jax`` differentiable Tersoff potential in JAX [extra: atomistic]
|
|
13
|
+
- ``sciml.progress`` tqdm wrappers suited to lecture notebooks
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__version__ = "2026.1.0"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from ase.build import bulk
|
|
6
|
+
from ase.optimize.precon import PreconLBFGS
|
|
7
|
+
from atomistica import Tersoff, Tersoff_PRB_39_5566_Si_C
|
|
8
|
+
|
|
9
|
+
Tersoff_parameter_names = list(Tersoff_PRB_39_5566_Si_C.keys())[2:]
|
|
10
|
+
del Tersoff_parameter_names[Tersoff_parameter_names.index('m')]
|
|
11
|
+
del Tersoff_parameter_names[Tersoff_parameter_names.index('mubo')]
|
|
12
|
+
|
|
13
|
+
Tersoff_parameter_values = np.array([Tersoff_PRB_39_5566_Si_C[key][-1] for key in Tersoff_parameter_names])
|
|
14
|
+
|
|
15
|
+
def tersoff_calculator(parameters):
|
|
16
|
+
# build a dictionary of parameters using values passed in
|
|
17
|
+
param_dict = { 'el': 'Si', 'm': 1 } # fixed parameters
|
|
18
|
+
param_dict.update(dict(zip(Tersoff_parameter_names, parameters)))
|
|
19
|
+
calc = Tersoff(**param_dict)
|
|
20
|
+
return calc
|
|
21
|
+
|
|
22
|
+
def vacancy_cell(Ncell=3, rattle=0.0):
|
|
23
|
+
"""
|
|
24
|
+
Build an Ncell x Ncell x Ncell cubic Si supercell and a copy with a vacancy.
|
|
25
|
+
|
|
26
|
+
The atom nearest the cell midpoint is removed. Positions in the vacancy cell
|
|
27
|
+
are optionally rattled (standard deviation `rattle`, in Angstrom) before the
|
|
28
|
+
atom is deleted.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
at_sup: perfect supercell
|
|
33
|
+
at_vac: supercell with the vacancy
|
|
34
|
+
vac_pos: position of the removed atom
|
|
35
|
+
"""
|
|
36
|
+
at_sup = bulk('Si', cubic=True) * Ncell
|
|
37
|
+
at_vac = at_sup.copy()
|
|
38
|
+
if rattle:
|
|
39
|
+
at_vac.rattle(rattle)
|
|
40
|
+
cell_midpoint = np.diag(at_vac.cell) * 0.5
|
|
41
|
+
vac_atom = ((at_vac.positions - cell_midpoint)**2).sum(axis=1).argmin()
|
|
42
|
+
vac_pos = at_vac.positions[vac_atom].copy()
|
|
43
|
+
del at_vac[vac_atom]
|
|
44
|
+
return at_sup, at_vac, vac_pos
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def vacancy_formation_energy(parameters, Ncell=3, relax=False, fmax=1e-4, logfile=None):
|
|
48
|
+
"""
|
|
49
|
+
Compute vacancy formation energy for a Ncell x Ncell x Ncell Si system
|
|
50
|
+
|
|
51
|
+
Modified to use given values of the Tersoff parameters
|
|
52
|
+
|
|
53
|
+
Arguments
|
|
54
|
+
---------
|
|
55
|
+
|
|
56
|
+
parameters: vector of the Tersoff parameters to be used
|
|
57
|
+
Ncell: number of unit cells (default 3)
|
|
58
|
+
relax: if true, rattle the atoms slightly and perform a geometry
|
|
59
|
+
optimisation before computing the vacancy formation energy
|
|
60
|
+
fmax: the maximum force tolerance to use when `relax=True`
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
|
|
65
|
+
e_vac Vacancy formation energy, in eV
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
at_sup, at_vac, _ = vacancy_cell(Ncell, rattle=0.01 if relax else 0.0)
|
|
69
|
+
|
|
70
|
+
calc = tersoff_calculator(parameters)
|
|
71
|
+
at_sup.calc = calc
|
|
72
|
+
e_bulk_per_atom = at_sup.get_potential_energy() / len(at_sup)
|
|
73
|
+
|
|
74
|
+
at_vac.calc = calc
|
|
75
|
+
if relax:
|
|
76
|
+
opt = PreconLBFGS(at_vac, logfile=logfile)
|
|
77
|
+
opt.run(fmax=fmax)
|
|
78
|
+
return at_vac.get_potential_energy() - e_bulk_per_atom * len(at_vac)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def vacancy_formation_energy_first5(x):
|
|
82
|
+
X = Tersoff_parameter_values.copy()
|
|
83
|
+
X[:5] = x
|
|
84
|
+
_stdout = sys.stdout
|
|
85
|
+
sys.stdout = None # suppress output
|
|
86
|
+
Ef = vacancy_formation_energy(X, relax=True)
|
|
87
|
+
sys.stdout = _stdout
|
|
88
|
+
return Ef
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def design_matrix(X, phi):
|
|
6
|
+
"""
|
|
7
|
+
Arguments:
|
|
8
|
+
|
|
9
|
+
X - The observed inputs
|
|
10
|
+
phi - The basis functions
|
|
11
|
+
"""
|
|
12
|
+
num_observations = X.shape[0]
|
|
13
|
+
num_basis = phi.num_basis
|
|
14
|
+
Phi = np.zeros((num_observations, num_basis))
|
|
15
|
+
for i in range(num_observations):
|
|
16
|
+
Phi[i, :] = phi(X[i, :])
|
|
17
|
+
return Phi
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LinearBasis:
|
|
21
|
+
"""
|
|
22
|
+
Represents a 1D linear basis.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.num_basis = 2 # The number of basis functions
|
|
27
|
+
|
|
28
|
+
def __call__(self, x):
|
|
29
|
+
"""
|
|
30
|
+
``x`` should be a 1D array of inputs
|
|
31
|
+
"""
|
|
32
|
+
return [1.0, x[0]]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PolynomialBasis:
|
|
36
|
+
"""
|
|
37
|
+
A set of polynomial basis functions.
|
|
38
|
+
|
|
39
|
+
Arguments:
|
|
40
|
+
degree - The degree of the polynomial.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, degree):
|
|
44
|
+
self.degree = degree
|
|
45
|
+
self.num_basis = degree + 1
|
|
46
|
+
|
|
47
|
+
def __call__(self, x):
|
|
48
|
+
return np.array([x[0] ** i for i in range(self.degree + 1)])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RadialBasisFunctions:
|
|
52
|
+
"""
|
|
53
|
+
A set of linear basis functions.
|
|
54
|
+
|
|
55
|
+
Arguments:
|
|
56
|
+
X - The centers of the radial basis functions.
|
|
57
|
+
ell - The assumed lengthscale.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, X, ell):
|
|
61
|
+
self.X = X
|
|
62
|
+
self.ell = ell
|
|
63
|
+
self.num_basis = X.shape[0]
|
|
64
|
+
|
|
65
|
+
def __call__(self, x):
|
|
66
|
+
return np.exp(-0.5 * (x - self.X) ** 2 / self.ell**2).flatten()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def least_squares_MLE(X, y):
|
|
70
|
+
"""Compute maximum likelihood estimate of mean and standard deviation of weights"""
|
|
71
|
+
w_MLE, res_MLE, _, _ = np.linalg.lstsq(X, y, rcond=None)
|
|
72
|
+
sigma_MLE = np.sqrt(res_MLE / X.shape[0])
|
|
73
|
+
return w_MLE, sigma_MLE
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def prior(alpha, N):
|
|
77
|
+
"""Compute mean and covariance matrices of the weight prior"""
|
|
78
|
+
m0 = np.zeros(N)
|
|
79
|
+
S0 = 1.0 / alpha * np.eye(N)
|
|
80
|
+
return m0, S0
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def posterior(Phi, y, alpha, beta, return_inverse=False):
|
|
84
|
+
"""Computes mean and covariance matrix of the posterior distribution."""
|
|
85
|
+
S_N_inv = alpha * np.eye(Phi.shape[1]) + beta * Phi.T.dot(Phi)
|
|
86
|
+
S_N = np.linalg.inv(S_N_inv)
|
|
87
|
+
m_N = beta * S_N.dot(Phi.T).dot(y)
|
|
88
|
+
m_N = m_N
|
|
89
|
+
if return_inverse:
|
|
90
|
+
return m_N, S_N, S_N_inv
|
|
91
|
+
else:
|
|
92
|
+
return m_N, S_N
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def posterior_predictive(Phi_test, m_N, S_N, beta):
|
|
96
|
+
"""Computes mean and variances of the posterior predictive distribution."""
|
|
97
|
+
y = Phi_test.dot(m_N).ravel()
|
|
98
|
+
# Only compute variances (diagonal elements of covariance matrix)
|
|
99
|
+
y_epi = np.sum(Phi_test.dot(S_N) * Phi_test, axis=1)
|
|
100
|
+
y_var = 1 / beta + y_epi
|
|
101
|
+
return y, y_epi, y_var
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def plot_data(X, y):
|
|
105
|
+
plt.plot(X[:, 0], y[:, 0], "kx", ms=10)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def plot_truth(X, y, label="Truth"):
|
|
109
|
+
plt.plot(X[:, 0], y[:, 0], "k--", label=label)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def plot_posterior_samples(X, y):
|
|
113
|
+
plt.plot(X, y, "r-")
|
|
114
|
+
plt.axis("equal")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def plot_predictive(X, y, y_epi, y_var):
|
|
118
|
+
sigma_epi = np.sqrt(y_epi) # epistemitic uncertainty
|
|
119
|
+
sigma_tot = np.sqrt(y_var) # total uncertainty
|
|
120
|
+
|
|
121
|
+
y_el = y - 2 * sigma_epi
|
|
122
|
+
y_tl = y - 2 * sigma_tot
|
|
123
|
+
y_eu = y + 2 * sigma_epi
|
|
124
|
+
y_tu = y + 2 * sigma_tot
|
|
125
|
+
|
|
126
|
+
plt.plot(X[:, 0], y, "b-", label="Prediction")
|
|
127
|
+
plt.fill_between(
|
|
128
|
+
X[:, 0], y_el, y_eu, color="C2", label="Epistemic uncertainty", alpha=0.3
|
|
129
|
+
)
|
|
130
|
+
plt.fill_between(
|
|
131
|
+
X[:, 0], y_tl, y_el, color="C1", label="Total uncertainty", alpha=0.3
|
|
132
|
+
)
|
|
133
|
+
plt.fill_between(X[:, 0], y_eu, y_tu, color="C1", alpha=0.3)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
|
|
4
|
+
def plot_mesh(XYZ, c='k', ls='--'):
|
|
5
|
+
wrap = list(range(len(XYZ))) + [0]
|
|
6
|
+
plt.plot(XYZ[wrap, 0], XYZ[wrap, 1], 's'+c+ls)
|
|
7
|
+
plt.xlabel("$x_1$")
|
|
8
|
+
# Set y axis label.
|
|
9
|
+
plt.ylabel("$x_2$")
|
|
10
|
+
|
|
11
|
+
def plane_stress(E,nu):
|
|
12
|
+
C = np.zeros((3,3))
|
|
13
|
+
De = np.array([[1,nu,0],
|
|
14
|
+
[nu,1,0], [0,0,(1-nu)/2]])
|
|
15
|
+
De1 = E/(1-nu**2)
|
|
16
|
+
C = np.dot(De1,De)
|
|
17
|
+
return C
|
|
18
|
+
|
|
19
|
+
def dispstrain_B(xyze, xi, eta):
|
|
20
|
+
natcoord = np.array([[-1, 1, 1, -1],[-1, -1, 1, 1]]) #natural nodal coordinates of a quad element
|
|
21
|
+
|
|
22
|
+
# derivatives of shape functions w.r.t. natural coordinates
|
|
23
|
+
dNdnat = np.zeros((2,4))
|
|
24
|
+
dNdnat[0,:]=(1/4)*natcoord[0,:]*(1+natcoord[1,:]*eta)
|
|
25
|
+
dNdnat[1,:]=(1/4)*natcoord[1,:]*(1+natcoord[0,:]*xi)
|
|
26
|
+
|
|
27
|
+
# element Jacobian matrix
|
|
28
|
+
Jmat = np.dot(dNdnat,xyze) # [2x2 matrix]
|
|
29
|
+
J = np.linalg.det(Jmat) # determinant of the Jacobian
|
|
30
|
+
|
|
31
|
+
JmatInv = np.linalg.inv(Jmat) # inverse of the Jacobian matrix
|
|
32
|
+
dNdx = np.dot(JmatInv, dNdnat) # effectively: Jmat^-1 * dNdna
|
|
33
|
+
|
|
34
|
+
#displacement-strain matrix
|
|
35
|
+
#linear QUAD element
|
|
36
|
+
dsB = np.zeros((3,8)) # [3 strain components X 8 DOFs]
|
|
37
|
+
dsB[0, 0::2] = dNdx[0, :]
|
|
38
|
+
dsB[1, 1::2] = dNdx[1, :]
|
|
39
|
+
dsB[2, 0::2] = dNdx[1, :]
|
|
40
|
+
dsB[2, 1::2] = dNdx[0, :]
|
|
41
|
+
|
|
42
|
+
return dsB, J
|
|
43
|
+
|
|
44
|
+
def keval(xyze, Ce, th):
|
|
45
|
+
ke=np.zeros((8,8)) # create element stiffness matrix (ndof x ndof)
|
|
46
|
+
|
|
47
|
+
a = 1/(np.sqrt(3)) # location of Gauss points (in natural coordinates)
|
|
48
|
+
w = 1 # weights
|
|
49
|
+
Gauss = np.array([[-a, a, a, -a],[-a, -a, a, a]]) # Gauss points matrix
|
|
50
|
+
|
|
51
|
+
for i in range(4): # introduce natural coordinates
|
|
52
|
+
xi=Gauss[0,i] # natural coordinate - horizontal
|
|
53
|
+
eta=Gauss[1,i] # natural coordinate - vertical
|
|
54
|
+
|
|
55
|
+
dsB, J = dispstrain_B(xyze,xi,eta) # evaluate dsB matrix and Jacobian
|
|
56
|
+
|
|
57
|
+
dsBT=dsB.transpose()
|
|
58
|
+
dot1=np.dot(dsBT,Ce)
|
|
59
|
+
dot2=np.dot(dot1,dsB)
|
|
60
|
+
ke=ke+dot2*J*th*w # evaluate element stiffness matrix
|
|
61
|
+
|
|
62
|
+
return ke
|
|
63
|
+
|
|
64
|
+
def sigma_vector(XYZ, C, de):
|
|
65
|
+
a=1/np.sqrt(3) # location of Gauss points (in parent coordinates)
|
|
66
|
+
w=1 # weights
|
|
67
|
+
Gauss = np.array([[-a, a, a, -a],[-a, -a, a, a]]) # Gauss points matrix
|
|
68
|
+
sigma = np.zeros(3) # stress (Voigt notation)
|
|
69
|
+
|
|
70
|
+
for j in range(4): # loop over element integration points
|
|
71
|
+
xi=Gauss[0,j] # natural coordinate - horizontal
|
|
72
|
+
eta=Gauss[1,j] # natural coordinate - vertical
|
|
73
|
+
dsB, J = dispstrain_B(XYZ,xi,eta) # evaluate dsB matrix and Jacobian
|
|
74
|
+
sG1 = np.dot(dsB,de) # strain vector (Voigt notation)
|
|
75
|
+
sG2 = sG1
|
|
76
|
+
sigmaGauss = np.dot(C, sG2) # stresses at j-th Gauss point within parent element
|
|
77
|
+
sigma = sigma + sigmaGauss*J*w # sum of the stresses over all Gauss points within physical element
|
|
78
|
+
|
|
79
|
+
return sigma
|
|
80
|
+
|
|
81
|
+
def quad_solver(x, plot=False):
|
|
82
|
+
"""
|
|
83
|
+
Wrapper around simple linear FE solver for trapezoidal geometry from PX912
|
|
84
|
+
|
|
85
|
+
Arguments:
|
|
86
|
+
x - vector containing [E (Pa), nu (dimensionless), t (m), fext_top (N)]
|
|
87
|
+
plot - if True, plot mesh before and after applying load
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
stress - array of length 3 containing [sigma_xx, sigma_yy, sigma_xy] in units of Pa
|
|
91
|
+
"""
|
|
92
|
+
E, nu, th, fext_top = x # unpack inputs
|
|
93
|
+
|
|
94
|
+
XYZ = np.array([[0,1],[0,0],[2,0.5],[2,1]]) # nodal coordinates
|
|
95
|
+
|
|
96
|
+
ndof = XYZ.size
|
|
97
|
+
BC = np.ones(ndof, dtype=int)
|
|
98
|
+
|
|
99
|
+
BC[[0, 1, 2, 3]] = 0
|
|
100
|
+
BCid = BC.nonzero()[0]
|
|
101
|
+
|
|
102
|
+
# Applied loads - define the RHS load vector
|
|
103
|
+
rhs = np.zeros(ndof)
|
|
104
|
+
rhs[[1, 7]] = fext_top
|
|
105
|
+
rhs = rhs[BCid]
|
|
106
|
+
|
|
107
|
+
C = plane_stress(E,nu)
|
|
108
|
+
ke = keval(XYZ, C, th)
|
|
109
|
+
ke = ke[np.ix_(BCid,BCid)]
|
|
110
|
+
ue = np.linalg.solve(ke, rhs)
|
|
111
|
+
de = np.zeros(XYZ.size)
|
|
112
|
+
de[BCid] = ue
|
|
113
|
+
if plot:
|
|
114
|
+
sdef=10000 # scale deformation
|
|
115
|
+
plot_mesh(XYZ)
|
|
116
|
+
plot_mesh(XYZ + sdef*de.reshape(XYZ.shape), c='r', ls='-')
|
|
117
|
+
sigma = sigma_vector(XYZ, C, de)
|
|
118
|
+
return sigma
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import scipy.sparse as sparse
|
|
3
|
+
import scipy.sparse.linalg as linalg
|
|
4
|
+
|
|
5
|
+
def poiseuille_solver_1(x, N=1000, return_all=False):
|
|
6
|
+
F, W = x # unpack parameters
|
|
7
|
+
F = F*np.ones(N) # vector for RHS
|
|
8
|
+
F[0] = F[-1] = 0 # enforce boundary conditions on RHS
|
|
9
|
+
h = 2*W/(N-1)
|
|
10
|
+
A = sparse.dia_matrix((N, N)) # assemble matrix A
|
|
11
|
+
|
|
12
|
+
# Build diagonals with boundary condition modifications
|
|
13
|
+
main_dia = np.ones(N) * 2/h**2
|
|
14
|
+
main_dia[0] = main_dia[-1] = 1 # enforce u[0] = u[-1] = 0
|
|
15
|
+
upper_dia = np.ones(N-1) * -1/h**2
|
|
16
|
+
upper_dia[0] = 0 # no coupling at left boundary
|
|
17
|
+
lower_dia = np.ones(N-1) * -1/h**2
|
|
18
|
+
lower_dia[-1] = 0 # no coupling at right boundary
|
|
19
|
+
|
|
20
|
+
A.setdiag(main_dia)
|
|
21
|
+
A.setdiag(upper_dia, 1)
|
|
22
|
+
A.setdiag(lower_dia, -1)
|
|
23
|
+
A = A.tocsc() # convert to CSC storage for efficient solve
|
|
24
|
+
u = linalg.spsolve(A, F)
|
|
25
|
+
Q = np.sum(u)*h
|
|
26
|
+
if return_all:
|
|
27
|
+
return u, F, Q
|
|
28
|
+
else:
|
|
29
|
+
return Q
|
|
30
|
+
|
|
31
|
+
def poiseuille_solver_2(x, N=1000, return_all=False):
|
|
32
|
+
F_l, F_h, W = x # unpack parameters
|
|
33
|
+
|
|
34
|
+
# build vector for F used for RHS of linear system
|
|
35
|
+
y = np.linspace(-W, W, N)
|
|
36
|
+
F = np.where((y > 0) & (y < W/2), F_l, F_h)
|
|
37
|
+
F[0] = F[-1] = 0 # enforce boundary conditions on RHS
|
|
38
|
+
|
|
39
|
+
h = 2*W/(N-1)
|
|
40
|
+
A = sparse.dia_matrix((N, N)) # assemble matrix A
|
|
41
|
+
|
|
42
|
+
# Build diagonals with boundary condition modifications
|
|
43
|
+
main_dia = np.ones(N) * 2/h**2
|
|
44
|
+
main_dia[0] = main_dia[-1] = 1 # enforce u[0] = u[-1] = 0
|
|
45
|
+
upper_dia = np.ones(N-1) * -1/h**2
|
|
46
|
+
upper_dia[0] = 0 # no coupling at left boundary
|
|
47
|
+
lower_dia = np.ones(N-1) * -1/h**2
|
|
48
|
+
lower_dia[-1] = 0 # no coupling at right boundary
|
|
49
|
+
|
|
50
|
+
A.setdiag(main_dia)
|
|
51
|
+
A.setdiag(upper_dia, 1)
|
|
52
|
+
A.setdiag(lower_dia, -1)
|
|
53
|
+
A = A.tocsc() # convert to CSC storage for efficient solve
|
|
54
|
+
u = linalg.spsolve(A, F)
|
|
55
|
+
Q = np.sum(u)*h
|
|
56
|
+
|
|
57
|
+
if return_all:
|
|
58
|
+
return u, F, Q
|
|
59
|
+
else:
|
|
60
|
+
return Q
|
|
61
|
+
|
|
62
|
+
def poiseuille_solver_3(F, W=2, return_all=False):
|
|
63
|
+
N = len(F)
|
|
64
|
+
F = F.copy() # avoid modifying input
|
|
65
|
+
F[0] = F[-1] = 0 # enforce boundary conditions on RHS
|
|
66
|
+
h = 2*W/(N-1)
|
|
67
|
+
A = sparse.dia_matrix((N, N)) # assemble matrix A
|
|
68
|
+
|
|
69
|
+
# Build diagonals with boundary condition modifications
|
|
70
|
+
main_dia = np.ones(N) * 2/h**2
|
|
71
|
+
main_dia[0] = main_dia[-1] = 1 # enforce u[0] = u[-1] = 0
|
|
72
|
+
upper_dia = np.ones(N-1) * -1/h**2
|
|
73
|
+
upper_dia[0] = 0 # no coupling at left boundary
|
|
74
|
+
lower_dia = np.ones(N-1) * -1/h**2
|
|
75
|
+
lower_dia[-1] = 0 # no coupling at right boundary
|
|
76
|
+
|
|
77
|
+
A.setdiag(main_dia)
|
|
78
|
+
A.setdiag(upper_dia, 1)
|
|
79
|
+
A.setdiag(lower_dia, -1)
|
|
80
|
+
A = A.tocsc() # convert to CSC storage for efficient solve
|
|
81
|
+
u = linalg.spsolve(A, F) # parameter vector specifies RHS
|
|
82
|
+
Q = np.sum(u)*h
|
|
83
|
+
if return_all:
|
|
84
|
+
return u, F, Q
|
|
85
|
+
else:
|
|
86
|
+
return Q
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# +
|
|
90
|
+
def u_analytic(y, F, W):
|
|
91
|
+
return F / 2 * (W ** 2 - y ** 2)
|
|
92
|
+
|
|
93
|
+
def Q_analytic(F, W):
|
|
94
|
+
return 2 * W ** 3 * F / 3
|