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
- [ ] 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`.
- [ ] Docstring parser for descriptions.
- [ ] 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.
## Blender Maxwell Bugs
See Issues.
## Testing
- [ ] `pytest` integration exhibits a bootstrapping problem when using https://github.com/mondeja/pytest-blender
## Blender Bugs
Reported:
@ -545,6 +549,7 @@ Unreported:
## 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.
- Frequency ranges don't check for repeated elements.

View File

@ -39,7 +39,8 @@ managed = true
virtual = true
dev-dependencies = [
"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]
@ -143,3 +144,9 @@ max-args = 6
quote-style = "single"
indent-style = "tab"
docstring-code-format = false
####################
# - Tooling: Pytest
####################
#[tool.pytest.ini_options]

View File

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

View File

@ -50,19 +50,21 @@ class FlowKind(enum.StrEnum):
def scale_to_unit_system(
cls,
kind: typ.Self,
value,
flow_obj,
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(
value,
flow_obj,
unit_system,
)
if kind == cls.LazyArrayRange:
return value.rescale_to_unit_system(unit_system)
if kind == FlowKind.LazyArrayRange:
log.debug([kind, flow_obj, unit_system])
return flow_obj.rescale_to_unit_system(unit_system)
if kind == cls.Params:
return value.rescale_to_unit_system(unit_system)
if kind == FlowKind.Params:
return flow_obj.rescale_to_unit_system(unit_system)
msg = 'Tried to scale unknown kind'
raise ValueError(msg)

View File

@ -8,11 +8,14 @@ import jaxtyping as jtyp
import sympy as sp
from blender_maxwell.utils import extra_sympy_units as spux
from blender_maxwell.utils import logger
from .array import ArrayFlow
from .flow_kinds import FlowKind
from .lazy_value_func import LazyValueFuncFlow
log = logger.get(__name__)
@dataclasses.dataclass(frozen=True, kw_only=True)
class LazyArrayRangeFlow:
@ -84,6 +87,16 @@ class LazyArrayRangeFlow:
@functools.cached_property
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
if isinstance(self.start, spux.SympyType):
start_mathtype = spux.MathType.from_expr(self.start)
@ -97,12 +110,22 @@ class LazyArrayRangeFlow:
stop_mathtype = spux.MathType.from_pytype(type(self.stop))
# Check Equal
if start_mathtype != stop_mathtype:
return spux.MathType.combine(start_mathtype, stop_mathtype)
return start_mathtype
combined_mathtype = spux.MathType.combine(start_mathtype, stop_mathtype)
log.debug(
'%s: Computed MathType as %s (start_mathtype=%s, stop_mathtype=%s)',
self,
combined_mathtype,
start_mathtype,
stop_mathtype,
)
return combined_mathtype
def __len__(self):
"""Compute the length of the array to be realized.
Returns:
The number of 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.
"""
if self.unit is not None:
log.debug(
'%s: Corrected unit to %s',
self,
corrected_unit,
)
return LazyArrayRangeFlow(
start=self.start,
stop=self.stop,
@ -146,6 +174,11 @@ class LazyArrayRangeFlow:
ValueError: If the existing unit is `None`, indicating that there is no unit to correct.
"""
if self.unit is not None:
log.debug(
'%s: Scaled to unit %s',
self,
unit,
)
return LazyArrayRangeFlow(
start=spux.scale_to_unit(self.start * 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.
"""
if self.unit is not None:
log.debug(
'%s: Scaled to unit system: %s',
self,
str(unit_system),
)
return LazyArrayRangeFlow(
start=spux.strip_unit_system(
spux.convert_to_unit_system(self.start * self.unit, unit_system),
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,
),
steps=self.steps,

View File

@ -320,7 +320,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
def on_sim_changed(self, props) -> None:
# Sim Linked | First Value Change
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.total_monitor_data = 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 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
# Sim Linked | Nth Time
@ -346,7 +346,9 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
## -> Luckily, since we know there's no sim, invalidation is cheap.
## -> Ends up being a "circuit breaker" for sim_info_invalidated.
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.total_monitor_data = bl_cache.Signal.InvalidateCache
self.is_sim_uploadable = bl_cache.Signal.InvalidateCache
@ -368,7 +370,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
props={'uploaded_task_id'},
)
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
if props['uploaded_task_id'] != '':

View File

@ -49,15 +49,19 @@ def set_offline():
def check_online() -> bool:
global IS_ONLINE # noqa: PLW0603
log.info('Checking Internet Connection...')
try:
urllib.request.urlopen(
'https://docs.flexcompute.com/projects/tidy3d/en/latest/index.html',
timeout=2,
)
except:
except: # noqa: E722
log.info('Internet is currently offline')
IS_ONLINE = False
return False
else:
log.info('Internet connection is working')
IS_ONLINE = True
return True
@ -67,26 +71,25 @@ def check_online() -> bool:
####################
def check_authentication() -> bool:
global IS_AUTHENTICATED # noqa: PLW0603
log.critical('Checking 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
log.info('Checking Tidy3D Authentication...')
api_key = td_web.core.http_util.api_key()
if api_key is not None:
log.info('Found stored Tidy3D API key')
try:
td_web.test()
set_online()
except td.exceptions.WebError:
set_offline()
log.info('Authenticated to Tidy3D cloud')
return False
else:
set_online()
log.info('Authenticated to Tidy3D cloud')
IS_AUTHENTICATED = True
return True
log.info('Tidy3D API key is missing')
return False
@ -99,6 +102,7 @@ TD_CONFIG = Path(td_web.cli.constants.CONFIG_FILE)
## TODO: Robustness is key - internet might be down.
## -> 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():
check_authentication()

View File

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