pyfemtet 0.4.2__py3-none-any.whl → 0.4.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.

Potentially problematic release.


This version of pyfemtet might be problematic. Click here for more details.

Files changed (49) hide show
  1. pyfemtet/FemtetPJTSample/_her_ex40_parametric.py +148 -0
  2. pyfemtet/__init__.py +1 -1
  3. pyfemtet/opt/_femopt.py +48 -16
  4. pyfemtet/opt/_femopt_core.py +14 -7
  5. pyfemtet/opt/femprj_sample/cad_ex01_NX.femprj +0 -0
  6. pyfemtet/opt/femprj_sample/cad_ex01_NX.prt +0 -0
  7. pyfemtet/opt/femprj_sample/cad_ex01_NX.py +132 -0
  8. pyfemtet/opt/femprj_sample/cad_ex01_SW.SLDPRT +0 -0
  9. pyfemtet/opt/femprj_sample/cad_ex01_SW.femprj +0 -0
  10. pyfemtet/opt/femprj_sample/cad_ex01_SW.py +132 -0
  11. pyfemtet/opt/femprj_sample/gal_ex58_parametric.femprj +0 -0
  12. pyfemtet/opt/femprj_sample/gal_ex58_parametric.py +75 -0
  13. pyfemtet/opt/femprj_sample/gau_ex08_parametric.femprj +0 -0
  14. pyfemtet/opt/femprj_sample/gau_ex08_parametric.py +59 -0
  15. pyfemtet/opt/femprj_sample/her_ex40_parametric.femprj +0 -0
  16. pyfemtet/opt/femprj_sample/her_ex40_parametric.py +137 -0
  17. pyfemtet/opt/femprj_sample/paswat_ex1_parametric.femprj +0 -0
  18. pyfemtet/opt/femprj_sample/paswat_ex1_parametric.py +61 -0
  19. pyfemtet/opt/femprj_sample/paswat_ex1_parametric_parallel.py +62 -0
  20. pyfemtet/opt/femprj_sample/wat_ex14_parametric.femprj +0 -0
  21. pyfemtet/opt/femprj_sample/wat_ex14_parametric.py +59 -0
  22. pyfemtet/opt/femprj_sample_jp/cad_ex01_NX_jp.femprj +0 -0
  23. pyfemtet/opt/femprj_sample_jp/cad_ex01_NX_jp.py +126 -0
  24. pyfemtet/opt/femprj_sample_jp/cad_ex01_SW_jp.femprj +0 -0
  25. pyfemtet/opt/femprj_sample_jp/cad_ex01_SW_jp.py +126 -0
  26. pyfemtet/opt/femprj_sample_jp/gal_ex58_parametric_jp.femprj +0 -0
  27. pyfemtet/opt/femprj_sample_jp/gal_ex58_parametric_jp.py +71 -0
  28. pyfemtet/opt/femprj_sample_jp/gau_ex08_parametric_jp.femprj +0 -0
  29. pyfemtet/opt/femprj_sample_jp/gau_ex08_parametric_jp.py +58 -0
  30. pyfemtet/opt/femprj_sample_jp/her_ex40_parametric_jp.femprj +0 -0
  31. pyfemtet/opt/femprj_sample_jp/her_ex40_parametric_jp.py +137 -0
  32. pyfemtet/opt/femprj_sample_jp/paswat_ex1_parametric_jp.femprj +0 -0
  33. pyfemtet/opt/femprj_sample_jp/paswat_ex1_parametric_jp.py +59 -0
  34. pyfemtet/opt/femprj_sample_jp/paswat_ex1_parametric_parallel_jp.py +60 -0
  35. pyfemtet/opt/femprj_sample_jp/wat_ex14_parametric_jp.femprj +0 -0
  36. pyfemtet/opt/femprj_sample_jp/wat_ex14_parametric_jp.py +57 -0
  37. pyfemtet/opt/interface/_femtet.py +36 -18
  38. pyfemtet/opt/interface/_femtet_parametric.py +72 -0
  39. pyfemtet/opt/interface/_femtet_with_nx/_interface.py +2 -8
  40. pyfemtet/opt/interface/_femtet_with_sldworks.py +2 -8
  41. pyfemtet/opt/opt/_base.py +12 -3
  42. pyfemtet/opt/visualization/_monitor.py +2 -2
  43. pyfemtet-0.4.4.dist-info/METADATA +97 -0
  44. pyfemtet-0.4.4.dist-info/RECORD +72 -0
  45. pyfemtet-0.4.2.dist-info/METADATA +0 -48
  46. pyfemtet-0.4.2.dist-info/RECORD +0 -38
  47. {pyfemtet-0.4.2.dist-info → pyfemtet-0.4.4.dist-info}/LICENSE +0 -0
  48. {pyfemtet-0.4.2.dist-info → pyfemtet-0.4.4.dist-info}/WHEEL +0 -0
  49. {pyfemtet-0.4.2.dist-info → pyfemtet-0.4.4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,148 @@
1
+ """Single-objective optimization: Resonant frequency of a circular patch antenna
2
+
3
+ Using Femtet’s electromagnetic wave analysis solver,
4
+ we explain an example of setting the resonant frequency
5
+ of a circular patch antenna to a specific value.
6
+ """
7
+ from time import sleep
8
+
9
+ import numpy as np
10
+ from scipy.signal import find_peaks
11
+ from tqdm import tqdm
12
+ from optuna.integration.botorch import BoTorchSampler
13
+
14
+ from pyfemtet.opt import OptunaOptimizer, FEMOpt
15
+
16
+
17
+ class SParameterCalculator:
18
+ """This class is for calculating S-parameters and resonance frequencies."""
19
+
20
+ def __init__(self):
21
+ self.freq = []
22
+ self.S = []
23
+ self.interpolated_function = None
24
+ self.resonance_frequency = None
25
+ self.minimum_S = None
26
+
27
+ def get_result_from_Femtet(self, Femtet):
28
+ """Obtain the relationship between frequency and S-parameter from the Femtet analysis results."""
29
+
30
+ # Preparation
31
+ Femtet.OpenCurrentResult(True)
32
+ Gogh = Femtet.Gogh
33
+
34
+ # Obtain the frequency and S(1,1) for each mode
35
+ mode = 0
36
+ freq_list = []
37
+ dB_S_list = []
38
+ for mode in tqdm(range(Gogh.Hertz.nMode), 'Obtaining frequency and S-parameter'):
39
+ # Femtet result screen mode settings
40
+ Gogh.Hertz.Mode = mode
41
+ sleep(0.01)
42
+ # Get frequency
43
+ freq = Gogh.Hertz.GetFreq().Real
44
+ # Get S-parameters
45
+ comp_S = Gogh.Hertz.GetSMatrix(0, 0)
46
+ norm = np.linalg.norm((comp_S.Real, comp_S.Imag))
47
+ dB_S = 20 * np.log10(norm)
48
+ # Get results
49
+ freq_list.append(freq)
50
+ dB_S_list.append(dB_S)
51
+ self.freq = freq_list
52
+ self.S = dB_S_list
53
+
54
+ def calc_resonance_frequency(self):
55
+ """Compute the frequency that gives the first peak for S-parameter."""
56
+ x = -np.array(self.S)
57
+ peaks, _ = find_peaks(x, height=None, threshold=None, distance=None, prominence=0.5, width=None, wlen=None, rel_height=0.5, plateau_size=None)
58
+ from pyfemtet.core import SolveError
59
+ if len(peaks) == 0:
60
+ raise SolveError('No peaks detected.')
61
+ self.resonance_frequency = self.freq[peaks[0]]
62
+ self.minimum_S = self.S[peaks[0]]
63
+
64
+ def get_resonance_frequency(self, Femtet):
65
+ """Calculate the resonant frequency.
66
+
67
+ Note:
68
+ The objective or constraint function
69
+ must take a Femtet as its first argument
70
+ and must return a single float.
71
+
72
+ Params:
73
+ Femtet: An instance for using Femtet macros. For more information, see "Femtet Macro Help / CFemtet Class".
74
+
75
+ Returns:
76
+ float: A resonance frequency.
77
+ """
78
+ self.get_result_from_Femtet(Femtet)
79
+ self.calc_resonance_frequency()
80
+ f = self.resonance_frequency * 1e-9
81
+ return f # GHz
82
+
83
+
84
+ def antenna_is_smaller_than_substrate(Femtet):
85
+ """Calculate the relationship between antenna size and board size.
86
+
87
+ This function is used to constrain the model
88
+ from breaking down while changing parameters.
89
+
90
+ Params:
91
+ Femtet: An instance for using Femtet macros.
92
+
93
+ Returns:
94
+ float: Difference between the board size and antenna size. Must be equal to or grater than 1 mm.
95
+ """
96
+ ant_r = Femtet.GetVariableValue('ant_r')
97
+ Sx = Femtet.GetVariableValue('sx')
98
+ return Sx/2 - ant_r
99
+
100
+
101
+ def port_is_inside_antenna(Femtet):
102
+ """Calculate the relationship between the feed port location and antenna size.
103
+
104
+ This function is used to constrain the model
105
+ from breaking down while changing parameters.
106
+
107
+ Params:
108
+ Femtet: An instance for using Femtet macros.
109
+
110
+ Returns:
111
+ float: Difference between the antenna edge and the position of the feed port. Must be equal to or grater than 1 mm.
112
+ """
113
+ ant_r = Femtet.GetVariableValue('ant_r')
114
+ xf = Femtet.GetVariableValue('xf')
115
+ return ant_r - xf
116
+
117
+
118
+ if __name__ == '__main__':
119
+ # Define the object for calculating S-parameters and resonance frequencies.
120
+ s = SParameterCalculator()
121
+
122
+ # Define mathematical optimization object.
123
+ opt = OptunaOptimizer(
124
+ sampler_class=BoTorchSampler,
125
+ sampler_kwargs=dict(
126
+ n_startup_trials=10,
127
+ )
128
+ )
129
+
130
+ # Define FEMOpt object (This process integrates mathematical optimization and FEM.).
131
+ femopt = FEMOpt(opt=opt)
132
+
133
+ # Add design variables (Use variable names set in Femtet) to the optimization problem.
134
+ femopt.add_parameter('ant_r', 10, 5, 20)
135
+ femopt.add_parameter('sx', 50, 40, 60)
136
+ femopt.add_parameter('xf', 5, 1, 20)
137
+
138
+ # Add constraint to the optimization problem.
139
+ femopt.add_constraint(antenna_is_smaller_than_substrate, 'board_antenna_clearance', lower_bound=1)
140
+ femopt.add_constraint(port_is_inside_antenna, 'antenna_port_clearance', lower_bound=1)
141
+
142
+ # Add objective to the optimization problem.
143
+ # The target frequency is 3 GHz.
144
+ femopt.add_objective(s.get_resonance_frequency, 'First_resonant_frequency(GHz)', direction=3.0)
145
+
146
+ femopt.set_random_seed(42)
147
+ femopt.optimize(n_trials=20)
148
+ femopt.terminate_all()
pyfemtet/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.4.2"
1
+ __version__ = "0.4.4"
pyfemtet/opt/_femopt.py CHANGED
@@ -81,6 +81,7 @@ class FEMOpt:
81
81
  self.monitor_process_future = None
82
82
  self.monitor_server_kwargs = dict()
83
83
  self.monitor_process_worker_name = None
84
+ self._is_error_exit = False
84
85
 
85
86
  # multiprocess 時に pickle できないオブジェクト参照の削除
86
87
  def __getstate__(self):
@@ -328,12 +329,27 @@ class FEMOpt:
328
329
  subprocess_indices = list(range(n_parallel))
329
330
  worker_addresses = list(self.client.nthreads().keys())
330
331
 
332
+ # worker が足りない場合はエラー
333
+ if n_parallel > len(worker_addresses):
334
+ raise RuntimeError(f'n_parallel({n_parallel}) > n_workers({len(worker_addresses)}). Worker 数が不足しています。')
335
+
336
+ # worker が多い場合は閉じる
337
+ if n_parallel < len(worker_addresses):
338
+ used_worker_addresses = worker_addresses[:n_parallel] # 前から順番に選ぶ:CPU の早い / メモリの多い順に並べることが望ましい
339
+ unused_worker_addresses = worker_addresses[n_parallel:]
340
+ self.client.retire_workers(unused_worker_addresses, close_workers=True)
341
+ worker_addresses = used_worker_addresses
342
+
331
343
  # monitor worker の設定
332
344
  logger.info('Launching monitor server. This may take a few seconds.')
333
- self.monitor_process_worker_name = datetime.datetime.now().strftime("Monitor-%Y%m%d-%H%M%S")
345
+ self.monitor_process_worker_name = datetime.datetime.now().strftime("Monitor%Y%m%d%H%M%S")
334
346
  current_n_workers = len(self.client.nthreads().keys())
335
- from dask.distributed import Worker
336
- Worker(scheduler_ip=self.client.scheduler.address, nthreads=1, name=self.monitor_process_worker_name)
347
+ from subprocess import Popen
348
+ import sys
349
+ Popen(
350
+ f'{sys.executable} -m dask worker {self.client.scheduler.address} --nthreads 1 --nworkers 1 --name {self.monitor_process_worker_name} --no-nanny',
351
+ shell=True
352
+ )
337
353
 
338
354
  # monitor 用 worker が増えるまで待つ
339
355
  self.client.wait_for_workers(n_workers=current_n_workers + 1)
@@ -357,12 +373,17 @@ class FEMOpt:
357
373
  # Femtet 特有の処理
358
374
  metadata = None
359
375
  if isinstance(self.fem, FemtetInterface):
376
+ # 結果 csv に記載する femprj に関する情報
360
377
  metadata = json.dumps(
361
378
  dict(
362
379
  femprj_path=self.fem.original_femprj_path,
363
380
  model_name=self.fem.model_name
364
381
  )
365
382
  )
383
+ # Femtet の parametric 設定を目的関数に用いるかどうか
384
+ if self.fem.use_parametric_as_objective:
385
+ from pyfemtet.opt.interface._femtet_parametric import add_parametric_results_as_objectives
386
+ add_parametric_results_as_objectives(self)
366
387
 
367
388
  # actor の設定
368
389
  self.status = OptimizationStatus(self.client)
@@ -389,7 +410,7 @@ class FEMOpt:
389
410
  # kwargs
390
411
  **self.monitor_server_kwargs,
391
412
  # kwargs of submit
392
- workers=self.monitor_process_worker_name, # if invalid arg,
413
+ workers=self.monitor_process_worker_name,
393
414
  allow_other_workers=False
394
415
  )
395
416
 
@@ -452,10 +473,13 @@ class FEMOpt:
452
473
  t_save_history.start()
453
474
 
454
475
  # 終了を待つ
455
- self.client.gather(calc_futures)
476
+ local_opt_crashed = False
477
+ opt_crashed_list = self.client.gather(calc_futures)
456
478
  if not self.opt.is_cluster: # 既存の fem を使っているならそれも待つ
457
479
  if t_main is not None:
458
480
  t_main.join()
481
+ local_opt_crashed = self.opt._is_error_exit
482
+ opt_crashed_list.append(local_opt_crashed)
459
483
  self.status.set(OptimizationStatus.TERMINATED)
460
484
  end = time()
461
485
 
@@ -465,6 +489,13 @@ class FEMOpt:
465
489
  logger.info(f'計算が終了しました. 実行時間は {int(end - start)} 秒でした。ウィンドウを閉じると終了します.')
466
490
  logger.info(f'結果は{self.history.path}を確認してください.')
467
491
 
492
+ # ひとつでも crashed ならばフラグを立てる
493
+ if any(opt_crashed_list):
494
+ self._is_error_exit = True
495
+
496
+ return self.history.local_data
497
+
498
+
468
499
  def terminate_all(self):
469
500
  """Try to terminate all launched processes.
470
501
 
@@ -476,7 +507,10 @@ class FEMOpt:
476
507
  sleep(1)
477
508
 
478
509
  # terminate monitor process
479
- self.status.set(OptimizationStatus.TERMINATE_ALL)
510
+ if self._is_error_exit:
511
+ self.status.set(OptimizationStatus.CRASHED)
512
+ else:
513
+ self.status.set(OptimizationStatus.TERMINATE_ALL)
480
514
  logger.info(self.monitor_process_future.result())
481
515
  sleep(1)
482
516
 
@@ -514,22 +548,20 @@ class FEMOpt:
514
548
  else:
515
549
  logger.warn('Monitor process worker not found.')
516
550
 
517
- # close scheduler, other workers(, cluster)
518
- self.client.close()
519
- while self.client.scheduler is not None:
520
- sleep(1)
521
- logger.info('Terminate client.')
522
-
523
551
  # close FEM (if specified to quit when deconstruct)
524
552
  del self.fem
525
553
  logger.info('Terminate FEM.')
554
+ sleep(1)
526
555
 
527
- # terminate dask relative processes.
528
- if not self.opt.is_cluster:
529
- self.client.shutdown()
530
- logger.info('Terminate all relative processes.')
556
+ # close scheduler, other workers(, cluster)
557
+ self.client.shutdown()
558
+ logger.info('Terminate all relative processes.')
531
559
  sleep(3)
532
560
 
561
+ # if optimization was crashed, raise Exception
562
+ if self._is_error_exit:
563
+ raise RuntimeError('At least 1 of optimization processes have been crashed. See console log.')
564
+
533
565
 
534
566
  def _start_monitor_server(
535
567
  history,
@@ -7,6 +7,7 @@ import datetime
7
7
  import inspect
8
8
  import ast
9
9
  import csv
10
+ import ctypes
10
11
 
11
12
  # 3rd-party
12
13
  import numpy as np
@@ -197,9 +198,12 @@ class Function:
197
198
 
198
199
  # serializable でない COM 定数を parallelize するため
199
200
  # COM 定数を一度 _Scapegoat 型のオブジェクトにする
200
- for varname in fun.__globals__:
201
- if isinstance(fun.__globals__[varname], Constants):
202
- fun.__globals__[varname] = _Scapegoat()
201
+ # ParametricIF で使う dll 関数は _FuncPtr 型であって __globals__ を持たないが、
202
+ # これは絶対に constants を持たないので単に無視すればよい。
203
+ if not isinstance(fun, ctypes._CFuncPtr):
204
+ for varname in fun.__globals__:
205
+ if isinstance(fun.__globals__[varname], Constants):
206
+ fun.__globals__[varname] = _Scapegoat()
203
207
 
204
208
  self.fun = fun
205
209
  self.name = name
@@ -224,10 +228,11 @@ class Function:
224
228
  def _restore_constants(self):
225
229
  """Helper function for parallelize Femtet."""
226
230
  fun = self.fun
227
- for varname in fun.__globals__:
228
- if isinstance(fun.__globals__[varname], _Scapegoat):
229
- if not fun.__globals__[varname]._ignore_when_restore_constants:
230
- fun.__globals__[varname] = constants
231
+ if not isinstance(fun, ctypes._CFuncPtr):
232
+ for varname in fun.__globals__:
233
+ if isinstance(fun.__globals__[varname], _Scapegoat):
234
+ if not fun.__globals__[varname]._ignore_when_restore_constants:
235
+ fun.__globals__[varname] = constants
231
236
 
232
237
 
233
238
  class Objective(Function):
@@ -691,6 +696,7 @@ class OptimizationStatus:
691
696
  RUNNING = 30
692
697
  INTERRUPTING = 40
693
698
  TERMINATED = 50
699
+ CRASHED = 55
694
700
  TERMINATE_ALL = 60
695
701
 
696
702
  def __init__(self, client, name='entire'):
@@ -712,6 +718,7 @@ class OptimizationStatus:
712
718
  if status_const == cls.INTERRUPTING: return 'Interrupting'
713
719
  if status_const == cls.TERMINATED: return 'Terminated'
714
720
  if status_const == cls.TERMINATE_ALL: return 'Terminate_all'
721
+ if status_const == cls.CRASHED: return 'Crashed'
715
722
 
716
723
  def set(self, status_const):
717
724
  """Set optimization status."""
@@ -0,0 +1,132 @@
1
+ """External CAD (NX) Integration
2
+
3
+ Using Femtet's stress analysis solver and Siemens' CAD software NX,
4
+ design a lightweight and high-strength H-shaped beam.
5
+
6
+ As a preliminary step, please perform the following procedures:
7
+ - Install NX
8
+ - Create a C:\temp folder
9
+ - Note: NX will save a .x_t file in this folder.
10
+ - Place the following files in the same folder:
11
+ - cad_ex01_NX.py (this file)
12
+ - cad_ex01_NX.prt
13
+ - cad_ex01_NX.femprj
14
+ """
15
+
16
+ import os
17
+
18
+ from win32com.client import constants
19
+
20
+ from pyfemtet.opt import FEMOpt
21
+ from pyfemtet.opt.interface import FemtetWithNXInterface
22
+ from pyfemtet.core import ModelError
23
+
24
+
25
+ here, me = os.path.split(__file__)
26
+ os.chdir(here)
27
+
28
+
29
+ def von_mises(Femtet):
30
+ """Obtain the maximum von Mises stress of the model.
31
+
32
+ Note:
33
+ The objective or constraint function should take Femtet
34
+ as its first argument and return a float as the output.
35
+
36
+ Warning:
37
+ CAD integration may assign boundary conditions to unintended locations.
38
+
39
+ In this example, if the boundary conditions are assigned as intended,
40
+ the maximum z displacement is always negative.
41
+ If the maximum displacement is not negative, it is assumed that
42
+ boundary condition assignment has failed.
43
+ Then this function raises a ModelError.
44
+
45
+ If a ModelError, MeshError, or SolveError occurs during optimization,
46
+ the optimization process considers the attempt a failure and skips to
47
+ the next trial.
48
+ """
49
+
50
+ # Simple check for the correctness of boundary conditions.
51
+ dx, dy, dz = Femtet.Gogh.Galileo.GetMaxDisplacement_py()
52
+ if dz >= 0:
53
+ raise ModelError('Assigning unintended boundary conditions.')
54
+
55
+ # Von Mises stress calculation.
56
+ Gogh = Femtet.Gogh
57
+ Gogh.Galileo.Potential = constants.GALILEO_VON_MISES_C
58
+ succeed, (x, y, z), mises = Gogh.Galileo.GetMAXPotentialPoint_py(constants.CMPX_REAL_C)
59
+
60
+ return mises
61
+
62
+
63
+ def mass(Femtet):
64
+ """Obtain model mass."""
65
+ return Femtet.Gogh.Galileo.GetMass('H_beam')
66
+
67
+
68
+ def C_minus_B(Femtet, opt):
69
+ """Calculate the difference between C and B dimensions.
70
+
71
+ Another example uses the following snippet to access design variables:
72
+
73
+ A = Femtet.GetVariableValue('A')
74
+
75
+ However, when performing CAD integration, this method does not work
76
+ because the variables are not set in the .femprj file.
77
+
78
+ In CAD integration, design variables are obtained in the following way.
79
+
80
+ # How to obtain a dictionary with the variable names of parameters
81
+ # added by add_parameter() as keys.
82
+ params: dict = opt.get_parameter()
83
+ A = params['A']
84
+
85
+ Or
86
+
87
+ # How to obtain an array of values of parameters added in the order
88
+ # by add_parameter().
89
+ values: np.ndarray = opt.get_parameter('values')
90
+ A, B, C = values
91
+
92
+ Objective functions and constraint functions can take arbitrary variables
93
+ after the first argument.
94
+ The FEMOpt member variable `opt` has a method called get_parameter().
95
+ This method allows you to retrieve design variables added by add_parameter().
96
+ By taking `opt` as the second argument, you can execute get_parameter()
97
+ within the objective or constraint function to retrieve design variables.
98
+ """
99
+ A, B, C = opt.get_parameter('values')
100
+ return C - B
101
+
102
+
103
+ if __name__ == '__main__':
104
+
105
+ # Initialize NX-Femtet integration object.
106
+ # At this point, Python is connected to the Femtet.
107
+ fem = FemtetWithNXInterface(
108
+ prt_path='cad_ex01_NX.prt',
109
+ open_result_with_gui=False, # To calculate von Mises stress, set this argument to False. See Femtet Macro Help.
110
+ )
111
+
112
+ # Initialize the FEMOpt object.
113
+ # (establish connection between the optimization problem and Femtet)
114
+ femopt = FEMOpt(fem=fem)
115
+
116
+ # Add design variables to the optimization problem.
117
+ # (Specify the variables registered in the femprj file.)
118
+ femopt.add_parameter('A', 10, lower_bound=1, upper_bound=59)
119
+ femopt.add_parameter('B', 10, lower_bound=1, upper_bound=40)
120
+ femopt.add_parameter('C', 20, lower_bound=5, upper_bound=59)
121
+
122
+ # Add the constraint function to the optimization problem.
123
+ femopt.add_constraint(C_minus_B, 'C>B', lower_bound=1, args=femopt.opt)
124
+
125
+ # Add the objective function to the optimization problem.
126
+ femopt.add_objective(von_mises, name='von Mises (Pa)')
127
+ femopt.add_objective(mass, name='mass (kg)')
128
+
129
+ # Run optimization.
130
+ femopt.set_random_seed(42)
131
+ femopt.optimize(n_trials=20)
132
+ femopt.terminate_all()
@@ -0,0 +1,132 @@
1
+ """External CAD (SOLIDWORKS) Integration
2
+
3
+ Using Femtet's stress analysis solver and Dassault Systemes' CAD software SOLIDWORKS,
4
+ design a lightweight and high-strength H-shaped beam.
5
+
6
+ As a preliminary step, please perform the following procedures:
7
+ - Install SOLIDWORKS
8
+ - Create a C:\temp folder
9
+ - Note: SOLIDWORKS will save a .x_t file in this folder.
10
+ - Place the following files in the same folder:
11
+ - cad_ex01_SW.py (this file)
12
+ - cad_ex01_SW.SLDPRT
13
+ - cad_ex01_SW.femprj
14
+ """
15
+
16
+ import os
17
+
18
+ from win32com.client import constants
19
+
20
+ from pyfemtet.opt import FEMOpt
21
+ from pyfemtet.opt.interface import FemtetWithSolidworksInterface
22
+ from pyfemtet.core import ModelError
23
+
24
+
25
+ here, me = os.path.split(__file__)
26
+ os.chdir(here)
27
+
28
+
29
+ def von_mises(Femtet):
30
+ """Obtain the maximum von Mises stress of the model.
31
+
32
+ Note:
33
+ The objective or constraint function should take Femtet
34
+ as its first argument and return a float as the output.
35
+
36
+ Warning:
37
+ CAD integration may assign boundary conditions to unintended locations.
38
+
39
+ In this example, if the boundary conditions are assigned as intended,
40
+ the maximum z displacement is always negative.
41
+ If the maximum displacement is not negative, it is assumed that
42
+ boundary condition assignment has failed.
43
+ Then this function raises a ModelError.
44
+
45
+ If a ModelError, MeshError, or SolveError occurs during optimization,
46
+ the optimization process considers the attempt a failure and skips to
47
+ the next trial.
48
+ """
49
+
50
+ # Simple check for the correctness of boundary conditions.
51
+ dx, dy, dz = Femtet.Gogh.Galileo.GetMaxDisplacement_py()
52
+ if dz >= 0:
53
+ raise ModelError('Assigning unintended boundary conditions.')
54
+
55
+ # Von Mises stress calculation.
56
+ Gogh = Femtet.Gogh
57
+ Gogh.Galileo.Potential = constants.GALILEO_VON_MISES_C
58
+ succeed, (x, y, z), mises = Gogh.Galileo.GetMAXPotentialPoint_py(constants.CMPX_REAL_C)
59
+
60
+ return mises
61
+
62
+
63
+ def mass(Femtet):
64
+ """Obtain model mass."""
65
+ return Femtet.Gogh.Galileo.GetMass('H_beam')
66
+
67
+
68
+ def C_minus_B(Femtet, opt):
69
+ """Calculate the difference between C and B dimensions.
70
+
71
+ Another example uses the following snippet to access design variables:
72
+
73
+ A = Femtet.GetVariableValue('A')
74
+
75
+ However, when performing CAD integration, this method does not work
76
+ because the variables are not set in the .femprj file.
77
+
78
+ In CAD integration, design variables are obtained in the following way.
79
+
80
+ # How to obtain a dictionary with the variable names of parameters
81
+ # added by add_parameter() as keys.
82
+ params: dict = opt.get_parameter()
83
+ A = params['A']
84
+
85
+ Or
86
+
87
+ # How to obtain an array of values of parameters added in the order
88
+ # by add_parameter().
89
+ values: np.ndarray = opt.get_parameter('values')
90
+ A, B, C = values
91
+
92
+ Objective functions and constraint functions can take arbitrary variables
93
+ after the first argument.
94
+ The FEMOpt member variable `opt` has a method called get_parameter().
95
+ This method allows you to retrieve design variables added by add_parameter().
96
+ By taking `opt` as the second argument, you can execute get_parameter()
97
+ within the objective or constraint function to retrieve design variables.
98
+ """
99
+ A, B, C = opt.get_parameter('values')
100
+ return C - B
101
+
102
+
103
+ if __name__ == '__main__':
104
+
105
+ # Initialize NX-Femtet integration object.
106
+ # At this point, Python is connected to the Femtet.
107
+ fem = FemtetWithSolidworksInterface(
108
+ sldprt_path='cad_ex01_SW.SLDPRT',
109
+ open_result_with_gui=False, # To calculate von Mises stress, set this argument to False. See Femtet Macro Help.
110
+ )
111
+
112
+ # Initialize the FEMOpt object.
113
+ # (establish connection between the optimization problem and Femtet)
114
+ femopt = FEMOpt(fem=fem)
115
+
116
+ # Add design variables to the optimization problem.
117
+ # (Specify the variables registered in the femprj file.)
118
+ femopt.add_parameter('A', 10, lower_bound=1, upper_bound=59)
119
+ femopt.add_parameter('B', 10, lower_bound=1, upper_bound=40)
120
+ femopt.add_parameter('C', 20, lower_bound=5, upper_bound=59)
121
+
122
+ # Add the constraint function to the optimization problem.
123
+ femopt.add_constraint(C_minus_B, 'C>B', lower_bound=1, args=femopt.opt)
124
+
125
+ # Add the objective function to the optimization problem.
126
+ femopt.add_objective(von_mises, name='von Mises (Pa)')
127
+ femopt.add_objective(mass, name='mass (kg)')
128
+
129
+ # Run optimization.
130
+ femopt.set_random_seed(42)
131
+ femopt.optimize(n_trials=20)
132
+ femopt.terminate_all()