diff --git a/TODO.md b/TODO.md index cd8b947..cd1fc1e 100644 --- a/TODO.md +++ b/TODO.md @@ -13,9 +13,9 @@ - [x] Unit System - [ ] Implement presets, including "Tidy3D" and "Blender", shown in the label row. -- [ ] Constants / Scientific Constant - - [ ] Create `utils.sci_constants` to map `scipy` constants to `sympy` units. - - [ ] Utilize `utils.sci_constants` to make it easy for the user to select appropriate constants with two-layered dropdowns. +- [x] Constants / Scientific Constant + - [x] Create `utils.sci_constants` to map `scipy` constants to `sympy` units. + - [x] Utilize `utils.sci_constants` to make it easy for the user to select appropriate constants with two-layered dropdowns. - [x] Constants / Number Constant - [ ] Constants / Physical Constant - [ ] Pol: Elliptical viz as 2D plot. @@ -455,6 +455,13 @@ We're trying to do our part by reporting bugs we find! This is where we keep track of them for now. +## Blender Maxwell Bugs +- [ ] Slow changing of socket sets / range on wave constant. +- [ ] API auth shouldn't show if everything is fine in Cloud Task socket +- [ ] Cloud task socket loads folders before its node shows, which can be slow (and error prone if offline) +- [ ] Dispersive fit is slow, which means lag on normal operations that rely on the fit result - fit computation should be integrated into the node, and the output socket should only appear when the fit is available. +- [ ] Numerical, Physical Constant is missing entries + ## Blender Bugs Reported: - (SOLVED) @@ -462,6 +469,6 @@ Reported: Unreported: - The `__mp_main__` bug. -# Tidy3D bugs +## Tidy3D bugs Unreported: - Directly running `SimulationTask.get()` is missing fields - it doesn't return some fields, including `created_at`. Listing tasks by folder is not broken. diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/__init__.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/__init__.py index b52aebc..9d2b74f 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/__init__.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/__init__.py @@ -1,15 +1,15 @@ # from . import scientific_constant # from . import physical_constant -from . import blender_constant, number_constant +from . import blender_constant, number_constant, scientific_constant BL_REGISTER = [ - # *scientific_constant.BL_REGISTER, + *scientific_constant.BL_REGISTER, *number_constant.BL_REGISTER, # *physical_constant.BL_REGISTER, *blender_constant.BL_REGISTER, ] BL_NODES = { - # **scientific_constant.BL_NODES, + **scientific_constant.BL_NODES, **number_constant.BL_NODES, # **physical_constant.BL_NODES, **blender_constant.BL_NODES, diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/scientific_constant.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/scientific_constant.py index 2b21dc4..c83dd9a 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/scientific_constant.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/scientific_constant.py @@ -1,6 +1,85 @@ -## TODO: Discover dropdown options from sci_constants.py +import typing as typ + +import bpy + +from ......utils import sci_constants as constants +from .... import contracts as ct +from .... import sockets +from ... import base, events + + +class ScientificConstantNode(base.MaxwellSimNode): + node_type = ct.NodeType.ScientificConstant + bl_label = 'Scientific Constant' + + output_sockets: typ.ClassVar = { + 'Value': sockets.AnySocketDef(), + } + + #################### + # - Properties + #################### + sci_constant: bpy.props.StringProperty( + name='Sci Constant', + description='The name of a scientific constant', + default='', + search=lambda self, _, edit_text: self.search_sci_constants(edit_text), + update=lambda self, context: self.on_update_sci_constant(context), + ) + + cache__units: bpy.props.StringProperty(default='') + cache__uncertainty: bpy.props.StringProperty(default='') + + def search_sci_constants( + self, + edit_text: str, + ): + return [name for name in constants.SCI_CONSTANTS if edit_text in name] + + def on_update_sci_constant( + self, + context: bpy.types.Context, + ): + if self.sci_constant: + self.cache__units = str( + constants.SCI_CONSTANTS_INFO[self.sci_constant]['units'] + ) + self.cache__uncertainty = str( + constants.SCI_CONSTANTS_INFO[self.sci_constant]['uncertainty'] + ) + else: + self.cache__units = '' + self.cache__uncertainty = '' + + self.sync_prop('sci_constant', context) + + #################### + # - UI + #################### + def draw_props(self, _: bpy.types.Context, col: bpy.types.UILayout) -> None: + col.prop(self, 'sci_constant', text='') + + def draw_info(self, _: bpy.types.Context, col: bpy.types.UILayout) -> None: + if self.sci_constant: + col.label(text=f'Units: {self.cache__units}') + col.label(text=f'Uncertainty: {self.cache__uncertainty}') + + col.label(text=f'Ref: {constants.SCI_CONSTANTS_REF[0]}') + + #################### + # - Callbacks + #################### + @events.computes_output_socket('Value', props={'sci_constant'}) + def compute_value(self, props: dict) -> typ.Any: + return constants.SCI_CONSTANTS[props['sci_constant']] + + #################### # - Blender Registration #################### -BL_REGISTER = [] -BL_NODES = {} +BL_REGISTER = [ + ScientificConstantNode, +] +BL_NODES = { + ct.NodeType.ScientificConstant: (ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS) +} diff --git a/src/blender_maxwell/utils/extra_sympy_units.py b/src/blender_maxwell/utils/extra_sympy_units.py index 48126b5..0d580a2 100644 --- a/src/blender_maxwell/utils/extra_sympy_units.py +++ b/src/blender_maxwell/utils/extra_sympy_units.py @@ -2,11 +2,8 @@ import functools import itertools import typing as typ -from . import pydeps - -with pydeps.syspath_from_bpy_prefs(): - import sympy as sp - import sympy.physics.units as spu +import sympy as sp +import sympy.physics.units as spu #################### @@ -39,6 +36,20 @@ femtosecond = fs = spu.Quantity('femtosecond', abbrev='fs') femtosecond.set_global_relative_scale_factor(spu.femto, spu.second) +#################### +# - Length +#################### +femtometer = fm = spu.Quantity('femtometer', abbrev='fm') +femtometer.set_global_relative_scale_factor(spu.femto, spu.meter) + + +#################### +# - Lum Flux +#################### +lumen = lm = spu.Quantity('lumen', abbrev='lm') +lumen.set_global_relative_scale_factor(1, spu.candela * spu.steradian) + + #################### # - Force #################### diff --git a/src/blender_maxwell/utils/sci_constants.py b/src/blender_maxwell/utils/sci_constants.py index a15a381..5b6aa73 100644 --- a/src/blender_maxwell/utils/sci_constants.py +++ b/src/blender_maxwell/utils/sci_constants.py @@ -1,6 +1,134 @@ +"""Access `scipy.constants` using `sympy` units. + +Notes: + See + +Attributes: + _SCIPY_COMBINED_UNITS: A mapping from scipy constant names to its combined unit, represented as a tuple of "base" units. + SCIPY_UNIT_TO_SYMPY_UNIT: A mapping from scipy constant names to its combined unit, represented as a tuple of "base" units. + SCI_CONSTANTS: A mapping from scipy constant names to an equivalent sympy expression.expression. + SCI_CONSTANTS_REF: Original source of constant data / choices. +""" + +import functools + import scipy as sc +import sympy as sp import sympy.physics.units as spu -# from . import extra_sympy_units as spux +from . import extra_sympy_units as spux -vac_speed_of_light = sc.constants.speed_of_light * spu.meter / spu.second +SUPPORTED_SCIPY_PREFIX = '1.12' +if not sc.version.full_version.startswith(SUPPORTED_SCIPY_PREFIX): + msg = f'The active scipy version "{sc.version.full_version}" has not been tested with the "sci_constants.py" module, which only supports scipy version(s prefixed with) "{SUPPORTED_SCIPY_PREFIX}". While the module may otherwise work, constants available in other versions of scipy may not conform to hard-coded unit lookups; as such, we throw an error, to ensure the absence of unfortunate resulting bugs' + raise RuntimeError(msg) + +#################### +# - Fundamental Constants +#################### +vac_speed_of_light = sp.nsimplify(sc.constants.speed_of_light) * spu.meter / spu.second +hartree_energy = ( + sp.nsimplify(sc.constants.physical_constants['Hartree energy in eV'][0]) + * spu.electronvolt +) + +#################### +# - Parse scipy.constants +#################### +_SCIPY_EXCLUDED_ENTRIES: set[str] = { + 'Faraday constant for conventional electric current', +} +_SCIPY_COMBINED_UNITS: dict[str, tuple[str]] = { + name: tuple(cst[1].split(' ')) + for name, cst in sc.constants.physical_constants.items() + if cst[1] and name not in _SCIPY_EXCLUDED_ENTRIES +} + +## TODO: Better interface to the official Units section? See https://docs.scipy.org/doc/scipy/reference/constants.html#units +SCIPY_UNIT_TO_SYMPY_UNIT = { + #'C_90': spu.coulombs, ## "Conventional electric current" + 'H': spu.henry, + 'C^4': spu.coulombs**4, + 'm^4': spu.meters**4, + 'm^-1': 1 / spu.meter, + 'J^-3': 1 / spu.joules**3, + 's^-2': 1 / spu.second**2, + 'kg': spu.kilogram, + 'eV': spu.electronvolt, + 'GeV': 10**9 * spu.electronvolt, + 'S': spu.siemens, + '(GeV/c^2)^-2': (10**9 * spu.electronvolt / vac_speed_of_light**2) ** -2, + 'GeV^-2': (10**9 * spu.electronvolt) ** -2, + 'E_h': hartree_energy, + 'Hz': spu.hertz, + 'MeV/c': (10**6 * spu.electronvolt) / vac_speed_of_light, + 'Pa': spu.pascal, + 'ohm': spu.ohms, + 'Wb': spu.weber, + 'W^-1': 1 / spu.watt, + 'u': spu.atomic_mass_constant, + 'm^-3': 1 / spu.meters**3, + 'J': spu.joules, + 'sr^-1': 1 / spu.steradian, + 'T^-1': 1 / spu.tesla, + 'C^2': spu.coulombs**2, + 'F': spu.farad, + 'MeV': 10**6 * spu.electronvolt, + 'J^-2': 1 / spu.joules**2, + 'mol^-1': 1 / spu.mole, + 'kg^-1': 1 / spu.kilogram, + 'T^-2': 1 / spu.tesla**2, + 'fm': spux.femtometer, + 'V^-1': 1 / spu.volt, + 'N': spu.newton, + 'A': spu.ampere, + 's^-1': 1 / spu.second, + 'lm': spux.lumen, + 'm^3': spu.meters**3, + 'm^2': spu.meters**2, + 'K^-1': 1 / spu.kelvin, + 'm^-2': 1 / spu.meters**2, + 'MHz': 10**6 * spu.hertz, + 'J^-1': 1 / spu.joules, + 'Hz^-1': 1 / spu.hertz, + 'm': spu.meter, + 'C^3': spu.coulombs**3, + 'K': spu.kelvin, + 'A^-2': 1 / spu.ampere**2, + 'T': spu.tesla, + 'K^-4': 1 / spu.kelvin**4, + 'W': spu.watt, + 'C': spu.coulombs, + 's': spu.second, + 'V': spu.volt, +} + +#################### +# - Extract Units +#################### +SCI_CONSTANT_UNITS = { + name: functools.reduce( + lambda a, b: a * SCIPY_UNIT_TO_SYMPY_UNIT[b], + combined_unit, + sp.S(1), + ) + for name, combined_unit in _SCIPY_COMBINED_UNITS.items() +} +SCI_CONSTANTS_INFO = { + name: { + 'units': SCI_CONSTANT_UNITS[name], + 'uncertainty': sc.constants.physical_constants[name][2], + 'scipy_units': sc.constants.physical_constants[name][1], + } + for name, combined_unit in _SCIPY_COMBINED_UNITS.items() +} +SCI_CONSTANTS_REF = ( + '[CODATA2018]', + 'CODATA Recommended Values of the Fundamental Physical Constants 2018.', + 'https://physics.nist.gov/cuu/Constants/', +) + +SCI_CONSTANTS = { + name: sc.constants.physical_constants[name][0] * SCI_CONSTANT_UNITS[name] + for name, combined_unit in _SCIPY_COMBINED_UNITS.items() +}