From ccfb13a15c63af854d882db82088201bb55e3e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Ne=C4=8Dada?= Date: Wed, 1 Jun 2022 16:07:16 +0300 Subject: [PATCH] argproc.py WIP constant t-matrix, mutual exclusivity checks --- qpms/argproc.py | 128 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 99 insertions(+), 29 deletions(-) diff --git a/qpms/argproc.py b/qpms/argproc.py index 533470f..aeddda5 100644 --- a/qpms/argproc.py +++ b/qpms/argproc.py @@ -6,6 +6,7 @@ import argparse import sys import warnings import ast +from collections import namedtuple def flatten(S): if S == []: @@ -175,6 +176,33 @@ def sint(string): else: raise exc return res +def _resolve_exclusive_groups(namespace, poslabel, *groups): + """ + namespace: arguments namespace whose elements + are dicts that possibly + each element of groups shall be a sequence of strings, + each of the strings should correspond + if mutual exclusivity is respected, returns index + of the "selected" group. + """ + selected = None + example = None + for i, group in enumerate(groups): + for name in group: + ledict = getattr(namespace, name, None) + if ledict is not None: # TODO Maybe it shuld always be not none? + res = ledict.get(poslabel, None) + if res is not None: + if selected is None: + selected = i + example = name + elif selected != i: + raise ArgumentProcessingError("Mutually exclusive parameters (%s and %s) specified for label %s." % + (name, example, poslabel)) + if selected is None and poslabel is not None: # try default params + return _resolve_exclusive_groups(namespace, None, *groups) + return selected + def material_spec(string): """Tries to parse a string as a material specification, i.e. a real or complex number or one of the string in built-in Lorentz-Drude models. @@ -193,14 +221,21 @@ def material_spec(string): raise argparse.ArgumentTypeError("Material specification must be a supported material name %s, or a number" % (str(lorentz_drude.keys()),)) from ve return lemat -def string2hashable_complex_matrix(string): +class hashable_complex_matrix(tuple): """ Converts string to a hashable equivalent of two-dimensional numpy.ndarray(..., dtype=complex) (which by itself is not hashable) """ - matrix = np.ndarray(ast.literal_eval(string), dtype=complex) - # TODO here could be some dimensionality checks etc. - return tuple(tuple(row) for row in matrix) + def __new__(self, matrix): + if isinstance(matrix, str): + matrix = ast.literal_eval(string) + matrix = np.ndarray(matrix, dtype=complex, copy=False) + return super().__new__(hashable_complex_matrix, (tuple(row) for row in matrix)) + +# auxiliary structures for t-matrix generator specifications +constant_tmatrix_spec = namedtuple("constant_tmatrix_spec", ("bspec", "matrix")) +cyl_sph_dimensions = namedtuple("cyl_sph_dimensions", ("radius", "height", "lMax_extend")) +tmgen_spec = namedtuple("tmgen_spec", ("bgspec", "fgspec", "dims")) def string2bspec(string): """ @@ -273,10 +308,10 @@ class ArgParser: action=make_dict_action(argtype=string2basespec, postaction='store', first_is_key=True), help='Manual specification of VSWF set codes (format as a python list of integers); see docs on qpms_uvswfi_t for valid codes or simply use ++lMax instead. Overrides ++lMax and --lMax.') mpgrp.add_argmuent("-T", "--constant-tmatrix", nargs=1, default={}, - action=make_dict_action(argtype=string2hashable_complex_matrix, postaction='store', first_is_key=False), + action=make_dict_action(argtype=hashable_complex_matrix, postaction='store', first_is_key=False), help='constant T-matrix (elements must correspond to --vswf-set)') mpgrp.add_argmuent("+T", "++constant-tmatrix", nargs=2, default={}, - action=make_dict_action(argtype=string2hashable_complex_matrix, postaction='store', first_is_key=True), + action=make_dict_action(argtype=hashable_complex_matrix, postaction='store', first_is_key=True), help='constant T-matrix (elements must correspond to ++vswf-set)') atomic_arguments = { @@ -355,14 +390,17 @@ class ArgParser: if tmgspec in self._tmg_register.keys(): return self._tmg_register[tmgspec] else: - from .cytmatrices import TMatrixGenerator - bgspec, fgspec, (radius, height, lMax_extend) = tmgspec - bg = self._add_emg(bgspec) - fg = self._add_emg(fgspec) - if height is None: - tmgen = TMatrixGenerator.sphere(bg, fg, radius) + from .cytmatrices import TMatrixGenerator, CTMatrix + if isinstance(tmgspec, constant_tmatrix_spec): + tmgen = TMatrixGenerator(CTMatrix(tmgspec.bspec, tmgspec.matrix)) else: - tmgen = TMatrixGenerator.cylinder(bg, fg, radius, height, lMax_extend=lMax_extend) + bgspec, fgspec, (radius, height, lMax_extend) = tmgspec + bg = self._add_emg(bgspec) + fg = self._add_emg(fgspec) + if height is None: + tmgen = TMatrixGenerator.sphere(bg, fg, radius) + else: + tmgen = TMatrixGenerator.cylinder(bg, fg, radius, height, lMax_extend=lMax_extend) self._tmg_register[tmgspec] = tmgen return tmgen @@ -446,7 +484,8 @@ class ArgParser: from .cymaterials import EpsMuGenerator, lorentz_drude from .cytmatrices import TMatrixGenerator self.foreground_emg = self._add_emg(a.material) - self.tmgen = self._add_tmg((a.background, a.material, (a.radius, a.height, a.lMax_extend))) + self.tmgen = self._add_tmg(tmgen_spec(a.background, a.material, + cyl_sph_dimensions(a.radius, a.height, a.lMax_extend))) self.bspec = self._add_bspec(a.lMax) def _eval_single_omega(self): # feature: single_omega @@ -525,34 +564,65 @@ class ArgParser: self.positions = {} pos13, pos23, pos33 = False, False, False # used to if len(a.position.keys()) == 0: - warnings.warn("No particle position (-p or +p) specified, assuming single particle in the origin / single particle per unit cell!") + warnings.warn("No particle position (-p or +p) specified, assuming single particle in the " + "origin / single particle per unit cell!") a.position[None] = [(0.,0.,0.)] for poslabel in a.position.keys(): - # TODO HERE GOES THE CODE TRYING TO LOAD CONSTANT T-MATRIX + _resolve_exclusive_groups(a, poslabel, ("lMax",), ("vswf_set",)) try: lMax_or_bspec = ( a.vswf_set.get(poslabel, False) or a.lMax.get(poslabel, False) or a.vswf_set.get(None, False) or a.lMax[None] ) - radius = a.radius.get(poslabel, False) or a.radius[None] - # Height is "inherited" only together with radius - height = a.height.get(poslabel, None) if poslabel in a.radius.keys() else a.height.get(None, None) - if hasattr(a, 'lMax_extend'): - lMax_extend = a.lMax_extend.get(poslabel, False) or a.lMax_extend.get(None, False) or None - else: - lMax_extend = None - material = a.material.get(poslabel, False) or a.material[None] + bspec = self._add_bspec(lMax_or_bspec) + self.bspecs[poslabel] = bspec except (TypeError, KeyError) as exc: if poslabel is None: - raise ArgumentProcessingError("Unlabeled particles' positions (-p) specified, but some default particle properties are missing (--lMax, --radius, and --material have to be specified)") from exc + raise ArgumentProcessingError("Unlabeled particles' positions (-p) specified, " + "but neither --lMax nor --vswf-set is specified") from exc else: raise ArgumentProcessingError(("Incomplete specification of '%s'-labeled particles: you must" - "provide at least ++lMax, ++radius, ++material arguments with the label, or the fallback arguments" - "--lMax, --radius, --material.")%(str(poslabel),)) from exc - tmspec = (a.background, material, (radius, height, lMax_extend)) + "provide either ++lMax or ++vswf-set argument with the label, " + "or one of the fallback arguments --lMax or --vswf-set." + )%(str(poslabel),)) from exc + + agi = _resolve_exclusive_groups(a, poslabel, + ("constant_tmatrix",), + ("radius", "height", "material", "lMax_extend"), + ) + if agi == 0: # constant T-matrix + tmspec = constant_tmatrix_spec(bspec, + hashable_complex_matrix(a.constant_tmatrix.get(poslabel, None) + or a.constant_tmatrix[None])) + elif agi == 1: # + try: + radius = a.radius.get(poslabel, False) or a.radius[None] + # Height is "inherited" only together with radius + height = a.height.get(poslabel, None) if poslabel in a.radius.keys() else a.height.get(None, None) + if hasattr(a, 'lMax_extend'): + lMax_extend = a.lMax_extend.get(poslabel, False) or a.lMax_extend.get(None, False) or None + else: + lMax_extend = None + material = a.material.get(poslabel, False) or a.material[None] + except (TypeError, KeyError) as exc: + if poslabel is None: + raise ArgumentProcessingError("Unlabeled particles' positions (-p) specified, " + "but some default particle properties are missing " + "(either --lMax or --vswf-set, and --constant-tmatrix or " + "--radius and --material have to be specified)") from exc + else: + raise ArgumentProcessingError(("Incomplete specification of '%s'-labeled particles: you must" + "provide at least ++lMax (or ++vswf-set), " + "++radius and ++material (or ++constant-tmatrix) arguments with the label, " + "or the fallback arguments --lMax (or --vswf-sit), --radius, --material " + "(or --constant-tmatrix)." + )%(str(poslabel),)) from exc + tmspec = tmgen_spec(a.background, material, + cyl_sph_dimensions(radius, height, lMax_extend)) + else: raise AssertionErrorw + self.tmspecs[poslabel] = tmspec self.tmgens[poslabel] = self._add_tmg(tmspec) - self.bspecs[poslabel] = self._add_bspec(lMax_or_bspec) poslist_cured = [] for pos in a.position[poslabel]: if len(pos) == 1: