luminarycloud 0.15.5__py3-none-any.whl → 0.16.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. luminarycloud/_client/client.py +5 -0
  2. luminarycloud/_helpers/__init__.py +1 -0
  3. luminarycloud/_helpers/_code_representation.py +21 -4
  4. luminarycloud/_helpers/download.py +67 -1
  5. luminarycloud/_proto/api/v0/luminarycloud/inference/inference_pb2.py +9 -9
  6. luminarycloud/_proto/api/v0/luminarycloud/inference/inference_pb2.pyi +7 -4
  7. luminarycloud/_proto/api/v0/luminarycloud/physics_ai/physics_ai_pb2.py +45 -21
  8. luminarycloud/_proto/api/v0/luminarycloud/physics_ai/physics_ai_pb2.pyi +65 -0
  9. luminarycloud/_proto/api/v0/luminarycloud/physics_ai/physics_ai_pb2_grpc.py +34 -0
  10. luminarycloud/_proto/api/v0/luminarycloud/physics_ai/physics_ai_pb2_grpc.pyi +12 -0
  11. luminarycloud/_proto/api/v0/luminarycloud/thirdpartyintegration/onshape/onshape_pb2.py +194 -7
  12. luminarycloud/_proto/api/v0/luminarycloud/thirdpartyintegration/onshape/onshape_pb2.pyi +407 -5
  13. luminarycloud/_proto/api/v0/luminarycloud/thirdpartyintegration/onshape/onshape_pb2_grpc.py +171 -0
  14. luminarycloud/_proto/api/v0/luminarycloud/thirdpartyintegration/onshape/onshape_pb2_grpc.pyi +64 -0
  15. luminarycloud/_proto/api/v0/luminarycloud/upload/upload_pb2.py +4 -2
  16. luminarycloud/_proto/api/v0/luminarycloud/upload/upload_pb2_grpc.py +34 -0
  17. luminarycloud/_proto/api/v0/luminarycloud/upload/upload_pb2_grpc.pyi +12 -0
  18. luminarycloud/_proto/api/v0/luminarycloud/vis/vis_pb2.py +128 -107
  19. luminarycloud/_proto/api/v0/luminarycloud/vis/vis_pb2.pyi +48 -3
  20. luminarycloud/_proto/assistant/assistant_pb2.py +82 -61
  21. luminarycloud/_proto/assistant/assistant_pb2.pyi +40 -0
  22. luminarycloud/_proto/assistant/assistant_pb2_grpc.py +34 -0
  23. luminarycloud/_proto/assistant/assistant_pb2_grpc.pyi +12 -0
  24. luminarycloud/_proto/base/base_pb2.py +7 -6
  25. luminarycloud/_proto/base/base_pb2.pyi +4 -0
  26. luminarycloud/_proto/client/simulation_pb2.py +351 -351
  27. luminarycloud/_proto/client/simulation_pb2.pyi +105 -97
  28. luminarycloud/_proto/geometry/geometry_pb2.py +68 -68
  29. luminarycloud/_proto/geometry/geometry_pb2.pyi +15 -7
  30. luminarycloud/_proto/hexmesh/hexmesh_pb2.py +40 -15
  31. luminarycloud/_proto/hexmesh/hexmesh_pb2.pyi +58 -1
  32. luminarycloud/_proto/inferenceservice/inferenceservice_pb2.py +11 -11
  33. luminarycloud/_proto/inferenceservice/inferenceservice_pb2.pyi +12 -4
  34. luminarycloud/_proto/lcstatus/codes_pb2.py +3 -2
  35. luminarycloud/_proto/lcstatus/codes_pb2.pyi +4 -0
  36. luminarycloud/_proto/quantity/quantity_pb2.py +11 -2
  37. luminarycloud/_proto/quantity/quantity_pb2.pyi +6 -0
  38. luminarycloud/_proto/table/table_pb2.pyi +4 -2
  39. luminarycloud/_proto/upload/upload_pb2.py +27 -7
  40. luminarycloud/_proto/upload/upload_pb2.pyi +31 -0
  41. luminarycloud/enum/quantity_type.py +19 -0
  42. luminarycloud/enum/tables.py +1 -0
  43. luminarycloud/enum/vis_enums.py +20 -0
  44. luminarycloud/feature_modification.py +6 -7
  45. luminarycloud/geometry.py +24 -0
  46. luminarycloud/geometry_version.py +23 -0
  47. luminarycloud/mesh.py +8 -1
  48. luminarycloud/params/simulation/adjoint_.py +4 -4
  49. luminarycloud/params/simulation/material/material_fluid_.py +1 -1
  50. luminarycloud/params/simulation/material/material_solid_.py +1 -1
  51. luminarycloud/params/simulation/output_.py +1 -1
  52. luminarycloud/params/simulation/physics/fluid/initialization/fluid_existing_solution_.py +28 -0
  53. luminarycloud/params/simulation/physics/fluid/solution_controls/fluid_relaxation_method/fluid_implicit_relaxation/robust_startup/__init__.py +1 -0
  54. luminarycloud/params/simulation/physics/fluid/solution_controls/fluid_relaxation_method/fluid_implicit_relaxation/robust_startup/robust_startup_auto_.py +30 -0
  55. luminarycloud/params/simulation/physics/fluid/solution_controls/fluid_relaxation_method/fluid_implicit_relaxation/robust_startup/robust_startup_on_.py +1 -1
  56. luminarycloud/params/simulation/physics/fluid/solution_controls/fluid_relaxation_method/fluid_implicit_relaxation_.py +6 -2
  57. luminarycloud/params/simulation/physics/fluid/solution_controls_fluid_.py +4 -0
  58. luminarycloud/params/simulation/simulation_param_.py +6 -0
  59. luminarycloud/physics_ai/__init__.py +4 -0
  60. luminarycloud/physics_ai/inference.py +140 -4
  61. luminarycloud/physics_ai/solution.py +60 -0
  62. luminarycloud/project.py +9 -7
  63. luminarycloud/simulation_param.py +29 -15
  64. luminarycloud/simulation_template.py +14 -10
  65. luminarycloud/tables.py +11 -12
  66. luminarycloud/thirdparty/__init__.py +12 -0
  67. luminarycloud/thirdparty/onshape.py +170 -0
  68. luminarycloud/vis/__init__.py +2 -0
  69. luminarycloud/vis/data_extraction.py +44 -6
  70. luminarycloud/vis/display.py +26 -11
  71. luminarycloud/vis/filters.py +226 -67
  72. luminarycloud/vis/primitives.py +3 -2
  73. luminarycloud/vis/visualization.py +198 -41
  74. luminarycloud/volume_selection.py +2 -2
  75. {luminarycloud-0.15.5.dist-info → luminarycloud-0.16.1.dist-info}/METADATA +6 -6
  76. {luminarycloud-0.15.5.dist-info → luminarycloud-0.16.1.dist-info}/RECORD +77 -73
  77. {luminarycloud-0.15.5.dist-info → luminarycloud-0.16.1.dist-info}/WHEEL +0 -0
@@ -12,7 +12,7 @@ from google.protobuf.internal.containers import RepeatedScalarFieldContainer
12
12
  class FeatureOperationType(Enum):
13
13
  """Enum representing the type of operation in a Feature."""
14
14
 
15
- IMPORT = auto()
15
+ IMPORT_GEOMETRY = auto()
16
16
  CREATE = auto()
17
17
  DELETE = auto()
18
18
  UNION = auto()
@@ -73,7 +73,7 @@ def get_operation_type(feature: gpb.Feature) -> FeatureOperationType:
73
73
 
74
74
  # Base operation map for simple one-to-one mappings
75
75
  base_operation_map = {
76
- "import": FeatureOperationType.IMPORT,
76
+ "import_geometry": FeatureOperationType.IMPORT_GEOMETRY,
77
77
  "create": FeatureOperationType.CREATE,
78
78
  "delete": FeatureOperationType.DELETE,
79
79
  "imprint": FeatureOperationType.IMPRINT,
@@ -165,21 +165,20 @@ def modify_import(
165
165
  Returns:
166
166
  A gpb.Modification object
167
167
  """
168
- if get_operation_type(feature) != FeatureOperationType.IMPORT:
168
+ if get_operation_type(feature) != FeatureOperationType.IMPORT_GEOMETRY:
169
169
  raise ValueError("Feature is not an import operation")
170
170
 
171
171
  feature_copy = deepcopy(feature)
172
- import_op = getattr(feature_copy, "import")
173
172
 
174
173
  if geometry_url is not None:
175
174
  # TODO(chiodi): Handle upload of file here.
176
- import_op.geometry_url = geometry_url
175
+ feature_copy.import_geometry.geometry_url = geometry_url
177
176
 
178
177
  if scaling is not None:
179
- import_op.scaling = scaling
178
+ feature_copy.import_geometry.scaling = scaling
180
179
 
181
180
  if force_discrete is not None:
182
- import_op.force_discrete = force_discrete
181
+ feature_copy.import_geometry.force_discrete = force_discrete
183
182
 
184
183
  return gpb.Modification(
185
184
  mod_type=gpb.Modification.ModificationType.MODIFICATION_TYPE_UPDATE_FEATURE,
luminarycloud/geometry.py CHANGED
@@ -554,6 +554,30 @@ class Geometry(ProtoWrapperBase):
554
554
  res: geometrypb.GetGeometryResponse = get_default_client().GetGeometry(req)
555
555
  self._proto = res.geometry
556
556
 
557
+ def to_code(self) -> str:
558
+ """
559
+ Returns the python code that creates (from scratch) an identical geometry.
560
+ If the geometry has been modified, the code will be generated for the latest version of the
561
+ geometry.
562
+
563
+ Returns
564
+ -------
565
+ str
566
+ The SDK code that can be used to recreate this geometry.
567
+
568
+ Examples
569
+ --------
570
+ >>> geometry = lc.get_geometry("geometry-id")
571
+ >>> python_code = geometry.to_code()
572
+ >>> print(python_code)
573
+ """
574
+ req = geometrypb.GetSdkCodeRequest(
575
+ geometry_id=self.id,
576
+ geometry_version_id="", # Use the latest version of the geometry by default
577
+ )
578
+ res: geometrypb.GetSdkCodeResponse = get_default_client().GetSdkCode(req)
579
+ return res.sdk_code
580
+
557
581
 
558
582
  def get_geometry(id: str) -> Geometry:
559
583
  """
@@ -126,6 +126,29 @@ class GeometryVersion(ProtoWrapperBase):
126
126
  )
127
127
  return list(res.features_issues)
128
128
 
129
+ def to_code(self) -> str:
130
+ """
131
+ Returns the Python code that creates (from scratch) an identical geometry of
132
+ this particular version.
133
+
134
+ Returns
135
+ -------
136
+ str
137
+ The SDK code that can be used to recreate this specific geometry version.
138
+
139
+ Examples
140
+ --------
141
+ >>> geometry_version = lc.get_geometry_version("version-id")
142
+ >>> python_code = geometry_version.to_code()
143
+ >>> print(python_code)
144
+ """
145
+ req = geometrypb.GetSdkCodeRequest(
146
+ geometry_id=self.geometry_id,
147
+ geometry_version_id=self.id,
148
+ )
149
+ res: geometrypb.GetSdkCodeResponse = get_default_client().GetSdkCode(req)
150
+ return res.sdk_code
151
+
129
152
 
130
153
  def get_geometry_version(id: str) -> GeometryVersion:
131
154
  """
luminarycloud/mesh.py CHANGED
@@ -44,10 +44,17 @@ class Mesh(ProtoWrapperBase):
44
44
  """
45
45
  return lc.get_project(ProjectID(self._proto.project_id))
46
46
 
47
- def geometry_version(self) -> GeometryVersion:
47
+ def geometry_version(self) -> GeometryVersion | None:
48
48
  """
49
49
  Get the geometry version associated with this mesh.
50
+
51
+ Returns
52
+ -------
53
+ GeometryVersion | None
54
+ The geometry version associated with this mesh, or None if the mesh has no geometry version.
50
55
  """
56
+ if self._proto.geometry_version_id == "":
57
+ return None
51
58
  return get_geometry_version(self._proto.geometry_version_id)
52
59
 
53
60
  def update(
@@ -22,7 +22,7 @@ from luminarycloud.params.simulation._lib import ParamGroupWrapper, create_uniqu
22
22
  class Adjoint(CodeRepr, ParamGroupWrapper[clientpb.Adjoint]):
23
23
  """Settings for adjoint sensitivity analysis."""
24
24
 
25
- output: Any | None = None
25
+ _output: Any | None = None
26
26
  "Function to differentiate."
27
27
  surfaces: list[str] = field(default_factory=list)
28
28
  ""
@@ -33,8 +33,8 @@ class Adjoint(CodeRepr, ParamGroupWrapper[clientpb.Adjoint]):
33
33
 
34
34
  def _to_proto(self) -> clientpb.Adjoint:
35
35
  _proto = clientpb.Adjoint()
36
- if self.output is not None:
37
- _proto.adjoint_output.CopyFrom(self.output)
36
+ if self._output is not None:
37
+ _proto.adjoint_output.CopyFrom(self._output)
38
38
  if self.surfaces is not None:
39
39
  _proto.surfaces.extend(self.surfaces)
40
40
  if self.deformed_coords_id is not None:
@@ -44,7 +44,7 @@ class Adjoint(CodeRepr, ParamGroupWrapper[clientpb.Adjoint]):
44
44
  return _proto
45
45
 
46
46
  def _from_proto(self, proto: clientpb.Adjoint) -> None:
47
- self.output = proto.adjoint_output
47
+ self._output = proto.adjoint_output
48
48
  self.surfaces.extend(proto.surfaces)
49
49
  self.deformed_coords_id = proto.deformed_coords_id
50
50
  self.primal_simulation_id = proto.primal_simulation_id
@@ -85,7 +85,7 @@ class MaterialFluid(CodeRepr, ParamGroupWrapper[clientpb.MaterialFluid]):
85
85
  "Model for the laminar thermal conductivity of a fluid. Possible types: ``PrescribedPrandtlNumber``, ``PrescribedConductivity``, ``TemperatureDependentConductivity`` from the ``thermal_conductivity_model`` module."
86
86
  boussinesq_approximation: BoussinesqApproximation = field(default_factory=BoussinesqOn)
87
87
  "Introduce a body force due to thermal expansion without modifying the material density. Possible types: ``BoussinesqOff``, ``BoussinesqOn`` from the ``boussinesq_approximation`` module."
88
- preset: enum.MaterialFluidPreset = enum.MaterialFluidPreset.CUSTOM_MATERIAL_FLUID
88
+ preset: enum.MaterialFluidPreset = enum.MaterialFluidPreset.STANDARD_AIR
89
89
  "Select a predefined set of material properties or allow a custom set of properties."
90
90
  viscosity_model: ViscosityModel = field(default_factory=Sutherland)
91
91
  "Models available for the dynamic viscosity of the fluid. Possible types: ``Sutherland``, ``PrescribedViscosity``, ``TemperatureDependentViscosity`` from the ``viscosity_model`` module."
@@ -30,7 +30,7 @@ class MaterialSolid(CodeRepr, ParamGroupWrapper[clientpb.MaterialSolid]):
30
30
  "The thermal conductivity of the material."
31
31
  thermal_conductivity_table: RectilinearTable | None = None
32
32
  "Correlation between thermal conductivity and temperature."
33
- preset: enum.MaterialSolidPreset = enum.MaterialSolidPreset.CUSTOM_MATERIAL_SOLID
33
+ preset: enum.MaterialSolidPreset = enum.MaterialSolidPreset.ALUMINUM
34
34
  "Select a predefined set of material properties or allow a custom set of properties."
35
35
 
36
36
  def _to_proto(self) -> clientpb.MaterialSolid:
@@ -22,7 +22,7 @@ from luminarycloud.params.simulation._lib import ParamGroupWrapper, create_uniqu
22
22
  class Output(CodeRepr, ParamGroupWrapper[clientpb.Output]):
23
23
  """Solution output settings."""
24
24
 
25
- iters_per_output: int = 1000
25
+ iters_per_output: int = 0
26
26
  "Number of (pseudo) timesteps between successive full solution output. If ≤0, only the final solution is written."
27
27
  include_residuals: bool = False
28
28
  "Include the residuals for each equation in the volume solution."
@@ -19,6 +19,10 @@ from luminarycloud.params.simulation._lib import ParamGroupWrapper, create_uniqu
19
19
 
20
20
  from luminarycloud.params.simulation.physics.fluid.initialization_fluid_ import InitializationFluid
21
21
  from luminarycloud.params.simulation.physics.fluid.initialization_fluid_ import *
22
+ from luminarycloud.params.simulation.physics.fluid.initialization.turbulence_initialization_ import (
23
+ TurbulenceInitialization,
24
+ )
25
+ from luminarycloud.params.simulation.physics.fluid.initialization.turbulence_initialization_ import *
22
26
 
23
27
 
24
28
  @dataclass(kw_only=True)
@@ -27,16 +31,40 @@ class FluidExistingSolution(InitializationFluid):
27
31
 
28
32
  solution_id: str = ""
29
33
  "ID for the existing solution to use for initialization."
34
+ turbulence: TurbulenceInitialization = field(default_factory=TurbulenceInitialization)
35
+ ""
30
36
 
31
37
  def _to_proto(self) -> clientpb.InitializationFluid:
32
38
  _proto = super()._to_proto()
33
39
  _proto.initialization_type = clientpb.InitializationType.EXISTING_SOLUTION
34
40
  if self.solution_id is not None:
35
41
  _proto.existing_solution_id = self.solution_id
42
+ _proto.turbulent_variable_initialization_type_sa = self.turbulence.spalart_allmaras.value
43
+ _proto.turbulent_variable_initialization_type_komega = self.turbulence.komega.value
44
+ _proto.init_turbulent_viscosity_ratio.CopyFrom(
45
+ _to_ad_proto(self.turbulence.viscosity_ratio)
46
+ )
47
+ _proto.init_turbulent_viscosity.CopyFrom(_to_ad_proto(self.turbulence.viscosity))
48
+ _proto.init_turbulence_intensity.CopyFrom(_to_ad_proto(self.turbulence.intensity))
49
+ _proto.uniform_nu_tilde.CopyFrom(_to_ad_proto(self.turbulence.sa_variable))
50
+ _proto.uniform_tke.CopyFrom(_to_ad_proto(self.turbulence.tke))
51
+ _proto.uniform_omega.CopyFrom(_to_ad_proto(self.turbulence.omega))
36
52
  return _proto
37
53
 
38
54
  def _from_proto(self, proto: clientpb.InitializationFluid) -> None:
39
55
  super()._from_proto(proto)
40
56
  assert proto.initialization_type == clientpb.InitializationType.EXISTING_SOLUTION
41
57
  self.solution_id = proto.existing_solution_id
58
+ self.turbulence.spalart_allmaras = enum.TurbulentVariableInitializationTypeSa(
59
+ proto.turbulent_variable_initialization_type_sa
60
+ )
61
+ self.turbulence.komega = enum.TurbulentVariableInitializationTypeKomega(
62
+ proto.turbulent_variable_initialization_type_komega
63
+ )
64
+ self.turbulence.viscosity_ratio = _from_ad_proto(proto.init_turbulent_viscosity_ratio)
65
+ self.turbulence.viscosity = _from_ad_proto(proto.init_turbulent_viscosity)
66
+ self.turbulence.intensity = _from_ad_proto(proto.init_turbulence_intensity)
67
+ self.turbulence.sa_variable = _from_ad_proto(proto.uniform_nu_tilde)
68
+ self.turbulence.tke = _from_ad_proto(proto.uniform_tke)
69
+ self.turbulence.omega = _from_ad_proto(proto.uniform_omega)
42
70
  return None
@@ -1,2 +1,3 @@
1
+ from .robust_startup_auto_ import RobustStartupAuto
1
2
  from .robust_startup_off_ import RobustStartupOff
2
3
  from .robust_startup_on_ import RobustStartupOn
@@ -0,0 +1,30 @@
1
+ # Generated by generate_sdk_wrappers.py. DO NOT EDIT
2
+
3
+ from abc import ABC, ABCMeta
4
+ from dataclasses import dataclass, field
5
+ from typing import Any
6
+ from uuid import uuid4
7
+
8
+ from google.protobuf.message import Message as _Message
9
+ from luminarycloud.tables import RectilinearTable, _param_name_to_table_type
10
+ from luminarycloud.types import Vector3, LcFloat
11
+ from luminarycloud.types.adfloat import _to_ad_proto, _from_ad_proto
12
+ from luminarycloud._helpers._entity_identifier import _create_entity_identifier
13
+ from luminarycloud._proto.client import simulation_pb2 as clientpb
14
+ from luminarycloud._proto.client.entity_pb2 import EntityIdentifier
15
+ from luminarycloud._helpers import CodeRepr
16
+ import luminarycloud.params.enum._enum_wrappers as enum
17
+
18
+ from luminarycloud.params.simulation._lib import ParamGroupWrapper, create_unique_id
19
+
20
+ from luminarycloud.params.simulation.physics.fluid.solution_controls.fluid_relaxation_method.fluid_implicit_relaxation.robust_startup_ import (
21
+ RobustStartup,
22
+ )
23
+ from luminarycloud.params.simulation.physics.fluid.solution_controls.fluid_relaxation_method.fluid_implicit_relaxation.robust_startup_ import *
24
+
25
+
26
+ @dataclass(kw_only=True)
27
+ class RobustStartupAuto(RobustStartup):
28
+ """Enable robust startup mode and automatically determine the settings."""
29
+
30
+ pass
@@ -25,7 +25,7 @@ from luminarycloud.params.simulation.physics.fluid.solution_controls.fluid_relax
25
25
 
26
26
  @dataclass(kw_only=True)
27
27
  class RobustStartupOn(RobustStartup):
28
- """Enable robust startup mode."""
28
+ """Enable robust startup mode and manually specify the settings."""
29
29
 
30
30
  robust_startup_initial_cfl: LcFloat = 1.0
31
31
  "Initial CFL number for robust startup mode. The CFL is ramped toward the target value during startup."
@@ -37,6 +37,10 @@ from luminarycloud.params.simulation.physics.fluid.solution_controls.fluid_relax
37
37
  RobustStartup,
38
38
  )
39
39
  from luminarycloud.params.simulation.physics.fluid.solution_controls.fluid_relaxation_method.fluid_implicit_relaxation.robust_startup_ import *
40
+ from luminarycloud.params.simulation.physics.fluid.solution_controls.fluid_relaxation_method.fluid_implicit_relaxation.robust_startup.robust_startup_auto_ import (
41
+ RobustStartupAuto,
42
+ )
43
+ from luminarycloud.params.simulation.physics.fluid.solution_controls.fluid_relaxation_method.fluid_implicit_relaxation.robust_startup.robust_startup_auto_ import *
40
44
  from luminarycloud.params.simulation.physics.fluid.solution_controls.fluid_relaxation_method.fluid_implicit_relaxation.robust_startup.robust_startup_off_ import (
41
45
  RobustStartupOff,
42
46
  )
@@ -63,8 +67,8 @@ class FluidImplicitRelaxation(FluidRelaxationMethod):
63
67
  "How many iterations in between updating the Jacobian values for implicit solving."
64
68
  jacobian_warmup_threshold: int = 300
65
69
  "How many iterations to update Jacobians every iteration before switching to the specified 'Jacobian Update Interval'."
66
- robust_startup: RobustStartup = field(default_factory=RobustStartupOn)
67
- "Applies a robust startup process during the initial transients of a simulation. Applicable to steady problems only. Possible types: ``RobustStartupOn``, ``RobustStartupOff`` from the ``robust_startup`` module."
70
+ robust_startup: RobustStartup = field(default_factory=RobustStartupAuto)
71
+ "Applies a robust startup process during the initial transients of a simulation. Applicable to steady problems only. Possible types: ``RobustStartupOn``, ``RobustStartupAuto``, ``RobustStartupOff`` from the ``robust_startup`` module."
68
72
  relaxation_flow: LcFloat = 1.0
69
73
  "Under-relaxation factor in [0,1] applied to the mean flow solution update with each implicit nonlinear iteration. Default of 1.0."
70
74
  relaxation_turb: LcFloat = 0.5
@@ -122,6 +122,8 @@ class SolutionControlsFluid(CodeRepr, ParamGroupWrapper[clientpb.SolutionControl
122
122
  _proto.robust_startup_iterations.value = (
123
123
  self.fluid_relaxation_method.robust_startup.robust_startup_iterations
124
124
  )
125
+ if isinstance(self.fluid_relaxation_method.robust_startup, RobustStartupAuto):
126
+ _proto.robust_startup = clientpb.ROBUST_STARTUP_AUTO
125
127
  if isinstance(self.fluid_relaxation_method.robust_startup, RobustStartupOff):
126
128
  _proto.robust_startup = clientpb.ROBUST_STARTUP_OFF
127
129
  _proto.relax_flow.CopyFrom(_to_ad_proto(self.fluid_relaxation_method.relaxation_flow))
@@ -211,6 +213,8 @@ class SolutionControlsFluid(CodeRepr, ParamGroupWrapper[clientpb.SolutionControl
211
213
  self.fluid_relaxation_method.robust_startup.robust_startup_iterations = (
212
214
  proto.robust_startup_iterations.value
213
215
  )
216
+ elif proto.robust_startup == clientpb.ROBUST_STARTUP_AUTO:
217
+ self.fluid_relaxation_method.robust_startup = RobustStartupAuto()
214
218
  elif proto.robust_startup == clientpb.ROBUST_STARTUP_OFF:
215
219
  self.fluid_relaxation_method.robust_startup = RobustStartupOff()
216
220
  self.fluid_relaxation_method.relaxation_flow = _from_ad_proto(proto.relax_flow)
@@ -77,6 +77,8 @@ class SimulationParam(CodeRepr, ParamGroupWrapper[clientpb.SimulationParam]):
77
77
  "Body frame."
78
78
  surface_name: dict[str, SurfaceName] = field(default_factory=dict)
79
79
  "Surface name map."
80
+ _table_references: dict[str, Any] = field(default_factory=dict)
81
+ "Solution inputs - metadata for general n-dimensional rectilinear grid data (Monitor points, radial distribution, blade geometry, airfoil performance)."
80
82
  output: Output = field(default_factory=Output)
81
83
  "Solution output settings."
82
84
  entity_relationships: EntityRelationships = field(default_factory=EntityRelationships)
@@ -115,6 +117,9 @@ class SimulationParam(CodeRepr, ParamGroupWrapper[clientpb.SimulationParam]):
115
117
  if self.surface_name is not None:
116
118
  for k, v in self.surface_name.items():
117
119
  _proto.surface_name[k].CopyFrom(v._to_proto())
120
+ if self._table_references is not None:
121
+ for k, v in self._table_references.items():
122
+ _proto.table_references[k].CopyFrom(v)
118
123
  if self.output is not None:
119
124
  _proto.output.CopyFrom(self.output._to_proto())
120
125
  if self.entity_relationships is not None:
@@ -140,6 +145,7 @@ class SimulationParam(CodeRepr, ParamGroupWrapper[clientpb.SimulationParam]):
140
145
  self.monitor_plane = [MonitorPlane.from_proto(v) for v in proto.monitor_plane]
141
146
  self.body_frame = BodyFrame.from_proto(proto.body_frame)
142
147
  self.surface_name = {k: SurfaceName.from_proto(v) for k, v in proto.surface_name.items()}
148
+ self._table_references.update(proto.table_references)
143
149
  self.output = Output.from_proto(proto.output)
144
150
  self.entity_relationships = EntityRelationships.from_proto(proto.entity_relationships)
145
151
  self.adaptive_mesh_refinement = AdaptiveMeshRefinement.from_proto(
@@ -8,3 +8,7 @@ from .models import (
8
8
  PhysicsAiModel as PhysicsAiModel,
9
9
  PhysicsAiModelVersion as PhysicsAiModelVersion,
10
10
  )
11
+
12
+ from .solution import (
13
+ _download_processed_solution_physics_ai as _download_processed_solution_physics_ai,
14
+ )
@@ -1,30 +1,165 @@
1
1
  # File: python/sdk/luminarycloud/inference/inference.py
2
2
  # Copyright 2025 Luminary Cloud, Inc. All Rights Reserved.
3
3
  from datetime import datetime
4
- from typing import Any
4
+ from typing import Any, Callable
5
5
  from json import loads as json_loads
6
+ from dataclasses import dataclass
7
+ import base64
8
+ import os
9
+ import urllib.request
6
10
 
7
11
  from .._client import get_default_client
8
12
  from .._helpers._timestamp_to_datetime import timestamp_to_datetime
9
13
  from .._proto.api.v0.luminarycloud.inference import inference_pb2 as inferencepb
10
14
  from .._proto.inferenceservice import inferenceservice_pb2 as inferenceservicepb
11
15
  from .._wrapper import ProtoWrapper, ProtoWrapperBase
16
+ from ..project import Project
12
17
  from .._helpers.warnings import experimental
18
+ from ..project import Project
19
+ from .._helpers import upload_file
20
+ from .._proto.upload import upload_pb2 as uploadpb
21
+
22
+
23
+ @dataclass
24
+ class ExtAeroInferenceResult:
25
+ """Result of an external aerodynamic inference job.
26
+
27
+ Attributes
28
+ ----------
29
+ drag_force: float
30
+ The drag force returned from the inference.
31
+ lift_force: float
32
+ The lift force returned from the inference.
33
+ wall_shear_stress:
34
+ A dict containing wall shear stress data, or None if not available.
35
+ pressure_surface:
36
+ A dict containing pressure surface stress data, or None if not available.
37
+ """
38
+
39
+ drag_force: float
40
+ lift_force: float
41
+ wall_shear_stress: dict[str, Any] | None
42
+ pressure_surface: dict[str, Any] | None
43
+
44
+ def __init__(self, inference_result: dict[str, Any]) -> None:
45
+ self.drag_force = inference_result["drag_force"]
46
+ self.lift_force = inference_result["lift_force"]
47
+ self.wall_shear_stress = inference_result.get("wall-shear-stress", None)
48
+ self.pressure_surface = inference_result.get("pressure_surface", None)
49
+
50
+
51
+ @experimental
52
+ def external_aero_inference(
53
+ project: Project, stl_file: str, checkpoint_file: str, config_name: str, stencil_size: int
54
+ ) -> ExtAeroInferenceResult:
55
+ """Performs an inference job returning external aerodynamic results.
56
+ Parameters
57
+ ----------
58
+ project : Project
59
+ The project to which the inference files will be added.
60
+ stl_file : str
61
+ Fullpath the STL file to be used for inference.
62
+ checkpoint_file : str
63
+ Fullpath of the model to be used for inference.
64
+ config_name :str
65
+ Name of the configuration to be used for inference.
66
+ stencil_size :int
67
+ Size of the stencil to be used for inference.
68
+
69
+ Returns
70
+ ExtAeroInferenceResult
71
+ Result of the external aerodynamic inference job.
72
+
73
+ warning:: This feature is experimental and may change or be removed without notice.
74
+ """
75
+
76
+ result = perform_inference(project, stl_file, checkpoint_file, config_name, stencil_size)
77
+ return ExtAeroInferenceResult(result)
78
+
79
+
80
+ @experimental
81
+ def perform_inference(
82
+ project: Project, stl_file: str, checkpoint_file: str, config_name: str, stencil_size: int
83
+ ) -> dict[str, Any]:
84
+ """Creates an inference service job.
85
+ Parameters
86
+ ----------
87
+ project : Project
88
+ The project to which the inference files will be added.
89
+ stl_file : str
90
+ Fullpath the STL file to be used for inference.
91
+ checkpoint_file : str
92
+ Fullpath of the model to be used for inference.
93
+ config_name :str
94
+ Name of the configuration to be used for inference.
95
+ stencil_size :int
96
+ Size of the stencil to be used for inference.
97
+
98
+
99
+ Returns
100
+ dict[str, Any]
101
+ Response from the server as key-value pairs.
102
+
103
+ warning:: This feature is experimental and may change or be removed without notice.
104
+ """
105
+
106
+ client = get_default_client()
107
+
108
+ def upload_if_file(fname: str) -> str:
109
+ if os.path.exists(fname) and os.path.isfile(fname):
110
+ params = uploadpb.ResourceParams()
111
+ result = upload_file(client, project.id, params, fname)
112
+ return result[1].url
113
+ if fname.startswith("gs://"):
114
+ return fname
115
+ raise RuntimeError("Unsupported file for inference")
116
+
117
+ def future_file(url: str) -> Callable[[], dict[str, Any]]:
118
+ def download_file() -> dict[str, Any]:
119
+ with urllib.request.urlopen(url) as f:
120
+ serialized = f.read()
121
+ jsondata = json_loads(serialized)
122
+ data = base64.b64decode(jsondata["data"])
123
+ jsondata["data"] = data
124
+ return jsondata
125
+
126
+ return download_file
127
+
128
+ stl_url = upload_if_file(stl_file)
129
+ check_url = upload_if_file(checkpoint_file)
130
+
131
+ raw = start_inference_job(project, stl_url, check_url, config_name, stencil_size)
132
+ currated: dict[str, Any] = {}
133
+ for k, v in raw.items():
134
+ if isinstance(v, str) and v.startswith("https://"):
135
+ tmp = future_file(v)
136
+ if k.endswith("_url"):
137
+ currated[k[:-4]] = tmp
138
+ currated[k] = v
139
+ else:
140
+ currated[k] = tmp
141
+ currated[k + "_url"] = v
142
+ else:
143
+ currated[k] = v
144
+ return currated
13
145
 
14
146
 
15
147
  @experimental
16
148
  def start_inference_job(
149
+ project: Project,
17
150
  stl_url: str,
18
- model_url: str,
151
+ checkpoint_url: str,
19
152
  config_name: str,
20
153
  stencil_size: int,
21
154
  ) -> dict[str, Any]:
22
155
  """Creates an inference service job.
23
156
  Parameters
24
157
  ----------
158
+ project : Project
159
+ Reference to a project.
25
160
  stl_url : str
26
161
  URL of the STL file to be used for inference.
27
- model_url : str
162
+ checkpoint_url : str
28
163
  URL of the model to be used for inference.
29
164
  config_name :str
30
165
  Name of the configuration to be used for inference.
@@ -41,9 +176,10 @@ def start_inference_job(
41
176
 
42
177
  req = inferencepb.CreateInferenceServiceJobRequest(
43
178
  stl_url=stl_url,
44
- model_url=model_url,
179
+ checkpoint_url=checkpoint_url,
45
180
  config_name=config_name,
46
181
  stencil_size=stencil_size,
182
+ project_id=project.id,
47
183
  )
48
184
 
49
185
  res: inferencepb.CreateInferenceServiceJobResponse = (
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ import tarfile
4
+ from typing import List, Optional, BinaryIO, cast
5
+
6
+ from .._client import get_default_client
7
+ from .._helpers.warnings import experimental
8
+ from .._helpers.download import download_solution_physics_ai as _download_solution_physics_ai
9
+ from ..enum.quantity_type import QuantityType
10
+
11
+
12
+ @experimental
13
+ def _download_processed_solution_physics_ai( # noqa: F841
14
+ solution_id: str,
15
+ exclude_surfaces: Optional[List[str]] = None,
16
+ fill_holes: float = -1.0,
17
+ surface_fields_to_keep: Optional[List[QuantityType]] = None,
18
+ volume_fields_to_keep: Optional[List[QuantityType]] = None,
19
+ process_volume: bool = False,
20
+ single_precision: bool = True,
21
+ ) -> tarfile.TarFile:
22
+ """
23
+ Download solution data with physics AI processing applied.
24
+
25
+ Returns a compressed archive containing processed solution files including
26
+ merged surfaces (VTP/STL) and optionally volume data (VTU).
27
+
28
+ Args:
29
+ solution_id: ID of the solution to download
30
+ exclude_surfaces: List of surface names to exclude from processing
31
+ fill_holes: Sets the maximum size of the hole to be filled for the STL file, measured as the radius of the bounding circumsphere.
32
+ If fill_holes is negative or zero, no holes will be filled.
33
+ surface_fields_to_keep: List of QuantityType enum values for surface fields to keep in output.
34
+ If None, all available surface fields are included.
35
+ volume_fields_to_keep: List of QuantityType enum values for volume fields to keep in output.
36
+ If None, all available volume fields are included.
37
+ process_volume: Whether to process volume data
38
+ single_precision: Whether to use single precision for floating point fields
39
+
40
+ Raises:
41
+ ValueError: If invalid field names are provided
42
+ """
43
+
44
+ stream = _download_solution_physics_ai(
45
+ get_default_client(),
46
+ solution_id,
47
+ exclude_surfaces=exclude_surfaces,
48
+ fill_holes=fill_holes,
49
+ surface_fields_to_keep=surface_fields_to_keep,
50
+ volume_fields_to_keep=volume_fields_to_keep,
51
+ process_volume=process_volume,
52
+ single_precision=single_precision,
53
+ )
54
+
55
+ assert stream is not None, "Failed to download solution data"
56
+ return tarfile.open(
57
+ name=stream.filename,
58
+ fileobj=cast(BinaryIO, stream),
59
+ mode="r|gz",
60
+ )
luminarycloud/project.py CHANGED
@@ -39,6 +39,7 @@ from ._proto.api.v0.luminarycloud.simulation_template import (
39
39
  simulation_template_pb2 as simtemplatepb,
40
40
  )
41
41
  from ._proto.client import simulation_pb2 as clientpb
42
+ from ._proto.table import table_pb2 as tablepb
42
43
  from ._proto.hexmesh import hexmesh_pb2 as hexmeshpb
43
44
  from ._proto.upload import upload_pb2 as uploadpb
44
45
  from ._wrapper import ProtoWrapper, ProtoWrapperBase
@@ -440,13 +441,14 @@ class Project(ProtoWrapperBase):
440
441
  raise RuntimeError("The table upload failed.")
441
442
 
442
443
  # Update the simulation template with the new table reference.
443
- params: clientpb.SimulationParam = simulation_template._proto.parameters
444
- params.table_references[name].url = url
445
- params.table_references[name].table_type = table_type.value
446
- params.table_references[name].uploaded_filename = uploaded_filename
447
- simulation_template._update(name=simulation_template.name, parameters=params)
448
-
449
- return RectilinearTable(id=name, name=uploaded_filename, table_type=table_type)
444
+ params: SimulationParam = simulation_template.get_parameters()
445
+ params._table_references[name] = tablepb.Metadata()
446
+ params._table_references[name].url = url
447
+ params._table_references[name].table_type = table_type.value
448
+ params._table_references[name].uploaded_filename = uploaded_filename
449
+ simulation_template.update(parameters=params)
450
+ # The name is lost in to/from proto conversions so make it equal to the id for consistency.
451
+ return RectilinearTable(id=name, name=name, table_type=table_type)
450
452
 
451
453
  @experimental
452
454
  def set_surface_deformation(