From 6e6a349b96d538b7b6d1b99bc03ad400db1423da Mon Sep 17 00:00:00 2001 From: dvezinet Date: Mon, 31 Aug 2026 22:46:25 +0200 Subject: [PATCH 01/16] [#devel] Created --- beampipe/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 beampipe/__init__.py diff --git a/beampipe/__init__.py b/beampipe/__init__.py new file mode 100644 index 0000000..e69de29 From 194427059b1eb3c4ed2ec1b7b627ed93b6c9caf0 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Mon, 31 Aug 2026 22:52:47 +0200 Subject: [PATCH 02/16] [#devel] pyproject.toml and CLASSIFIERS --- CLASSIFIERS.txt | 7 ++++++ pyproject.toml | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 CLASSIFIERS.txt create mode 100644 pyproject.toml diff --git a/CLASSIFIERS.txt b/CLASSIFIERS.txt new file mode 100644 index 0000000..fc9f4fa --- /dev/null +++ b/CLASSIFIERS.txt @@ -0,0 +1,7 @@ +Development Status :: 5 - Production/Stable +Intended Audience :: Science/Research +Programming Language :: Python :: 3 +Programming Language :: Python :: 3.10 +Programming Language :: Python :: 3.11 +Programming Language :: Python :: 3.12 +Natural Language :: English diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..63b7898 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,64 @@ +[build-system] +requires = ["setuptools", "setuptools_scm"] +build-backend = "setuptools.build_meta" + + +#[tool.setuptools.packages.find] +#where = ["datastock"] +#include = ["datastock*"] +#namespaces = false + +[tool.setuptools] +packages = ["beampipe"] # , "beampipe.tests"] + + +[tool.setuptools_scm] +version_file = "beampipe/_version.py" + +[tool.setuptools.package-data] +mypkg = ["*.txt", "*.npz"] + + +[tool.setuptools.dynamic] +classifiers = {file = ["CLASSIFIERS.txt"]} + + +[project] +name = "beampipe" +readme = "README.md" +license = {text = "MIT"} +dynamic = ["version", "classifiers"] +description = "Generic handler for 3d pipes" +authors = [ + {name = "Didier VEZINET", email = "didier.vezinet@gmail.com"}, +] +maintainers = [ + {name = "Didier VEZINET", email = "didier.vezinet@gmail.com"}, +] +keywords = [ + "Collection", "modelling", +] +requires-python = ">=3.10" +dependencies = [ + 'datastock>=0.0.56', +] + + +[project.urls] +Homepage = "https://github.com/ToFuProject/beampipe" +Issues = "https://github.com/ToFuProject/beampipe/issues" + + +[dependency-groups] +dev = [ + "pytest", +] + + +[project.optional-dependencies] +linting = [ + 'ruff' +] +formatting = [ + 'ruff' +] From 6da2684f5ecdd6d876982ca413719886fab3b10c Mon Sep 17 00:00:00 2001 From: dvezinet Date: Mon, 31 Aug 2026 22:59:41 +0200 Subject: [PATCH 03/16] [#devel] Initialize class00 --- beampipe/_class00_CSYS1D.py | 63 +++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 beampipe/_class00_CSYS1D.py diff --git a/beampipe/_class00_CSYS1D.py b/beampipe/_class00_CSYS1D.py new file mode 100644 index 0000000..b1cad69 --- /dev/null +++ b/beampipe/_class00_CSYS1D.py @@ -0,0 +1,63 @@ +import copy + + +import numpy as np +import datastock as ds +import ds.Collection as Previous + + + +# from . import _class00_check as _check +from . import _class00_check as _check + + +__all__ = ['SYS1D'] + + +############################################# +############################################# +# DEFAULT VALUES +############################################# + + +_QUANT_NE = 'ne' +_QUANT_TE = 'Te' +_UNITS_LAMBDA0 = 'm' + + +############################################# +############################################# +# Spectral Lines +############################################# + + +class CSYS1D(Previous): + + _ddef = copy.deepcopy(Previous._ddef) + _which_csys1d = 'csys1d' + + # ------------------- + # add csys1d + # ------------------- + + def add_csys1d(self, key=None, csys_cent=None, csys_vect=None): + """ Add a csys1d + """ + _check.add_csys1d( + coll=self, + key=key, + ) + + + + + + + + + + + + + + From 4fe85ba66726e2189ed1067089544b90a497cc3f Mon Sep 17 00:00:00 2001 From: dvezinet Date: Tue, 1 Sep 2026 02:25:26 +0200 Subject: [PATCH 04/16] [#devel] progress in _class0_check.py --- beampipe/_class00_CSYS.py | 84 +++++++++++++++++++++++++++++++++ beampipe/_class00_CSYS1D.py | 63 ------------------------- beampipe/_class00_check.py | 94 +++++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 63 deletions(-) create mode 100644 beampipe/_class00_CSYS.py delete mode 100644 beampipe/_class00_CSYS1D.py create mode 100644 beampipe/_class00_check.py diff --git a/beampipe/_class00_CSYS.py b/beampipe/_class00_CSYS.py new file mode 100644 index 0000000..c20d337 --- /dev/null +++ b/beampipe/_class00_CSYS.py @@ -0,0 +1,84 @@ +import copy + + +import datastock as ds +import ds.Collection as Previous + + +# from . import _class00_check as _check +from . import _class00_check as _check + + +__all__ = ['CSYS'] + + +############################################# +############################################# +# DEFAULT VALUES +############################################# + + +_WHICH_CSYS = 'csys' + + +############################################# +############################################# +# Spectral Lines +############################################# + + +class CSYS(Previous): + + _ddef = copy.deepcopy(Previous._ddef) + _which_csys1d = _WHICH_CSYS + + # ------------------- + # add csys + # ------------------- + + def add_csys( + self, + key=None, + # cent + cent=None, + # vect + e0=None, + e1=None, + e2=None, + # vecto options + norm=None, + direct=None, + # ref csys + kcsys0=None, + ): + """ Add a csys + + Can be 1d, 2d or 3d + cent must be an iterable with accordingly 1, 2 or 3 coordinates + 1 2 pr 3 base vectors must be provided accordingly + if norm = True => they will be normalized + if direct = True, they should form a direct base (for 2d and 3d only) + + Coordinates are given with respect to a ref csys kcsys0 + + """ + + # ------------ + # check inputs + # ------------ + + dref, ddata, dobj = _check.main(coll=self, **locals()) + + # ------------ + # Populate + # ------------ + + return + + # ------------------- + # remove csys + # ------------------- + + # ------------------- + # convert csys + # ------------------- diff --git a/beampipe/_class00_CSYS1D.py b/beampipe/_class00_CSYS1D.py deleted file mode 100644 index b1cad69..0000000 --- a/beampipe/_class00_CSYS1D.py +++ /dev/null @@ -1,63 +0,0 @@ -import copy - - -import numpy as np -import datastock as ds -import ds.Collection as Previous - - - -# from . import _class00_check as _check -from . import _class00_check as _check - - -__all__ = ['SYS1D'] - - -############################################# -############################################# -# DEFAULT VALUES -############################################# - - -_QUANT_NE = 'ne' -_QUANT_TE = 'Te' -_UNITS_LAMBDA0 = 'm' - - -############################################# -############################################# -# Spectral Lines -############################################# - - -class CSYS1D(Previous): - - _ddef = copy.deepcopy(Previous._ddef) - _which_csys1d = 'csys1d' - - # ------------------- - # add csys1d - # ------------------- - - def add_csys1d(self, key=None, csys_cent=None, csys_vect=None): - """ Add a csys1d - """ - _check.add_csys1d( - coll=self, - key=key, - ) - - - - - - - - - - - - - - diff --git a/beampipe/_class00_check.py b/beampipe/_class00_check.py new file mode 100644 index 0000000..52a7ffd --- /dev/null +++ b/beampipe/_class00_check.py @@ -0,0 +1,94 @@ +import numpy as np +import datastock as ds + + +from . import _utils_csys + + +############################################# +############################################# +# DEFAULT VALUES +############################################# + + +############################################# +############################################# +# main +############################################# + + +def main( + coll=None, + key=None, + # cent + cent=None, + # vect + e0=None, + e1=None, + e2=None, + # vecto options + norm=None, + direct=None, + # ref csys + kcsys0=None, + # unused + **kwdargs, +): + + # --------------- + # check inputs + # --------------- + + kwd = _check(**locals()) + + # --------------- + # dref + # --------------- + + # --------------- + # ddata + # --------------- + + # --------------- + # dobj + # --------------- + + return + + +############################################# +############################################# +# check +############################################# + + +def _check( + **kwdargs, +): + + # ------------ + # key + # ------------ + + wcsys = coll._which_csys + lout = list(coll.dobj.get(wcsys, {}).keys()) + key = ds._generic_check._check_var( + key, 'key', + types=str, + excluded=lout, + ) + + # ------------ + # kcsys0 + # ------------ + + # ------------ + # clean + # ------------ + + lok = ['key', 'cent', 'e0', 'e1', 'e2', 'norm', 'direct', 'kcsys0'] + lout = [kk for kk in kwdargs.keys() if kk not in lok] + for kk in lout: + del kwdargs[kk] + + return {} From 49aed97034155a4ce10575bd3a8c9a8f6f03854f Mon Sep 17 00:00:00 2001 From: dvezinet Date: Tue, 1 Sep 2026 16:56:03 +0000 Subject: [PATCH 05/16] [#devel] update --- beampipe/_class00_check.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/beampipe/_class00_check.py b/beampipe/_class00_check.py index 52a7ffd..5218371 100644 --- a/beampipe/_class00_check.py +++ b/beampipe/_class00_check.py @@ -70,18 +70,21 @@ def _check( # key # ------------ + coll = kwdargs['coll'] wcsys = coll._which_csys - lout = list(coll.dobj.get(wcsys, {}).keys()) - key = ds._generic_check._check_var( - key, 'key', - types=str, - excluded=lout, + kwdargs['key'] = ds._generic_check._obj_key( + d0=coll.get(wcsys, {}), + short='csys', + key=kwdargs['key'], + ndigits=None, ) # ------------ # kcsys0 # ------------ + + # ------------ # clean # ------------ From c538a3401eefa835c9feac3fe10aa20413452dc8 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Tue, 1 Sep 2026 23:34:14 +0200 Subject: [PATCH 06/16] [#devel] Almost operation add_csys() --- beampipe/__init__.py | 2 + beampipe/_class00_CSYS.py | 33 ++++- beampipe/_class00_check.py | 258 +++++++++++++++++++++++++++++++++++-- beampipe/_class00_show.py | 103 +++++++++++++++ 4 files changed, 379 insertions(+), 17 deletions(-) create mode 100644 beampipe/_class00_show.py diff --git a/beampipe/__init__.py b/beampipe/__init__.py index e69de29..d6e1358 100644 --- a/beampipe/__init__.py +++ b/beampipe/__init__.py @@ -0,0 +1,2 @@ +from ._class00_CSYS import CSYS as Collection + diff --git a/beampipe/_class00_CSYS.py b/beampipe/_class00_CSYS.py index c20d337..95b5b75 100644 --- a/beampipe/_class00_CSYS.py +++ b/beampipe/_class00_CSYS.py @@ -1,12 +1,12 @@ import copy -import datastock as ds -import ds.Collection as Previous +from datastock import DataStock as Previous # from . import _class00_check as _check from . import _class00_check as _check +from . import _class00_show as _show __all__ = ['CSYS'] @@ -30,7 +30,7 @@ class CSYS(Previous): _ddef = copy.deepcopy(Previous._ddef) - _which_csys1d = _WHICH_CSYS + _which_csys = _WHICH_CSYS # ------------------- # add csys @@ -40,7 +40,9 @@ def add_csys( self, key=None, # cent - cent=None, + origin=None, + # ctype + ctype=None, # vect e0=None, e1=None, @@ -54,8 +56,9 @@ def add_csys( """ Add a csys Can be 1d, 2d or 3d - cent must be an iterable with accordingly 1, 2 or 3 coordinates - 1 2 pr 3 base vectors must be provided accordingly + origin must be an iterable with accordingly 1, 2 or 3 coordinates + ctype is the + 1 2 or 3 base vectors must be provided accordingly if norm = True => they will be normalized if direct = True, they should form a direct base (for 2d and 3d only) @@ -73,8 +76,26 @@ def add_csys( # Populate # ------------ + self.update(dref=dref, ddata=ddata, dobj=dobj) + return + # ------------------- + # show + # ------------------- + + def _get_show_obj(self, which=None): + if which == self._which_csys: + return _show._show + else: + return super()._get_show_obj(which) + + def _get_show_details(self, which=None): + if which == self._which_csys: + return _show._show_details + else: + return super()._get_show_details(which) + # ------------------- # remove csys # ------------------- diff --git a/beampipe/_class00_check.py b/beampipe/_class00_check.py index 5218371..ef227fd 100644 --- a/beampipe/_class00_check.py +++ b/beampipe/_class00_check.py @@ -1,8 +1,8 @@ -import numpy as np -import datastock as ds +import copy -from . import _utils_csys +import numpy as np +import datastock as ds ############################################# @@ -10,6 +10,12 @@ # DEFAULT VALUES ############################################# +_LOK_ND = { + 'cart': ['1d', '2d', '3d'], + 'cyl': ['2d', '3d'], + 'sph': ['2d', '3d'], +} + ############################################# ############################################# @@ -21,12 +27,17 @@ def main( coll=None, key=None, # cent - cent=None, + origin=None, + # ctype + ctype=None, + # nd + nd=None, # vect e0=None, e1=None, e2=None, # vecto options + ortho=None, norm=None, direct=None, # ref csys @@ -42,18 +53,23 @@ def main( kwd = _check(**locals()) # --------------- - # dref + # 1d vs 2d vs 3d # --------------- - # --------------- - # ddata - # --------------- + _unit_vectors(kwd) # --------------- # dobj # --------------- - return + wcsys = coll._which_csys + dobj = { + wcsys: { + key: copy.deepcopy(kwd), + } + } + + return None, None, dobj ############################################# @@ -65,6 +81,8 @@ def main( def _check( **kwdargs, ): + """ check inputs for main() + """ # ------------ # key @@ -83,15 +101,233 @@ def _check( # kcsys0 # ------------ + lref = [kk for kk in lok if coll.dobj[wcsys][kk][''] == kk] + lok = list(coll.get(wcsys, {}).keys()) + [key] + kwdargs['kcsys0'] = ds._generic_check._check_var( + kwdargs['kcsys0'], 'kcsys0', + types=str, + allowed=lok, + default=lref[0], + ) + + # ------------ + # ctype + # ------------ + + lok = sorted(_LOK_ND.keys()) + kwdargs['ctype'] = ds._generic_check._check_var( + kwdargs['ctype'], 'ctype', + types=str, + allowed=lok, + default=lok[0], + ) + nd_ok = _LOK_ND[kwdargs['ctype']] + + # ------------ + # nd + # ------------ + + dv = { + kk: kwdargs[kk] for k in ['e0', 'e1', 'e2'] + if kwdargs[kk] is not None + } + nd_min = len(dv) + lok = [f"{ii}d" for ii in range(nd_min, 4)] + lok = [kk for kk in lok if kk in nd_ok] + kwdargs['nd'] = ds._generic_check._check_var( + kwdargs['nd'], 'nd', + types=str, + allowed=lok, + ) + size = int(kwdargs['nd'][0]) + + # vs vectors + if len(dv) > 0: + if size - len(dv) > 1: + msg = ( + f"For a csys of nd = '{kwdargs['nd']}' provide either:\n" + "\t- No unit vectors (all default)\n" + "\t- All or all but one (derived) unit vectors\n" + ) + raise Exception(msg) + + # ------------ + # origin - provided => finite array of proper size + # ------------ + + oo = np.atleast_1d(kwdargs['origin']).ravel().astype(float) + + if np.any(~np.isfinite(oo)) or oo.size != size: + msg = ( + f"Arg 'origin' must be:\n" + "\t- a flat np.ndarray of finite values with size = {size}\n" + "Provided:\n\t{kwdargs['origin']}\n" + ) + raise Exception(msg) + kwdargs['origin'] = oo + + # ------------ + # unit vectors - provided => finite array of proper size + # ------------ + + for kk, vv in dv.items(): + vv = np.atleast_1d(vv).ravel().astype(float) + + if np.any(~np.isfinite(vv)) or vv.size != size: + msg = ( + f"Arg '{kk}' must be:\n" + "\t- a flat np.ndarray of finite values with size = {size}\n" + "Provided:\n\t{kwdargs[kk]}\n" + ) + raise Exception(msg) + kwdargs[kk] = vv + + # ------------ + # bool + # ------------ + for kk in ['ortho', 'norm', 'direct']: + kwdargs[kk] = ds._generic_check._check_var( + kwdargs[kk], kk, + types=bool, + default=True, + ) # ------------ # clean # ------------ - lok = ['key', 'cent', 'e0', 'e1', 'e2', 'norm', 'direct', 'kcsys0'] + lok = [ + 'key', 'nd', 'ctype', + 'origin,''e0', 'e1', 'e2', + 'ortho', 'norm', 'direct', 'kcsys0', + ] lout = [kk for kk in kwdargs.keys() if kk not in lok] for kk in lout: del kwdargs[kk] - return {} + return kwdargs + + +############################################# +############################################# +# Unit vectors +############################################# + + +def _unit_vectors( + **kwd, +): + + # ---------- + # all default + # ---------- + + lv = ['e0', 'e1', 'e2'] + if all([kwd[kk] is None for kk in lv]): + if kwd['nd'] == '1d': + kwd['e0'] = np.r_[0.] + elif kwd['nd'] == '2d': + kwd['e0'] = np.r_[1, 0.] + kwd['e1'] = np.r_[0, 1.] + else: + kwd['e0'] = np.r_[1, 0., 0] + kwd['e1'] = np.r_[0, 1., 0] + kwd['e2'] = np.r_[0, 0., 1] + + # ---------- + # Not all default + # ---------- + + # 2d + if kwd['nd'] == '2d': + if kwd['e0'] is None: + kwd['e0'] = np.r_[kwd['e1'][1], -kwd['e1'][0]] + elif kwd['e1']: + kwd['e1'] = np.r_[-kwd['e0'][1], kwd['e0'][0]] + + # 3d + elif kwd['nd'] == '3d': + if kwd['e0'] is None: + kwd['e0'] = np.cross(kwd['e1'], kwd['e2']) + elif kwd['e1'] is None: + kwd['e1'] = np.cross(kwd['e2'], kwd['e0']) + elif kwd['e2'] is None: + kwd['e2'] = np.cross(kwd['e0'], kwd['e1']) + + # ---------- + # basis - not colinear + # ---------- + + # nd + if kwd['nd'] != '1d': + if kwd['nd'] == '2d': + lcross = [np.cross(kwd['e0'], kwd['e1'])] + emax = np.max([kwd['e0'], kwd['e1']]) + elif kwd['nd'] == '3d': + lcross = [ + np.cross(kwd['e0'], kwd['e1']), + np.cross(kwd['e1'], kwd['e2']), + np.cross(kwd['e2'], kwd['e0']), + ] + emax = np.max([kwd['e0'], kwd['e1'], kwd['e2']]) + + # check colinearity + if np.any(np.abs(lcross) < 1e-9 * emax): + msg = ( + "Unit vectors must not be co-linear!" + ) + raise Exception(msg) + + # ---------- + # norm + # ---------- + + if kwd['norm'] is True: + for kk in lv: + kwd[kk] = kwd[kk] / np.sqrt(np.sum(kwd[kk]**2)) + + # ----------- + # ortho + # ----------- + + if kwd['ortho'] is True and kwd['nd'] != '1d': + if kwd['nd'] == '2d': + lsca = [np.sum(kwd['e0'] * kwd['e1'])] + elif kwd['nd'] == '3d': + lsca = [ + np.sum(kwd['e0'] * kwd['e1']), + np.sum(kwd['e1'] * kwd['e2']), + np.sum(kwd['e2'] * kwd['e0']), + ] + + # check perpendicularity + if np.any(np.abs(lsca) > 1e-9 * emax): + msg = ( + "Unit vectors must be perpendicular!" + ) + raise Exception(msg) + + # ---------- + # direct + # ---------- + + if kwd['direct'] is True and kwd['nd'] != '1d': + if kwd['nd'] == '2d': + vect = np.r_[-kwd['e0'][1], kwd['e0'][0]] + sca = np.sum(vect * kwd['e1']) + if sca < 0.: + msg = ( + "The vector basis must be direct!" + ) + raise Exception(msg) + elif kwd['nd'] == '3d': + vect = np.cross(kwd['e0'], kwd['e1']) + sca = np.sum(vect * kwd['e2']) + if sca < 0.: + msg = ( + "The vector basis must be direct!" + ) + raise Exception(msg) + + return diff --git a/beampipe/_class00_show.py b/beampipe/_class00_show.py new file mode 100644 index 0000000..d31a9a0 --- /dev/null +++ b/beampipe/_class00_show.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + + +############################################# +############################################# +# DEFAULTS +############################################# + + +_LORDER = [ + 'nd', 'ctype', + 'ortho', 'norm', 'direct', + 'kcsys0', +] + + +############################################# +############################################# +# Show +############################################# + + +def _show(coll=None, which=None, lcol=None, lar=None, show=None): + + # --------------------------- + # column names + # --------------------------- + + lcol.append([which] + _LORDER) + + # --------------------------- + # data + # --------------------------- + + lkey = [ + k1 for k1 in coll._dobj.get(which, {}).keys() + if show is None or k1 in show + ] + + lar0 = [] + for k0 in lkey: + + # initialize with key + arr = [k0] + + # loop + for k1 in _LORDER: + + # parameters + nn = str(coll.dobj[which][k0].get(k1)) + arr.append(nn) + + lar0.append(arr) + + lar.append(lar0) + + return lcol, lar + + +############################################# +############################################# +# Show single diag +############################################# + + +def _show_details(coll=None, key=None, lcol=None, lar=None, show=None): + + wcsys = coll._which_csys + size = int(coll.dobj[wcsys][key]['nd'][0]) + + # --------------------------- + # column names + # --------------------------- + + lcol.append([ + 'attr', 'x0', 'x1', 'x2', + ]) + + # --------------------------- + # data + # --------------------------- + + lar0 = [] + lk = ['origin', 'e0', 'e1', 'e2'] + for kk in lk: + + # initialize with key, type + arr = [kk] + + # is2d + for ii in range(3): + if ii < size: + nn = coll.dobj[wcsys][key][kk][ii] + else: + nn = '' + arr.append(nn) + + # aggregate + lar0.append(arr) + + lar.append(lar0) + + return lcol, lar From 768ed0b3d34a2fbf89a2f1a862347eb1a2ac25ed Mon Sep 17 00:00:00 2001 From: dvezinet Date: Wed, 2 Sep 2026 00:09:43 +0200 Subject: [PATCH 07/16] [#devel] add_csys() and show() and show_details() operational --- beampipe/_class00_check.py | 49 +++++++++++++++++++++++--------------- beampipe/_class00_show.py | 10 +++++--- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/beampipe/_class00_check.py b/beampipe/_class00_check.py index ef227fd..821b24a 100644 --- a/beampipe/_class00_check.py +++ b/beampipe/_class00_check.py @@ -16,6 +16,8 @@ 'sph': ['2d', '3d'], } +_ORIGIN = np.r_[0., 0., 0.] + ############################################# ############################################# @@ -91,7 +93,7 @@ def _check( coll = kwdargs['coll'] wcsys = coll._which_csys kwdargs['key'] = ds._generic_check._obj_key( - d0=coll.get(wcsys, {}), + d0=coll.dobj.get(wcsys, {}), short='csys', key=kwdargs['key'], ndigits=None, @@ -101,13 +103,13 @@ def _check( # kcsys0 # ------------ - lref = [kk for kk in lok if coll.dobj[wcsys][kk][''] == kk] - lok = list(coll.get(wcsys, {}).keys()) + [key] + lok = list(coll.dobj.get(wcsys, {}).keys()) + lref = [kk for kk in lok if coll.dobj[wcsys][kk]['kcsys0'] == kk] kwdargs['kcsys0'] = ds._generic_check._check_var( kwdargs['kcsys0'], 'kcsys0', types=str, - allowed=lok, - default=lref[0], + allowed=lok + [kwdargs['key']], + default=(lref + [kwdargs['key']])[0], ) # ------------ @@ -128,7 +130,7 @@ def _check( # ------------ dv = { - kk: kwdargs[kk] for k in ['e0', 'e1', 'e2'] + kk: kwdargs[kk] for kk in ['e0', 'e1', 'e2'] if kwdargs[kk] is not None } nd_min = len(dv) @@ -138,6 +140,7 @@ def _check( kwdargs['nd'], 'nd', types=str, allowed=lok, + default=lok[-1], ) size = int(kwdargs['nd'][0]) @@ -155,13 +158,16 @@ def _check( # origin - provided => finite array of proper size # ------------ + if kwdargs['origin'] is None: + kwdargs['origin'] = np.copy(_ORIGIN) + oo = np.atleast_1d(kwdargs['origin']).ravel().astype(float) if np.any(~np.isfinite(oo)) or oo.size != size: msg = ( - f"Arg 'origin' must be:\n" - "\t- a flat np.ndarray of finite values with size = {size}\n" - "Provided:\n\t{kwdargs['origin']}\n" + "Arg 'origin' must be:\n" + f"\t- a flat np.ndarray of finite values with size = {size}\n" + f"Provided:\n\t{kwdargs['origin']}\n" ) raise Exception(msg) kwdargs['origin'] = oo @@ -199,7 +205,7 @@ def _check( lok = [ 'key', 'nd', 'ctype', - 'origin,''e0', 'e1', 'e2', + 'origin', 'e0', 'e1', 'e2', 'ortho', 'norm', 'direct', 'kcsys0', ] lout = [kk for kk in kwdargs.keys() if kk not in lok] @@ -216,7 +222,7 @@ def _check( def _unit_vectors( - **kwd, + kwd, ): # ---------- @@ -262,20 +268,25 @@ def _unit_vectors( # nd if kwd['nd'] != '1d': if kwd['nd'] == '2d': - lcross = [np.cross(kwd['e0'], kwd['e1'])] + dcross = {'e0 x e1': np.cross(kwd['e0'], kwd['e1'])} emax = np.max([kwd['e0'], kwd['e1']]) elif kwd['nd'] == '3d': - lcross = [ - np.cross(kwd['e0'], kwd['e1']), - np.cross(kwd['e1'], kwd['e2']), - np.cross(kwd['e2'], kwd['e0']), - ] + dcross = { + 'e0 x e1': np.linalg.norm(np.cross(kwd['e0'], kwd['e1'])), + 'e1 x e2': np.linalg.norm(np.cross(kwd['e1'], kwd['e2'])), + 'e2 x e0': np.linalg.norm(np.cross(kwd['e2'], kwd['e0'])), + } emax = np.max([kwd['e0'], kwd['e1'], kwd['e2']]) # check colinearity - if np.any(np.abs(lcross) < 1e-9 * emax): + dfail = { + kk: vv for kk, vv in dcross.items() if np.abs(vv) < 1e-9 * emax + } + if len(dfail) > 0: + lstr = [f"\t- {kk} = vv" for kk, vv in dfail.items()] msg = ( - "Unit vectors must not be co-linear!" + "Unit vectors must not be co-linear!\n" + + "\n".join(lstr) ) raise Exception(msg) diff --git a/beampipe/_class00_show.py b/beampipe/_class00_show.py index d31a9a0..418100f 100644 --- a/beampipe/_class00_show.py +++ b/beampipe/_class00_show.py @@ -47,7 +47,11 @@ def _show(coll=None, which=None, lcol=None, lar=None, show=None): for k1 in _LORDER: # parameters - nn = str(coll.dobj[which][k0].get(k1)) + if k1 == 'kcsys0' and coll.dobj[which][k0][k1] == k0: + nn = '' + else: + nn = str(coll.dobj[which][k0].get(k1)) + arr.append(nn) lar0.append(arr) @@ -73,7 +77,7 @@ def _show_details(coll=None, key=None, lcol=None, lar=None, show=None): # --------------------------- lcol.append([ - 'attr', 'x0', 'x1', 'x2', + 'attr', 'x0 (kcsys0_e0)', 'x1 (kcsys0_e1)', 'x2 (kcsys0_e2)', ]) # --------------------------- @@ -90,7 +94,7 @@ def _show_details(coll=None, key=None, lcol=None, lar=None, show=None): # is2d for ii in range(3): if ii < size: - nn = coll.dobj[wcsys][key][kk][ii] + nn = f"{coll.dobj[wcsys][key][kk][ii]:4.3e}" else: nn = '' arr.append(nn) From 2f1c82d0a683f7b25cf1f90b157bf9feabb04eba Mon Sep 17 00:00:00 2001 From: dvezinet Date: Wed, 2 Sep 2026 02:07:47 +0200 Subject: [PATCH 08/16] [#devel] transform_csys_coords() almost done, needs get_csys_transform() --- beampipe/_class00_CSYS.py | 37 ++++- beampipe/_class00_transform.py | 255 +++++++++++++++++++++++++++++++++ 2 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 beampipe/_class00_transform.py diff --git a/beampipe/_class00_CSYS.py b/beampipe/_class00_CSYS.py index 95b5b75..8b2e4fc 100644 --- a/beampipe/_class00_CSYS.py +++ b/beampipe/_class00_CSYS.py @@ -7,6 +7,7 @@ # from . import _class00_check as _check from . import _class00_check as _check from . import _class00_show as _show +from . import _class00_transform as _transform __all__ = ['CSYS'] @@ -101,5 +102,39 @@ def _get_show_details(self, which=None): # ------------------- # ------------------- - # convert csys + # transform # ------------------- + + def transform_csys_coords( + self, + key_in=None, + key_out=None, + # coordinates + x0=None, + x1=None, + x2=None, + ): + """ Transform coordinates from a given csys into another + + coordinates have to be broadcastable arrays + can be provided as key to broadcastable ddata + """ + + return _transform.main(coll=self, **locals()) + + # ------------------- + # move within csys + # ------------------- + + def move_translate_by(): + return + + def move_rotate_by(): + return + + # ------------------- + # move to align with something + # ------------------- + + def move_align_to(): + return diff --git a/beampipe/_class00_transform.py b/beampipe/_class00_transform.py new file mode 100644 index 0000000..15f3424 --- /dev/null +++ b/beampipe/_class00_transform.py @@ -0,0 +1,255 @@ +import warnings + + +import numpy as np +import datastock as ds + + +# ############################################# +# ############################################# +# main +# ############################################# + + +def main( + coll=None, + key_in=None, + key_out=None, + # coordinates + x0=None, + x1=None, + x2=None, + # unused + **kwdargs, +): + + # ----------- + # check + # ----------- + + kwd = _check(**locals()) + + # ----------- + # transform + # ----------- + + return _transform( + coll=coll, + kwd=kwd, + ) + + +# ############################################# +# ############################################# +# check +# ############################################# + + +def _check(**kwd): + + # ----------- + # key_in vs key_out + # ----------- + + coll = kwd['coll'] + wcsys = coll._which_csys + lok = list(coll.dobj.get(wcsys, {}).keys()) + + # key_in + kwd['key_in'] = ds._generic_check._check_var( + kwd['key_in'], 'key_in', + types=str, + allowed=lok, + ) + + # ctype, nd + ctype = coll.dobj[wcsys][kwd['key_in']]['ctype'] + nd = coll.dobj[wcsys][kwd['key_in']]['nd'] + kcsys0 = coll.dobj[wcsys][kwd['key_in']]['kcsys0'] + + # key_out + lok = [ + kk for kk in lok + if coll.dobj[wcsys][kk]['ctype'] == ctype + and coll.dobj[wcsys][kk]['nd'] == nd + and coll.dobj[wcsys][kk]['kcsys0'] == kcsys0 + ] + kwd['key_out'] = ds._generic_check._check_var( + kwd['key_out'], 'key_out', + types=str, + allowed=lok, + ) + + # ------------- + # coordinates + # ------------- + + dfail = {} + size = int(nd[0]) + lx = ['x0', 'x1', 'x2'] + for ii in range(size): + + # None + if kwd[lx[ii]] is None: + dfail[lx[ii]] = "is None" + + # str + elif isinstance(kwd[lx[ii]], str): + if kwd[lx[ii]] not in coll.ddata.keys(): + dfail[lx[ii]] = f"not found in ddata ({kwd[lx[ii]]})" + + # array + else: + try: + kwd[lx[ii]] = np.atleast_1d(kwd[lx[ii]]) + except Exception: + dfail[lx[ii]] = ( + f"not convertible to np.ndarray ({type(kwd[lx[ii]])})" + ) + + # errors + if len(dfail) > 0: + lstr = [f"\t- {kk}: vv" for kk, vv in dfail.items()] + msg = ( + "Coordinates are not valid:\n" + + "\n".join(lstr) + ) + raise Exception(msg) + + # clean-up + for ii in range(size, 3): + kwd[lx[ii]] = None + + # ------------- + # broadcastable coordinates + # ------------- + + dshapes = { + lx[ii]: kwd[lx[ii]].shape if isinstance(kwd[lx[ii]], np.ndarray) + else coll.ddata[lx[ii]]['data'].shape + for ii in range(size) + } + + try: + _ = np.broadcast_shapes(*list(dshapes.values())) + except Exception: + lstr = [f"\t- {kk}: vv" for kk, vv in dshapes.items()] + msg = ( + "All coordinates must be broadcastable!\n" + + "\n".join(lstr) + ) + raise Exception(msg) + + # ------------- + # clean + # ------------- + + lok = ['key_in', 'key_out', 'x0', 'x1', 'x2'] + lout = [kk for kk in kwd.keys() if kk not in lok] + for kk in lout: + del kwd[kk] + + return kwd + + +# ############################################# +# ############################################# +# transform +# ############################################# + + +def _transform(coll=None, kwd=None): + + # ------------ + # basics + # ------------ + + wcsys = coll._which_csys + ctype = coll.dobj[wcsys][kwd['key_in']]['ctype'] + kcsys0 = coll.dobj[wcsys][kwd['key_in']]['kcsys0'] + nd = coll.dobj[wcsys][kwd['key_in']]['nd'] + size = int(nd[0]) + + lx = ['x0', 'x1', 'x2'] + + # ------------ + # units + # ------------ + + lunits = [ + coll.ddata[lx[ii]]['units'] for ii in range(size) + if isinstance(lx[ii], str) + ] + if len(set(lunits)) > 1: + msg = f"Units are different for each coordinates : {lunits}\n" + warnings.warn(msg) + units = None + elif len(set(lunits)) == 1: + units = lunits[0] + else: + units = None + + # ------------ + # ref + # ------------ + + lref = [ + coll.ddata[lx[ii]]['ref'] for ii in range(size) + if isinstance(lx[ii], str) + ] + # TBF + + # ------------ + # values + # ------------ + + dval = { + lx[ii]: kwd[lx[ii]] if isinstance(lx[ii], np.ndarray) + else coll.ddata[lx[ii]]['data'] + for ii in range(size) + } + + # ------------ + # dout + # ------------ + + dout = { + lx[ii]: { + 'data': None, + 'units': units, + 'ref': ref, + } + for ii in range(size) + } + + # ------------ + # easy + # ------------ + + if kwd['key_in'] == kwd['key_out']: + for ii in range(size): + dout[lx[ii]]['data'] = dval[lx[ii]] + + # ------------ + # cartesian + # ------------ + + if ctype == 'cart': + trans, rot = coll.get_csys_transform(kwd) + for ii in range(size): + dout[lx[ii]]['data'] = ( + trans[lx[ii]] + + np.sum( + [ + dval[lx[jj]] * rot[f'cos_e{jj}_e{ii}'] + for jj in range(size) + ], + axis=0, + ) + ) + + else: + msg = f"tranform for csys of ctype '{ctype}' not implemented yet!" + raise NotImplementedError(msg) + + return dout From 7901f0d4af4352052b996eca342fe164db7291c9 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Wed, 2 Sep 2026 13:28:01 +0000 Subject: [PATCH 09/16] [#devel] get_csys_transform() done --- beampipe/_class00_CSYS.py | 39 +++-- beampipe/_class00_transform.py | 166 +++++--------------- beampipe/_class00_transform_coords.py | 211 ++++++++++++++++++++++++++ 3 files changed, 273 insertions(+), 143 deletions(-) create mode 100644 beampipe/_class00_transform_coords.py diff --git a/beampipe/_class00_CSYS.py b/beampipe/_class00_CSYS.py index 8b2e4fc..b405a12 100644 --- a/beampipe/_class00_CSYS.py +++ b/beampipe/_class00_CSYS.py @@ -1,6 +1,8 @@ import copy +from typing import Annotated +import numpy as np from datastock import DataStock as Previous @@ -8,6 +10,7 @@ from . import _class00_check as _check from . import _class00_show as _show from . import _class00_transform as _transform +from . import _class00_transform_coords as _transform_coords __all__ = ['CSYS'] @@ -39,21 +42,22 @@ class CSYS(Previous): def add_csys( self, - key=None, + key: Annotated[str | None, 'key of the csys to be added'] = None, # cent - origin=None, + origin: Annotated[np.ndarray | None, "coords of the csys origin"] = None, # ctype - ctype=None, + ctype: Annotated[str | None, "type of csys (cart, ...)"] = None, # vect - e0=None, - e1=None, - e2=None, - # vecto options - norm=None, - direct=None, + e0: Annotated[np.ndarray | None, "coords of unit vector e0"] = None, + e1: Annotated[np.ndarray | None, "coords of unit vector e1"] = None, + e2: Annotated[np.ndarray | None, "coords of unit vector e2"] = None, + # vector options + ortho: Annotated[bool | None, "Are vectors orthogonal"] = None, + norm: Annotated[bool | None, "Are vectors normalized"] = None, + direct: Annotated[bool | None, "Is the vectors basis direct"] = None, # ref csys - kcsys0=None, - ): + kcsys0: Annotated[str | None, "csys in which coords are expressed"] = None, + ) -> None: """ Add a csys Can be 1d, 2d or 3d @@ -105,6 +109,17 @@ def _get_show_details(self, which=None): # transform # ------------------- + def get_csys_transform( + self, + key_in: Annotated[str | None, 'key to csys'] = None, + key_out: Annotated[str | None, 'key to csys'] = None, + ) -> dict: + """ Get the transform needed to get from one to another csys + + Returns a dict + """ + return _transform.main(coll=self, key_in=key_in, key_out=key_out) + def transform_csys_coords( self, key_in=None, @@ -120,7 +135,7 @@ def transform_csys_coords( can be provided as key to broadcastable ddata """ - return _transform.main(coll=self, **locals()) + return _transform_coords.main(coll=self, **locals()) # ------------------- # move within csys diff --git a/beampipe/_class00_transform.py b/beampipe/_class00_transform.py index 15f3424..4beda07 100644 --- a/beampipe/_class00_transform.py +++ b/beampipe/_class00_transform.py @@ -15,25 +15,19 @@ def main( coll=None, key_in=None, key_out=None, - # coordinates - x0=None, - x1=None, - x2=None, - # unused - **kwdargs, ): # ----------- # check # ----------- - kwd = _check(**locals()) + kwd = _check(coll=coll, key_in=key_in, key_out=key_out) # ----------- # transform # ----------- - return _transform( + return _get_transform( coll=coll, kwd=kwd, ) @@ -70,6 +64,7 @@ def _check(**kwd): # key_out lok = [ kk for kk in lok + if kk != kwd['key_in'] if coll.dobj[wcsys][kk]['ctype'] == ctype and coll.dobj[wcsys][kk]['nd'] == nd and coll.dobj[wcsys][kk]['kcsys0'] == kcsys0 @@ -80,75 +75,14 @@ def _check(**kwd): allowed=lok, ) - # ------------- - # coordinates - # ------------- - - dfail = {} - size = int(nd[0]) - lx = ['x0', 'x1', 'x2'] - for ii in range(size): - - # None - if kwd[lx[ii]] is None: - dfail[lx[ii]] = "is None" - - # str - elif isinstance(kwd[lx[ii]], str): - if kwd[lx[ii]] not in coll.ddata.keys(): - dfail[lx[ii]] = f"not found in ddata ({kwd[lx[ii]]})" - - # array - else: - try: - kwd[lx[ii]] = np.atleast_1d(kwd[lx[ii]]) - except Exception: - dfail[lx[ii]] = ( - f"not convertible to np.ndarray ({type(kwd[lx[ii]])})" - ) - - # errors - if len(dfail) > 0: - lstr = [f"\t- {kk}: vv" for kk, vv in dfail.items()] - msg = ( - "Coordinates are not valid:\n" - + "\n".join(lstr) - ) - raise Exception(msg) + # ----------- + # implemented ? + # ----------- - # clean-up - for ii in range(size, 3): - kwd[lx[ii]] = None - - # ------------- - # broadcastable coordinates - # ------------- - - dshapes = { - lx[ii]: kwd[lx[ii]].shape if isinstance(kwd[lx[ii]], np.ndarray) - else coll.ddata[lx[ii]]['data'].shape - for ii in range(size) - } - - try: - _ = np.broadcast_shapes(*list(dshapes.values())) - except Exception: - lstr = [f"\t- {kk}: vv" for kk, vv in dshapes.items()] - msg = ( - "All coordinates must be broadcastable!\n" - + "\n".join(lstr) - ) + if ctype != 'cart': + msg = f"csys transform not implement for ctype = '{ctype}'\n" raise Exception(msg) - # ------------- - # clean - # ------------- - - lok = ['key_in', 'key_out', 'x0', 'x1', 'x2'] - lout = [kk for kk in kwd.keys() if kk not in lok] - for kk in lout: - del kwd[kk] - return kwd @@ -158,7 +92,7 @@ def _check(**kwd): # ############################################# -def _transform(coll=None, kwd=None): +def _get_transform(coll=None, kwd=None): # ------------ # basics @@ -166,12 +100,24 @@ def _transform(coll=None, kwd=None): wcsys = coll._which_csys ctype = coll.dobj[wcsys][kwd['key_in']]['ctype'] - kcsys0 = coll.dobj[wcsys][kwd['key_in']]['kcsys0'] nd = coll.dobj[wcsys][kwd['key_in']]['nd'] size = int(nd[0]) lx = ['x0', 'x1', 'x2'] + # ------------ + # data + # ------------ + + origin_in = coll.dobj[wcsys][kwd['key_in']]['origin'] + origin_out = coll.dobj[wcsys][kwd['key_out']]['origin'] + dorigin = origin_in - origin_out + + de = {} + for ii in range(size): + de[f'e{ii}_in'] = coll.dobj[wcsys][kwd['key_in']][f'e{ii}'] + de[f'e{ii}_out'] = coll.dobj[wcsys][kwd['key_out']][f'e{ii}'] + # ------------ # units # ------------ @@ -189,67 +135,25 @@ def _transform(coll=None, kwd=None): else: units = None - # ------------ - # ref - # ------------ - - lref = [ - coll.ddata[lx[ii]]['ref'] for ii in range(size) - if isinstance(lx[ii], str) - ] - # TBF - - # ------------ - # values - # ------------ - - dval = { - lx[ii]: kwd[lx[ii]] if isinstance(lx[ii], np.ndarray) - else coll.ddata[lx[ii]]['data'] - for ii in range(size) - } - - # ------------ - # dout - # ------------ - - dout = { - lx[ii]: { - 'data': None, - 'units': units, - 'ref': ref, - } - for ii in range(size) - } - - # ------------ - # easy - # ------------ - - if kwd['key_in'] == kwd['key_out']: - for ii in range(size): - dout[lx[ii]]['data'] = dval[lx[ii]] - # ------------ # cartesian # ------------ + dout = {} if ctype == 'cart': - trans, rot = coll.get_csys_transform(kwd) for ii in range(size): - dout[lx[ii]]['data'] = ( - trans[lx[ii]] - + np.sum( - [ - dval[lx[jj]] * rot[f'cos_e{jj}_e{ii}'] - for jj in range(size) - ], - axis=0, - ) - ) - else: - msg = f"tranform for csys of ctype '{ctype}' not implemented yet!" - raise NotImplementedError(msg) + # translation + dout[f'd{lx[ii]}'] = { + 'data': np.sum(dorigin * de[f'e{ii}_out']), + 'units': units, + } + + # rotation + for jj in range(size): + dout[f"cos_e{jj}_e{ii}"] = { + 'data': np.sum(de[f'e{jj}_in'] * de[f"e{ii}_out"]), + 'units': None, + } return dout diff --git a/beampipe/_class00_transform_coords.py b/beampipe/_class00_transform_coords.py new file mode 100644 index 0000000..da1403c --- /dev/null +++ b/beampipe/_class00_transform_coords.py @@ -0,0 +1,211 @@ +import warnings + + +import numpy as np + + +# ############################################# +# ############################################# +# main +# ############################################# + + +def main( + coll=None, + key_in=None, + key_out=None, + # coordinates + x0=None, + x1=None, + x2=None, + # unused + **kwdargs, +): + + # ----------- + # get transform + # ----------- + + dtrans = coll.get_csys_transform(key_in=key_in, key_out=key_out) + + # ----------- + # check + # ----------- + + kwd = _check(**locals()) + + # ----------- + # transform + # ----------- + + return _transform( + coll=coll, + kwd=kwd, + dtrans=dtrans, + ) + + +# ############################################# +# ############################################# +# check +# ############################################# + + +def _check(**kwd): + + # ----------- + # key_in vs key_out + # ----------- + + coll = kwd['coll'] + wcsys = coll._which_csys + + # ctype, nd + nd = coll.dobj[wcsys][kwd['key_in']]['nd'] + + # ------------- + # coordinates + # ------------- + + dfail = {} + size = int(nd[0]) + lx = ['x0', 'x1', 'x2'] + for ii in range(size): + + # None + if kwd[lx[ii]] is None: + dfail[lx[ii]] = "is None" + + # str + elif isinstance(kwd[lx[ii]], str): + if kwd[lx[ii]] not in coll.ddata.keys(): + dfail[lx[ii]] = f"not found in ddata ({kwd[lx[ii]]})" + + # array + else: + try: + kwd[lx[ii]] = np.atleast_1d(kwd[lx[ii]]) + except Exception: + dfail[lx[ii]] = ( + f"not convertible to np.ndarray ({type(kwd[lx[ii]])})" + ) + + # errors + if len(dfail) > 0: + lstr = [f"\t- {kk}: vv" for kk, vv in dfail.items()] + msg = ( + "Coordinates are not valid:\n" + + "\n".join(lstr) + ) + raise Exception(msg) + + # clean-up + for ii in range(size, 3): + kwd[lx[ii]] = None + + # ------------- + # broadcastable coordinates + # ------------- + + dshapes = { + lx[ii]: kwd[lx[ii]].shape if isinstance(kwd[lx[ii]], np.ndarray) + else coll.ddata[lx[ii]]['data'].shape + for ii in range(size) + } + + try: + _ = np.broadcast_shapes(*list(dshapes.values())) + except Exception: + lstr = [f"\t- {kk}: vv" for kk, vv in dshapes.items()] + msg = ( + "All coordinates must be broadcastable!\n" + + "\n".join(lstr) + ) + raise Exception(msg) + + # ------------- + # clean + # ------------- + + lok = ['key_in', 'key_out', 'x0', 'x1', 'x2'] + lout = [kk for kk in kwd.keys() if kk not in lok] + for kk in lout: + del kwd[kk] + + return kwd + + +# ############################################# +# ############################################# +# transform +# ############################################# + + +def _transform(coll=None, kwd=None, dtrans=None): + + # ------------ + # basics + # ------------ + + wcsys = coll._which_csys + ctype = coll.dobj[wcsys][kwd['key_in']]['ctype'] + nd = coll.dobj[wcsys][kwd['key_in']]['nd'] + size = int(nd[0]) + + lx = ['x0', 'x1', 'x2'] + + # ------------ + # ref + # ------------ + + lref = [ + coll.ddata[lx[ii]]['ref'] for ii in range(size) + if isinstance(lx[ii], str) + ] + # TBF + + # ------------ + # values + # ------------ + + dval = { + lx[ii]: kwd[lx[ii]] if isinstance(lx[ii], np.ndarray) + else coll.ddata[lx[ii]]['data'] + for ii in range(size) + } + + # ------------ + # dout + # ------------ + + dout = { + lx[ii]: { + 'data': None, + 'units': dtrans[f"d{lx[ii]}"]['units'], + 'ref': ref, + } + for ii in range(size) + } + + # ------------ + # cartesian + # ------------ + + if ctype == 'cart': + for ii in range(size): + dout[lx[ii]]['data'] = ( + dtrans[f"d{lx[ii]}"]['data'] + + np.sum( + [ + dval[lx[jj]] * dtrans[f'cos_e{jj}_e{ii}']['data'] + for jj in range(size) + ], + axis=0, + ) + ) + + else: + msg = f"tranform for csys of ctype '{ctype}' not implemented yet!" + raise NotImplementedError(msg) + + return dout From 414ab2b8668726f396cde35d6368233f0c7c11c2 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Wed, 2 Sep 2026 13:36:00 +0000 Subject: [PATCH 10/16] [#devel] transform_csys_coords() operational, to be tested --- beampipe/_class00_transform_coords.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/beampipe/_class00_transform_coords.py b/beampipe/_class00_transform_coords.py index da1403c..32cee87 100644 --- a/beampipe/_class00_transform_coords.py +++ b/beampipe/_class00_transform_coords.py @@ -162,7 +162,16 @@ def _transform(coll=None, kwd=None, dtrans=None): coll.ddata[lx[ii]]['ref'] for ii in range(size) if isinstance(lx[ii], str) ] - # TBF + + if len(lref) > 0: + if len(set(lref)) > 1: + msg = "Coordinates do not share the same ref!" + warnings.warn(msg) + ref = None + else: + ref = lref[0] + else: + ref = None # ------------ # values From c741eeb7603b1b14f5362a04ba0605571d05f91e Mon Sep 17 00:00:00 2001 From: dvezinet Date: Wed, 2 Sep 2026 13:49:19 +0000 Subject: [PATCH 11/16] [#devel] Started introducing units in csys unit vectors, TBF --- beampipe/_class00_CSYS.py | 4 ++++ beampipe/_class00_check.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/beampipe/_class00_CSYS.py b/beampipe/_class00_CSYS.py index b405a12..161dc2a 100644 --- a/beampipe/_class00_CSYS.py +++ b/beampipe/_class00_CSYS.py @@ -51,6 +51,10 @@ def add_csys( e0: Annotated[np.ndarray | None, "coords of unit vector e0"] = None, e1: Annotated[np.ndarray | None, "coords of unit vector e1"] = None, e2: Annotated[np.ndarray | None, "coords of unit vector e2"] = None, + # units + units0: Annotated[str | None, "units of coords along e0"] = None, + units1: Annotated[str | None, "units of coords along e1"] = None, + units2: Annotated[str | None, "units of coords along e2"] = None, # vector options ortho: Annotated[bool | None, "Are vectors orthogonal"] = None, norm: Annotated[bool | None, "Are vectors normalized"] = None, diff --git a/beampipe/_class00_check.py b/beampipe/_class00_check.py index 821b24a..934d0b4 100644 --- a/beampipe/_class00_check.py +++ b/beampipe/_class00_check.py @@ -38,6 +38,10 @@ def main( e0=None, e1=None, e2=None, + # units + units0=None, + units1=None, + units2=None, # vecto options ortho=None, norm=None, From 468f40cc05ece7fcf307dc60d29fce44474732d1 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Wed, 9 Sep 2026 03:53:54 +0200 Subject: [PATCH 12/16] [#devel] unit vectors now have units --- beampipe/_class00_check.py | 81 +++++++++++++++++++++++++++++++++----- beampipe/_class00_show.py | 14 ++++++- 2 files changed, 85 insertions(+), 10 deletions(-) diff --git a/beampipe/_class00_check.py b/beampipe/_class00_check.py index 934d0b4..b87af56 100644 --- a/beampipe/_class00_check.py +++ b/beampipe/_class00_check.py @@ -2,6 +2,7 @@ import numpy as np +import astropy.units as asunits import datastock as ds @@ -18,6 +19,10 @@ _ORIGIN = np.r_[0., 0., 0.] +_DUNITS = { + 'cart': 'm', +} + ############################################# ############################################# @@ -68,13 +73,23 @@ def main( # dobj # --------------- - wcsys = coll._which_csys - dobj = { - wcsys: { - key: copy.deepcopy(kwd), - } + dcsys = { + kk: vv for kk, vv in kwd.items() + if not kk.startswith('e') + and not kk.startswith('units') + and kk != 'key' } + le = [kk for kk in ['e0', 'e1', 'e2'] if kwd.get(kk) is not None] + for ie, ke in enumerate(le): + dcsys[ke] = { + 'data': kwd[ke], + 'units': kwd[f'units{ie}'], + } + + wcsys = coll._which_csys + dobj = {wcsys: {kwd['key']: dcsys}} + return None, None, dobj @@ -209,7 +224,9 @@ def _check( lok = [ 'key', 'nd', 'ctype', - 'origin', 'e0', 'e1', 'e2', + 'origin', + 'e0', 'e1', 'e2', + 'units0', 'units1', 'units2', 'ortho', 'norm', 'direct', 'kcsys0', ] lout = [kk for kk in kwdargs.keys() if kk not in lok] @@ -225,9 +242,7 @@ def _check( ############################################# -def _unit_vectors( - kwd, -): +def _unit_vectors(kwd): # ---------- # all default @@ -265,6 +280,20 @@ def _unit_vectors( elif kwd['e2'] is None: kwd['e2'] = np.cross(kwd['e0'], kwd['e1']) + # ---------- + # clean extra + # ---------- + + size = int(kwd['nd'][0]) + for ii in range(size, 3): + estr = f"e{ii}" + if kwd[estr] is not None: + msg = ( + f"Arg '{estr}' provided for a '{kwd['nd']}' csys!\n" + f"Provided: {kwd[estr]}\n" + ) + raise Exception(msg) + # ---------- # basis - not colinear # ---------- @@ -345,4 +374,38 @@ def _unit_vectors( ) raise Exception(msg) + # ---------- + # units + # ---------- + + lunits = ['units0', 'units1', 'units2'] + for ii in range(size): + kwd[lunits[ii]] = ds._generic_check._check_var( + kwd[lunits[ii]], lunits[ii], + types=str, + default=_DUNITS[kwd['ctype']], + ) + try: + kwd[lunits[ii]] = asunits.Unit(kwd[lunits[ii]]) + except Exception: + pass + + # clean + for ii in range(size, 3): + if kwd[lunits[ii]] is not None: + msg = ( + f"Arg '{lunits[ii]}' provided for a '{kwd['nd']}' csys!\n" + f"Provided: {kwd[lunits[ii]]}\n" + ) + raise Exception(msg) + + # --------------- + # clean and check + # --------------- + + lNone = [kk for kk, vv in kwd.items() if vv is None] + assert len(lNone) == (3-size)*2 + for kk in lNone: + del kwd[kk] + return diff --git a/beampipe/_class00_show.py b/beampipe/_class00_show.py index 418100f..bcc9bd5 100644 --- a/beampipe/_class00_show.py +++ b/beampipe/_class00_show.py @@ -10,6 +10,7 @@ _LORDER = [ 'nd', 'ctype', 'ortho', 'norm', 'direct', + 'units', 'kcsys0', ] @@ -52,6 +53,14 @@ def _show(coll=None, which=None, lcol=None, lar=None, show=None): else: nn = str(coll.dobj[which][k0].get(k1)) + # units + if k1 == 'units': + size = int(coll.dobj[which][k0]['nd'][0]) + nn = str(tuple([ + str(coll.dobj[which][k0][f'e{ii}']['units']) + for ii in range(size) + ])) + arr.append(nn) lar0.append(arr) @@ -94,7 +103,10 @@ def _show_details(coll=None, key=None, lcol=None, lar=None, show=None): # is2d for ii in range(3): if ii < size: - nn = f"{coll.dobj[wcsys][key][kk][ii]:4.3e}" + if kk == 'origin': + nn = f"{coll.dobj[wcsys][key][kk][ii]:4.3e}" + else: + nn = f"{coll.dobj[wcsys][key][kk]['data'][ii]:4.3e}" else: nn = '' arr.append(nn) From b532ed4f86857056b47c8f5673693a45f69dbf4a Mon Sep 17 00:00:00 2001 From: dvezinet Date: Wed, 9 Sep 2026 04:13:33 +0200 Subject: [PATCH 13/16] [#devel] Fixed 2d vs 3d cssys --- beampipe/_class00_check.py | 49 +++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/beampipe/_class00_check.py b/beampipe/_class00_check.py index b87af56..0e447d0 100644 --- a/beampipe/_class00_check.py +++ b/beampipe/_class00_check.py @@ -118,19 +118,6 @@ def _check( ndigits=None, ) - # ------------ - # kcsys0 - # ------------ - - lok = list(coll.dobj.get(wcsys, {}).keys()) - lref = [kk for kk in lok if coll.dobj[wcsys][kk]['kcsys0'] == kk] - kwdargs['kcsys0'] = ds._generic_check._check_var( - kwdargs['kcsys0'], 'kcsys0', - types=str, - allowed=lok + [kwdargs['key']], - default=(lref + [kwdargs['key']])[0], - ) - # ------------ # ctype # ------------ @@ -155,11 +142,21 @@ def _check( nd_min = len(dv) lok = [f"{ii}d" for ii in range(nd_min, 4)] lok = [kk for kk in lok if kk in nd_ok] + + nn = list(set([len(vv) for vv in dv.values()])) + if len(nn) > 1: + msg = "Unit vectors do not seem to have consistent size!" + raise Exception(msg) + elif len(nn) == 1: + nd_def = f"{nn[0]}d" + else: + nd_def = lok[-1] + kwdargs['nd'] = ds._generic_check._check_var( kwdargs['nd'], 'nd', types=str, allowed=lok, - default=lok[-1], + default=nd_def, ) size = int(kwdargs['nd'][0]) @@ -173,6 +170,23 @@ def _check( ) raise Exception(msg) + # ------------ + # kcsys0 + # ------------ + + lok = [ + kk for kk, vv in coll.dobj.get(wcsys, {}).items() + if vv['ctype'] == kwdargs['ctype'] + and vv['nd'] == kwdargs['nd'] + ] + lref = [kk for kk in lok if coll.dobj[wcsys][kk]['kcsys0'] == kk] + kwdargs['kcsys0'] = ds._generic_check._check_var( + kwdargs['kcsys0'], 'kcsys0', + types=str, + allowed=lok + [kwdargs['key']], + default=(lref + [kwdargs['key']])[0], + ) + # ------------ # origin - provided => finite array of proper size # ------------ @@ -268,7 +282,7 @@ def _unit_vectors(kwd): if kwd['nd'] == '2d': if kwd['e0'] is None: kwd['e0'] = np.r_[kwd['e1'][1], -kwd['e1'][0]] - elif kwd['e1']: + elif kwd['e1'] is None: kwd['e1'] = np.r_[-kwd['e0'][1], kwd['e0'][0]] # 3d @@ -328,8 +342,9 @@ def _unit_vectors(kwd): # ---------- if kwd['norm'] is True: - for kk in lv: - kwd[kk] = kwd[kk] / np.sqrt(np.sum(kwd[kk]**2)) + for ii in range(size): + estr = f"e{ii}" + kwd[estr] = kwd[estr] / np.sqrt(np.sum(kwd[estr]**2)) # ----------- # ortho From d0bb61b0cf807151024c93e3ee960d9db2dc1410 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Wed, 9 Sep 2026 04:22:56 +0200 Subject: [PATCH 14/16] [#devel] coll.get_csys_transform() operational --- beampipe/_class00_transform.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/beampipe/_class00_transform.py b/beampipe/_class00_transform.py index 4beda07..e3b539d 100644 --- a/beampipe/_class00_transform.py +++ b/beampipe/_class00_transform.py @@ -115,19 +115,19 @@ def _get_transform(coll=None, kwd=None): de = {} for ii in range(size): - de[f'e{ii}_in'] = coll.dobj[wcsys][kwd['key_in']][f'e{ii}'] - de[f'e{ii}_out'] = coll.dobj[wcsys][kwd['key_out']][f'e{ii}'] + de[f'e{ii}_in'] = coll.dobj[wcsys][kwd['key_in']][f'e{ii}']['data'] + de[f'e{ii}_out'] = coll.dobj[wcsys][kwd['key_out']][f'e{ii}']['data'] # ------------ # units # ------------ lunits = [ - coll.ddata[lx[ii]]['units'] for ii in range(size) - if isinstance(lx[ii], str) + coll.dobj[wcsys][kwd['key_out']][f'e{ii}']['units'] + for ii in range(size) ] if len(set(lunits)) > 1: - msg = f"Units are different for each coordinates : {lunits}\n" + msg = f"Units are different for each coordinates: {lunits}\n" warnings.warn(msg) units = None elif len(set(lunits)) == 1: From 7b45adc5eaad76493f087adfd4860e5ed335c422 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Wed, 9 Sep 2026 04:48:35 +0200 Subject: [PATCH 15/16] [#devel] coll.transform_csys_coords() operationa --- beampipe/_class00_check.py | 6 +++++ beampipe/_class00_transform.py | 2 ++ beampipe/_class00_transform_coords.py | 36 ++++++++++++++++++++++----- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/beampipe/_class00_check.py b/beampipe/_class00_check.py index 0e447d0..869a24c 100644 --- a/beampipe/_class00_check.py +++ b/beampipe/_class00_check.py @@ -414,6 +414,12 @@ def _unit_vectors(kwd): ) raise Exception(msg) + # uniformity + lunits = [kwd[f'units{ii}'] for ii in range(size)] + if len(set(lunits)) != 1: + msg = "Non-uniform units!" + raise Exception(msg) + # --------------- # clean and check # --------------- diff --git a/beampipe/_class00_transform.py b/beampipe/_class00_transform.py index e3b539d..e2e42ef 100644 --- a/beampipe/_class00_transform.py +++ b/beampipe/_class00_transform.py @@ -60,6 +60,7 @@ def _check(**kwd): ctype = coll.dobj[wcsys][kwd['key_in']]['ctype'] nd = coll.dobj[wcsys][kwd['key_in']]['nd'] kcsys0 = coll.dobj[wcsys][kwd['key_in']]['kcsys0'] + units = coll.dobj[wcsys][kwd['key_in']]['e0']['units'] # key_out lok = [ @@ -68,6 +69,7 @@ def _check(**kwd): if coll.dobj[wcsys][kk]['ctype'] == ctype and coll.dobj[wcsys][kk]['nd'] == nd and coll.dobj[wcsys][kk]['kcsys0'] == kcsys0 + and coll.dobj[wcsys][kk]['e0']['units'] == units ] kwd['key_out'] = ds._generic_check._check_var( kwd['key_out'], 'key_out', diff --git a/beampipe/_class00_transform_coords.py b/beampipe/_class00_transform_coords.py index 32cee87..c59aba8 100644 --- a/beampipe/_class00_transform_coords.py +++ b/beampipe/_class00_transform_coords.py @@ -62,13 +62,13 @@ def _check(**kwd): # ctype, nd nd = coll.dobj[wcsys][kwd['key_in']]['nd'] + size = int(nd[0]) # ------------- # coordinates # ------------- dfail = {} - size = int(nd[0]) lx = ['x0', 'x1', 'x2'] for ii in range(size): @@ -131,6 +131,8 @@ def _check(**kwd): lout = [kk for kk in kwd.keys() if kk not in lok] for kk in lout: del kwd[kk] + for ii in range(size, 3): + del kwd[f"x{ii}"] return kwd @@ -159,8 +161,8 @@ def _transform(coll=None, kwd=None, dtrans=None): # ------------ lref = [ - coll.ddata[lx[ii]]['ref'] for ii in range(size) - if isinstance(lx[ii], str) + coll.ddata[kwd[lx[ii]]]['ref'] for ii in range(size) + if isinstance(kwd[lx[ii]], str) ] if len(lref) > 0: @@ -173,13 +175,35 @@ def _transform(coll=None, kwd=None, dtrans=None): else: ref = None + # ------------ + # units + # ------------ + + units = coll.dobj[wcsys][kwd['key_in']]['e0']['units'] + + lunits = [ + coll.ddata[kwd[lx[ii]]]['units'] for ii in range(size) + if isinstance(kwd[lx[ii]], str) + ] + + if len(lunits) > 0: + + if len(set(lunits)) > 1: + msg = "Coordinates do not share the same units!" + raise Exception(msg) + + elif lunits[0] != units: + msg = "Coordinates do not share the same units as unit vectors!" + raise Exception(msg) + + # ------------ # values # ------------ dval = { - lx[ii]: kwd[lx[ii]] if isinstance(lx[ii], np.ndarray) - else coll.ddata[lx[ii]]['data'] + lx[ii]: kwd[lx[ii]] if isinstance(kwd[lx[ii]], np.ndarray) + else coll.ddata[kwd[lx[ii]]]['data'] for ii in range(size) } @@ -190,7 +214,7 @@ def _transform(coll=None, kwd=None, dtrans=None): dout = { lx[ii]: { 'data': None, - 'units': dtrans[f"d{lx[ii]}"]['units'], + 'units': units, 'ref': ref, } for ii in range(size) From 5464dbc48c8cc9a62dfa1e77f61cace025d8a9c9 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Wed, 9 Sep 2026 05:16:31 +0200 Subject: [PATCH 16/16] [#devel] Started add_outline2d() --- beampipe/__init__.py | 2 +- beampipe/_class00_check.py | 3 -- beampipe/_class01_Outline2d.py | 68 ++++++++++++++++++++++++++++++++++ beampipe/_class01_check.py | 66 +++++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 beampipe/_class01_Outline2d.py create mode 100644 beampipe/_class01_check.py diff --git a/beampipe/__init__.py b/beampipe/__init__.py index d6e1358..1ca2fe2 100644 --- a/beampipe/__init__.py +++ b/beampipe/__init__.py @@ -1,2 +1,2 @@ -from ._class00_CSYS import CSYS as Collection +from ._class01_Outline2d import Outline2d as Collection diff --git a/beampipe/_class00_check.py b/beampipe/_class00_check.py index 869a24c..ea0deb7 100644 --- a/beampipe/_class00_check.py +++ b/beampipe/_class00_check.py @@ -1,6 +1,3 @@ -import copy - - import numpy as np import astropy.units as asunits import datastock as ds diff --git a/beampipe/_class01_Outline2d.py b/beampipe/_class01_Outline2d.py new file mode 100644 index 0000000..4ad3d0e --- /dev/null +++ b/beampipe/_class01_Outline2d.py @@ -0,0 +1,68 @@ +import copy +from typing import Annotated + + +from ._class00_CSYS import CSYS as Previous +from . import _class01_check as _check +# from . import _class01_show as _show + + +__all__ = ['Outline2d'] + + +############################################# +############################################# +# DEFAULT VALUES +############################################# + + +_WHICH_OUTLINE2D = 'outline2d' + + +############################################# +############################################# +# Spectral Lines +############################################# + + +class Outline2d(Previous): + + _ddef = copy.deepcopy(Previous._ddef) + _which_outline2d = _WHICH_OUTLINE2D + + # ------------------- + # add csys + # ------------------- + + def add_outline2d( + self, + key: Annotated[str | None, 'key of the outline2d to be added'] = None, + key_csys: Annotated[str | None, 'key of the csys'] = None, + # circle + center=None, + radius=None, + # polygon + outline_x0=None, + outline_x1=None, + # from svg + ) -> None: + """ Add a outline2d + + Coordinates are given with respect to key_csys + + """ + + # ------------ + # check inputs + # ------------ + + dref, ddata, dobj = _check.main(coll=self, **locals()) + + # ------------ + # Populate + # ------------ + + self.update(dref=dref, ddata=ddata, dobj=dobj) + + return + diff --git a/beampipe/_class01_check.py b/beampipe/_class01_check.py new file mode 100644 index 0000000..d5fea92 --- /dev/null +++ b/beampipe/_class01_check.py @@ -0,0 +1,66 @@ +import numpy as np +import datastock as ds + + +############################################# +############################################# +# DEFAULT VALUES +############################################# + + +############################################# +############################################# +# main +############################################# + + +def main( + coll=None, + key=None, + key_csys=None, + # circle + center=None, + radius=None, + # polygon + outline_x0=None, + outline_x1=None, + # unused + **kwdargs, +): + + # --------------- + # check inputs + # --------------- + + kwd = _check(**locals()) + + # --------------- + # dref, ddata + # --------------- + + dref = None + ddata = None + + # --------------- + # dobj + # --------------- + + wout2d = coll._which_outline2d + dobj = {wout2d: {kwd['key']: kwd}} + + return dref, ddata, dobj + + +############################################# +############################################# +# check +############################################# + + +def _check(kwd): + + # ---------- + # key + # ---------- + + return kwd