pytopo3d 0.1.0__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.
- pytopo3d/__init__.py +0 -0
- pytopo3d/cli/__init__.py +3 -0
- pytopo3d/cli/command.py +207 -0
- pytopo3d/cli/parser.py +235 -0
- pytopo3d/core/__init__.py +0 -0
- pytopo3d/core/compliance.py +32 -0
- pytopo3d/core/optimizer.py +256 -0
- pytopo3d/preprocessing/__init__.py +3 -0
- pytopo3d/preprocessing/geometry.py +213 -0
- pytopo3d/runners/__init__.py +3 -0
- pytopo3d/runners/experiment.py +227 -0
- pytopo3d/runners/pipeline.py +140 -0
- pytopo3d/utils/__init__.py +3 -0
- pytopo3d/utils/assembly.py +108 -0
- pytopo3d/utils/boundary.py +63 -0
- pytopo3d/utils/export.py +133 -0
- pytopo3d/utils/filter.py +145 -0
- pytopo3d/utils/import_design_space.py +109 -0
- pytopo3d/utils/logger.py +161 -0
- pytopo3d/utils/metrics.py +169 -0
- pytopo3d/utils/obstacles.py +269 -0
- pytopo3d/utils/oc_update.py +88 -0
- pytopo3d/utils/results_manager.py +334 -0
- pytopo3d/utils/solver.py +28 -0
- pytopo3d/utils/stiffness.py +104 -0
- pytopo3d/visualization/__init__.py +3 -0
- pytopo3d/visualization/animation.py +278 -0
- pytopo3d/visualization/display.py +325 -0
- pytopo3d/visualization/runner.py +133 -0
- pytopo3d/visualization/visualizer.py +255 -0
- pytopo3d-0.1.0.dist-info/METADATA +369 -0
- pytopo3d-0.1.0.dist-info/RECORD +35 -0
- pytopo3d-0.1.0.dist-info/WHEEL +5 -0
- pytopo3d-0.1.0.dist-info/licenses/LICENSE +21 -0
- pytopo3d-0.1.0.dist-info/top_level.txt +1 -0
pytopo3d/__init__.py
ADDED
|
File without changes
|
pytopo3d/cli/__init__.py
ADDED
pytopo3d/cli/command.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Command-line entry point for the pytopo3d package.
|
|
4
|
+
This module provides the main entry point when installed as a package.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
|
|
10
|
+
# Import the main function from the main module
|
|
11
|
+
from pytopo3d.cli.parser import parse_args
|
|
12
|
+
from pytopo3d.preprocessing.geometry import load_geometry_data
|
|
13
|
+
from pytopo3d.runners.experiment import (
|
|
14
|
+
execute_optimization,
|
|
15
|
+
export_result_to_stl,
|
|
16
|
+
setup_experiment,
|
|
17
|
+
)
|
|
18
|
+
from pytopo3d.utils.metrics import collect_metrics
|
|
19
|
+
from pytopo3d.visualization.visualizer import (
|
|
20
|
+
create_optimization_animation,
|
|
21
|
+
visualize_final_result,
|
|
22
|
+
visualize_initial_setup,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main(args: Optional[List[str]] = None) -> int:
|
|
27
|
+
"""
|
|
28
|
+
Main function to run the optimization from command-line arguments.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
args: Command line arguments (sys.argv[1:] by default)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Exit code (0 for success, non-zero for errors)
|
|
35
|
+
"""
|
|
36
|
+
# Parse command-line arguments
|
|
37
|
+
if args is None:
|
|
38
|
+
args = sys.argv[1:]
|
|
39
|
+
|
|
40
|
+
parsed_args = parse_args(args)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
# Setup experiment, logging and results manager
|
|
44
|
+
logger, results_mgr = setup_experiment(
|
|
45
|
+
verbose=parsed_args.verbose,
|
|
46
|
+
quiet=parsed_args.quiet,
|
|
47
|
+
log_level=parsed_args.log_level,
|
|
48
|
+
log_file=parsed_args.log_file,
|
|
49
|
+
experiment_name=getattr(parsed_args, "experiment_name", None),
|
|
50
|
+
description=parsed_args.description,
|
|
51
|
+
nelx=parsed_args.nelx,
|
|
52
|
+
nely=parsed_args.nely,
|
|
53
|
+
nelz=parsed_args.nelz,
|
|
54
|
+
volfrac=parsed_args.volfrac,
|
|
55
|
+
penal=parsed_args.penal,
|
|
56
|
+
rmin=parsed_args.rmin,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Update args.experiment_name if it was generated in setup_experiment
|
|
60
|
+
if (
|
|
61
|
+
not hasattr(parsed_args, "experiment_name")
|
|
62
|
+
or not parsed_args.experiment_name
|
|
63
|
+
):
|
|
64
|
+
parsed_args.experiment_name = results_mgr.experiment_name
|
|
65
|
+
|
|
66
|
+
# Load design space and obstacle data
|
|
67
|
+
design_space_mask, obstacle_mask, combined_obstacle_mask = load_geometry_data(
|
|
68
|
+
nelx=parsed_args.nelx,
|
|
69
|
+
nely=parsed_args.nely,
|
|
70
|
+
nelz=parsed_args.nelz,
|
|
71
|
+
design_space_stl=getattr(parsed_args, "design_space_stl", None),
|
|
72
|
+
pitch=getattr(parsed_args, "pitch", 1.0),
|
|
73
|
+
invert_design_space=getattr(parsed_args, "invert_design_space", False),
|
|
74
|
+
obstacle_config=getattr(parsed_args, "obstacle_config", None),
|
|
75
|
+
experiment_name=parsed_args.experiment_name,
|
|
76
|
+
logger=logger,
|
|
77
|
+
results_mgr=results_mgr,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Create and save initial visualization
|
|
81
|
+
loads_array, constraints_array, _ = visualize_initial_setup(
|
|
82
|
+
nelx=parsed_args.nelx,
|
|
83
|
+
nely=parsed_args.nely,
|
|
84
|
+
nelz=parsed_args.nelz,
|
|
85
|
+
experiment_name=parsed_args.experiment_name,
|
|
86
|
+
logger=logger,
|
|
87
|
+
results_mgr=results_mgr,
|
|
88
|
+
combined_obstacle_mask=combined_obstacle_mask,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Run the optimization
|
|
92
|
+
xPhys, history, run_time = execute_optimization(
|
|
93
|
+
nelx=parsed_args.nelx,
|
|
94
|
+
nely=parsed_args.nely,
|
|
95
|
+
nelz=parsed_args.nelz,
|
|
96
|
+
volfrac=parsed_args.volfrac,
|
|
97
|
+
penal=parsed_args.penal,
|
|
98
|
+
rmin=parsed_args.rmin,
|
|
99
|
+
disp_thres=parsed_args.disp_thres,
|
|
100
|
+
tolx=getattr(parsed_args, "tolx", 0.01),
|
|
101
|
+
maxloop=getattr(parsed_args, "maxloop", 2000),
|
|
102
|
+
create_animation=getattr(parsed_args, "create_animation", False),
|
|
103
|
+
animation_frequency=getattr(parsed_args, "animation_frequency", 10),
|
|
104
|
+
logger=logger,
|
|
105
|
+
combined_obstacle_mask=combined_obstacle_mask,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Save the result to the experiment directory
|
|
109
|
+
result_path = results_mgr.save_result(xPhys, "optimized_design.npy")
|
|
110
|
+
logger.debug(f"Optimization result saved to {result_path}")
|
|
111
|
+
|
|
112
|
+
# Create visualization of the final result
|
|
113
|
+
visualize_final_result(
|
|
114
|
+
nelx=parsed_args.nelx,
|
|
115
|
+
nely=parsed_args.nely,
|
|
116
|
+
nelz=parsed_args.nelz,
|
|
117
|
+
experiment_name=parsed_args.experiment_name,
|
|
118
|
+
disp_thres=parsed_args.disp_thres,
|
|
119
|
+
logger=logger,
|
|
120
|
+
results_mgr=results_mgr,
|
|
121
|
+
xPhys=xPhys,
|
|
122
|
+
combined_obstacle_mask=combined_obstacle_mask,
|
|
123
|
+
loads_array=loads_array,
|
|
124
|
+
constraints_array=constraints_array,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Create animation if history was captured
|
|
128
|
+
gif_path = None
|
|
129
|
+
if history:
|
|
130
|
+
gif_path = create_optimization_animation(
|
|
131
|
+
nelx=parsed_args.nelx,
|
|
132
|
+
nely=parsed_args.nely,
|
|
133
|
+
nelz=parsed_args.nelz,
|
|
134
|
+
experiment_name=parsed_args.experiment_name,
|
|
135
|
+
disp_thres=parsed_args.disp_thres,
|
|
136
|
+
animation_frames=getattr(parsed_args, "animation_frames", 50),
|
|
137
|
+
animation_fps=getattr(parsed_args, "animation_fps", 5),
|
|
138
|
+
logger=logger,
|
|
139
|
+
results_mgr=results_mgr,
|
|
140
|
+
history=history,
|
|
141
|
+
combined_obstacle_mask=combined_obstacle_mask,
|
|
142
|
+
loads_array=loads_array,
|
|
143
|
+
constraints_array=constraints_array,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Export result as STL if requested
|
|
147
|
+
stl_exported = export_result_to_stl(
|
|
148
|
+
export_stl=getattr(parsed_args, "export_stl", False),
|
|
149
|
+
stl_level=getattr(parsed_args, "stl_level", 0.5),
|
|
150
|
+
smooth_stl=getattr(parsed_args, "smooth_stl", False),
|
|
151
|
+
smooth_iterations=getattr(parsed_args, "smooth_iterations", 3),
|
|
152
|
+
logger=logger,
|
|
153
|
+
results_mgr=results_mgr,
|
|
154
|
+
result_path=result_path,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Collect and save metrics
|
|
158
|
+
metrics = collect_metrics(
|
|
159
|
+
nelx=parsed_args.nelx,
|
|
160
|
+
nely=parsed_args.nely,
|
|
161
|
+
nelz=parsed_args.nelz,
|
|
162
|
+
volfrac=parsed_args.volfrac,
|
|
163
|
+
penal=parsed_args.penal,
|
|
164
|
+
rmin=parsed_args.rmin,
|
|
165
|
+
disp_thres=parsed_args.disp_thres,
|
|
166
|
+
tolx=getattr(parsed_args, "tolx", 0.01),
|
|
167
|
+
maxloop=getattr(parsed_args, "maxloop", 2000),
|
|
168
|
+
design_space_stl=getattr(parsed_args, "design_space_stl", None),
|
|
169
|
+
pitch=getattr(parsed_args, "pitch", 1.0),
|
|
170
|
+
obstacle_config=getattr(parsed_args, "obstacle_config", None),
|
|
171
|
+
animation_fps=getattr(parsed_args, "animation_fps", 5),
|
|
172
|
+
stl_level=getattr(parsed_args, "stl_level", 0.5),
|
|
173
|
+
smooth_stl=getattr(parsed_args, "smooth_stl", False),
|
|
174
|
+
smooth_iterations=getattr(parsed_args, "smooth_iterations", 3),
|
|
175
|
+
xPhys=xPhys,
|
|
176
|
+
design_space_mask=design_space_mask,
|
|
177
|
+
obstacle_mask=obstacle_mask,
|
|
178
|
+
combined_obstacle_mask=combined_obstacle_mask,
|
|
179
|
+
run_time=run_time,
|
|
180
|
+
gif_path=gif_path,
|
|
181
|
+
stl_exported=stl_exported,
|
|
182
|
+
)
|
|
183
|
+
results_mgr.update_metrics(metrics)
|
|
184
|
+
logger.debug("Metrics updated")
|
|
185
|
+
|
|
186
|
+
logger.info(f"Optimization complete in {run_time:.2f} seconds.")
|
|
187
|
+
logger.info(f"Result saved to {result_path}")
|
|
188
|
+
logger.info(f"All experiment files are in {results_mgr.experiment_dir}")
|
|
189
|
+
|
|
190
|
+
except Exception as e:
|
|
191
|
+
if "logger" in locals():
|
|
192
|
+
logger.error(f"Error in main function: {e}")
|
|
193
|
+
import traceback
|
|
194
|
+
|
|
195
|
+
logger.debug(f"Error details: {traceback.format_exc()}")
|
|
196
|
+
else:
|
|
197
|
+
print(f"Error during initialization: {e}", file=sys.stderr)
|
|
198
|
+
import traceback
|
|
199
|
+
|
|
200
|
+
print(f"Error details: {traceback.format_exc()}", file=sys.stderr)
|
|
201
|
+
return 1
|
|
202
|
+
|
|
203
|
+
return 0
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
if __name__ == "__main__":
|
|
207
|
+
sys.exit(main())
|
pytopo3d/cli/parser.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line argument parsing for the 3D topology optimization package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def parse_args(args: Optional[List[str]] = None) -> argparse.Namespace:
|
|
12
|
+
"""
|
|
13
|
+
Parse command-line arguments for the topology optimization.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
args : Optional[List[str]], optional
|
|
18
|
+
Command line arguments, by default None (uses sys.argv[1:])
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
argparse.Namespace
|
|
23
|
+
Parsed command-line arguments.
|
|
24
|
+
"""
|
|
25
|
+
parser = argparse.ArgumentParser(
|
|
26
|
+
description="3D Topology Optimization",
|
|
27
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Basic parameters
|
|
31
|
+
basic_group = parser.add_argument_group("Basic parameters")
|
|
32
|
+
basic_group.add_argument(
|
|
33
|
+
"--nelx", type=int, default=60, help="Number of elements in x direction"
|
|
34
|
+
)
|
|
35
|
+
basic_group.add_argument(
|
|
36
|
+
"--nely", type=int, default=30, help="Number of elements in y direction"
|
|
37
|
+
)
|
|
38
|
+
basic_group.add_argument(
|
|
39
|
+
"--nelz", type=int, default=20, help="Number of elements in z direction"
|
|
40
|
+
)
|
|
41
|
+
basic_group.add_argument(
|
|
42
|
+
"--volfrac", type=float, default=0.3, help="Volume fraction constraint"
|
|
43
|
+
)
|
|
44
|
+
basic_group.add_argument(
|
|
45
|
+
"--penal", type=float, default=3.0, help="Penalty parameter"
|
|
46
|
+
)
|
|
47
|
+
basic_group.add_argument("--rmin", type=float, default=3.0, help="Filter radius")
|
|
48
|
+
basic_group.add_argument(
|
|
49
|
+
"--disp_thres",
|
|
50
|
+
type=float,
|
|
51
|
+
default=0.5,
|
|
52
|
+
help="Threshold for displaying elements in visualization",
|
|
53
|
+
)
|
|
54
|
+
basic_group.add_argument(
|
|
55
|
+
"--tolx",
|
|
56
|
+
type=float,
|
|
57
|
+
default=0.01,
|
|
58
|
+
help="Convergence tolerance on design change",
|
|
59
|
+
)
|
|
60
|
+
basic_group.add_argument(
|
|
61
|
+
"--maxloop",
|
|
62
|
+
type=int,
|
|
63
|
+
default=2000,
|
|
64
|
+
help="Maximum number of iterations",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Output parameters
|
|
68
|
+
output_group = parser.add_argument_group("Output parameters")
|
|
69
|
+
output_group.add_argument(
|
|
70
|
+
"--output",
|
|
71
|
+
type=str,
|
|
72
|
+
default="optimized_design.npy",
|
|
73
|
+
help="Output filename for the optimized design",
|
|
74
|
+
)
|
|
75
|
+
output_group.add_argument(
|
|
76
|
+
"--export-stl",
|
|
77
|
+
action="store_true",
|
|
78
|
+
help="Export the final optimization result as an STL file",
|
|
79
|
+
)
|
|
80
|
+
output_group.add_argument(
|
|
81
|
+
"--stl-level",
|
|
82
|
+
type=float,
|
|
83
|
+
default=0.5,
|
|
84
|
+
help="Contour level for STL export (default: 0.5)",
|
|
85
|
+
)
|
|
86
|
+
output_group.add_argument(
|
|
87
|
+
"--smooth-stl",
|
|
88
|
+
action="store_true",
|
|
89
|
+
default=True,
|
|
90
|
+
help="Apply smoothing to the exported STL (default: True)",
|
|
91
|
+
)
|
|
92
|
+
output_group.add_argument(
|
|
93
|
+
"--smooth-iterations",
|
|
94
|
+
type=int,
|
|
95
|
+
default=5,
|
|
96
|
+
help="Number of smoothing iterations for STL export (default: 5)",
|
|
97
|
+
)
|
|
98
|
+
output_group.add_argument(
|
|
99
|
+
"--experiment-name",
|
|
100
|
+
type=str,
|
|
101
|
+
default=None,
|
|
102
|
+
help="Custom name for the experiment (optional)",
|
|
103
|
+
)
|
|
104
|
+
output_group.add_argument(
|
|
105
|
+
"--description",
|
|
106
|
+
type=str,
|
|
107
|
+
default=None,
|
|
108
|
+
help="Description of the experiment (optional)",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Animation parameters
|
|
112
|
+
animation_group = parser.add_argument_group("Animation parameters")
|
|
113
|
+
animation_group.add_argument(
|
|
114
|
+
"--create-animation",
|
|
115
|
+
action="store_true",
|
|
116
|
+
help="Create a GIF animation of the optimization process",
|
|
117
|
+
)
|
|
118
|
+
animation_group.add_argument(
|
|
119
|
+
"--animation-frequency",
|
|
120
|
+
type=int,
|
|
121
|
+
default=10,
|
|
122
|
+
help="Store every N iterations for the animation (default: 10)",
|
|
123
|
+
)
|
|
124
|
+
animation_group.add_argument(
|
|
125
|
+
"--animation-frames",
|
|
126
|
+
type=int,
|
|
127
|
+
default=50,
|
|
128
|
+
help="Target number of frames to include in the animation (default: 50)",
|
|
129
|
+
)
|
|
130
|
+
animation_group.add_argument(
|
|
131
|
+
"--animation-fps",
|
|
132
|
+
type=int,
|
|
133
|
+
default=5,
|
|
134
|
+
help="Frames per second in the animation (default: 5)",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Design space parameters
|
|
138
|
+
design_space_group = parser.add_argument_group("Design space parameters")
|
|
139
|
+
design_space_group.add_argument(
|
|
140
|
+
"--design-space-stl",
|
|
141
|
+
type=str,
|
|
142
|
+
help="Path to an STL file defining the design space geometry",
|
|
143
|
+
)
|
|
144
|
+
design_space_group.add_argument(
|
|
145
|
+
"--pitch",
|
|
146
|
+
type=float,
|
|
147
|
+
default=1.0,
|
|
148
|
+
help="Distance between voxel centers when voxelizing STL (smaller values create finer detail)",
|
|
149
|
+
)
|
|
150
|
+
design_space_group.add_argument(
|
|
151
|
+
"--invert-design-space",
|
|
152
|
+
action="store_true",
|
|
153
|
+
help="Invert the design space (treat STL as void space rather than design space)",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Obstacle related arguments
|
|
157
|
+
obstacle_group = parser.add_argument_group("Obstacle parameters")
|
|
158
|
+
obstacle_group.add_argument(
|
|
159
|
+
"--obstacle-config", type=str, help="Path to a JSON file defining obstacles"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Logging parameters
|
|
163
|
+
log_group = parser.add_argument_group("Logging parameters")
|
|
164
|
+
log_group.add_argument(
|
|
165
|
+
"--log-level",
|
|
166
|
+
type=str,
|
|
167
|
+
default="INFO",
|
|
168
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
169
|
+
help="Logging level",
|
|
170
|
+
)
|
|
171
|
+
log_group.add_argument("--log-file", type=str, default=None, help="Log file path")
|
|
172
|
+
log_group.add_argument(
|
|
173
|
+
"--verbose",
|
|
174
|
+
"-v",
|
|
175
|
+
action="store_true",
|
|
176
|
+
help="Enable verbose output (DEBUG level)",
|
|
177
|
+
)
|
|
178
|
+
log_group.add_argument(
|
|
179
|
+
"--quiet", "-q", action="store_true", help="Suppress output (WARNING level)"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
return parser.parse_args(args)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def generate_experiment_name(args: argparse.Namespace) -> str:
|
|
186
|
+
"""
|
|
187
|
+
Generate an experiment name from command-line arguments.
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
args : argparse.Namespace
|
|
192
|
+
Command-line arguments.
|
|
193
|
+
|
|
194
|
+
Returns
|
|
195
|
+
-------
|
|
196
|
+
str
|
|
197
|
+
Generated experiment name.
|
|
198
|
+
"""
|
|
199
|
+
if args.experiment_name:
|
|
200
|
+
return args.experiment_name
|
|
201
|
+
|
|
202
|
+
dims = f"{args.nelx}x{args.nely}x{args.nelz}"
|
|
203
|
+
|
|
204
|
+
# Include obstacle info in experiment name
|
|
205
|
+
obstacle_type = "no_obstacle"
|
|
206
|
+
if args.obstacle_config:
|
|
207
|
+
obstacle_type = os.path.basename(args.obstacle_config).replace(".json", "")
|
|
208
|
+
|
|
209
|
+
# Include design space STL info in experiment name if provided
|
|
210
|
+
design_space = ""
|
|
211
|
+
if hasattr(args, "design_space_stl") and args.design_space_stl:
|
|
212
|
+
stl_name = os.path.basename(args.design_space_stl).replace(".stl", "")
|
|
213
|
+
pitch_info = f"_p{args.pitch}".replace(".", "p")
|
|
214
|
+
design_space = f"_ds_{stl_name}{pitch_info}"
|
|
215
|
+
|
|
216
|
+
return f"{dims}_{obstacle_type}{design_space}"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def create_config_dict(args: argparse.Namespace) -> Dict[str, Any]:
|
|
220
|
+
"""
|
|
221
|
+
Create a configuration dictionary from command-line arguments.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
args : argparse.Namespace
|
|
226
|
+
Command-line arguments.
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
Dict[str, Any]
|
|
231
|
+
Configuration dictionary.
|
|
232
|
+
"""
|
|
233
|
+
config = vars(args)
|
|
234
|
+
config["timestamp"] = datetime.now().isoformat()
|
|
235
|
+
return config
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Element compliance calculation for 3D topology optimization.
|
|
3
|
+
|
|
4
|
+
This module contains functions for computing element-wise compliance.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
def element_compliance(U, edofMat, KE):
|
|
10
|
+
"""
|
|
11
|
+
Compute element-wise compliance:
|
|
12
|
+
ce[e] = Ue @ KE @ Ue^T for each element e.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
U : ndarray
|
|
17
|
+
Global displacement vector.
|
|
18
|
+
edofMat : ndarray
|
|
19
|
+
Element DOF mapping matrix.
|
|
20
|
+
KE : ndarray
|
|
21
|
+
Element stiffness matrix.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
ndarray
|
|
26
|
+
A 1D array of length nele with element compliance values.
|
|
27
|
+
"""
|
|
28
|
+
nele = edofMat.shape[0]
|
|
29
|
+
# edofMat is 1-based indexing, so subtract 1 for 0-based U
|
|
30
|
+
Ue = U[edofMat.astype(int) - 1] # shape (nele, 24)
|
|
31
|
+
ce = np.sum((Ue @ KE) * Ue, axis=1)
|
|
32
|
+
return ce
|