luminarycloud 0.15.2__py3-none-any.whl → 0.15.3__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 (43) hide show
  1. luminarycloud/_helpers/_code_representation.py +44 -19
  2. luminarycloud/_proto/api/v0/luminarycloud/geometry/geometry_pb2.py +81 -81
  3. luminarycloud/_proto/api/v0/luminarycloud/geometry/geometry_pb2.pyi +4 -1
  4. luminarycloud/_proto/api/v0/luminarycloud/simulation/simulation_pb2.py +46 -46
  5. luminarycloud/_proto/api/v0/luminarycloud/simulation/simulation_pb2.pyi +7 -1
  6. luminarycloud/_proto/assistant/assistant_pb2.py +23 -23
  7. luminarycloud/_proto/assistant/assistant_pb2.pyi +21 -11
  8. luminarycloud/_proto/assistant/assistant_pb2_grpc.py +13 -13
  9. luminarycloud/_proto/assistant/assistant_pb2_grpc.pyi +6 -6
  10. luminarycloud/_proto/client/simulation_pb2.py +333 -324
  11. luminarycloud/_proto/client/simulation_pb2.pyi +26 -1
  12. luminarycloud/_proto/geometry/geometry_pb2.py +64 -63
  13. luminarycloud/_proto/geometry/geometry_pb2.pyi +11 -3
  14. luminarycloud/_proto/hexmesh/hexmesh_pb2.py +18 -14
  15. luminarycloud/_proto/hexmesh/hexmesh_pb2.pyi +14 -4
  16. luminarycloud/_proto/luminarycloud/luminarycloud_api.pb +0 -0
  17. luminarycloud/_proto/named_variable_set/named_variable_set_pb2.py +49 -0
  18. luminarycloud/_proto/named_variable_set/named_variable_set_pb2.pyi +53 -0
  19. luminarycloud/_proto/quantity/quantity_pb2.py +8 -5
  20. luminarycloud/_proto/quantity/quantity_pb2.pyi +2 -0
  21. luminarycloud/enum/__init__.py +3 -0
  22. luminarycloud/meshing/mesh_generation_params.py +6 -5
  23. luminarycloud/meshing/sizing_strategy/sizing_strategies.py +2 -1
  24. luminarycloud/named_variable_set.py +3 -1
  25. luminarycloud/pipeline_util/dictable.py +27 -0
  26. luminarycloud/pipeline_util/yaml.py +55 -0
  27. luminarycloud/pipelines/__init__.py +29 -0
  28. luminarycloud/pipelines/core.py +225 -0
  29. luminarycloud/pipelines/operators.py +197 -0
  30. luminarycloud/pipelines/parameters.py +42 -0
  31. luminarycloud/project.py +6 -6
  32. luminarycloud/simulation.py +35 -4
  33. luminarycloud/simulation_param.py +16 -12
  34. luminarycloud/simulation_template.py +10 -6
  35. luminarycloud/types/vector3.py +2 -1
  36. luminarycloud/vis/__init__.py +0 -3
  37. luminarycloud/vis/display.py +3 -2
  38. luminarycloud/vis/filters.py +1 -2
  39. luminarycloud/vis/interactive_scene.py +1 -1
  40. luminarycloud/vis/visualization.py +17 -1
  41. {luminarycloud-0.15.2.dist-info → luminarycloud-0.15.3.dist-info}/METADATA +2 -1
  42. {luminarycloud-0.15.2.dist-info → luminarycloud-0.15.3.dist-info}/RECORD +43 -34
  43. {luminarycloud-0.15.2.dist-info → luminarycloud-0.15.3.dist-info}/WHEEL +0 -0
@@ -0,0 +1,225 @@
1
+ # Copyright 2025 Luminary Cloud, Inc. All Rights Reserved.
2
+ from abc import ABC, abstractmethod
3
+ from dataclasses import is_dataclass, fields
4
+ from typing import Type, TypeVar, Generic
5
+ import re
6
+ import yaml
7
+
8
+ from .._helpers.warnings import experimental
9
+ from ..pipeline_util.yaml import ensure_yamlizable
10
+
11
+
12
+ class PipelineParameter(ABC):
13
+ """
14
+ Base class for all concrete PipelineParameters.
15
+ """
16
+
17
+ def __init__(self, name: str):
18
+ self.name = name
19
+ self._validate()
20
+
21
+ @property
22
+ def type(self) -> str:
23
+ return self._type()
24
+
25
+ @abstractmethod
26
+ def _type(self) -> str:
27
+ pass
28
+
29
+ def _validate(self) -> None:
30
+ if not re.match(r"^[a-zA-Z0-9_-]+$", self.name):
31
+ raise ValueError(
32
+ "name must only contain alphanumeric characters, underscores and hyphens"
33
+ )
34
+
35
+ def _add_to_params(self, params: dict) -> None:
36
+ if self.name in params and params[self.name]["type"] != self.type:
37
+ raise ValueError(
38
+ f"Parameter name {self.name} used with multiple types: {params[self.name]['type']} != {self.type}"
39
+ )
40
+ params[self.name] = {"type": self.type}
41
+
42
+ def _to_pipeline_dict(self) -> tuple[dict, list["PipelineParameter"]]:
43
+ return {"$pipeline_param": self.name}, [self]
44
+
45
+
46
+ class PipelineInput:
47
+ """
48
+ A named input for an Operator instance (i.e. a Task). Explicitly connected to a PipelineOutput.
49
+ """
50
+
51
+ def __init__(self, upstream_output: "PipelineOutput", owner: "Operator", name: str):
52
+ self.upstream_output = upstream_output
53
+ self.owner = owner
54
+ self.name = name
55
+
56
+ def _to_dict(self, id_for_task: dict) -> dict:
57
+ if self.upstream_output.owner not in id_for_task:
58
+ raise ValueError(
59
+ f"Task {self.owner} depends on a task, {self.upstream_output.owner}, that isn't in the Pipeline. Did you forget to add it?"
60
+ )
61
+ upstream_task_id = id_for_task[self.upstream_output.owner]
62
+ upstream_output_name = self.upstream_output.name
63
+ return {self.name: f"{upstream_task_id}.{upstream_output_name}"}
64
+
65
+
66
+ class PipelineOutput(ABC):
67
+ """
68
+ A named output for an Operator instance (i.e. a Task). Can be used to spawn any number of
69
+ connected PipelineInputs.
70
+ """
71
+
72
+ def __init__(self, owner: "Operator", name: str):
73
+ self.owner = owner
74
+ self.name = name
75
+ self.downstream_inputs: list[PipelineInput] = []
76
+
77
+ def _spawn_input(self, owner: "Operator", name: str) -> PipelineInput:
78
+ input = PipelineInput(self, owner, name)
79
+ self.downstream_inputs.append(input)
80
+ return input
81
+
82
+
83
+ class OperatorInputs:
84
+ """
85
+ A collection of all PipelineInputs for an Operator instance (i.e. a Task).
86
+ """
87
+
88
+ def __init__(
89
+ self, owner: "Operator", **input_descriptors: tuple[Type[PipelineOutput], PipelineOutput]
90
+ ):
91
+ """
92
+ input_descriptors is a dict of input name -> (required_upstream_output_type, upstream_output)
93
+ We have that required_upstream_output_type so we can do runtime validation that each given
94
+ output is of the correct type for the input it's hooked up to.
95
+ """
96
+ self.inputs: set[PipelineInput] = set()
97
+ for name, (required_upstream_output_type, upstream_output) in input_descriptors.items():
98
+ if not isinstance(upstream_output, required_upstream_output_type):
99
+ raise ValueError(
100
+ f"Input {name} must be a {required_upstream_output_type.__name__}, got {upstream_output.__class__.__name__}"
101
+ )
102
+ self.inputs.add(upstream_output._spawn_input(owner, name))
103
+
104
+ def _to_dict(self, id_for_task: dict) -> dict[str, str]:
105
+ d: dict[str, str] = {}
106
+ for input in self.inputs:
107
+ d |= input._to_dict(id_for_task)
108
+ return d
109
+
110
+
111
+ T = TypeVar("T", bound="OperatorOutputs")
112
+
113
+
114
+ class OperatorOutputs(ABC):
115
+ """
116
+ A collection of all PipelineOutputs for an Operator instance (i.e. a Task). Must be subclassed,
117
+ and the subclass must also be a dataclass whose fields are all PipelineOutput subclasses. Then
118
+ that subclass should be instantiated with `_instantiate_for`. Sounds a little complicated,
119
+ perhaps, but it's not bad. See the existing subclasses in `./operators.py` for examples.
120
+ """
121
+
122
+ @classmethod
123
+ def _instantiate_for(cls: type[T], owner: "Operator") -> T:
124
+ # create an instance with all fields instantiated with the given owner, and named by the
125
+ # field name.
126
+ # Also validate here that we are a dataclass, and all our fields are PipelineOutput types.
127
+ # Would love to get this done in the type system, but I think it's impossible, so this is
128
+ # the next best thing.
129
+ if not is_dataclass(cls):
130
+ raise TypeError(f"'{cls.__name__}' must be a dataclass")
131
+ outputs = {}
132
+ for field in fields(cls):
133
+ assert not isinstance(field.type, str)
134
+ if not issubclass(field.type, PipelineOutput):
135
+ raise TypeError(
136
+ f"Field '{field.name}' in '{cls.__name__}' must be a subclass of PipelineOutput"
137
+ )
138
+ outputs[field.name] = field.type(owner, field.name)
139
+ return cls(**outputs)
140
+
141
+
142
+ TOutputs = TypeVar("TOutputs", bound=OperatorOutputs)
143
+
144
+
145
+ class Operator(Generic[TOutputs], ABC):
146
+ def __init__(
147
+ self,
148
+ task_name: str | None,
149
+ params: dict,
150
+ inputs: OperatorInputs,
151
+ outputs: TOutputs,
152
+ ):
153
+ self._operator_name = self.__class__.__name__
154
+ self._task_name = task_name if task_name is not None else self._operator_name
155
+ self._params = params
156
+ self._inputs = inputs
157
+ self.outputs = outputs
158
+ ensure_yamlizable(self._params_dict()[0], "Operator parameters")
159
+
160
+ def _to_dict(self, id_for_task: dict) -> tuple[dict, list[PipelineParameter]]:
161
+ params, params_list = self._params_dict()
162
+ d = {
163
+ "name": self._task_name,
164
+ "operator": self._operator_name,
165
+ "params": params,
166
+ "inputs": self._inputs._to_dict(id_for_task),
167
+ }
168
+ return d, params_list
169
+
170
+ def _params_dict(self) -> tuple[dict, list[PipelineParameter]]:
171
+ d = {}
172
+ params = []
173
+ for name, value in self._params.items():
174
+ if hasattr(value, "_to_pipeline_dict"):
175
+ d[name], downstream_params = value._to_pipeline_dict()
176
+ params.extend(downstream_params)
177
+ else:
178
+ d[name] = value
179
+ return d, params
180
+
181
+ def __str__(self) -> str:
182
+ return f'{self._operator_name}(name="{self._task_name}")'
183
+
184
+
185
+ @experimental
186
+ class Pipeline:
187
+ def __init__(self, name: str, tasks: list[Operator]):
188
+ self.name = name
189
+ self.tasks = tasks
190
+
191
+ def to_yaml(self) -> str:
192
+ return yaml.safe_dump(self._to_dict())
193
+
194
+ def _to_dict(self) -> dict:
195
+ id_for_task = self._assign_ids_to_tasks()
196
+ tasks = {}
197
+ params = []
198
+ for task in id_for_task.keys():
199
+ task_dict, referenced_params = task._to_dict(id_for_task)
200
+ tasks[id_for_task[task]] = task_dict
201
+ params.extend(referenced_params)
202
+
203
+ d = {
204
+ "lc_pipeline": {
205
+ "schema_version": 1,
206
+ "name": self.name,
207
+ "params": self._pipeline_params_dict(params),
208
+ "tasks": tasks,
209
+ }
210
+ }
211
+ ensure_yamlizable(d, "Pipeline")
212
+ return d
213
+
214
+ def _assign_ids_to_tasks(self) -> dict[Operator, str]:
215
+ return {task: f"t{i + 1}-{task._operator_name}" for i, task in enumerate(self.tasks)}
216
+
217
+ def _pipeline_params_dict(self, params: list[PipelineParameter]) -> dict:
218
+ d: dict[str, dict] = {}
219
+ for p in params:
220
+ if p.name in d and d[p.name]["type"] != p.type:
221
+ raise ValueError(
222
+ f'PipelineParameter "{p.name}" used with multiple types: {d[p.name]["type"]} != {p.type}'
223
+ )
224
+ d[p.name] = {"type": p.type}
225
+ return d
@@ -0,0 +1,197 @@
1
+ # Copyright 2025 Luminary Cloud, Inc. All Rights Reserved.
2
+ from dataclasses import dataclass
3
+
4
+ from .._helpers.warnings import experimental
5
+ from .core import Operator, OperatorInputs, OperatorOutputs, PipelineOutput
6
+ from .parameters import StringPipelineParameter
7
+ from ..meshing import MeshGenerationParams
8
+
9
+
10
+ # Concrete PipelineOutput classes, i.e. the things that can "flow" in a Pipeline
11
+
12
+
13
+ class PipelineOutputGeometry(PipelineOutput):
14
+ """A representation of a Geometry in a Pipeline."""
15
+
16
+ pass
17
+
18
+
19
+ class PipelineOutputMesh(PipelineOutput):
20
+ """A representation of a Mesh in a Pipeline."""
21
+
22
+ pass
23
+
24
+
25
+ class PipelineOutputSimulation(PipelineOutput):
26
+ """A representation of a Simulation in a Pipeline."""
27
+
28
+ pass
29
+
30
+
31
+ # Operators
32
+
33
+
34
+ @dataclass
35
+ class ReadGeometryOutputs(OperatorOutputs):
36
+ geometry: PipelineOutputGeometry
37
+ """
38
+ The Geometry identified by the given `geometry_id`, in the state it was in when the Pipeline was
39
+ invoked. I.e. the latest GeometryVersion at that moment.
40
+ """
41
+
42
+
43
+ @experimental
44
+ class ReadGeometry(Operator[ReadGeometryOutputs]):
45
+ """
46
+ Reads a Geometry into the Pipeline.
47
+
48
+ Parameters
49
+ ----------
50
+ geometry_id : str | StringPipelineParameter
51
+ The ID of the Geometry to retrieve.
52
+
53
+ Outputs
54
+ -------
55
+ geometry : PipelineOutputGeometry
56
+ The latest GeometryVersion of the Geometry as of the moment the Pipeline was invoked.
57
+
58
+ .. warning:: This feature is experimental and may change or be removed in the future.
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ *,
64
+ task_name: str | None = None,
65
+ geometry_id: str | StringPipelineParameter,
66
+ ):
67
+ super().__init__(
68
+ task_name,
69
+ {"geometry_id": geometry_id},
70
+ OperatorInputs(self),
71
+ ReadGeometryOutputs._instantiate_for(self),
72
+ )
73
+
74
+
75
+ @dataclass
76
+ class ModifyGeometryOutputs(OperatorOutputs):
77
+ geometry: PipelineOutputGeometry
78
+ """The modified Geometry, represented as a new GeometryVersion."""
79
+
80
+
81
+ # TODO: figure out what `mods` actually is. What does the non-pipeline geo mod interface look like?
82
+ @experimental
83
+ class ModifyGeometry(Operator[ModifyGeometryOutputs]):
84
+ """
85
+ Modifies a Geometry.
86
+
87
+ Parameters
88
+ ----------
89
+ mods : dict
90
+ The modifications to apply to the Geometry.
91
+ geometry : PipelineOutputGeometry
92
+ The Geometry to modify.
93
+
94
+ Outputs
95
+ -------
96
+ geometry : PipelineOutputGeometry
97
+ The modified Geometry, represented as a new GeometryVersion.
98
+
99
+ .. warning:: This feature is experimental and may change or be removed in the future.
100
+ """
101
+
102
+ def __init__(
103
+ self,
104
+ *,
105
+ task_name: str | None = None,
106
+ mods: list[dict],
107
+ geometry: PipelineOutputGeometry,
108
+ ):
109
+ raise NotImplementedError("ModifyGeometry is not implemented yet.")
110
+ super().__init__(
111
+ task_name,
112
+ {"mods": mods},
113
+ OperatorInputs(self, geometry=(PipelineOutputGeometry, geometry)),
114
+ ModifyGeometryOutputs._instantiate_for(self),
115
+ )
116
+
117
+
118
+ @dataclass
119
+ class MeshOutputs(OperatorOutputs):
120
+ mesh: PipelineOutputMesh
121
+ """The Mesh generated from the given Geometry."""
122
+
123
+
124
+ @experimental
125
+ class Mesh(Operator[MeshOutputs]):
126
+ """
127
+ Generates a Mesh from a Geometry.
128
+
129
+ Parameters
130
+ ----------
131
+ mesh_gen_params : MeshGenerationParams
132
+ The parameters to use for mesh generation.
133
+ geometry : PipelineOutputGeometry
134
+ The Geometry to mesh.
135
+
136
+ Outputs
137
+ -------
138
+ mesh : PipelineOutputMesh
139
+ The generated Mesh.
140
+
141
+ .. warning:: This feature is experimental and may change or be removed in the future.
142
+ """
143
+
144
+ def __init__(
145
+ self,
146
+ *,
147
+ task_name: str | None = None,
148
+ mesh_gen_params: MeshGenerationParams,
149
+ geometry: PipelineOutputGeometry,
150
+ ):
151
+ super().__init__(
152
+ task_name,
153
+ {"mesh_gen_params": mesh_gen_params},
154
+ OperatorInputs(self, geometry=(PipelineOutputGeometry, geometry)),
155
+ MeshOutputs._instantiate_for(self),
156
+ )
157
+
158
+
159
+ @dataclass
160
+ class SimulateOutputs(OperatorOutputs):
161
+ simulation: PipelineOutputSimulation
162
+ """The Simulation."""
163
+
164
+
165
+ @experimental
166
+ class Simulate(Operator[SimulateOutputs]):
167
+ """
168
+ Runs a Simulation.
169
+
170
+ Parameters
171
+ ----------
172
+ sim_template_id : str | StringPipelineParameter
173
+ The ID of the SimulationTemplate to use for the Simulation.
174
+ mesh : PipelineOutputMesh
175
+ The Mesh to use for the Simulation.
176
+
177
+ Outputs
178
+ -------
179
+ simulation : PipelineOutputSimulation
180
+ The Simulation.
181
+
182
+ .. warning:: This feature is experimental and may change or be removed in the future.
183
+ """
184
+
185
+ def __init__(
186
+ self,
187
+ *,
188
+ task_name: str | None = None,
189
+ sim_template_id: str | StringPipelineParameter,
190
+ mesh: PipelineOutputMesh,
191
+ ):
192
+ super().__init__(
193
+ task_name,
194
+ {"sim_template_id": sim_template_id},
195
+ OperatorInputs(self, mesh=(PipelineOutputMesh, mesh)),
196
+ SimulateOutputs._instantiate_for(self),
197
+ )
@@ -0,0 +1,42 @@
1
+ # Copyright 2025 Luminary Cloud, Inc. All Rights Reserved.
2
+ from .core import PipelineParameter
3
+
4
+
5
+ class StringPipelineParameter(PipelineParameter):
6
+ """
7
+ A String Pipeline Parameter can replace a hard-coded string in Pipeline operator arguments to
8
+ allow its value to be set when the Pipeline is invoked.
9
+ """
10
+
11
+ def _type(self) -> str:
12
+ return "string"
13
+
14
+
15
+ class FloatPipelineParameter(PipelineParameter):
16
+ """
17
+ A Float Pipeline Parameter can replace a hard-coded float in Pipeline operator arguments to
18
+ allow its value to be set when the Pipeline is invoked.
19
+ """
20
+
21
+ def _type(self) -> str:
22
+ return "float"
23
+
24
+
25
+ class IntPipelineParameter(PipelineParameter):
26
+ """
27
+ An Int Pipeline Parameter can replace a hard-coded int in Pipeline operator arguments to
28
+ allow its value to be set when the Pipeline is invoked.
29
+ """
30
+
31
+ def _type(self) -> str:
32
+ return "int"
33
+
34
+
35
+ class BoolPipelineParameter(PipelineParameter):
36
+ """
37
+ A Bool Pipeline Parameter can replace a hard-coded bool in Pipeline operator arguments to
38
+ allow its value to be set when the Pipeline is invoked.
39
+ """
40
+
41
+ def _type(self) -> str:
42
+ return "bool"
luminarycloud/project.py CHANGED
@@ -331,8 +331,7 @@ class Project(ProtoWrapperBase):
331
331
  existing geometry, use MeshGenerationParams. If adapting a mesh from an existing,
332
332
  solution use MeshAdaptationParams.
333
333
  name : str
334
- (Optional) Mesh name. Max 256 characters. Will be ignored if a mesh with the same
335
- parameters already exists.
334
+ Mesh name. Max 256 characters.
336
335
  """
337
336
 
338
337
  try:
@@ -352,6 +351,7 @@ class Project(ProtoWrapperBase):
352
351
  self,
353
352
  names_to_file_paths: Dict[str, Union[PathLike[Any], str]],
354
353
  params: hexmeshpb.HexMeshSpec,
354
+ use_internal_wrap: bool = False,
355
355
  ) -> "Mesh":
356
356
  """
357
357
  Creates a hex mesh. Only for internal use.
@@ -387,6 +387,8 @@ class Project(ProtoWrapperBase):
387
387
  for name, url in names_to_uploaded_file_paths.items():
388
388
  params.names_to_file_urls[name] = url
389
389
 
390
+ params.use_wrap = use_internal_wrap
391
+
390
392
  req = meshpb.CreateHexMeshRequest(project_id=self.id, hex_mesh_config=params)
391
393
 
392
394
  res: meshpb.CreateHexMeshResponse = client.CreateHexMesh(req)
@@ -573,11 +575,9 @@ class Project(ProtoWrapperBase):
573
575
  int(params_json_path is not None)
574
576
  + int(parameters is not None)
575
577
  + int(copy_from is not None)
576
- != 1
578
+ > 1
577
579
  ):
578
- raise ValueError(
579
- "Exactly one of parameters, params_json_path, or copy_from must be set"
580
- )
580
+ raise ValueError("Only one of parameters, params_json_path, or copy_from can be set")
581
581
 
582
582
  param_proto: clientpb.SimulationParam | None = None
583
583
  copy_from_id: str | None = None
@@ -291,6 +291,10 @@ class Simulation(ProtoWrapperBase):
291
291
  return [Solution(s) for s in res.solutions]
292
292
 
293
293
  def get_parameters(self) -> SimulationParam:
294
+ """
295
+ Returns the simulation parameters associated with this simulation to allow customization of
296
+ the parameters.
297
+ """
294
298
  req = simulationpb.GetSimulationParametersRequest(id=self.id)
295
299
  return SimulationParam.from_proto(get_default_client().GetSimulationParameters(req))
296
300
 
@@ -298,12 +302,39 @@ class Simulation(ProtoWrapperBase):
298
302
  "Use get_parameters() instead. This method will be removed in a future release.",
299
303
  )
300
304
  def get_simulation_param(self) -> SimulationParam:
301
- """
302
- Returns the simulation parameters associated with this template to allow customization of
303
- the parameters.
304
- """
305
305
  return self.get_parameters()
306
306
 
307
+ # This is used by the assistant for the SDK Code shown in the Results tab.
308
+ def _to_code(self) -> str:
309
+ return f"""# This code shows how to modify the parameters of the current simulation to create a new one.
310
+ import luminarycloud as lc
311
+
312
+ current_simulation = lc.get_simulation("{self.id}")
313
+ params = current_simulation.get_parameters()
314
+
315
+ # TODO(USER): Modify the parameters.
316
+ # You can use params.find_parameter to help you find the parameters you wish to modify.
317
+ # params.find_parameter("mach")
318
+ # Alternatively, the Simulation SDK Code shown in the Setup tab, shows how to create the
319
+ # entire params object from scratch. The following line produces a similar result.
320
+ # print(params.to_code())
321
+
322
+ project = lc.get_project("{self.project_id}")
323
+ # Modify the setup (synced with UI), or an existing template, or create a new one.
324
+ template = project.list_simulation_templates()[0]
325
+ # template = lc.get_simulation_template("...")
326
+ # template = project.create_simulation_template("New Template")
327
+
328
+ template.update(parameters=params)
329
+
330
+ # NOTE: This starts a new simulation.
331
+ # This uses the mesh from the current simulation, you can use project.list_meshes() to find
332
+ # other meshes available in the project.
333
+ simulation = project.create_simulation(current_simulation.mesh_id, "New Simulation", template.id)
334
+ # Waiting for the simulation to finish is optional.
335
+ status = simulation.wait()
336
+ """
337
+
307
338
 
308
339
  def get_simulation(id: SimulationID) -> Simulation:
309
340
  """
@@ -308,37 +308,41 @@ class SimulationParam(_SimulationParam):
308
308
  def __repr__(self) -> str:
309
309
  return pformat(vars(self), compact=True, sort_dicts=True)
310
310
 
311
- def to_code(self, hide_defaults: bool = True) -> str:
311
+ def to_code(self, hide_defaults: bool = True, use_tmp_objs: bool = True) -> str:
312
312
  """
313
313
  Returns the python code that generates an identical SimulationParam object.
314
- This is a verbose representation that does not make use of helper functions like
314
+ This is an automatic representation that does not make use of helper functions like
315
315
  assign_material and assign_physics.
316
316
  Parameters with default values are omitted by default, with hide_defaults=False, code will
317
317
  also be generated for those parameters.
318
+ By default the idiom used to populate lists and maps is to create a temporary object and
319
+ then append or insert, respectively. The alternative, using [] to access items, is used if
320
+ use_tmp_objs=False.
318
321
  """
319
- code = """## NOTE: This is a verbose representation of a SimulationParam object as the
320
- ## code that creates an identical object. The verbosity is intended to facilitate
321
- ## the discovery of available parameters.
322
+ code = """## NOTE: This is an automatic representation of a SimulationParam object as the
323
+ ## code that creates (from scratch) an identical object. As such, some parts do
324
+ ## not match 1 to 1 with hand-written examples.
322
325
  import luminarycloud
323
- from luminarycloud import *
324
326
  from luminarycloud.types import Vector3
327
+ from luminarycloud.tables import RectilinearTable
325
328
  from luminarycloud.enum import *
326
329
  from luminarycloud.params.enum import *
327
- from luminarycloud.params import simulation as params
330
+ from luminarycloud.params import simulation as sim_params
331
+ from luminarycloud import outputs, SimulationParam, EntityIdentifier
328
332
 
329
333
 
330
334
  """
331
- return code + self._to_code_helper("obj", hide_defaults).replace(
335
+ return code + self._to_code_helper("obj", hide_defaults, use_tmp_objs).replace(
332
336
  "luminarycloud.simulation_param.SimulationParam()", "SimulationParam()", 1
333
337
  )
334
338
 
335
339
  def find_parameter(self, parameter: str) -> None:
336
340
  """
337
- Searches the full output of "to_code()" for occurrences of "parameter" and prints the lines
338
- with matches. This is a helper function to assist users in finding, for example, how to
339
- change farfield conditions, material properties, etc.
341
+ Searches the full output of "to_code(False, False)" for occurrences of "parameter" and prints
342
+ the lines with matches. This is a helper function to assist users in finding, for example,
343
+ how to change farfield conditions, material properties, etc.
340
344
  """
341
- for i, line in enumerate(self.to_code(False).split("\n")):
345
+ for i, line in enumerate(self.to_code(False, False).split("\n")):
342
346
  if parameter.lower() in line.lower():
343
347
  print(f"line {i}: {line}")
344
348
 
@@ -159,7 +159,10 @@ class SimulationTemplate(ProtoWrapperBase):
159
159
  get_default_client().DeleteSimulationTemplate(req)
160
160
 
161
161
  def get_parameters(self) -> SimulationParam:
162
- return self.get_simulation_param()
162
+ """
163
+ Returns a copy of the simulation parameters associated with this template.
164
+ """
165
+ return SimulationParam.from_proto(self._proto.parameters)
163
166
 
164
167
  @deprecated(
165
168
  "Use get_parameters() instead. This method will be removed in a future release.",
@@ -168,7 +171,7 @@ class SimulationTemplate(ProtoWrapperBase):
168
171
  """
169
172
  Returns a copy of the simulation parameters associated with this template.
170
173
  """
171
- return SimulationParam.from_proto(self._proto.parameters)
174
+ return self.get_parameters()
172
175
 
173
176
  def list_tables(
174
177
  self,
@@ -460,20 +463,21 @@ class SimulationTemplate(ProtoWrapperBase):
460
463
 
461
464
  def to_code(self, hide_defaults: bool = True) -> str:
462
465
  """
463
- Returns the python code that generates an identical SimulationTemplate object.
466
+ Returns the python code that generates (from scratch) an identical SimulationTemplate object.
464
467
  By default parameters with default values are omitted, this change be changed with
465
468
  hide_defaults=False.
466
469
  """
467
- param_code = self.get_simulation_param().to_code(hide_defaults)
470
+ param_code = self.get_parameters().to_code(hide_defaults)
468
471
  code, param_code = param_code.split("\n\n\n")
469
472
 
470
473
  # Modify the header note included by SimulationParam.to_code.
471
474
  code = code.replace("SimulationParam", "SimulationTemplate")
472
475
  code += "\n\n\n"
473
- code += '# Create a new simulation template or modify the "Setup" template.\n'
476
+ code += '# Create a new simulation template or modify the one that is synced with the UI "Setup" tab.\n'
477
+ code += "# TODO(USER): Replace ... with project ID, or create a project.\n"
474
478
  code += 'project = get_project("...")\n'
475
479
  code += f"# template = project.create_simulation_template(name={self.name})\n"
476
- code += "template = project.list_simulation_templates()[0]\n\n"
480
+ code += "template = project.list_simulation_templates()[0] # Setup template\n\n"
477
481
 
478
482
  code += "# Define the simulation parameters.\n"
479
483
  code += param_code
@@ -8,10 +8,11 @@ from .adfloat import (
8
8
  _to_ad_proto as _float_to_ad_proto,
9
9
  _from_ad_proto as _float_from_ad_proto,
10
10
  )
11
+ from ..pipeline_util.dictable import PipelineDictable
11
12
 
12
13
 
13
14
  @dataclass
14
- class Vector3:
15
+ class Vector3(PipelineDictable):
15
16
  """Represents a 3-dimensional vector.
16
17
 
17
18
  Supports direct component access, indexing, iteration, and conversion to numpy arrays.