Big set of changes, to bring project into more organised state
This commit is contained in:
parent
56e88759fe
commit
e37849295a
1
.gitignore
vendored
1
.gitignore
vendored
@ -45,6 +45,7 @@ htmlcov/
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
pytest.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
|
73
Jenkinsfile
vendored
Normal file
73
Jenkinsfile
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
pipeline {
|
||||
agent {
|
||||
kubernetes {
|
||||
label 'pathfinder' // all your pods will be named with this prefix, followed by a unique id
|
||||
idleMinutes 5 // how long the pod will live after no jobs have run on it
|
||||
yamlFile 'jenkins/ci-agent-pod.yaml' // path to the pod definition relative to the root of our project
|
||||
defaultContainer 'python' // define a default container if more than a few stages use it, will default to jnlp container
|
||||
}
|
||||
}
|
||||
|
||||
options {
|
||||
parallelsAlwaysFailFast()
|
||||
}
|
||||
|
||||
environment {
|
||||
POETRY_HOME="/opt/poetry"
|
||||
POETRY_VERSION="1.1.4"
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Build') {
|
||||
steps {
|
||||
echo 'Building...'
|
||||
sh 'python --version'
|
||||
sh 'curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python'
|
||||
sh '${POETRY_HOME}/bin/poetry --version'
|
||||
sh '${POETRY_HOME}/bin/poetry install'
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
parallel{
|
||||
stage('pytest') {
|
||||
steps {
|
||||
sh '${POETRY_HOME}/bin/poetry run pytest'
|
||||
}
|
||||
}
|
||||
stage('lint') {
|
||||
steps {
|
||||
sh '${POETRY_HOME}/bin/poetry run flake8'
|
||||
}
|
||||
}
|
||||
stage('mypy') {
|
||||
steps {
|
||||
sh '${POETRY_HOME}/bin/poetry run mypy pathfinder'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
post {
|
||||
always {
|
||||
echo 'This will always run'
|
||||
junit 'pytest.xml'
|
||||
cobertura coberturaReportFile: 'coverage.xml'
|
||||
mail (bcc: '',
|
||||
body: "Project: ${env.JOB_NAME} <br>Build Number: ${env.BUILD_NUMBER} <br> Build URL: ${env.BUILD_URL}", cc: '', charset: 'UTF-8', from: 'jenkins@jenkins.deepak.science', mimeType: 'text/html', replyTo: 'dmallubhotla+jenkins@gmail.com', subject: "${env.JOB_NAME} #${env.BUILD_NUMBER}: Build ${currentBuild.currentResult}", to: "dmallubhotla+ci@gmail.com")
|
||||
}
|
||||
success {
|
||||
echo 'This will run only if successful'
|
||||
}
|
||||
failure {
|
||||
echo 'This will run only if failed'
|
||||
}
|
||||
unstable {
|
||||
echo 'This will run only if the run was marked as unstable'
|
||||
}
|
||||
changed {
|
||||
echo 'This will run only if the state of the Pipeline has changed'
|
||||
echo 'For example, if the Pipeline was previously failing but is now successful'
|
||||
}
|
||||
}
|
||||
}
|
4
do.sh
4
do.sh
@ -16,6 +16,10 @@ test() {
|
||||
poetry run pytest
|
||||
}
|
||||
|
||||
htmlcov() {
|
||||
poetry run pytest --cov-report=html
|
||||
}
|
||||
|
||||
all() {
|
||||
build && test
|
||||
}
|
||||
|
@ -1,16 +1,4 @@
|
||||
from pathfinder.model.dot import Dot
|
||||
from pathfinder.model.model import DotDipoleModel
|
||||
|
||||
|
||||
class DipoleModel():
|
||||
'''
|
||||
Model object represents a physical dipole finding problem.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
n : int
|
||||
The number of dipoles expected.
|
||||
m: int
|
||||
The number of dots used to sample the potential.
|
||||
'''
|
||||
def __init__(self, n, m):
|
||||
self.n = n
|
||||
serf.m = m
|
||||
__all__ = ['Dot', 'DotDipoleModel', ]
|
||||
|
37
pathfinder/model/dot.py
Normal file
37
pathfinder/model/dot.py
Normal file
@ -0,0 +1,37 @@
|
||||
from dataclasses import dataclass
|
||||
import numpy
|
||||
import numpy.typing
|
||||
|
||||
|
||||
@dataclass
|
||||
class Dot():
|
||||
'''
|
||||
Representation of a dot measuring static dipoles.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
v : float
|
||||
The voltage measured at the dot.
|
||||
r : numpy.ndarray
|
||||
The number of dots used to sample the potential.
|
||||
'''
|
||||
v: float
|
||||
r: numpy.typing.ArrayLike
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.r = numpy.array(self.r)
|
||||
|
||||
def v_for_point(self, pt: numpy.ndarray) -> float:
|
||||
p = pt[0:3] # hardcoded here because chances
|
||||
s = pt[3:6] # are we'll only ever work in 3d.
|
||||
|
||||
diff = self.r - s
|
||||
return p.dot(diff) / (numpy.linalg.norm(diff)**3)
|
||||
|
||||
def cost(self, pts: numpy.ndarray) -> float:
|
||||
# 6 because dipole in 3d has 6 degrees of freedom.
|
||||
pt_length = 6
|
||||
# creates numpy.ndarrays in groups of pt_length.
|
||||
# Will throw problems for irregular points, but that's okay for now.
|
||||
chunked_pts = [pts[i: i + pt_length] for i in range(0, len(pts), pt_length)]
|
||||
return sum(self.v_for_point(pt) for pt in chunked_pts) - self.v
|
31
pathfinder/model/model.py
Normal file
31
pathfinder/model/model.py
Normal file
@ -0,0 +1,31 @@
|
||||
from typing import Callable, Sequence
|
||||
import numpy
|
||||
|
||||
from pathfinder.model.dot import Dot
|
||||
|
||||
|
||||
class DotDipoleModel():
|
||||
'''
|
||||
Model of n static dipoles with a collection of voltage measurements
|
||||
at dots at different positions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dots : Sequence[Dot]
|
||||
A collection of dots representing a series of measured voltages.
|
||||
n: int
|
||||
The number of dipoles to assume.
|
||||
'''
|
||||
def __init__(self, dots: Sequence[Dot], n: int) -> None:
|
||||
self.dots = dots
|
||||
self.m = len(dots)
|
||||
self.n = n
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'DotDipoleModel({repr(list(self.dots))}, {self.n})'
|
||||
|
||||
def costs(self) -> Callable[[numpy.ndarray], numpy.ndarray]:
|
||||
def costs_to_return(pt: numpy.ndarray) -> numpy.ndarray:
|
||||
return numpy.array([dot.cost(pt) for dot in self.dots])
|
||||
|
||||
return costs_to_return
|
14
poetry.lock
generated
14
poetry.lock
generated
@ -68,6 +68,14 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "more-itertools"
|
||||
version = "8.8.0"
|
||||
description = "More routines for operating on iterables, beyond itertools"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "0.790"
|
||||
@ -229,7 +237,7 @@ python-versions = "*"
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.8,<3.10"
|
||||
content-hash = "bde9b5d449e7257dc8c24675658295cf82950d7ec381d873e936ad7cc4bcf6d8"
|
||||
content-hash = "223211dbc0d0b43607b649f98a88b1d7c2f07c9d7574508bd8f68f36787966b3"
|
||||
|
||||
[metadata.files]
|
||||
atomicwrites = [
|
||||
@ -310,6 +318,10 @@ mccabe = [
|
||||
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
||||
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
||||
]
|
||||
more-itertools = [
|
||||
{file = "more-itertools-8.8.0.tar.gz", hash = "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"},
|
||||
{file = "more_itertools-8.8.0-py3-none-any.whl", hash = "sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d"},
|
||||
]
|
||||
mypy = [
|
||||
{file = "mypy-0.790-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669"},
|
||||
{file = "mypy-0.790-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802"},
|
||||
|
@ -8,6 +8,7 @@ authors = ["Deepak <dmallubhotla+github@gmail.com>"]
|
||||
python = "^3.8,<3.10"
|
||||
numpy = "^1.21.1"
|
||||
scipy = "~1.5"
|
||||
more-itertools = "^8.8.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = ">=6"
|
||||
@ -21,3 +22,8 @@ build-backend = "poetry.masonry.api"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
addopts = "--junitxml pytest.xml --cov pathfinder --cov-report=xml:coverage.xml --cov-fail-under=90"
|
||||
junit_family = "xunit1"
|
||||
|
||||
[tool.mypy]
|
||||
plugins = "numpy.typing.mypy_plugin"
|
||||
|
25
tests/model/test_dot.py
Normal file
25
tests/model/test_dot.py
Normal file
@ -0,0 +1,25 @@
|
||||
import numpy
|
||||
import numpy.testing
|
||||
|
||||
import pathfinder.model as model
|
||||
|
||||
|
||||
def test_dot():
|
||||
dot = model.Dot(0.235, (1, 2, 3))
|
||||
assert dot.v == 0.235
|
||||
numpy.testing.assert_array_equal(dot.r, (1, 2, 3), "These arrays should have been equal!")
|
||||
|
||||
|
||||
def test_dot_v_from_dipole():
|
||||
# for a dot located at (1, 2, 3)
|
||||
dot = model.Dot(50, (1, 2, 3))
|
||||
|
||||
# and dipole located at (4, 7, 11) with p=(8, 9, 10)
|
||||
pt = numpy.array((8, 9, 10, 4, 7, 11))
|
||||
|
||||
# V should be -0.153584
|
||||
target = -0.1535844174880402
|
||||
cost = -50.1535844174880402
|
||||
|
||||
numpy.testing.assert_allclose(dot.v_for_point(pt), target, err_msg="v from dipole at a dot was incorrect!")
|
||||
numpy.testing.assert_allclose(dot.cost(pt), cost, err_msg="cost from dipole at a dot was incorrect!")
|
11
tests/model/test_model.py
Normal file
11
tests/model/test_model.py
Normal file
@ -0,0 +1,11 @@
|
||||
import pathfinder.model as model
|
||||
|
||||
|
||||
def test_dotdipolemodel_repr():
|
||||
mod = model.DotDipoleModel((), 1)
|
||||
assert repr(mod) == "DotDipoleModel([], 1)"
|
||||
|
||||
|
||||
def test_dotdipolemodel_m():
|
||||
mod = model.DotDipoleModel([model.Dot(1, (0, 0, 0)), model.Dot(2, (0, 0, 0))], 1)
|
||||
assert mod.m == 2
|
@ -1,165 +0,0 @@
|
||||
import numpy
|
||||
import scipy.optimize
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def circ_cost(radius, center=(0, 0)):
|
||||
|
||||
def cf(pt):
|
||||
pt2 = numpy.array(pt) - numpy.array(center)
|
||||
return (radius**2 - pt2.dot(pt2))
|
||||
|
||||
return cf
|
||||
|
||||
|
||||
def test_circ_cost():
|
||||
cost = circ_cost(5)
|
||||
actual = cost([3, 4])
|
||||
expected = 0
|
||||
assert actual == expected
|
||||
|
||||
cost = circ_cost(13, [12, 5])
|
||||
actual = cost([0, 0])
|
||||
expected = 0
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_find_sols():
|
||||
c1 = circ_cost(5)
|
||||
c2 = circ_cost(13, [8, -8])
|
||||
|
||||
def costs(pt):
|
||||
return numpy.array(
|
||||
[c1(pt), c2(pt)]
|
||||
)
|
||||
|
||||
def jac(pt):
|
||||
x, y = pt
|
||||
return numpy.array([[-2 * x, -2 * y], [-2 * (x - 8), -2 * (y + 8)]])
|
||||
|
||||
print(scipy.optimize.minimize(lambda x: costs(x).dot(costs(x)), numpy.array([1, 2])))
|
||||
#
|
||||
# message, iterations, result = pathfinder.gradient_descent.find_sols(costs, jac, step_size=0.01, max_iterations=5000, initial=(2, 10), desired_cost=1e-6)
|
||||
# numpy.testing.assert_almost_equal(
|
||||
# result, (3, 4),
|
||||
# decimal=7, err_msg='the result was off', verbose=True
|
||||
# )
|
||||
|
||||
|
||||
def dipole_cost(vn, xn_raw):
|
||||
xn = numpy.array(xn_raw)
|
||||
|
||||
def dc(pt):
|
||||
p = pt[0:3]
|
||||
s = pt[3:6]
|
||||
|
||||
diff = xn - s
|
||||
return (vn * (numpy.linalg.norm(diff)**3)) - p.dot(diff)
|
||||
|
||||
return dc
|
||||
|
||||
def test_actual_dipole_finding():
|
||||
def c0(pt):
|
||||
p = pt[0:3]
|
||||
return (p.dot(p) - 35)
|
||||
|
||||
v1 = -0.05547767706400186526225414
|
||||
v2 = -0.06018573388098888319642888
|
||||
v3 = -0.06364032191901859480476888
|
||||
v4 = -0.06488383879243851188402150
|
||||
v5 = -0.06297148063759813929659130
|
||||
v6 = -0.05735489606460216
|
||||
v7 = -0.07237320672886623
|
||||
|
||||
# the 0 here is a red herring for index purposes later
|
||||
vns = [0, v1, v2, v3, v4, v5]
|
||||
# the 0 here is a red herring
|
||||
xns = [numpy.array([0, 0, n]) for n in range(0, 6)]
|
||||
|
||||
# the 0 here is a red herring for index purposes later
|
||||
vns2 = [0, v1, v2, v3, v4, v5, v6, v7]
|
||||
# the 0 here is a red herring
|
||||
xns2 = [numpy.array([0, 0, n]) for n in range(0, 7)]
|
||||
xns2.append([1, 1, 7])
|
||||
|
||||
c1 = dipole_cost(v1, [0, 0, 1])
|
||||
c2 = dipole_cost(v2, [0, 0, 2])
|
||||
c3 = dipole_cost(v3, [0, 0, 3])
|
||||
c4 = dipole_cost(v4, [0, 0, 4])
|
||||
c5 = dipole_cost(v5, [0, 0, 5])
|
||||
c6 = dipole_cost(v6, [0, 0, 6])
|
||||
c6 = dipole_cost(v6, [0, 0, 6])
|
||||
c7 = dipole_cost(v7, [1, 1, 7])
|
||||
|
||||
def costs(pt):
|
||||
return numpy.array(
|
||||
[c0(pt), c1(pt), c2(pt), c3(pt), c4(pt), c5(pt)]
|
||||
)
|
||||
def costs2(pt):
|
||||
return numpy.array(
|
||||
[c0(pt), c1(pt), c2(pt), c3(pt), c4(pt), c5(pt), c6(pt), c7(pt)]
|
||||
)
|
||||
|
||||
def jac_row(n):
|
||||
def jr(pt):
|
||||
p = pt[0:3]
|
||||
s = pt[3:6]
|
||||
vn = vns2[n]
|
||||
xn = xns2[n]
|
||||
diff = xn - s
|
||||
return [
|
||||
-diff[0], -diff[1], -diff[2],
|
||||
p[0] - vn * 3 * numpy.linalg.norm(diff) * (diff)[0],
|
||||
p[1] - vn * 3 * numpy.linalg.norm(diff) * (diff)[1],
|
||||
p[2] - vn * 3 * numpy.linalg.norm(diff) * (diff)[2]
|
||||
]
|
||||
return jr
|
||||
|
||||
def jac(pt):
|
||||
return numpy.array([
|
||||
[2 * pt[0], 2 * pt[1], 2 * pt[2], 0, 0, 0],
|
||||
jac_row(1)(pt),
|
||||
jac_row(2)(pt),
|
||||
jac_row(3)(pt),
|
||||
jac_row(4)(pt),
|
||||
jac_row(5)(pt),
|
||||
])
|
||||
|
||||
def jac2(pt):
|
||||
return numpy.array([
|
||||
[2 * pt[0], 2 * pt[1], 2 * pt[2], 0, 0, 0],
|
||||
jac_row(1)(pt),
|
||||
jac_row(2)(pt),
|
||||
jac_row(3)(pt),
|
||||
jac_row(4)(pt),
|
||||
jac_row(5)(pt),
|
||||
jac_row(6)(pt),
|
||||
jac_row(7)(pt),
|
||||
])
|
||||
|
||||
def print_result(msg, result):
|
||||
print(msg)
|
||||
print(f"\tResult: {result.x}")
|
||||
print(f"\tSuccess: {result.success}. {result.message}")
|
||||
try:
|
||||
print(f"\tFunc evals: {result.nfev}")
|
||||
except AttributeError as e:
|
||||
pass
|
||||
try:
|
||||
print(f"\tJacb evals: {result.njev}")
|
||||
except AttributeError as e:
|
||||
pass
|
||||
print("Minimising the squared costs")
|
||||
print(scipy.optimize.minimize(lambda x: costs(x).dot(costs(x)), numpy.array([1, 2, 3, 4, 5, 6])))
|
||||
# print(scipy.optimize.broyden1(costs, numpy.array([1, 2, 3, 4, 5, 6])))
|
||||
# print(scipy.optimize.newton_krylov(costs, numpy.array([1, 2, 3, 4, 5, 6])))
|
||||
# print(scipy.optimize.anderson(costs, numpy.array([1, 2, 3, 4, 5, 6])))
|
||||
print_result("Using root", scipy.optimize.root(costs, numpy.array([1, 2, 3, 4, 5, 6])))
|
||||
print_result("Using root with jacobian", scipy.optimize.root(costs, numpy.array([1, 2, 3, 4, 5, 6]), jac=jac, tol=1e-12))
|
||||
print_result("Using least squares", scipy.optimize.least_squares(costs, numpy.array([1, 2, 3, 4, 5, 6]), gtol=1e-12))
|
||||
print_result("Using least squares, with jacobian", scipy.optimize.least_squares(costs, numpy.array([1, 2, 3, 4, 5, 6]), jac=jac, ftol=3e-16, gtol=3e-16, xtol=3e-16))
|
||||
print_result("Using least squares, with jacobian, lm", scipy.optimize.least_squares(costs, numpy.array([1, 2, 3, 4, 5, 6]), jac=jac, ftol=3e-16, gtol=3e-16, xtol=3e-16, method="lm"))
|
||||
print_result("Using least squares extra dot", scipy.optimize.least_squares(costs2, numpy.array([1, 2, 3, 4, 5, 6])))
|
||||
print_result("Using least squares extra dot, with jacobian", scipy.optimize.least_squares(costs2, numpy.array([1, 2, 3, 4, 5, 6]), jac=jac2, ftol=1e-12))
|
||||
print(scipy.optimize.least_squares(costs2, numpy.array([1, 2, 3, 4, 5, 6]), jac=jac2, ftol=1e-12).x[0])
|
Reference in New Issue
Block a user