emlib 1.16.5__py3-none-any.whl
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.
- emlib/__init__.py +23 -0
- emlib/_dialogsqt.py +263 -0
- emlib/calculus.py +189 -0
- emlib/classproperty.py +32 -0
- emlib/colordata.py +167 -0
- emlib/combinatorics.py +412 -0
- emlib/common.py +34 -0
- emlib/containers.py +386 -0
- emlib/csvtools.py +211 -0
- emlib/dialogs.py +461 -0
- emlib/doctools.py +1441 -0
- emlib/electronics.py +518 -0
- emlib/envir.py +151 -0
- emlib/filetools.py +177 -0
- emlib/graphlib.py +53 -0
- emlib/img.py +213 -0
- emlib/iterlib.py +791 -0
- emlib/jsontools.py +110 -0
- emlib/logutils.py +62 -0
- emlib/mathlib.py +835 -0
- emlib/matplotting.py +386 -0
- emlib/misc.py +1876 -0
- emlib/net.py +65 -0
- emlib/numberseries.py +84 -0
- emlib/numpytools.py +316 -0
- emlib/numtheory.py +199 -0
- emlib/parabolicinterpol.py +73 -0
- emlib/py.typed +1 -0
- emlib/readbytes.py +87 -0
- emlib/result.py +80 -0
- emlib/shelling.py +100 -0
- emlib/smooth.py +253 -0
- emlib/stochastic.py +224 -0
- emlib/textlib.py +328 -0
- emlib/video.py +49 -0
- emlib/xmlprinter.py +278 -0
- emlib-1.16.5.dist-info/METADATA +55 -0
- emlib-1.16.5.dist-info/RECORD +40 -0
- emlib-1.16.5.dist-info/WHEEL +6 -0
- emlib-1.16.5.dist-info/top_level.txt +1 -0
emlib/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
**emlib** is a set of modules with miscellaneous functionality:
|
|
3
|
+
|
|
4
|
+
Repository: https://github.com/gesellkammer/emlib
|
|
5
|
+
|
|
6
|
+
Features
|
|
7
|
+
--------
|
|
8
|
+
|
|
9
|
+
- maths:
|
|
10
|
+
- base conversion, euclidian distance, min. common denominator, etc.
|
|
11
|
+
- combinatorics
|
|
12
|
+
- number theory
|
|
13
|
+
- number series
|
|
14
|
+
- markov chains
|
|
15
|
+
- utilities to traverse graphs
|
|
16
|
+
- matplotlib enhancements
|
|
17
|
+
- csv utilities
|
|
18
|
+
- iterator tools (similar to more_itertools)
|
|
19
|
+
- containers: RecordList (a list of named tuples)
|
|
20
|
+
- doctools: tools to generate documentation (for mkdocs particularly)
|
|
21
|
+
- etc
|
|
22
|
+
"""
|
|
23
|
+
|
emlib/_dialogsqt.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import sys
|
|
3
|
+
import logging
|
|
4
|
+
import emlib.misc
|
|
5
|
+
from PyQt5 import QtWidgets, QtCore, QtGui
|
|
6
|
+
from PyQt5.QtCore import Qt
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from typing import *
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
__all__ = (
|
|
15
|
+
'FilteredList',
|
|
16
|
+
'selectItem',
|
|
17
|
+
'selectFile',
|
|
18
|
+
'saveDialog',
|
|
19
|
+
'showInfo'
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _makeApp():
|
|
24
|
+
app = QtWidgets.QApplication.instance()
|
|
25
|
+
if app is None:
|
|
26
|
+
logger.debug("Making new qt QApplication")
|
|
27
|
+
app = QtWidgets.QApplication([])
|
|
28
|
+
else:
|
|
29
|
+
logger.debug("Using existing QApplication instance")
|
|
30
|
+
return app
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _FilterEdit(QtWidgets.QLineEdit):
|
|
34
|
+
def __init__(self, parent: FilteredList, font:Tuple[str, int]=None):
|
|
35
|
+
self.parent = parent
|
|
36
|
+
super().__init__()
|
|
37
|
+
if font:
|
|
38
|
+
self.setFont(QtGui.QFont(*font))
|
|
39
|
+
|
|
40
|
+
def keyPressEvent(self, keyEvent):
|
|
41
|
+
k = keyEvent.key()
|
|
42
|
+
if k == Qt.Key_Return:
|
|
43
|
+
self.parent.accept()
|
|
44
|
+
elif k in (Qt.Key_Up, Qt.Key_Down):
|
|
45
|
+
self.parent.view.keyPressEvent(keyEvent)
|
|
46
|
+
elif k == Qt.Key_Escape:
|
|
47
|
+
self.parent.dismiss()
|
|
48
|
+
else:
|
|
49
|
+
# re = QRegularExpression(pattern, QRegularExpression.CaseInsensitiveOption | QRegularExpression.DotMatchesEverythingOption)
|
|
50
|
+
rx = QtCore.QRegularExpression(self.text(), QtCore.QRegularExpression.CaseInsensitiveOption | QtCore.QRegularExpression.DotMatchesEverythingOption)
|
|
51
|
+
self.parent.proxy_model.setFilterRegularExpression(rx)
|
|
52
|
+
self.parent.proxy_model.setFilterWildcard("*" + self.text() + "*")
|
|
53
|
+
super().keyPressEvent(keyEvent)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class _FilteredListView(QtWidgets.QListView):
|
|
57
|
+
def __init__(self, parent: FilteredList, font:Tuple[str, int]=None):
|
|
58
|
+
self.parent = parent
|
|
59
|
+
super().__init__()
|
|
60
|
+
if font:
|
|
61
|
+
self.setFont(QtGui.QFont(*font))
|
|
62
|
+
|
|
63
|
+
def keyPressEvent(self, keyEvent):
|
|
64
|
+
k = keyEvent.key()
|
|
65
|
+
if k == Qt.Key_Return:
|
|
66
|
+
self.parent.accept()
|
|
67
|
+
elif k == Qt.Key_Escape:
|
|
68
|
+
self.parent.dismiss()
|
|
69
|
+
else:
|
|
70
|
+
super().keyPressEvent(keyEvent)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _calculateTextWidth(s: str, fontfamily: str, size: int) -> float:
|
|
74
|
+
f = QtGui.QFont(fontfamily, size)
|
|
75
|
+
fm = QtGui.QFontMetrics(f)
|
|
76
|
+
width = fm.horizontalAdvance(s)
|
|
77
|
+
return width
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class FilteredList(QtWidgets.QMainWindow):
|
|
81
|
+
def __init__(self, items:Sequence[str], title:str,
|
|
82
|
+
listFont:Tuple[str, int]=None,
|
|
83
|
+
entryFont:Tuple[str, int]=None):
|
|
84
|
+
super().__init__()
|
|
85
|
+
self.out = None
|
|
86
|
+
self.view = _FilteredListView(self, font=listFont) # QListView()
|
|
87
|
+
self.model = QtCore.QStringListModel(items)
|
|
88
|
+
self.proxy_model = QtCore.QSortFilterProxyModel()
|
|
89
|
+
self.proxy_model.setSourceModel(self.model)
|
|
90
|
+
self.proxy_model.setSortCaseSensitivity(Qt.CaseInsensitive)
|
|
91
|
+
self.view.setModel(self.proxy_model)
|
|
92
|
+
self.setWindowTitle(title)
|
|
93
|
+
self.searchbar = _FilterEdit(self, font=entryFont) # QLineEdit()
|
|
94
|
+
# choose the type of search by connecting to a different slot here.
|
|
95
|
+
# see https://doc.qt.io/qt-5/qsortfilterproxymodel.html#public-slots
|
|
96
|
+
self.searchbar.textChanged.connect(self.proxy_model.setFilterFixedString)
|
|
97
|
+
minw = _calculateTextWidth(max(items, key=lambda s:len(s)), listFont[0], listFont[1])
|
|
98
|
+
self.setMinimumWidth(int(minw + 60))
|
|
99
|
+
|
|
100
|
+
layout = QtWidgets.QVBoxLayout()
|
|
101
|
+
layout.addWidget(self.searchbar)
|
|
102
|
+
layout.addWidget(self.view)
|
|
103
|
+
|
|
104
|
+
container = QtWidgets.QWidget()
|
|
105
|
+
container.setLayout(layout)
|
|
106
|
+
self.setCentralWidget(container)
|
|
107
|
+
|
|
108
|
+
def accept(self):
|
|
109
|
+
idx = self.view.currentIndex()
|
|
110
|
+
selection = idx.data()
|
|
111
|
+
if selection is None and self.proxy_model.rowCount():
|
|
112
|
+
selection = self.proxy_model.index(0, 0).data()
|
|
113
|
+
self.out = selection
|
|
114
|
+
self.close()
|
|
115
|
+
|
|
116
|
+
def dismiss(self):
|
|
117
|
+
self.out = None
|
|
118
|
+
self.close()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def selectItem(items: Sequence[str], title='Select',
|
|
122
|
+
listFont:Tuple[str, int]=None,
|
|
123
|
+
entryFont: Tuple[str, int]=None
|
|
124
|
+
) -> Optional[str]:
|
|
125
|
+
app = _makeApp()
|
|
126
|
+
w = FilteredList(items, title=title, listFont=listFont, entryFont=entryFont)
|
|
127
|
+
w.show()
|
|
128
|
+
app.exec_()
|
|
129
|
+
return w.out
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class _FilteredComboBox(QtWidgets.QComboBox):
|
|
133
|
+
def __init__(self, parent=None, title:str=''):
|
|
134
|
+
super().__init__(parent)
|
|
135
|
+
self.dismissed = False
|
|
136
|
+
if title:
|
|
137
|
+
self.setWindowTitle(title)
|
|
138
|
+
|
|
139
|
+
self.setFocusPolicy(Qt.StrongFocus)
|
|
140
|
+
self.setEditable(True)
|
|
141
|
+
|
|
142
|
+
# add a filter model to filter matching items
|
|
143
|
+
self.pFilterModel = QtCore.QSortFilterProxyModel(self)
|
|
144
|
+
self.pFilterModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
|
|
145
|
+
self.pFilterModel.setSourceModel(self.model())
|
|
146
|
+
|
|
147
|
+
# add a completer, which uses the filter model
|
|
148
|
+
self.completer = QtCore.QCompleter(self.pFilterModel, self)
|
|
149
|
+
# always show all (filtered) completions
|
|
150
|
+
self.completer.setCompletionMode(QtCore.QCompleter.UnfilteredPopupCompletion)
|
|
151
|
+
self.setCompleter(self.completer)
|
|
152
|
+
|
|
153
|
+
# connect signals
|
|
154
|
+
self.lineEdit().textEdited.connect(self.pFilterModel.setFilterFixedString)
|
|
155
|
+
self.completer.activated.connect(self.on_completer_activated)
|
|
156
|
+
|
|
157
|
+
# on selection of an item from the completer, select the corresponding item from combobox
|
|
158
|
+
def on_completer_activated(self, text):
|
|
159
|
+
if text:
|
|
160
|
+
index = self.findText(text)
|
|
161
|
+
self.setCurrentIndex(index)
|
|
162
|
+
self.activated[str].emit(self.itemText(index))
|
|
163
|
+
|
|
164
|
+
# on model change, update the models of the filter and completer as well
|
|
165
|
+
def setModel(self, model):
|
|
166
|
+
super().setModel(model)
|
|
167
|
+
self.pFilterModel.setSourceModel(model)
|
|
168
|
+
self.completer.setModel(self.pFilterModel)
|
|
169
|
+
|
|
170
|
+
# on model column change, update the model column of the filter and completer as well
|
|
171
|
+
def setModelColumn(self, column):
|
|
172
|
+
self.completer.setCompletionColumn(column)
|
|
173
|
+
self.pFilterModel.setFilterKeyColumn(column)
|
|
174
|
+
super().setModelColumn(column)
|
|
175
|
+
|
|
176
|
+
def keyPressEvent(self, e):
|
|
177
|
+
if e.key() == Qt.Key_Escape:
|
|
178
|
+
self.dismissed = True
|
|
179
|
+
self.close()
|
|
180
|
+
elif e.key() == Qt.Key_Enter or e.key() == Qt.Key_Return:
|
|
181
|
+
self.close()
|
|
182
|
+
else:
|
|
183
|
+
super().keyPressEvent(e)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def selectFromCombobox(items: List[str], title="Select", width=300, height=40
|
|
187
|
+
) -> Optional[str]:
|
|
188
|
+
app = _makeApp()
|
|
189
|
+
combo = _FilteredComboBox(title=title)
|
|
190
|
+
# either fill the standard model of the combobox
|
|
191
|
+
# combo.addItems(options)
|
|
192
|
+
|
|
193
|
+
# or use another model
|
|
194
|
+
combo.setModel(QtCore.QStringListModel(items))
|
|
195
|
+
|
|
196
|
+
combo.resize(width, height)
|
|
197
|
+
combo.show()
|
|
198
|
+
app.exec_()
|
|
199
|
+
return None if combo.dismissed else combo.currentText()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def selectFile(directory:str=None, filter="All (*.*)", title="Open file") -> str:
|
|
203
|
+
app = _makeApp()
|
|
204
|
+
options = QtWidgets.QFileDialog.Options()
|
|
205
|
+
options |= QtWidgets.QFileDialog.DontUseNativeDialog
|
|
206
|
+
filter = filter.replace(",", " ")
|
|
207
|
+
name, mask = QtWidgets.QFileDialog.getOpenFileName(None, title, directory=directory,
|
|
208
|
+
filter=filter)
|
|
209
|
+
return name
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def saveDialog(filter="All (*.*)", title="Save file", directory:str=None) -> str:
|
|
213
|
+
app = _makeApp()
|
|
214
|
+
filter = filter.replace(",", " ")
|
|
215
|
+
name, mask = QtWidgets.QFileDialog.getSaveFileName(None, title, filter=filter,
|
|
216
|
+
directory=directory)
|
|
217
|
+
return name
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def showInfo(msg:str, title:str='Info', font:Tuple[str,int]=None, icon:str=None) -> None:
|
|
221
|
+
"""
|
|
222
|
+
Open a message box with a text
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
msg: the text to display (one line)
|
|
226
|
+
title: the title of the dialog
|
|
227
|
+
font: if given, a tuple (fontfamily, size)
|
|
228
|
+
icon: either None or one of 'question', 'information', 'warning', 'critical'
|
|
229
|
+
"""
|
|
230
|
+
app = _makeApp()
|
|
231
|
+
mbox = QtWidgets.QMessageBox()
|
|
232
|
+
mbox.setText(msg)
|
|
233
|
+
mbox.setBaseSize(QtCore.QSize(600, 120));
|
|
234
|
+
if title:
|
|
235
|
+
mbox.setWindowTitle(title)
|
|
236
|
+
if font:
|
|
237
|
+
mbox.setFont(QtCore.QFont(*font))
|
|
238
|
+
if icon:
|
|
239
|
+
if icon == 'question':
|
|
240
|
+
mbox.setIcon(QtWidgets.QMessageBox.Question)
|
|
241
|
+
elif icon == 'information':
|
|
242
|
+
mbox.setIcon(QtWidgets.QMessageBox.Information)
|
|
243
|
+
elif icon == 'warning':
|
|
244
|
+
mbox.setIcon(QtWidgets.QMessageBox.Warning)
|
|
245
|
+
elif icon == 'critical':
|
|
246
|
+
mbox.setIcon(QtWidgets.QMessageBox.Critical)
|
|
247
|
+
mbox.exec_()
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# init
|
|
251
|
+
if sys.platform == 'darwin' and emlib.misc.inside_ipython() and not emlib.misc.inside_jupyter():
|
|
252
|
+
# macos needs loop integration inside ipython
|
|
253
|
+
ip = get_ipython()
|
|
254
|
+
if ip.active_eventloop is None:
|
|
255
|
+
logger.debug("Starting ipython/qt eventloop integration")
|
|
256
|
+
ip.run_line_magic('gui', 'qt')
|
|
257
|
+
elif ip.active_eventloop not in ('qt', 'qt5'):
|
|
258
|
+
logger.warning(
|
|
259
|
+
f"IPython has an active eventloop for {ip.active_eventloop}, but "
|
|
260
|
+
f"emlib.dialogs needs the qt eventloop to be able to open qt dialogs"
|
|
261
|
+
f" without blocking the shell. ")
|
|
262
|
+
ip.run_line_magic('gui', 'qt')
|
|
263
|
+
|
emlib/calculus.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Calculus utilities, based on calculus.jl
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
import math
|
|
7
|
+
import struct as _struct
|
|
8
|
+
import random as _random
|
|
9
|
+
from typing import Callable
|
|
10
|
+
|
|
11
|
+
NAN = float('nan')
|
|
12
|
+
epsilon = math.ldexp(1.0, -53) # smallest double such that eps+0.5!=0.5
|
|
13
|
+
maxfloat = float(2**1024 - 2**971) # From the IEEE 754 standard
|
|
14
|
+
minfloat = math.ldexp(1.0, -1022) # min positive normalized double
|
|
15
|
+
smalleps = math.ldexp(1.0, -1074) # smallest increment for doubles < minfloat
|
|
16
|
+
infinity = math.ldexp(1.0, 1023) * 2
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def nextafter(x:float, direction=1) -> float:
|
|
20
|
+
"""
|
|
21
|
+
returns the next float after x in the direction indicated
|
|
22
|
+
|
|
23
|
+
if not possible, returns x
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
x: the value to evaluate
|
|
27
|
+
direction: if 1, the next float is searched upwards, otherwise downwards
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
a next representable float from `x` in the direction indicated
|
|
31
|
+
"""
|
|
32
|
+
if math.isnan(x) or math.isinf(x):
|
|
33
|
+
return x
|
|
34
|
+
# return small numbers for x very close to 0.0
|
|
35
|
+
if -minfloat < x < minfloat:
|
|
36
|
+
if direction > 0:
|
|
37
|
+
return x + smalleps
|
|
38
|
+
else:
|
|
39
|
+
return x - smalleps
|
|
40
|
+
|
|
41
|
+
# it looks like we have a normalized number
|
|
42
|
+
# break x down into a mantissa and exponent
|
|
43
|
+
m, e = math.frexp(x)
|
|
44
|
+
|
|
45
|
+
# all the special cases have been handled
|
|
46
|
+
if direction > 0:
|
|
47
|
+
m += epsilon
|
|
48
|
+
else:
|
|
49
|
+
m -= epsilon
|
|
50
|
+
return math.ldexp(m, e)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def eps(x:float) -> float:
|
|
54
|
+
"""
|
|
55
|
+
Difference with the next representable float
|
|
56
|
+
"""
|
|
57
|
+
if math.isinf(x):
|
|
58
|
+
return NAN
|
|
59
|
+
return abs(nextafter(x) - x)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def next_float_up(x:float) -> float:
|
|
63
|
+
"""
|
|
64
|
+
Return the next representable float
|
|
65
|
+
"""
|
|
66
|
+
# NaNs and positive infinity map to themselves.
|
|
67
|
+
if math.isnan(x) or (math.isinf(x) and x > 0):
|
|
68
|
+
return x
|
|
69
|
+
|
|
70
|
+
# 0.0 and -0.0 both map to the smallest +ve float.
|
|
71
|
+
if x == 0.0:
|
|
72
|
+
x = 0.0
|
|
73
|
+
|
|
74
|
+
n = _struct.unpack('<q', _struct.pack('<d', x))[0]
|
|
75
|
+
if n >= 0:
|
|
76
|
+
n += 1
|
|
77
|
+
else:
|
|
78
|
+
n -= 1
|
|
79
|
+
return _struct.unpack('<d', _struct.pack('<q', n))[0]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def next_float_down(x:float) -> float:
|
|
83
|
+
"""
|
|
84
|
+
return the previous representable float
|
|
85
|
+
"""
|
|
86
|
+
return -next_float_up(-x)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def next_toward(x:float, y:float) -> float:
|
|
90
|
+
"""
|
|
91
|
+
return the next representable float between x and y
|
|
92
|
+
"""
|
|
93
|
+
# If either argument is a NaN, return that argument.
|
|
94
|
+
# This matches the implementation in decimal.Decimal
|
|
95
|
+
if math.isnan(x):
|
|
96
|
+
return x
|
|
97
|
+
if math.isnan(y):
|
|
98
|
+
return y
|
|
99
|
+
if y == x:
|
|
100
|
+
return y
|
|
101
|
+
elif y > x:
|
|
102
|
+
return next_float_up(x)
|
|
103
|
+
else:
|
|
104
|
+
return next_float_down(x)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def cbrt(x:float) -> float:
|
|
108
|
+
""" cubic root """
|
|
109
|
+
return math.pow(x, 1.0 / 3)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def finite_difference_forward(func, x:float, h:float=None) -> float:
|
|
113
|
+
epsilon = math.sqrt(eps(max(1, abs(x)))) if h is None else h
|
|
114
|
+
# use machine-representable numbers
|
|
115
|
+
return (func(x + epsilon) - func(x)) / epsilon
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def finite_difference_central(func, x:float, h:float=None) -> float:
|
|
119
|
+
epsilon = cbrt(eps(max(1, abs(x)))) if h is None else h
|
|
120
|
+
return (func(x + epsilon) - func(x - epsilon)) / (2 * epsilon)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def finite_difference(func, x:float, mode='central') -> float:
|
|
124
|
+
"""
|
|
125
|
+
derivative of func at x
|
|
126
|
+
"""
|
|
127
|
+
if mode == 'forward':
|
|
128
|
+
return finite_difference_forward(func, x)
|
|
129
|
+
elif mode == 'central':
|
|
130
|
+
return finite_difference_central(func, x)
|
|
131
|
+
else:
|
|
132
|
+
raise ValueError("mode must be 'forward' or 'central'")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def derivative(func) -> Callable[[float], float]:
|
|
136
|
+
"""
|
|
137
|
+
return a new function representing the derivative of func
|
|
138
|
+
"""
|
|
139
|
+
return lambda x: finite_difference_central(func, x)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _integrate_adaptive_simpsons_inner(f:Callable, a:float, b:float, eps:float, S:float,
|
|
143
|
+
fa:float, fb:float, fc:float, bottom:float) -> float:
|
|
144
|
+
c = (a + b) / 2
|
|
145
|
+
h = b - a
|
|
146
|
+
d = (a + c) / 2
|
|
147
|
+
g = (c + b) / 2
|
|
148
|
+
fd = f(d)
|
|
149
|
+
fe = f(g)
|
|
150
|
+
Sleft = (h / 12) * (fa + 4 * fd + fc)
|
|
151
|
+
Sright = (h / 12) * (fc + 4 * fe + fb)
|
|
152
|
+
S2 = Sleft + Sright
|
|
153
|
+
if bottom <= 0 or abs(S2 - S) <= 15 * epsilon:
|
|
154
|
+
return S2 + (S2 - S) / 15
|
|
155
|
+
inner = _integrate_adaptive_simpsons_inner
|
|
156
|
+
return (
|
|
157
|
+
inner(f, a, c, eps/2, Sleft, fa, fc, fd, bottom - 1) +
|
|
158
|
+
inner(f, c, b, eps/2, Sright, fc, fb, fe, bottom - 1)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def integrate_adaptive_simpsons(f:Callable, a:float, b:float, accuracy=10e-10,
|
|
163
|
+
max_iterations=50) -> float:
|
|
164
|
+
c = (a + b) / 2
|
|
165
|
+
h = b - a
|
|
166
|
+
fa = f(a)
|
|
167
|
+
fb = f(b)
|
|
168
|
+
fc = f(c)
|
|
169
|
+
S = (h / 6) * (fa + 4 * fc + fb)
|
|
170
|
+
return _integrate_adaptive_simpsons_inner(f, a, b, accuracy,
|
|
171
|
+
S, fa, fb, fc, max_iterations)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def integrate_monte_carlo(f:Callable, a:float, b:float, iterations:int):
|
|
175
|
+
estimate = 0.0
|
|
176
|
+
width = (b - a)
|
|
177
|
+
for i in range(iterations):
|
|
178
|
+
x = width * _random.random() + a
|
|
179
|
+
estimate += f(x) * width
|
|
180
|
+
return estimate / iterations
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def integrate(f:Callable, a:float, b:float, method='simpsons') -> float:
|
|
184
|
+
if method == 'simpsons':
|
|
185
|
+
return integrate_adaptive_simpsons(f, a, b)
|
|
186
|
+
elif method == 'montecarlo':
|
|
187
|
+
return integrate_monte_carlo(f, a, b, 10000)
|
|
188
|
+
else:
|
|
189
|
+
raise ValueError("Unknown method of integration")
|
emlib/classproperty.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Very simple implementation of a class property
|
|
3
|
+
|
|
4
|
+
Example
|
|
5
|
+
~~~~~~~
|
|
6
|
+
|
|
7
|
+
class Foo:
|
|
8
|
+
_active: Foo | None = None
|
|
9
|
+
_initdone = False
|
|
10
|
+
|
|
11
|
+
def __init__(self, bar=None):
|
|
12
|
+
self.bar = bar
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def _initclass(self):
|
|
16
|
+
if Foo._initdone:
|
|
17
|
+
return
|
|
18
|
+
Foo._initdone = True
|
|
19
|
+
Foo._active = Foo()
|
|
20
|
+
|
|
21
|
+
@classproperty
|
|
22
|
+
def active(cls) -> Foo
|
|
23
|
+
assert Foo._initdone and Foo._active is not None
|
|
24
|
+
return Foo._active
|
|
25
|
+
|
|
26
|
+
Foo._initclass()
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class classproperty(property):
|
|
31
|
+
def __get__(self, owner_self, owner_cls):
|
|
32
|
+
return self.fget(owner_cls)
|
emlib/colordata.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
These color definitions are taken from matplotlib._color_data.py
|
|
3
|
+
|
|
4
|
+
We include it here to be able to access color names without needing to import matplotlib
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
BASE_COLORS = {
|
|
8
|
+
'b': (0, 0, 1), # blue
|
|
9
|
+
'g': (0, 0.5, 0), # green
|
|
10
|
+
'r': (1, 0, 0), # red
|
|
11
|
+
'c': (0, 0.75, 0.75), # cyan
|
|
12
|
+
'm': (0.75, 0, 0.75), # magenta
|
|
13
|
+
'y': (0.75, 0.75, 0), # yellow
|
|
14
|
+
'k': (0, 0, 0), # black
|
|
15
|
+
'w': (1, 1, 1), # white
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# https://drafts.csswg.org/css-color-4/#named-colors
|
|
19
|
+
CSS4_COLORS = {
|
|
20
|
+
'aliceblue': '#F0F8FF',
|
|
21
|
+
'antiquewhite': '#FAEBD7',
|
|
22
|
+
'aqua': '#00FFFF',
|
|
23
|
+
'aquamarine': '#7FFFD4',
|
|
24
|
+
'azure': '#F0FFFF',
|
|
25
|
+
'beige': '#F5F5DC',
|
|
26
|
+
'bisque': '#FFE4C4',
|
|
27
|
+
'black': '#000000',
|
|
28
|
+
'blanchedalmond': '#FFEBCD',
|
|
29
|
+
'blue': '#0000FF',
|
|
30
|
+
'blueviolet': '#8A2BE2',
|
|
31
|
+
'brown': '#A52A2A',
|
|
32
|
+
'burlywood': '#DEB887',
|
|
33
|
+
'cadetblue': '#5F9EA0',
|
|
34
|
+
'chartreuse': '#7FFF00',
|
|
35
|
+
'chocolate': '#D2691E',
|
|
36
|
+
'coral': '#FF7F50',
|
|
37
|
+
'cornflowerblue': '#6495ED',
|
|
38
|
+
'cornsilk': '#FFF8DC',
|
|
39
|
+
'crimson': '#DC143C',
|
|
40
|
+
'cyan': '#00FFFF',
|
|
41
|
+
'darkblue': '#00008B',
|
|
42
|
+
'darkcyan': '#008B8B',
|
|
43
|
+
'darkgoldenrod': '#B8860B',
|
|
44
|
+
'darkgray': '#A9A9A9',
|
|
45
|
+
'darkgreen': '#006400',
|
|
46
|
+
'darkgrey': '#A9A9A9',
|
|
47
|
+
'darkkhaki': '#BDB76B',
|
|
48
|
+
'darkmagenta': '#8B008B',
|
|
49
|
+
'darkolivegreen': '#556B2F',
|
|
50
|
+
'darkorange': '#FF8C00',
|
|
51
|
+
'darkorchid': '#9932CC',
|
|
52
|
+
'darkred': '#8B0000',
|
|
53
|
+
'darksalmon': '#E9967A',
|
|
54
|
+
'darkseagreen': '#8FBC8F',
|
|
55
|
+
'darkslateblue': '#483D8B',
|
|
56
|
+
'darkslategray': '#2F4F4F',
|
|
57
|
+
'darkslategrey': '#2F4F4F',
|
|
58
|
+
'darkturquoise': '#00CED1',
|
|
59
|
+
'darkviolet': '#9400D3',
|
|
60
|
+
'deeppink': '#FF1493',
|
|
61
|
+
'deepskyblue': '#00BFFF',
|
|
62
|
+
'dimgray': '#696969',
|
|
63
|
+
'dimgrey': '#696969',
|
|
64
|
+
'dodgerblue': '#1E90FF',
|
|
65
|
+
'firebrick': '#B22222',
|
|
66
|
+
'floralwhite': '#FFFAF0',
|
|
67
|
+
'forestgreen': '#228B22',
|
|
68
|
+
'fuchsia': '#FF00FF',
|
|
69
|
+
'gainsboro': '#DCDCDC',
|
|
70
|
+
'ghostwhite': '#F8F8FF',
|
|
71
|
+
'gold': '#FFD700',
|
|
72
|
+
'goldenrod': '#DAA520',
|
|
73
|
+
'gray': '#808080',
|
|
74
|
+
'green': '#008000',
|
|
75
|
+
'greenyellow': '#ADFF2F',
|
|
76
|
+
'grey': '#808080',
|
|
77
|
+
'honeydew': '#F0FFF0',
|
|
78
|
+
'hotpink': '#FF69B4',
|
|
79
|
+
'indianred': '#CD5C5C',
|
|
80
|
+
'indigo': '#4B0082',
|
|
81
|
+
'ivory': '#FFFFF0',
|
|
82
|
+
'khaki': '#F0E68C',
|
|
83
|
+
'lavender': '#E6E6FA',
|
|
84
|
+
'lavenderblush': '#FFF0F5',
|
|
85
|
+
'lawngreen': '#7CFC00',
|
|
86
|
+
'lemonchiffon': '#FFFACD',
|
|
87
|
+
'lightblue': '#ADD8E6',
|
|
88
|
+
'lightcoral': '#F08080',
|
|
89
|
+
'lightcyan': '#E0FFFF',
|
|
90
|
+
'lightgoldenrodyellow': '#FAFAD2',
|
|
91
|
+
'lightgray': '#D3D3D3',
|
|
92
|
+
'lightgreen': '#90EE90',
|
|
93
|
+
'lightgrey': '#D3D3D3',
|
|
94
|
+
'lightpink': '#FFB6C1',
|
|
95
|
+
'lightsalmon': '#FFA07A',
|
|
96
|
+
'lightseagreen': '#20B2AA',
|
|
97
|
+
'lightskyblue': '#87CEFA',
|
|
98
|
+
'lightslategray': '#778899',
|
|
99
|
+
'lightslategrey': '#778899',
|
|
100
|
+
'lightsteelblue': '#B0C4DE',
|
|
101
|
+
'lightyellow': '#FFFFE0',
|
|
102
|
+
'lime': '#00FF00',
|
|
103
|
+
'limegreen': '#32CD32',
|
|
104
|
+
'linen': '#FAF0E6',
|
|
105
|
+
'magenta': '#FF00FF',
|
|
106
|
+
'maroon': '#800000',
|
|
107
|
+
'mediumaquamarine': '#66CDAA',
|
|
108
|
+
'mediumblue': '#0000CD',
|
|
109
|
+
'mediumorchid': '#BA55D3',
|
|
110
|
+
'mediumpurple': '#9370DB',
|
|
111
|
+
'mediumseagreen': '#3CB371',
|
|
112
|
+
'mediumslateblue': '#7B68EE',
|
|
113
|
+
'mediumspringgreen': '#00FA9A',
|
|
114
|
+
'mediumturquoise': '#48D1CC',
|
|
115
|
+
'mediumvioletred': '#C71585',
|
|
116
|
+
'midnightblue': '#191970',
|
|
117
|
+
'mintcream': '#F5FFFA',
|
|
118
|
+
'mistyrose': '#FFE4E1',
|
|
119
|
+
'moccasin': '#FFE4B5',
|
|
120
|
+
'navajowhite': '#FFDEAD',
|
|
121
|
+
'navy': '#000080',
|
|
122
|
+
'oldlace': '#FDF5E6',
|
|
123
|
+
'olive': '#808000',
|
|
124
|
+
'olivedrab': '#6B8E23',
|
|
125
|
+
'orange': '#FFA500',
|
|
126
|
+
'orangered': '#FF4500',
|
|
127
|
+
'orchid': '#DA70D6',
|
|
128
|
+
'palegoldenrod': '#EEE8AA',
|
|
129
|
+
'palegreen': '#98FB98',
|
|
130
|
+
'paleturquoise': '#AFEEEE',
|
|
131
|
+
'palevioletred': '#DB7093',
|
|
132
|
+
'papayawhip': '#FFEFD5',
|
|
133
|
+
'peachpuff': '#FFDAB9',
|
|
134
|
+
'peru': '#CD853F',
|
|
135
|
+
'pink': '#FFC0CB',
|
|
136
|
+
'plum': '#DDA0DD',
|
|
137
|
+
'powderblue': '#B0E0E6',
|
|
138
|
+
'purple': '#800080',
|
|
139
|
+
'rebeccapurple': '#663399',
|
|
140
|
+
'red': '#FF0000',
|
|
141
|
+
'rosybrown': '#BC8F8F',
|
|
142
|
+
'royalblue': '#4169E1',
|
|
143
|
+
'saddlebrown': '#8B4513',
|
|
144
|
+
'salmon': '#FA8072',
|
|
145
|
+
'sandybrown': '#F4A460',
|
|
146
|
+
'seagreen': '#2E8B57',
|
|
147
|
+
'seashell': '#FFF5EE',
|
|
148
|
+
'sienna': '#A0522D',
|
|
149
|
+
'silver': '#C0C0C0',
|
|
150
|
+
'skyblue': '#87CEEB',
|
|
151
|
+
'slateblue': '#6A5ACD',
|
|
152
|
+
'slategray': '#708090',
|
|
153
|
+
'slategrey': '#708090',
|
|
154
|
+
'snow': '#FFFAFA',
|
|
155
|
+
'springgreen': '#00FF7F',
|
|
156
|
+
'steelblue': '#4682B4',
|
|
157
|
+
'tan': '#D2B48C',
|
|
158
|
+
'teal': '#008080',
|
|
159
|
+
'thistle': '#D8BFD8',
|
|
160
|
+
'tomato': '#FF6347',
|
|
161
|
+
'turquoise': '#40E0D0',
|
|
162
|
+
'violet': '#EE82EE',
|
|
163
|
+
'wheat': '#F5DEB3',
|
|
164
|
+
'white': '#FFFFFF',
|
|
165
|
+
'whitesmoke': '#F5F5F5',
|
|
166
|
+
'yellow': '#FFFF00',
|
|
167
|
+
'yellowgreen': '#9ACD32'}
|