gsim 0.0.2__py3-none-any.whl → 0.0.4__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.
- gsim/__init__.py +1 -1
- gsim/common/__init__.py +9 -13
- gsim/common/stack/extractor.py +4 -4
- gsim/common/stack/materials.py +2 -2
- gsim/common/stack/visualization.py +3 -3
- gsim/gcloud.py +80 -25
- gsim/palace/__init__.py +53 -64
- gsim/palace/base.py +313 -8
- gsim/palace/driven.py +26 -302
- gsim/palace/eigenmode.py +44 -264
- gsim/palace/electrostatic.py +35 -259
- gsim/palace/mesh/__init__.py +13 -1
- gsim/palace/mesh/config_generator.py +367 -0
- gsim/palace/mesh/generator.py +40 -899
- gsim/palace/mesh/geometry.py +472 -0
- gsim/palace/mesh/groups.py +170 -0
- gsim/palace/models/__init__.py +8 -15
- gsim/palace/models/mesh.py +9 -9
- gsim/palace/models/numerical.py +9 -9
- gsim/palace/models/ports.py +4 -5
- gsim/palace/models/problems.py +1 -1
- gsim/palace/models/results.py +5 -4
- gsim/viz.py +9 -6
- {gsim-0.0.2.dist-info → gsim-0.0.4.dist-info}/METADATA +7 -6
- gsim-0.0.4.dist-info/RECORD +35 -0
- {gsim-0.0.2.dist-info → gsim-0.0.4.dist-info}/WHEEL +1 -1
- gsim-0.0.2.dist-info/RECORD +0 -32
- {gsim-0.0.2.dist-info → gsim-0.0.4.dist-info}/top_level.txt +0 -0
gsim/palace/eigenmode.py
CHANGED
|
@@ -8,11 +8,8 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
import tempfile
|
|
11
|
-
import warnings
|
|
12
11
|
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
15
|
-
from typing import TYPE_CHECKING, Any, Literal
|
|
12
|
+
from typing import Any, Literal
|
|
16
13
|
|
|
17
14
|
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
|
|
18
15
|
|
|
@@ -29,16 +26,14 @@ from gsim.palace.models import (
|
|
|
29
26
|
ValidationResult,
|
|
30
27
|
)
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
from gdsfactory.component import Component
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
34
30
|
|
|
35
31
|
|
|
36
32
|
class EigenmodeSim(PalaceSimMixin, BaseModel):
|
|
37
33
|
"""Eigenmode simulation for finding resonant frequencies.
|
|
38
34
|
|
|
39
35
|
This class configures and runs eigenmode simulations to find
|
|
40
|
-
resonant frequencies and mode shapes of structures.
|
|
41
|
-
(no inheritance) with shared Geometry and Stack components from gsim.common.
|
|
36
|
+
resonant frequencies and mode shapes of structures.
|
|
42
37
|
|
|
43
38
|
Example:
|
|
44
39
|
>>> from gsim.palace import EigenmodeSim
|
|
@@ -48,7 +43,8 @@ class EigenmodeSim(PalaceSimMixin, BaseModel):
|
|
|
48
43
|
>>> sim.set_stack(air_above=300.0)
|
|
49
44
|
>>> sim.add_port("o1", layer="topmetal2", length=5.0)
|
|
50
45
|
>>> sim.set_eigenmode(num_modes=10, target=50e9)
|
|
51
|
-
>>> sim.
|
|
46
|
+
>>> sim.set_output_dir("./sim")
|
|
47
|
+
>>> sim.mesh(preset="default")
|
|
52
48
|
>>> results = sim.simulate()
|
|
53
49
|
|
|
54
50
|
Attributes:
|
|
@@ -89,66 +85,7 @@ class EigenmodeSim(PalaceSimMixin, BaseModel):
|
|
|
89
85
|
_configured_ports: bool = PrivateAttr(default=False)
|
|
90
86
|
|
|
91
87
|
# -------------------------------------------------------------------------
|
|
92
|
-
#
|
|
93
|
-
# -------------------------------------------------------------------------
|
|
94
|
-
|
|
95
|
-
def set_geometry(self, component: Component) -> None:
|
|
96
|
-
"""Set the gdsfactory component for simulation.
|
|
97
|
-
|
|
98
|
-
Args:
|
|
99
|
-
component: gdsfactory Component to simulate
|
|
100
|
-
|
|
101
|
-
Example:
|
|
102
|
-
>>> sim.set_geometry(my_component)
|
|
103
|
-
"""
|
|
104
|
-
self.geometry = Geometry(component=component)
|
|
105
|
-
|
|
106
|
-
@property
|
|
107
|
-
def component(self) -> Component | None:
|
|
108
|
-
"""Get the current component (for backward compatibility)."""
|
|
109
|
-
return self.geometry.component if self.geometry else None
|
|
110
|
-
|
|
111
|
-
@property
|
|
112
|
-
def _component(self) -> Component | None:
|
|
113
|
-
"""Internal component access (backward compatibility)."""
|
|
114
|
-
return self.component
|
|
115
|
-
|
|
116
|
-
# -------------------------------------------------------------------------
|
|
117
|
-
# Stack methods
|
|
118
|
-
# -------------------------------------------------------------------------
|
|
119
|
-
|
|
120
|
-
def set_stack(
|
|
121
|
-
self,
|
|
122
|
-
*,
|
|
123
|
-
yaml_path: str | Path | None = None,
|
|
124
|
-
air_above: float = 200.0,
|
|
125
|
-
substrate_thickness: float = 2.0,
|
|
126
|
-
include_substrate: bool = False,
|
|
127
|
-
**kwargs,
|
|
128
|
-
) -> None:
|
|
129
|
-
"""Configure the layer stack.
|
|
130
|
-
|
|
131
|
-
Args:
|
|
132
|
-
yaml_path: Path to custom YAML stack file
|
|
133
|
-
air_above: Air box height above top metal in um
|
|
134
|
-
substrate_thickness: Thickness below z=0 in um
|
|
135
|
-
include_substrate: Include lossy silicon substrate
|
|
136
|
-
**kwargs: Additional args passed to extract_layer_stack
|
|
137
|
-
|
|
138
|
-
Example:
|
|
139
|
-
>>> sim.set_stack(air_above=300.0, substrate_thickness=2.0)
|
|
140
|
-
"""
|
|
141
|
-
self._stack_kwargs = {
|
|
142
|
-
"yaml_path": yaml_path,
|
|
143
|
-
"air_above": air_above,
|
|
144
|
-
"substrate_thickness": substrate_thickness,
|
|
145
|
-
"include_substrate": include_substrate,
|
|
146
|
-
**kwargs,
|
|
147
|
-
}
|
|
148
|
-
self.stack = None
|
|
149
|
-
|
|
150
|
-
# -------------------------------------------------------------------------
|
|
151
|
-
# Port methods
|
|
88
|
+
# Port methods (Eigenmode can have ports for Q-factor calculation)
|
|
152
89
|
# -------------------------------------------------------------------------
|
|
153
90
|
|
|
154
91
|
def add_port(
|
|
@@ -259,86 +196,11 @@ class EigenmodeSim(PalaceSimMixin, BaseModel):
|
|
|
259
196
|
tolerance=tolerance,
|
|
260
197
|
)
|
|
261
198
|
|
|
262
|
-
# -------------------------------------------------------------------------
|
|
263
|
-
# Material methods
|
|
264
|
-
# -------------------------------------------------------------------------
|
|
265
|
-
|
|
266
|
-
def set_material(
|
|
267
|
-
self,
|
|
268
|
-
name: str,
|
|
269
|
-
*,
|
|
270
|
-
type: Literal["conductor", "dielectric", "semiconductor"] | None = None,
|
|
271
|
-
conductivity: float | None = None,
|
|
272
|
-
permittivity: float | None = None,
|
|
273
|
-
loss_tangent: float | None = None,
|
|
274
|
-
) -> None:
|
|
275
|
-
"""Override or add material properties.
|
|
276
|
-
|
|
277
|
-
Args:
|
|
278
|
-
name: Material name
|
|
279
|
-
type: Material type (conductor, dielectric, semiconductor)
|
|
280
|
-
conductivity: Conductivity in S/m (for conductors)
|
|
281
|
-
permittivity: Relative permittivity (for dielectrics)
|
|
282
|
-
loss_tangent: Dielectric loss tangent
|
|
283
|
-
|
|
284
|
-
Example:
|
|
285
|
-
>>> sim.set_material("aluminum", type="conductor", conductivity=3.8e7)
|
|
286
|
-
"""
|
|
287
|
-
if type is None:
|
|
288
|
-
if conductivity is not None and conductivity > 1e4:
|
|
289
|
-
type = "conductor"
|
|
290
|
-
elif permittivity is not None:
|
|
291
|
-
type = "dielectric"
|
|
292
|
-
else:
|
|
293
|
-
type = "dielectric"
|
|
294
|
-
|
|
295
|
-
self.materials[name] = MaterialConfig(
|
|
296
|
-
type=type,
|
|
297
|
-
conductivity=conductivity,
|
|
298
|
-
permittivity=permittivity,
|
|
299
|
-
loss_tangent=loss_tangent,
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
def set_numerical(
|
|
303
|
-
self,
|
|
304
|
-
*,
|
|
305
|
-
order: int = 2,
|
|
306
|
-
tolerance: float = 1e-6,
|
|
307
|
-
max_iterations: int = 400,
|
|
308
|
-
solver_type: Literal["Default", "SuperLU", "STRUMPACK", "MUMPS"] = "Default",
|
|
309
|
-
preconditioner: Literal["Default", "AMS", "BoomerAMG"] = "Default",
|
|
310
|
-
device: Literal["CPU", "GPU"] = "CPU",
|
|
311
|
-
num_processors: int | None = None,
|
|
312
|
-
) -> None:
|
|
313
|
-
"""Configure numerical solver parameters.
|
|
314
|
-
|
|
315
|
-
Args:
|
|
316
|
-
order: Finite element order (1-4)
|
|
317
|
-
tolerance: Linear solver tolerance
|
|
318
|
-
max_iterations: Maximum solver iterations
|
|
319
|
-
solver_type: Linear solver type
|
|
320
|
-
preconditioner: Preconditioner type
|
|
321
|
-
device: Compute device (CPU or GPU)
|
|
322
|
-
num_processors: Number of processors (None = auto)
|
|
323
|
-
|
|
324
|
-
Example:
|
|
325
|
-
>>> sim.set_numerical(order=3, tolerance=1e-8)
|
|
326
|
-
"""
|
|
327
|
-
self.numerical = NumericalConfig(
|
|
328
|
-
order=order,
|
|
329
|
-
tolerance=tolerance,
|
|
330
|
-
max_iterations=max_iterations,
|
|
331
|
-
solver_type=solver_type,
|
|
332
|
-
preconditioner=preconditioner,
|
|
333
|
-
device=device,
|
|
334
|
-
num_processors=num_processors,
|
|
335
|
-
)
|
|
336
|
-
|
|
337
199
|
# -------------------------------------------------------------------------
|
|
338
200
|
# Validation
|
|
339
201
|
# -------------------------------------------------------------------------
|
|
340
202
|
|
|
341
|
-
def
|
|
203
|
+
def validate_config(self) -> ValidationResult:
|
|
342
204
|
"""Validate the simulation configuration.
|
|
343
205
|
|
|
344
206
|
Returns:
|
|
@@ -360,18 +222,19 @@ class EigenmodeSim(PalaceSimMixin, BaseModel):
|
|
|
360
222
|
# Eigenmode simulations may not require ports
|
|
361
223
|
if not self.ports and not self.cpw_ports:
|
|
362
224
|
warnings_list.append(
|
|
363
|
-
"No ports configured. Eigenmode
|
|
225
|
+
"No ports configured. Eigenmode finds all modes without port loading."
|
|
364
226
|
)
|
|
365
227
|
|
|
366
228
|
# Validate port configurations
|
|
367
229
|
for port in self.ports:
|
|
368
230
|
if port.geometry == "inplane" and port.layer is None:
|
|
369
231
|
errors.append(f"Port '{port.name}': inplane ports require 'layer'")
|
|
370
|
-
if port.geometry == "via"
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
232
|
+
if port.geometry == "via" and (
|
|
233
|
+
port.from_layer is None or port.to_layer is None
|
|
234
|
+
):
|
|
235
|
+
errors.append(
|
|
236
|
+
f"Port '{port.name}': via ports require 'from_layer' and 'to_layer'"
|
|
237
|
+
)
|
|
375
238
|
|
|
376
239
|
valid = len(errors) == 0
|
|
377
240
|
return ValidationResult(valid=valid, errors=errors, warnings=warnings_list)
|
|
@@ -380,22 +243,7 @@ class EigenmodeSim(PalaceSimMixin, BaseModel):
|
|
|
380
243
|
# Internal helpers
|
|
381
244
|
# -------------------------------------------------------------------------
|
|
382
245
|
|
|
383
|
-
def
|
|
384
|
-
"""Resolve the layer stack from PDK or YAML."""
|
|
385
|
-
from gsim.common.stack import get_stack
|
|
386
|
-
|
|
387
|
-
yaml_path = self._stack_kwargs.pop("yaml_path", None)
|
|
388
|
-
legacy_stack = get_stack(yaml_path=yaml_path, **self._stack_kwargs)
|
|
389
|
-
self._stack_kwargs["yaml_path"] = yaml_path
|
|
390
|
-
|
|
391
|
-
for name, props in self.materials.items():
|
|
392
|
-
legacy_stack.materials[name] = props.to_dict()
|
|
393
|
-
|
|
394
|
-
self.stack = legacy_stack
|
|
395
|
-
|
|
396
|
-
return legacy_stack
|
|
397
|
-
|
|
398
|
-
def _configure_ports_on_component(self, stack: LayerStack) -> None:
|
|
246
|
+
def _configure_ports_on_component(self, stack: LayerStack) -> None: # noqa: ARG002
|
|
399
247
|
"""Configure ports on the component."""
|
|
400
248
|
from gsim.palace.ports import (
|
|
401
249
|
configure_cpw_port,
|
|
@@ -423,7 +271,7 @@ class EigenmodeSim(PalaceSimMixin, BaseModel):
|
|
|
423
271
|
f"Available: {[p.name for p in component.ports]}"
|
|
424
272
|
)
|
|
425
273
|
|
|
426
|
-
if port_config.geometry == "inplane":
|
|
274
|
+
if port_config.geometry == "inplane" and port_config.layer is not None:
|
|
427
275
|
configure_inplane_port(
|
|
428
276
|
gf_port,
|
|
429
277
|
layer=port_config.layer,
|
|
@@ -431,7 +279,9 @@ class EigenmodeSim(PalaceSimMixin, BaseModel):
|
|
|
431
279
|
impedance=port_config.impedance,
|
|
432
280
|
excited=port_config.excited,
|
|
433
281
|
)
|
|
434
|
-
elif port_config.geometry == "via"
|
|
282
|
+
elif port_config.geometry == "via" and (
|
|
283
|
+
port_config.from_layer is not None and port_config.to_layer is not None
|
|
284
|
+
):
|
|
435
285
|
configure_via_port(
|
|
436
286
|
gf_port,
|
|
437
287
|
from_layer=port_config.from_layer,
|
|
@@ -466,57 +316,6 @@ class EigenmodeSim(PalaceSimMixin, BaseModel):
|
|
|
466
316
|
|
|
467
317
|
self._configured_ports = True
|
|
468
318
|
|
|
469
|
-
def _build_mesh_config(
|
|
470
|
-
self,
|
|
471
|
-
preset: Literal["coarse", "default", "fine"] | None,
|
|
472
|
-
refined_mesh_size: float | None,
|
|
473
|
-
max_mesh_size: float | None,
|
|
474
|
-
margin: float | None,
|
|
475
|
-
air_above: float | None,
|
|
476
|
-
fmax: float | None,
|
|
477
|
-
show_gui: bool,
|
|
478
|
-
) -> MeshConfig:
|
|
479
|
-
"""Build mesh config from preset with optional overrides."""
|
|
480
|
-
if preset == "coarse":
|
|
481
|
-
mesh_config = MeshConfig.coarse()
|
|
482
|
-
elif preset == "fine":
|
|
483
|
-
mesh_config = MeshConfig.fine()
|
|
484
|
-
else:
|
|
485
|
-
mesh_config = MeshConfig.default()
|
|
486
|
-
|
|
487
|
-
overrides = []
|
|
488
|
-
if preset is not None:
|
|
489
|
-
if refined_mesh_size is not None:
|
|
490
|
-
overrides.append(f"refined_mesh_size={refined_mesh_size}")
|
|
491
|
-
if max_mesh_size is not None:
|
|
492
|
-
overrides.append(f"max_mesh_size={max_mesh_size}")
|
|
493
|
-
if margin is not None:
|
|
494
|
-
overrides.append(f"margin={margin}")
|
|
495
|
-
if air_above is not None:
|
|
496
|
-
overrides.append(f"air_above={air_above}")
|
|
497
|
-
if fmax is not None:
|
|
498
|
-
overrides.append(f"fmax={fmax}")
|
|
499
|
-
|
|
500
|
-
if overrides:
|
|
501
|
-
warnings.warn(
|
|
502
|
-
f"Preset '{preset}' values overridden by: {', '.join(overrides)}",
|
|
503
|
-
stacklevel=4,
|
|
504
|
-
)
|
|
505
|
-
|
|
506
|
-
if refined_mesh_size is not None:
|
|
507
|
-
mesh_config.refined_mesh_size = refined_mesh_size
|
|
508
|
-
if max_mesh_size is not None:
|
|
509
|
-
mesh_config.max_mesh_size = max_mesh_size
|
|
510
|
-
if margin is not None:
|
|
511
|
-
mesh_config.margin = margin
|
|
512
|
-
if air_above is not None:
|
|
513
|
-
mesh_config.air_above = air_above
|
|
514
|
-
if fmax is not None:
|
|
515
|
-
mesh_config.fmax = fmax
|
|
516
|
-
mesh_config.show_gui = show_gui
|
|
517
|
-
|
|
518
|
-
return mesh_config
|
|
519
|
-
|
|
520
319
|
def _generate_mesh_internal(
|
|
521
320
|
self,
|
|
522
321
|
output_dir: Path,
|
|
@@ -609,11 +408,9 @@ class EigenmodeSim(PalaceSimMixin, BaseModel):
|
|
|
609
408
|
|
|
610
409
|
component = self.geometry.component if self.geometry else None
|
|
611
410
|
|
|
612
|
-
validation = self.
|
|
411
|
+
validation = self.validate_config()
|
|
613
412
|
if not validation.valid:
|
|
614
|
-
raise ValueError(
|
|
615
|
-
f"Invalid configuration:\n" + "\n".join(validation.errors)
|
|
616
|
-
)
|
|
413
|
+
raise ValueError("Invalid configuration:\n" + "\n".join(validation.errors))
|
|
617
414
|
|
|
618
415
|
mesh_config = self._build_mesh_config(
|
|
619
416
|
preset=preset,
|
|
@@ -648,37 +445,12 @@ class EigenmodeSim(PalaceSimMixin, BaseModel):
|
|
|
648
445
|
config=legacy_mesh_config,
|
|
649
446
|
)
|
|
650
447
|
|
|
651
|
-
# -------------------------------------------------------------------------
|
|
652
|
-
# Convenience methods
|
|
653
|
-
# -------------------------------------------------------------------------
|
|
654
|
-
|
|
655
|
-
def show_stack(self) -> None:
|
|
656
|
-
"""Print the layer stack table."""
|
|
657
|
-
from gsim.common.stack import print_stack_table
|
|
658
|
-
|
|
659
|
-
if self.stack is None:
|
|
660
|
-
self._resolve_stack()
|
|
661
|
-
|
|
662
|
-
if self.stack is not None:
|
|
663
|
-
print_stack_table(self.stack)
|
|
664
|
-
|
|
665
|
-
def plot_stack(self) -> None:
|
|
666
|
-
"""Plot the layer stack visualization."""
|
|
667
|
-
from gsim.common.stack import plot_stack
|
|
668
|
-
|
|
669
|
-
if self.stack is None:
|
|
670
|
-
self._resolve_stack()
|
|
671
|
-
|
|
672
|
-
if self.stack is not None:
|
|
673
|
-
plot_stack(self.stack)
|
|
674
|
-
|
|
675
448
|
# -------------------------------------------------------------------------
|
|
676
449
|
# Mesh generation
|
|
677
450
|
# -------------------------------------------------------------------------
|
|
678
451
|
|
|
679
452
|
def mesh(
|
|
680
453
|
self,
|
|
681
|
-
output_dir: str | Path,
|
|
682
454
|
*,
|
|
683
455
|
preset: Literal["coarse", "default", "fine"] | None = None,
|
|
684
456
|
refined_mesh_size: float | None = None,
|
|
@@ -690,25 +462,37 @@ class EigenmodeSim(PalaceSimMixin, BaseModel):
|
|
|
690
462
|
model_name: str = "palace",
|
|
691
463
|
verbose: bool = True,
|
|
692
464
|
) -> SimulationResult:
|
|
693
|
-
"""Generate the mesh
|
|
465
|
+
"""Generate the mesh for Palace simulation.
|
|
466
|
+
|
|
467
|
+
Requires set_output_dir() to be called first.
|
|
694
468
|
|
|
695
469
|
Args:
|
|
696
|
-
output_dir: Directory for output files
|
|
697
470
|
preset: Mesh quality preset ("coarse", "default", "fine")
|
|
698
|
-
refined_mesh_size: Mesh size near conductors (um)
|
|
699
|
-
max_mesh_size: Max mesh size in air/dielectric (um)
|
|
700
|
-
margin: XY margin around design (um)
|
|
701
|
-
air_above: Air above top metal (um)
|
|
702
|
-
fmax: Max frequency for mesh sizing (Hz)
|
|
471
|
+
refined_mesh_size: Mesh size near conductors (um), overrides preset
|
|
472
|
+
max_mesh_size: Max mesh size in air/dielectric (um), overrides preset
|
|
473
|
+
margin: XY margin around design (um), overrides preset
|
|
474
|
+
air_above: Air above top metal (um), overrides preset
|
|
475
|
+
fmax: Max frequency for mesh sizing (Hz), overrides preset
|
|
703
476
|
show_gui: Show gmsh GUI during meshing
|
|
704
477
|
model_name: Base name for output files
|
|
705
478
|
verbose: Print progress messages
|
|
706
479
|
|
|
707
480
|
Returns:
|
|
708
|
-
SimulationResult with mesh
|
|
481
|
+
SimulationResult with mesh path
|
|
482
|
+
|
|
483
|
+
Raises:
|
|
484
|
+
ValueError: If output_dir not set or configuration is invalid
|
|
485
|
+
|
|
486
|
+
Example:
|
|
487
|
+
>>> sim.set_output_dir("./sim")
|
|
488
|
+
>>> result = sim.mesh(preset="fine")
|
|
489
|
+
>>> print(f"Mesh saved to: {result.mesh_path}")
|
|
709
490
|
"""
|
|
710
491
|
from gsim.palace.ports import extract_ports
|
|
711
492
|
|
|
493
|
+
if self._output_dir is None:
|
|
494
|
+
raise ValueError("Output directory not set. Call set_output_dir() first.")
|
|
495
|
+
|
|
712
496
|
component = self.geometry.component if self.geometry else None
|
|
713
497
|
|
|
714
498
|
mesh_config = self._build_mesh_config(
|
|
@@ -721,15 +505,11 @@ class EigenmodeSim(PalaceSimMixin, BaseModel):
|
|
|
721
505
|
show_gui=show_gui,
|
|
722
506
|
)
|
|
723
507
|
|
|
724
|
-
validation = self.
|
|
508
|
+
validation = self.validate_config()
|
|
725
509
|
if not validation.valid:
|
|
726
|
-
raise ValueError(
|
|
727
|
-
f"Invalid configuration:\n" + "\n".join(validation.errors)
|
|
728
|
-
)
|
|
510
|
+
raise ValueError("Invalid configuration:\n" + "\n".join(validation.errors))
|
|
729
511
|
|
|
730
|
-
output_dir =
|
|
731
|
-
output_dir.mkdir(parents=True, exist_ok=True)
|
|
732
|
-
self._output_dir = output_dir
|
|
512
|
+
output_dir = self._output_dir
|
|
733
513
|
|
|
734
514
|
stack = self._resolve_stack()
|
|
735
515
|
|