fix: Unit conversion of LazyValueRange.

The unit conversion was indeed botched, with a typo causing the start of
the range to be converted as if it were the end of the range.

Closes #3.
main
Sofus Albert Høgsbro Rose 2024-05-04 15:51:40 +02:00
parent 3a53e4ce46
commit 3c00530524
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
8 changed files with 94 additions and 35 deletions

View File

@ -510,7 +510,7 @@ Header color style can't be done, unfortunately. Body color feels unclean, so no
## BLCache ## BLCache
- [ ] Replace every raw property with `BLField`. - [ ] Replace every raw property with `BLField`.
- [ ] Add matrix property support: https://developer.blender.org/docs/release_notes/3.0/python_api/#other-additions - [x] Add matrix property support: https://developer.blender.org/docs/release_notes/3.0/python_api/#other-additions
- [ ] Fix many problems by persisting `_enum_cb_cache` and `_str_cb_cache`. - [ ] Fix many problems by persisting `_enum_cb_cache` and `_str_cb_cache`.
- [ ] Docstring parser for descriptions. - [ ] Docstring parser for descriptions.
- [ ] Method of dynamically setting property options after creation, using `idproperty_ui_data` - [ ] Method of dynamically setting property options after creation, using `idproperty_ui_data`
@ -524,6 +524,10 @@ We're trying to do our part by reporting bugs we find!
This is where we keep track of them for now, if they're not covered by the above listings. This is where we keep track of them for now, if they're not covered by the above listings.
## Blender Maxwell Bugs ## Blender Maxwell Bugs
See Issues.
## Testing
- [ ] `pytest` integration exhibits a bootstrapping problem when using https://github.com/mondeja/pytest-blender
## Blender Bugs ## Blender Bugs
Reported: Reported:
@ -545,6 +549,7 @@ Unreported:
## Tidy3D bugs ## Tidy3D bugs
Unreported: Unreported:
- Directly running `SimulationTask.get()` is missing fields - it doesn't return some fields, including `created_at`. Listing tasks by folder is not broken. - Directly running `SimulationTask.get()` is missing fields - it doesn't return some fields, including `created_at`. Listing tasks by folder is not broken.
- Frequency ranges don't check for repeated elements.

View File

@ -39,7 +39,8 @@ managed = true
virtual = true virtual = true
dev-dependencies = [ dev-dependencies = [
"ruff>=0.3.2", "ruff>=0.3.2",
"fake-bpy-module-4-0>=20231118", ## TODO: Update to Blender 4.1.0 "fake-bpy-module-4-0>=20231118",
## TODO: Update to Blender 4.1.0
] ]
[tool.rye.scripts] [tool.rye.scripts]
@ -143,3 +144,9 @@ max-args = 6
quote-style = "single" quote-style = "single"
indent-style = "tab" indent-style = "tab"
docstring-code-format = false docstring-code-format = false
####################
# - Tooling: Pytest
####################
#[tool.pytest.ini_options]

View File

@ -1,17 +1,19 @@
from .array import ArrayFlow from .array import ArrayFlow
from .capabiltiies import CapabilitiesFlow from .capabilities import CapabilitiesFlow
from .flow_kinds import FlowKind from .flow_kinds import FlowKind
from .lazy_array_range import LazyArrayRange from .info import InfoFlow
from .lazy_value_func import LazyValueFunc from .lazy_array_range import LazyArrayRangeFlow
from .params import Params from .lazy_value_func import LazyValueFuncFlow
from .params import ParamsFlow
from .value import ValueFlow from .value import ValueFlow
__all__ = [ __all__ = [
'ArrayFlow', 'ArrayFlow',
'CapabilitiesFlow', 'CapabilitiesFlow',
'FlowKind', 'FlowKind',
'LazyArrayRange', 'InfoFlow',
'LazyValueFunc', 'LazyArrayRangeFlow',
'Params', 'LazyValueFuncFlow',
'ParamsFlow',
'ValueFlow', 'ValueFlow',
] ]

View File

@ -50,19 +50,21 @@ class FlowKind(enum.StrEnum):
def scale_to_unit_system( def scale_to_unit_system(
cls, cls,
kind: typ.Self, kind: typ.Self,
value, flow_obj,
unit_system: spux.UnitSystem, unit_system: spux.UnitSystem,
): ):
if kind == cls.Value: log.debug('%s: Scaling "%s" to Unit System', kind, str(flow_obj))
if kind == FlowKind.Value:
return spux.scale_to_unit_system( return spux.scale_to_unit_system(
value, flow_obj,
unit_system, unit_system,
) )
if kind == cls.LazyArrayRange: if kind == FlowKind.LazyArrayRange:
return value.rescale_to_unit_system(unit_system) log.debug([kind, flow_obj, unit_system])
return flow_obj.rescale_to_unit_system(unit_system)
if kind == cls.Params: if kind == FlowKind.Params:
return value.rescale_to_unit_system(unit_system) return flow_obj.rescale_to_unit_system(unit_system)
msg = 'Tried to scale unknown kind' msg = 'Tried to scale unknown kind'
raise ValueError(msg) raise ValueError(msg)

View File

@ -8,11 +8,14 @@ import jaxtyping as jtyp
import sympy as sp import sympy as sp
from blender_maxwell.utils import extra_sympy_units as spux from blender_maxwell.utils import extra_sympy_units as spux
from blender_maxwell.utils import logger
from .array import ArrayFlow from .array import ArrayFlow
from .flow_kinds import FlowKind from .flow_kinds import FlowKind
from .lazy_value_func import LazyValueFuncFlow from .lazy_value_func import LazyValueFuncFlow
log = logger.get(__name__)
@dataclasses.dataclass(frozen=True, kw_only=True) @dataclasses.dataclass(frozen=True, kw_only=True)
class LazyArrayRangeFlow: class LazyArrayRangeFlow:
@ -84,6 +87,16 @@ class LazyArrayRangeFlow:
@functools.cached_property @functools.cached_property
def mathtype(self) -> spux.MathType: def mathtype(self) -> spux.MathType:
"""Conservatively compute the most stringent `spux.MathType` that can represent both `self.start` and `self.stop`.
Notes:
The mathtype is determined from start/stop either using `sympy` assumptions, or as Python types.
For precise information on how start/stop are "combined", see `spux.MathType.combine()`.
Returns:
All symbols valid for use in the expression.
"""
# Get Start Mathtype # Get Start Mathtype
if isinstance(self.start, spux.SympyType): if isinstance(self.start, spux.SympyType):
start_mathtype = spux.MathType.from_expr(self.start) start_mathtype = spux.MathType.from_expr(self.start)
@ -97,12 +110,22 @@ class LazyArrayRangeFlow:
stop_mathtype = spux.MathType.from_pytype(type(self.stop)) stop_mathtype = spux.MathType.from_pytype(type(self.stop))
# Check Equal # Check Equal
if start_mathtype != stop_mathtype: combined_mathtype = spux.MathType.combine(start_mathtype, stop_mathtype)
return spux.MathType.combine(start_mathtype, stop_mathtype) log.debug(
'%s: Computed MathType as %s (start_mathtype=%s, stop_mathtype=%s)',
return start_mathtype self,
combined_mathtype,
start_mathtype,
stop_mathtype,
)
return combined_mathtype
def __len__(self): def __len__(self):
"""Compute the length of the array to be realized.
Returns:
The number of steps.
"""
return self.steps return self.steps
#################### ####################
@ -121,6 +144,11 @@ class LazyArrayRangeFlow:
ValueError: If the existing unit is `None`, indicating that there is no unit to correct. ValueError: If the existing unit is `None`, indicating that there is no unit to correct.
""" """
if self.unit is not None: if self.unit is not None:
log.debug(
'%s: Corrected unit to %s',
self,
corrected_unit,
)
return LazyArrayRangeFlow( return LazyArrayRangeFlow(
start=self.start, start=self.start,
stop=self.stop, stop=self.stop,
@ -146,6 +174,11 @@ class LazyArrayRangeFlow:
ValueError: If the existing unit is `None`, indicating that there is no unit to correct. ValueError: If the existing unit is `None`, indicating that there is no unit to correct.
""" """
if self.unit is not None: if self.unit is not None:
log.debug(
'%s: Scaled to unit %s',
self,
unit,
)
return LazyArrayRangeFlow( return LazyArrayRangeFlow(
start=spux.scale_to_unit(self.start * self.unit, unit), start=spux.scale_to_unit(self.start * self.unit, unit),
stop=spux.scale_to_unit(self.stop * self.unit, unit), stop=spux.scale_to_unit(self.stop * self.unit, unit),
@ -171,13 +204,18 @@ class LazyArrayRangeFlow:
ValueError: If the existing unit is `None`, indicating that there is no unit to correct. ValueError: If the existing unit is `None`, indicating that there is no unit to correct.
""" """
if self.unit is not None: if self.unit is not None:
log.debug(
'%s: Scaled to unit system: %s',
self,
str(unit_system),
)
return LazyArrayRangeFlow( return LazyArrayRangeFlow(
start=spux.strip_unit_system( start=spux.strip_unit_system(
spux.convert_to_unit_system(self.start * self.unit, unit_system), spux.convert_to_unit_system(self.start * self.unit, unit_system),
unit_system, unit_system,
), ),
stop=spux.strip_unit_system( stop=spux.strip_unit_system(
spux.convert_to_unit_system(self.start * self.unit, unit_system), spux.convert_to_unit_system(self.stop * self.unit, unit_system),
unit_system, unit_system,
), ),
steps=self.steps, steps=self.steps,

View File

@ -320,7 +320,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
def on_sim_changed(self, props) -> None: def on_sim_changed(self, props) -> None:
# Sim Linked | First Value Change # Sim Linked | First Value Change
if self.inputs['Sim'].is_linked and not props['sim_info_available']: if self.inputs['Sim'].is_linked and not props['sim_info_available']:
log.critical('First Change: Mark Sim Info Available') log.debug('%s: First Change; Mark Sim Info Available', self.sim_node_name)
self.sim = bl_cache.Signal.InvalidateCache self.sim = bl_cache.Signal.InvalidateCache
self.total_monitor_data = bl_cache.Signal.InvalidateCache self.total_monitor_data = bl_cache.Signal.InvalidateCache
self.is_sim_uploadable = bl_cache.Signal.InvalidateCache self.is_sim_uploadable = bl_cache.Signal.InvalidateCache
@ -332,7 +332,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
and props['sim_info_available'] and props['sim_info_available']
and not props['sim_info_invalidated'] and not props['sim_info_invalidated']
): ):
log.critical('Second Change: Mark Sim Info Invalided') log.debug('%s: Second Change; Mark Sim Info Invalided', self.sim_node_name)
self.sim_info_invalidated = True self.sim_info_invalidated = True
# Sim Linked | Nth Time # Sim Linked | Nth Time
@ -346,7 +346,9 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
## -> Luckily, since we know there's no sim, invalidation is cheap. ## -> Luckily, since we know there's no sim, invalidation is cheap.
## -> Ends up being a "circuit breaker" for sim_info_invalidated. ## -> Ends up being a "circuit breaker" for sim_info_invalidated.
elif not self.inputs['Sim'].is_linked: elif not self.inputs['Sim'].is_linked:
log.critical('Unlinked: Short Circuit Zap Cache') log.debug(
'%s: Unlinked; Short Circuit the Sim Info Cache', self.sim_node_name
)
self.sim = bl_cache.Signal.InvalidateCache self.sim = bl_cache.Signal.InvalidateCache
self.total_monitor_data = bl_cache.Signal.InvalidateCache self.total_monitor_data = bl_cache.Signal.InvalidateCache
self.is_sim_uploadable = bl_cache.Signal.InvalidateCache self.is_sim_uploadable = bl_cache.Signal.InvalidateCache
@ -368,7 +370,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
props={'uploaded_task_id'}, props={'uploaded_task_id'},
) )
def on_uploaded_task_changed(self, props): def on_uploaded_task_changed(self, props):
log.critical('Uploaded Task Changed') log.debug('Uploaded Task Changed')
self.is_sim_uploadable = bl_cache.Signal.InvalidateCache self.is_sim_uploadable = bl_cache.Signal.InvalidateCache
if props['uploaded_task_id'] != '': if props['uploaded_task_id'] != '':

View File

@ -49,15 +49,19 @@ def set_offline():
def check_online() -> bool: def check_online() -> bool:
global IS_ONLINE # noqa: PLW0603 global IS_ONLINE # noqa: PLW0603
log.info('Checking Internet Connection...')
try: try:
urllib.request.urlopen( urllib.request.urlopen(
'https://docs.flexcompute.com/projects/tidy3d/en/latest/index.html', 'https://docs.flexcompute.com/projects/tidy3d/en/latest/index.html',
timeout=2, timeout=2,
) )
except: except: # noqa: E722
log.info('Internet is currently offline')
IS_ONLINE = False IS_ONLINE = False
return False return False
else: else:
log.info('Internet connection is working')
IS_ONLINE = True IS_ONLINE = True
return True return True
@ -67,26 +71,25 @@ def check_online() -> bool:
#################### ####################
def check_authentication() -> bool: def check_authentication() -> bool:
global IS_AUTHENTICATED # noqa: PLW0603 global IS_AUTHENTICATED # noqa: PLW0603
log.critical('Checking Authentication') log.info('Checking Tidy3D Authentication...')
# Check Previous Authentication
## If we authenticated once, we presume that it'll work again.
## TODO: API keys can change... It would just look like "offline" for now.
if IS_AUTHENTICATED:
return True
api_key = td_web.core.http_util.api_key() api_key = td_web.core.http_util.api_key()
if api_key is not None: if api_key is not None:
log.info('Found stored Tidy3D API key')
try: try:
td_web.test() td_web.test()
set_online()
except td.exceptions.WebError: except td.exceptions.WebError:
set_offline() set_offline()
log.info('Authenticated to Tidy3D cloud')
return False return False
else:
set_online()
log.info('Authenticated to Tidy3D cloud')
IS_AUTHENTICATED = True IS_AUTHENTICATED = True
return True return True
log.info('Tidy3D API key is missing')
return False return False
@ -99,6 +102,7 @@ TD_CONFIG = Path(td_web.cli.constants.CONFIG_FILE)
## TODO: Robustness is key - internet might be down. ## TODO: Robustness is key - internet might be down.
## -> I'm not a huge fan of the max 2sec startup time burden ## -> I'm not a huge fan of the max 2sec startup time burden
## -> I also don't love "calling" Tidy3D on startup, privacy-wise
if TD_CONFIG.is_file() and check_online(): if TD_CONFIG.is_file() and check_online():
check_authentication() check_authentication()

View File

@ -83,7 +83,6 @@ if __name__ == '__main__':
# Run Addon # Run Addon
print(f'Blender: Running "{info.ADDON_NAME}"...') print(f'Blender: Running "{info.ADDON_NAME}"...')
subprocess.run
return_code, output = run_blender( return_code, output = run_blender(
None, headless=False, load_devfile=True, monitor=True None, headless=False, load_devfile=True, monitor=True
) )