Skip to content

Commit e1f77bd

Browse files
eric-wieserutensil
authored andcommitted
Be stricter about kwargs to Mv.__init__ (#97)
Also add much better documentation for construction
1 parent be31380 commit e1f77bd

File tree

4 files changed

+126
-12
lines changed

4 files changed

+126
-12
lines changed

galgebra/deprecated.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ def setup(basis, metric=None, coords=None, rframe=False, debug=False, curv=(None
3838
return list(MV.GA.mv())
3939

4040

41-
def __init__(self, base, mvtype, fct=False, blade_rep=True):
42-
Mv.__init__(self, base, mvtype, f=fct, ga=MV.GA)
41+
def __init__(self, base, mvtype, fct=None, blade_rep=True):
42+
kwargs = {}
43+
if fct is not None:
44+
kwargs['f'] = fct # only forward this argument if we received it
45+
Mv.__init__(self, base, mvtype, ga=MV.GA, **kwargs)
4346

4447
def Fmt(self, fmt=1, title=None):
4548
print(Mv.Fmt(self, fmt=fmt, title=title))

galgebra/mv.py

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from . import metric
2020
from . import utils
2121
from .printer import ZERO_STR
22+
from .utils import _KwargParser
2223

2324
ONE = S(1)
2425
ZERO = S(0)
@@ -61,10 +62,6 @@ class Mv(object):
6162
fmt = 1
6263
latex_flg = False
6364
restore = False
64-
init_slots = {'f': (False, 'True if function of coordinates'),
65-
'ga': (None, 'Geometric algebra to be used with multivectors'),
66-
'coords': (None, 'Coordinates to be used with multivector function'),
67-
'recp': (None, 'Normalization for reciprocal vector')}
6865
dual_mode_lst = ['+I','I+','+Iinv','Iinv+','-I','I-','-Iinv','Iinv-']
6966

7067
@staticmethod
@@ -178,9 +175,11 @@ def add_superscript(root, s):
178175
return root
179176
return '{}__{}'.format(root, s)
180177
grade = __grade
178+
kw = _KwargParser('_make_grade', kwargs)
181179
if utils.isstr(__name_or_coeffs):
182180
name = __name_or_coeffs
183-
f = kwargs['f']
181+
f = kw.pop('f', False)
182+
kw.reject_remaining()
184183
if isinstance(f, bool):
185184
if f: # Is a multivector function of all coordinates
186185
return sum([Function(add_superscript(name, super_script), real=True)(*ga.coords) * base
@@ -193,6 +192,7 @@ def add_superscript(root, s):
193192
for (super_script, base) in zip(ga.blade_super_scripts[grade], ga.blades[grade])])
194193
elif isinstance(__name_or_coeffs, (list, tuple)):
195194
coeffs = __name_or_coeffs
195+
kw.reject_remaining()
196196
if len(coeffs) <= len(ga.blades[grade]):
197197
return sum([coef * base
198198
for (coef, base) in zip(coeffs, ga.blades[grade][:len(coeffs)])])
@@ -259,14 +259,74 @@ def _make_odd(ga, __name_or_coeffs, **kwargs):
259259
_make_even = _make_spinor
260260

261261
def __init__(self, *args, **kwargs):
262+
"""
263+
__init__(self, *args, ga, recp=None, **kwargs)
264+
265+
Note this constructor is overloaded, based on the type and number of
266+
positional arguments:
267+
268+
.. class:: Mv(*, ga, recp=None)
269+
270+
Create a zero multivector
271+
.. class:: Mv(expr, /, *, ga, recp=None)
272+
273+
Create a multivector from an existing vector or sympy expression
274+
.. class:: Mv(coeffs, grade, /, ga, recp=None)
275+
276+
Create a multivector constant with a given grade
277+
.. class:: Mv(name, category, /, *cat_args, ga, recp=None, f=False)
278+
279+
Create a multivector constant with a given category
280+
.. class:: Mv(name, grade, /, ga, recp=None, f=False)
262281
263-
if 'ga' not in kwargs:
264-
raise ValueError("Geometric algebra key inplut 'ga' required")
282+
Create a multivector variable or function of a given grade
283+
.. class:: Mv(coeffs, category, /, *cat_args, ga, recp=None)
265284
266-
kwargs = metric.test_init_slots(Mv.init_slots, **kwargs)
285+
Create a multivector variable or function of a given category
267286
268-
self.Ga = kwargs.pop('ga')
269-
self.recp = kwargs.pop('recp') # Normalization for reciprocal vectors
287+
288+
``*`` and ``/`` in the signatures above are python
289+
3.8 syntax, and respectively indicate the boundaries between
290+
positional-only, normal, and keyword-only arguments.
291+
292+
Parameters
293+
----------
294+
ga : ~galgebra.ga.Ga
295+
Geometric algebra to be used with multivectors
296+
recp : object, optional
297+
Normalization for reciprocal vector. Unused.
298+
name : str
299+
Name of this multivector, if it is a variable or function
300+
coeffs : sequence
301+
Sequence of coefficients for the given category.
302+
This is only meaningful
303+
category : str
304+
One of:
305+
306+
* ``"grade"`` - this takes an additional argument, the grade to
307+
create, in ``cat_args``
308+
* ``"scalar"``
309+
* ``"vector"``
310+
* ``"bivector"`` / ``"grade2"``
311+
* ``"pseudo"``
312+
* ``"mv"``
313+
* ``"even"`` / ``"spinor"``
314+
* ``"odd"``
315+
316+
f : bool, tuple
317+
True if function of coordinates, or a tuple of those coordinates.
318+
Only valid if a name is passed
319+
320+
coords :
321+
This argument is always accepted but ignored.
322+
323+
It is incorrectly described internally as the coordinates to be
324+
used with multivector functions.
325+
"""
326+
kw = _KwargParser('__init__', kwargs)
327+
self.Ga = kw.pop('ga')
328+
self.recp = kw.pop('recp', None) # not used
329+
kw.pop('coords', None) # ignored
270330

271331
self.char_Mv = False
272332
self.i_grade = None # if pure grade mv, grade value
@@ -280,6 +340,7 @@ def __init__(self, *args, **kwargs):
280340
if len(args) == 0: # default constructor 0
281341
self.obj = S(0)
282342
self.i_grade = 0
343+
kw.reject_remaining()
283344
elif len(args) == 1 and not utils.isstr(args[0]): # copy constructor
284345
x = args[0]
285346
if isinstance(x, Mv):
@@ -293,6 +354,7 @@ def __init__(self, *args, **kwargs):
293354
self.obj = S(x)
294355
self.is_blade_rep = True
295356
self.characterise_Mv()
357+
kw.reject_remaining()
296358
else:
297359
if utils.isstr(args[1]):
298360
make_args = list(args)

galgebra/utils.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,45 @@ def flatten(x):
3232

3333
def isstr(s):
3434
return isinstance(s, string_types)
35+
36+
37+
class _KwargParser:
38+
"""
39+
Helper function to emulate Python 3 keyword-only arguments.
40+
41+
Use as::
42+
43+
def func(x1, **kwargs):
44+
kw = KwargParser('func', kwargs)
45+
a = kw.pop('a')
46+
b = kw.pop('b', 2)
47+
kw.reject_remaining()
48+
...
49+
50+
To emulate the Python 3 syntax::
51+
52+
def func(x1, *, a, b=2):
53+
...
54+
"""
55+
def __init__(self, func_name, kwargs):
56+
self._func_name = func_name
57+
self._kwargs = kwargs
58+
59+
def pop(self, arg_name, *default):
60+
try:
61+
return self._kwargs.pop(arg_name, *default)
62+
except KeyError:
63+
pass
64+
raise TypeError(
65+
'{}() missing required keyword-only argument {!r}'
66+
.format(self._func_name, arg_name)
67+
)
68+
69+
def reject_remaining(self):
70+
if self._kwargs:
71+
# match the error message to what Python 3 produces
72+
bad_arg = next(iter(self._kwargs))
73+
raise TypeError(
74+
'{}() got an unexpected keyword argument {!r}'
75+
.format(self._func_name, bad_arg)
76+
)

test/test_mv.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,15 @@ def check(x, expected_grades):
108108
check(ga.mv('A', 'odd'), [1, 3])
109109
check(ga.mv('A', 'mv'), [0, 1, 2, 3])
110110

111+
# value construction
112+
check(ga.mv([1, 2, 3], 'vector'), [1])
113+
111114
# illegal arguments
112115
with self.assertRaises(TypeError):
113116
ga.mv('A', 'vector', "too many arguments")
114117
with self.assertRaises(TypeError):
115118
ga.mv('A', 'grade') # too few arguments
119+
with self.assertRaises(TypeError):
120+
ga.mv('A', 'grade', not_an_argument=True) # invalid kwarg
121+
with self.assertRaises(TypeError):
122+
ga.mv([1, 2, 3], 'vector', f=True) # can't pass f with coefficients

0 commit comments

Comments
 (0)