gsim 0.0.2__py3-none-any.whl → 0.0.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.
- 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 +78 -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.3.dist-info}/METADATA +6 -5
- gsim-0.0.3.dist-info/RECORD +35 -0
- {gsim-0.0.2.dist-info → gsim-0.0.3.dist-info}/WHEEL +1 -1
- gsim-0.0.2.dist-info/RECORD +0 -32
- {gsim-0.0.2.dist-info → gsim-0.0.3.dist-info}/top_level.txt +0 -0
gsim/palace/electrostatic.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
|
|
|
@@ -28,8 +25,7 @@ from gsim.palace.models import (
|
|
|
28
25
|
ValidationResult,
|
|
29
26
|
)
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
from gdsfactory.component import Component
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
33
29
|
|
|
34
30
|
|
|
35
31
|
class ElectrostaticSim(PalaceSimMixin, BaseModel):
|
|
@@ -37,8 +33,7 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
|
|
|
37
33
|
|
|
38
34
|
This class configures and runs electrostatic simulations to extract
|
|
39
35
|
the capacitance matrix between conductor terminals. Unlike driven
|
|
40
|
-
and eigenmode simulations, this does not use ports.
|
|
41
|
-
(no inheritance) with shared Geometry and Stack components from gsim.common.
|
|
36
|
+
and eigenmode simulations, this does not use ports.
|
|
42
37
|
|
|
43
38
|
Example:
|
|
44
39
|
>>> from gsim.palace import ElectrostaticSim
|
|
@@ -49,7 +44,8 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
|
|
|
49
44
|
>>> sim.add_terminal("T1", layer="topmetal2")
|
|
50
45
|
>>> sim.add_terminal("T2", layer="topmetal2")
|
|
51
46
|
>>> sim.set_electrostatic()
|
|
52
|
-
>>> sim.
|
|
47
|
+
>>> sim.set_output_dir("./sim")
|
|
48
|
+
>>> sim.mesh(preset="default")
|
|
53
49
|
>>> results = sim.simulate()
|
|
54
50
|
|
|
55
51
|
Attributes:
|
|
@@ -87,65 +83,6 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
|
|
|
87
83
|
_output_dir: Path | None = PrivateAttr(default=None)
|
|
88
84
|
_configured_terminals: bool = PrivateAttr(default=False)
|
|
89
85
|
|
|
90
|
-
# -------------------------------------------------------------------------
|
|
91
|
-
# Geometry methods
|
|
92
|
-
# -------------------------------------------------------------------------
|
|
93
|
-
|
|
94
|
-
def set_geometry(self, component: Component) -> None:
|
|
95
|
-
"""Set the gdsfactory component for simulation.
|
|
96
|
-
|
|
97
|
-
Args:
|
|
98
|
-
component: gdsfactory Component to simulate
|
|
99
|
-
|
|
100
|
-
Example:
|
|
101
|
-
>>> sim.set_geometry(my_component)
|
|
102
|
-
"""
|
|
103
|
-
self.geometry = Geometry(component=component)
|
|
104
|
-
|
|
105
|
-
@property
|
|
106
|
-
def component(self) -> Component | None:
|
|
107
|
-
"""Get the current component (for backward compatibility)."""
|
|
108
|
-
return self.geometry.component if self.geometry else None
|
|
109
|
-
|
|
110
|
-
@property
|
|
111
|
-
def _component(self) -> Component | None:
|
|
112
|
-
"""Internal component access (backward compatibility)."""
|
|
113
|
-
return self.component
|
|
114
|
-
|
|
115
|
-
# -------------------------------------------------------------------------
|
|
116
|
-
# Stack methods
|
|
117
|
-
# -------------------------------------------------------------------------
|
|
118
|
-
|
|
119
|
-
def set_stack(
|
|
120
|
-
self,
|
|
121
|
-
*,
|
|
122
|
-
yaml_path: str | Path | None = None,
|
|
123
|
-
air_above: float = 200.0,
|
|
124
|
-
substrate_thickness: float = 2.0,
|
|
125
|
-
include_substrate: bool = False,
|
|
126
|
-
**kwargs,
|
|
127
|
-
) -> None:
|
|
128
|
-
"""Configure the layer stack.
|
|
129
|
-
|
|
130
|
-
Args:
|
|
131
|
-
yaml_path: Path to custom YAML stack file
|
|
132
|
-
air_above: Air box height above top metal in um
|
|
133
|
-
substrate_thickness: Thickness below z=0 in um
|
|
134
|
-
include_substrate: Include lossy silicon substrate
|
|
135
|
-
**kwargs: Additional args passed to extract_layer_stack
|
|
136
|
-
|
|
137
|
-
Example:
|
|
138
|
-
>>> sim.set_stack(air_above=300.0, substrate_thickness=2.0)
|
|
139
|
-
"""
|
|
140
|
-
self._stack_kwargs = {
|
|
141
|
-
"yaml_path": yaml_path,
|
|
142
|
-
"air_above": air_above,
|
|
143
|
-
"substrate_thickness": substrate_thickness,
|
|
144
|
-
"include_substrate": include_substrate,
|
|
145
|
-
**kwargs,
|
|
146
|
-
}
|
|
147
|
-
self.stack = None
|
|
148
|
-
|
|
149
86
|
# -------------------------------------------------------------------------
|
|
150
87
|
# Terminal methods
|
|
151
88
|
# -------------------------------------------------------------------------
|
|
@@ -198,86 +135,11 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
|
|
|
198
135
|
save_fields=save_fields,
|
|
199
136
|
)
|
|
200
137
|
|
|
201
|
-
# -------------------------------------------------------------------------
|
|
202
|
-
# Material methods
|
|
203
|
-
# -------------------------------------------------------------------------
|
|
204
|
-
|
|
205
|
-
def set_material(
|
|
206
|
-
self,
|
|
207
|
-
name: str,
|
|
208
|
-
*,
|
|
209
|
-
type: Literal["conductor", "dielectric", "semiconductor"] | None = None,
|
|
210
|
-
conductivity: float | None = None,
|
|
211
|
-
permittivity: float | None = None,
|
|
212
|
-
loss_tangent: float | None = None,
|
|
213
|
-
) -> None:
|
|
214
|
-
"""Override or add material properties.
|
|
215
|
-
|
|
216
|
-
Args:
|
|
217
|
-
name: Material name
|
|
218
|
-
type: Material type (conductor, dielectric, semiconductor)
|
|
219
|
-
conductivity: Conductivity in S/m (for conductors)
|
|
220
|
-
permittivity: Relative permittivity (for dielectrics)
|
|
221
|
-
loss_tangent: Dielectric loss tangent
|
|
222
|
-
|
|
223
|
-
Example:
|
|
224
|
-
>>> sim.set_material("aluminum", type="conductor", conductivity=3.8e7)
|
|
225
|
-
"""
|
|
226
|
-
if type is None:
|
|
227
|
-
if conductivity is not None and conductivity > 1e4:
|
|
228
|
-
type = "conductor"
|
|
229
|
-
elif permittivity is not None:
|
|
230
|
-
type = "dielectric"
|
|
231
|
-
else:
|
|
232
|
-
type = "dielectric"
|
|
233
|
-
|
|
234
|
-
self.materials[name] = MaterialConfig(
|
|
235
|
-
type=type,
|
|
236
|
-
conductivity=conductivity,
|
|
237
|
-
permittivity=permittivity,
|
|
238
|
-
loss_tangent=loss_tangent,
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
def set_numerical(
|
|
242
|
-
self,
|
|
243
|
-
*,
|
|
244
|
-
order: int = 2,
|
|
245
|
-
tolerance: float = 1e-6,
|
|
246
|
-
max_iterations: int = 400,
|
|
247
|
-
solver_type: Literal["Default", "SuperLU", "STRUMPACK", "MUMPS"] = "Default",
|
|
248
|
-
preconditioner: Literal["Default", "AMS", "BoomerAMG"] = "Default",
|
|
249
|
-
device: Literal["CPU", "GPU"] = "CPU",
|
|
250
|
-
num_processors: int | None = None,
|
|
251
|
-
) -> None:
|
|
252
|
-
"""Configure numerical solver parameters.
|
|
253
|
-
|
|
254
|
-
Args:
|
|
255
|
-
order: Finite element order (1-4)
|
|
256
|
-
tolerance: Linear solver tolerance
|
|
257
|
-
max_iterations: Maximum solver iterations
|
|
258
|
-
solver_type: Linear solver type
|
|
259
|
-
preconditioner: Preconditioner type
|
|
260
|
-
device: Compute device (CPU or GPU)
|
|
261
|
-
num_processors: Number of processors (None = auto)
|
|
262
|
-
|
|
263
|
-
Example:
|
|
264
|
-
>>> sim.set_numerical(order=3, tolerance=1e-8)
|
|
265
|
-
"""
|
|
266
|
-
self.numerical = NumericalConfig(
|
|
267
|
-
order=order,
|
|
268
|
-
tolerance=tolerance,
|
|
269
|
-
max_iterations=max_iterations,
|
|
270
|
-
solver_type=solver_type,
|
|
271
|
-
preconditioner=preconditioner,
|
|
272
|
-
device=device,
|
|
273
|
-
num_processors=num_processors,
|
|
274
|
-
)
|
|
275
|
-
|
|
276
138
|
# -------------------------------------------------------------------------
|
|
277
139
|
# Validation
|
|
278
140
|
# -------------------------------------------------------------------------
|
|
279
141
|
|
|
280
|
-
def
|
|
142
|
+
def validate_config(self) -> ValidationResult:
|
|
281
143
|
"""Validate the simulation configuration.
|
|
282
144
|
|
|
283
145
|
Returns:
|
|
@@ -304,9 +166,11 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
|
|
|
304
166
|
)
|
|
305
167
|
|
|
306
168
|
# Validate terminal configurations
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
169
|
+
errors.extend(
|
|
170
|
+
f"Terminal '{terminal.name}': 'layer' is required"
|
|
171
|
+
for terminal in self.terminals
|
|
172
|
+
if not terminal.layer
|
|
173
|
+
)
|
|
310
174
|
|
|
311
175
|
valid = len(errors) == 0
|
|
312
176
|
return ValidationResult(valid=valid, errors=errors, warnings=warnings_list)
|
|
@@ -315,71 +179,6 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
|
|
|
315
179
|
# Internal helpers
|
|
316
180
|
# -------------------------------------------------------------------------
|
|
317
181
|
|
|
318
|
-
def _resolve_stack(self) -> LayerStack:
|
|
319
|
-
"""Resolve the layer stack from PDK or YAML."""
|
|
320
|
-
from gsim.common.stack import get_stack
|
|
321
|
-
|
|
322
|
-
yaml_path = self._stack_kwargs.pop("yaml_path", None)
|
|
323
|
-
stack = get_stack(yaml_path=yaml_path, **self._stack_kwargs)
|
|
324
|
-
self._stack_kwargs["yaml_path"] = yaml_path
|
|
325
|
-
|
|
326
|
-
for name, props in self.materials.items():
|
|
327
|
-
stack.materials[name] = props.to_dict()
|
|
328
|
-
|
|
329
|
-
self.stack = stack
|
|
330
|
-
return stack
|
|
331
|
-
|
|
332
|
-
def _build_mesh_config(
|
|
333
|
-
self,
|
|
334
|
-
preset: Literal["coarse", "default", "fine"] | None,
|
|
335
|
-
refined_mesh_size: float | None,
|
|
336
|
-
max_mesh_size: float | None,
|
|
337
|
-
margin: float | None,
|
|
338
|
-
air_above: float | None,
|
|
339
|
-
fmax: float | None,
|
|
340
|
-
show_gui: bool,
|
|
341
|
-
) -> MeshConfig:
|
|
342
|
-
"""Build mesh config from preset with optional overrides."""
|
|
343
|
-
if preset == "coarse":
|
|
344
|
-
mesh_config = MeshConfig.coarse()
|
|
345
|
-
elif preset == "fine":
|
|
346
|
-
mesh_config = MeshConfig.fine()
|
|
347
|
-
else:
|
|
348
|
-
mesh_config = MeshConfig.default()
|
|
349
|
-
|
|
350
|
-
overrides = []
|
|
351
|
-
if preset is not None:
|
|
352
|
-
if refined_mesh_size is not None:
|
|
353
|
-
overrides.append(f"refined_mesh_size={refined_mesh_size}")
|
|
354
|
-
if max_mesh_size is not None:
|
|
355
|
-
overrides.append(f"max_mesh_size={max_mesh_size}")
|
|
356
|
-
if margin is not None:
|
|
357
|
-
overrides.append(f"margin={margin}")
|
|
358
|
-
if air_above is not None:
|
|
359
|
-
overrides.append(f"air_above={air_above}")
|
|
360
|
-
if fmax is not None:
|
|
361
|
-
overrides.append(f"fmax={fmax}")
|
|
362
|
-
|
|
363
|
-
if overrides:
|
|
364
|
-
warnings.warn(
|
|
365
|
-
f"Preset '{preset}' values overridden by: {', '.join(overrides)}",
|
|
366
|
-
stacklevel=4,
|
|
367
|
-
)
|
|
368
|
-
|
|
369
|
-
if refined_mesh_size is not None:
|
|
370
|
-
mesh_config.refined_mesh_size = refined_mesh_size
|
|
371
|
-
if max_mesh_size is not None:
|
|
372
|
-
mesh_config.max_mesh_size = max_mesh_size
|
|
373
|
-
if margin is not None:
|
|
374
|
-
mesh_config.margin = margin
|
|
375
|
-
if air_above is not None:
|
|
376
|
-
mesh_config.air_above = air_above
|
|
377
|
-
if fmax is not None:
|
|
378
|
-
mesh_config.fmax = fmax
|
|
379
|
-
mesh_config.show_gui = show_gui
|
|
380
|
-
|
|
381
|
-
return mesh_config
|
|
382
|
-
|
|
383
182
|
def _generate_mesh_internal(
|
|
384
183
|
self,
|
|
385
184
|
output_dir: Path,
|
|
@@ -427,10 +226,6 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
|
|
|
427
226
|
mesh_stats=mesh_result.mesh_stats,
|
|
428
227
|
)
|
|
429
228
|
|
|
430
|
-
def _get_ports_for_preview(self, stack: LayerStack) -> list:
|
|
431
|
-
"""Get ports for preview (none for electrostatic)."""
|
|
432
|
-
return []
|
|
433
|
-
|
|
434
229
|
# -------------------------------------------------------------------------
|
|
435
230
|
# Preview
|
|
436
231
|
# -------------------------------------------------------------------------
|
|
@@ -465,11 +260,9 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
|
|
|
465
260
|
|
|
466
261
|
component = self.geometry.component if self.geometry else None
|
|
467
262
|
|
|
468
|
-
validation = self.
|
|
263
|
+
validation = self.validate_config()
|
|
469
264
|
if not validation.valid:
|
|
470
|
-
raise ValueError(
|
|
471
|
-
f"Invalid configuration:\n" + "\n".join(validation.errors)
|
|
472
|
-
)
|
|
265
|
+
raise ValueError("Invalid configuration:\n" + "\n".join(validation.errors))
|
|
473
266
|
|
|
474
267
|
mesh_config = self._build_mesh_config(
|
|
475
268
|
preset=preset,
|
|
@@ -503,37 +296,12 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
|
|
|
503
296
|
config=legacy_mesh_config,
|
|
504
297
|
)
|
|
505
298
|
|
|
506
|
-
# -------------------------------------------------------------------------
|
|
507
|
-
# Convenience methods
|
|
508
|
-
# -------------------------------------------------------------------------
|
|
509
|
-
|
|
510
|
-
def show_stack(self) -> None:
|
|
511
|
-
"""Print the layer stack table."""
|
|
512
|
-
from gsim.common.stack import print_stack_table
|
|
513
|
-
|
|
514
|
-
if self.stack is None:
|
|
515
|
-
self._resolve_stack()
|
|
516
|
-
|
|
517
|
-
if self.stack is not None:
|
|
518
|
-
print_stack_table(self.stack)
|
|
519
|
-
|
|
520
|
-
def plot_stack(self) -> None:
|
|
521
|
-
"""Plot the layer stack visualization."""
|
|
522
|
-
from gsim.common.stack import plot_stack
|
|
523
|
-
|
|
524
|
-
if self.stack is None:
|
|
525
|
-
self._resolve_stack()
|
|
526
|
-
|
|
527
|
-
if self.stack is not None:
|
|
528
|
-
plot_stack(self.stack)
|
|
529
|
-
|
|
530
299
|
# -------------------------------------------------------------------------
|
|
531
300
|
# Mesh generation
|
|
532
301
|
# -------------------------------------------------------------------------
|
|
533
302
|
|
|
534
303
|
def mesh(
|
|
535
304
|
self,
|
|
536
|
-
output_dir: str | Path,
|
|
537
305
|
*,
|
|
538
306
|
preset: Literal["coarse", "default", "fine"] | None = None,
|
|
539
307
|
refined_mesh_size: float | None = None,
|
|
@@ -545,23 +313,35 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
|
|
|
545
313
|
model_name: str = "palace",
|
|
546
314
|
verbose: bool = True,
|
|
547
315
|
) -> SimulationResult:
|
|
548
|
-
"""Generate the mesh
|
|
316
|
+
"""Generate the mesh for Palace simulation.
|
|
317
|
+
|
|
318
|
+
Requires set_output_dir() to be called first.
|
|
549
319
|
|
|
550
320
|
Args:
|
|
551
|
-
output_dir: Directory for output files
|
|
552
321
|
preset: Mesh quality preset ("coarse", "default", "fine")
|
|
553
|
-
refined_mesh_size: Mesh size near conductors (um)
|
|
554
|
-
max_mesh_size: Max mesh size in air/dielectric (um)
|
|
555
|
-
margin: XY margin around design (um)
|
|
556
|
-
air_above: Air above top metal (um)
|
|
322
|
+
refined_mesh_size: Mesh size near conductors (um), overrides preset
|
|
323
|
+
max_mesh_size: Max mesh size in air/dielectric (um), overrides preset
|
|
324
|
+
margin: XY margin around design (um), overrides preset
|
|
325
|
+
air_above: Air above top metal (um), overrides preset
|
|
557
326
|
fmax: Max frequency for mesh sizing (Hz) - less relevant for electrostatic
|
|
558
327
|
show_gui: Show gmsh GUI during meshing
|
|
559
328
|
model_name: Base name for output files
|
|
560
329
|
verbose: Print progress messages
|
|
561
330
|
|
|
562
331
|
Returns:
|
|
563
|
-
SimulationResult with mesh
|
|
332
|
+
SimulationResult with mesh path
|
|
333
|
+
|
|
334
|
+
Raises:
|
|
335
|
+
ValueError: If output_dir not set or configuration is invalid
|
|
336
|
+
|
|
337
|
+
Example:
|
|
338
|
+
>>> sim.set_output_dir("./sim")
|
|
339
|
+
>>> result = sim.mesh(preset="fine")
|
|
340
|
+
>>> print(f"Mesh saved to: {result.mesh_path}")
|
|
564
341
|
"""
|
|
342
|
+
if self._output_dir is None:
|
|
343
|
+
raise ValueError("Output directory not set. Call set_output_dir() first.")
|
|
344
|
+
|
|
565
345
|
mesh_config = self._build_mesh_config(
|
|
566
346
|
preset=preset,
|
|
567
347
|
refined_mesh_size=refined_mesh_size,
|
|
@@ -572,15 +352,11 @@ class ElectrostaticSim(PalaceSimMixin, BaseModel):
|
|
|
572
352
|
show_gui=show_gui,
|
|
573
353
|
)
|
|
574
354
|
|
|
575
|
-
validation = self.
|
|
355
|
+
validation = self.validate_config()
|
|
576
356
|
if not validation.valid:
|
|
577
|
-
raise ValueError(
|
|
578
|
-
f"Invalid configuration:\n" + "\n".join(validation.errors)
|
|
579
|
-
)
|
|
357
|
+
raise ValueError("Invalid configuration:\n" + "\n".join(validation.errors))
|
|
580
358
|
|
|
581
|
-
output_dir =
|
|
582
|
-
output_dir.mkdir(parents=True, exist_ok=True)
|
|
583
|
-
self._output_dir = output_dir
|
|
359
|
+
output_dir = self._output_dir
|
|
584
360
|
|
|
585
361
|
self._resolve_stack()
|
|
586
362
|
|
gsim/palace/mesh/__init__.py
CHANGED
|
@@ -28,7 +28,16 @@ Usage:
|
|
|
28
28
|
|
|
29
29
|
from __future__ import annotations
|
|
30
30
|
|
|
31
|
-
from gsim.palace.mesh.generator import
|
|
31
|
+
from gsim.palace.mesh.generator import (
|
|
32
|
+
GeometryData,
|
|
33
|
+
write_config,
|
|
34
|
+
)
|
|
35
|
+
from gsim.palace.mesh.generator import (
|
|
36
|
+
MeshResult as MeshResultDirect,
|
|
37
|
+
)
|
|
38
|
+
from gsim.palace.mesh.generator import (
|
|
39
|
+
generate_mesh as generate_mesh_direct,
|
|
40
|
+
)
|
|
32
41
|
from gsim.palace.mesh.pipeline import (
|
|
33
42
|
GroundPlane,
|
|
34
43
|
MeshConfig,
|
|
@@ -40,11 +49,14 @@ from gsim.palace.mesh.pipeline import (
|
|
|
40
49
|
from . import gmsh_utils
|
|
41
50
|
|
|
42
51
|
__all__ = [
|
|
52
|
+
"GeometryData",
|
|
43
53
|
"GroundPlane",
|
|
44
54
|
"MeshConfig",
|
|
45
55
|
"MeshPreset",
|
|
46
56
|
"MeshResult",
|
|
57
|
+
"MeshResultDirect",
|
|
47
58
|
"generate_mesh",
|
|
48
59
|
"generate_mesh_direct",
|
|
49
60
|
"gmsh_utils",
|
|
61
|
+
"write_config",
|
|
50
62
|
]
|