PyFemtet 0.3.6__tar.gz → 0.3.8__tar.gz

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 (26) hide show
  1. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/PKG-INFO +1 -1
  2. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.py +1 -1
  3. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/FemtetPJTSample/gau_ex08_parametric.py +0 -1
  4. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/FemtetPJTSample/her_ex40_parametric.py +0 -1
  5. pyfemtet-0.3.8/pyfemtet/__init__.py +1 -0
  6. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/dispatch_extensions.py +4 -25
  7. pyfemtet-0.3.8/pyfemtet/opt/__init__.py +2 -0
  8. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/opt/base.py +79 -48
  9. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/opt/interface.py +253 -95
  10. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyproject.toml +1 -1
  11. pyfemtet-0.3.6/pyfemtet/__init__.py +0 -1
  12. pyfemtet-0.3.6/pyfemtet/_old/_SW_Femtet.py +0 -163
  13. pyfemtet-0.3.6/pyfemtet/opt/__init__.py +0 -2
  14. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/LICENSE +0 -0
  15. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/README.md +0 -0
  16. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.femprj +0 -0
  17. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.prt +0 -0
  18. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/FemtetPJTSample/gau_ex08_parametric.femprj +0 -0
  19. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/FemtetPJTSample/her_ex40_parametric.femprj +0 -0
  20. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/FemtetPJTSample/wat_ex14_parallel_parametric.py +0 -0
  21. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/FemtetPJTSample/wat_ex14_parametric.femprj +0 -0
  22. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/FemtetPJTSample/wat_ex14_parametric.py +0 -0
  23. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/core.py +0 -0
  24. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/logger.py +0 -0
  25. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/opt/_FemtetWithNX/update_model.py +0 -0
  26. {pyfemtet-0.3.6 → pyfemtet-0.3.8}/pyfemtet/opt/monitor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyfemtet
3
- Version: 0.3.6
3
+ Version: 0.3.8
4
4
  Summary: Design parameter optimization using Femtet.
5
5
  Home-page: https://github.com/pyfemtet/pyfemtet
6
6
  License: BSD-3-Clause
@@ -85,4 +85,4 @@ if __name__ == '__main__':
85
85
 
86
86
  # 最適化の実行
87
87
  femopt.set_random_seed(42)
88
- femopt.main(n_trials=20, n_parallel=3)
88
+ femopt.main(n_trials=20)
@@ -34,7 +34,6 @@ if __name__ == '__main__':
34
34
  opt = OptunaOptimizer(
35
35
  sampler_class=BoTorchSampler,
36
36
  sampler_kwargs=dict(
37
- consider_running_trials=True,
38
37
  n_startup_trials=5,
39
38
  )
40
39
  )
@@ -112,7 +112,6 @@ if __name__ == '__main__':
112
112
  opt = OptunaOptimizer(
113
113
  sampler_class=BoTorchSampler,
114
114
  sampler_kwargs=dict(
115
- consider_running_trials=True,
116
115
  n_startup_trials=5,
117
116
  )
118
117
  )
@@ -0,0 +1 @@
1
+ __version__ = "0.3.8"
@@ -144,12 +144,12 @@ def _get_subprocess_log_prefix():
144
144
  return f'({current_process().name}) '
145
145
 
146
146
 
147
- def launch_and_dispatch_femtet(timeout=DISPATCH_TIMEOUT, strict_pid_specify=True) -> Tuple[IFemtet, int]:
147
+ def launch_and_dispatch_femtet(timeout=DISPATCH_TIMEOUT, strictly_pid_specify=True) -> Tuple[IFemtet, int]:
148
148
  """Launch Femtet by new process and connect to it.
149
149
 
150
150
  Args:
151
151
  timeout (int or float, optional): Seconds to wait for connection. Defaults to DISPATCH_TIMEOUT.
152
- strict_pid_specify (bool): If True, try to connect the launched femtet process strictly. If False, launch new process but try to connect any connectable femtet.
152
+ strictly_pid_specify (bool): If True, try to connect the launched femtet process strictly. If False, launch new process but try to connect any connectable femtet.
153
153
 
154
154
  Raises:
155
155
  FemtetNotFoundException: Launched Femtet is not found for some reason (i.e. failed to launch Femtet).
@@ -166,7 +166,7 @@ def launch_and_dispatch_femtet(timeout=DISPATCH_TIMEOUT, strict_pid_specify=True
166
166
  sleep(1)
167
167
 
168
168
  # dispatch femtet
169
- if strict_pid_specify:
169
+ if strictly_pid_specify:
170
170
  Femtet, pid = dispatch_specific_femtet(pid, timeout)
171
171
  else:
172
172
  # worker process なら排他処理する
@@ -350,6 +350,7 @@ def dispatch_specific_femtet_core(pid, timeout=DISPATCH_TIMEOUT) -> Tuple[IFemte
350
350
  If you have 2 free Femtet processes (their pid is 1000 and 1001),
351
351
  you can only connect to first connected Femtet
352
352
  and cannot change the connection.
353
+
353
354
  >>> from pyfemtet.dispatch_extensions import dispatch_specific_femtet
354
355
  >>> Femtet1, pid1 = dispatch_specific_femtet(pid=1000)
355
356
  >>> print(pid1) # 1000
@@ -361,28 +362,6 @@ def dispatch_specific_femtet_core(pid, timeout=DISPATCH_TIMEOUT) -> Tuple[IFemte
361
362
  If you want to connect 2 more Femtet processes from single script or interprete,
362
363
  please consider parallel processing.
363
364
 
364
- Code:
365
-
366
- from multiprocessing import Process
367
- from pyfemtet.dispatch_extensions import dispatch_specific_femtet
368
-
369
- def connect_femtet(pid):
370
- Femtet, pid = dispatch_specific_femtet(pid=pid)
371
- ... # some processing
372
-
373
- if __name__ == '__main__':
374
- # setup parallel processing
375
- p1 = Process(target=connect_femtet, args=(1000,))
376
- p2 = Process(target=connect_femtet, args=(1001,))
377
-
378
- # start parallel processing
379
- p1.start()
380
- p2.start()
381
-
382
- # wait for finish processes
383
- p1.join()
384
- p2.join()
385
-
386
365
  Args:
387
366
  pid (int): Process id of Femtet that you want to connect.
388
367
  timeout (int or float, optional): Seconds to wait for connection. Defaults to 10.
@@ -0,0 +1,2 @@
1
+ from .base import FEMOpt, OptunaOptimizer
2
+ from .interface import FemtetInterface, NoFEM, FemtetWithNXInterface, FemtetWithSolidworksInterface
@@ -204,9 +204,6 @@ class Function:
204
204
  def calc(self, fem: FEMInterface):
205
205
  """Execute user-defined fun.
206
206
 
207
- If fem is a FemtetInterface,
208
- the 1st argument of fun is set to fem automatically.
209
-
210
207
  Args:
211
208
  fem (FEMInterface)
212
209
 
@@ -217,7 +214,7 @@ class Function:
217
214
  # Femtet 特有の処理
218
215
  if isinstance(fem, FemtetInterface):
219
216
  args = (fem.Femtet, *args)
220
- return self.fun(*args, **self.kwargs)
217
+ return float(self.fun(*args, **self.kwargs))
221
218
 
222
219
  def _restore_constants(self):
223
220
  """Helper function for parallelize Femtet."""
@@ -246,6 +243,11 @@ class Objective(Function):
246
243
  Raises:
247
244
  ValueError: If the direction is not valid.
248
245
 
246
+ Note:
247
+ If FEMOpt.fem is a instance of FemtetInterface or its subclass,
248
+ the 1st argument of fun is set to fem automatically.
249
+
250
+
249
251
  """
250
252
  _check_direction(direction)
251
253
  self.direction = direction
@@ -257,7 +259,7 @@ class Objective(Function):
257
259
  When direction is `'minimize'`, ``value`` is calculated.
258
260
  When direction is `'maximize'`, ``-value`` is calculated.
259
261
  When direction is float, ``abs(value - direction)`` is calculated.
260
- Finally, the calculated value is passed to the symlog function.
262
+ Finally, the calculated value is passed to the symlog function and returns it.
261
263
 
262
264
  ``value`` is the return value of the user-defined function.
263
265
 
@@ -280,7 +282,7 @@ class Objective(Function):
280
282
 
281
283
  ret = symlog(ret)
282
284
 
283
- return ret
285
+ return float(ret)
284
286
 
285
287
 
286
288
  class Constraint(Function):
@@ -312,7 +314,7 @@ class Constraint(Function):
312
314
  super().__init__(fun, name, args, kwargs)
313
315
 
314
316
 
315
- class HistoryDfCore:
317
+ class _HistoryDfCore:
316
318
  """Class for managing a DataFrame object in a distributed manner."""
317
319
 
318
320
  def __init__(self):
@@ -334,6 +336,7 @@ class History:
334
336
  param_names (list): The names of the parameters in the study.
335
337
  obj_names (list): The names of the objectives in the study.
336
338
  cns_names (list): The names of the constraints in the study.
339
+ actor_data (pd.DataFrame): The history data of optimization.
337
340
 
338
341
  """
339
342
  def __init__(self, history_path, client):
@@ -347,7 +350,7 @@ class History:
347
350
  # 引数の処理
348
351
  self.path = history_path # .csv
349
352
  self.is_restart = False
350
- self._future = client.submit(HistoryDfCore, actor=True)
353
+ self._future = client.submit(_HistoryDfCore, actor=True)
351
354
  self._actor_data = self._future.result()
352
355
  self.tmp_data = pd.DataFrame()
353
356
  self.param_names = []
@@ -544,7 +547,7 @@ class History:
544
547
  df.loc[i, 'hypervolume'] = 0
545
548
 
546
549
 
547
- class OptimizationStatusActor:
550
+ class _OptimizationStatusActor:
548
551
  status_int = -1
549
552
  status = 'undefined'
550
553
 
@@ -554,11 +557,7 @@ class OptimizationStatusActor:
554
557
 
555
558
 
556
559
  class OptimizationStatus:
557
- """Optimization status.
558
-
559
- undefined -> setting up -> launching -> running (-> interrupting) -> terminated
560
-
561
- """
560
+ """Optimization status."""
562
561
  UNDEFINED = -1
563
562
  INITIALIZING = 0
564
563
  SETTING_UP = 10
@@ -571,7 +570,7 @@ class OptimizationStatus:
571
570
  TERMINATE_ALL = 60
572
571
 
573
572
  def __init__(self, client, name='entire'):
574
- self._future = client.submit(OptimizationStatusActor, actor=True)
573
+ self._future = client.submit(_OptimizationStatusActor, actor=True)
575
574
  self._actor = self._future.result()
576
575
  self.name = name
577
576
  self.set(self.INITIALIZING)
@@ -606,6 +605,25 @@ class OptimizationStatus:
606
605
 
607
606
 
608
607
  class AbstractOptimizer(ABC):
608
+ """Abstract base class for an interface of optimization library.
609
+
610
+ Attributes:
611
+ fem (FEMInterface): The finite element method object.
612
+ fem_class (type): The class of the finite element method object.
613
+ fem_kwargs (dict): The keyword arguments used to instantiate the finite element method object.
614
+ parameters (pd.DataFrame): The parameters used in the optimization.
615
+ objectives (dict): A dictionary containing the objective functions used in the optimization.
616
+ constraints (dict): A dictionary containing the constraint functions used in the optimization.
617
+ entire_status (OptimizationStatus): The status of the entire optimization process.
618
+ history (History): An actor object that records the history of each iteration in the optimization process.
619
+ worker_status (OptimizationStatus): The status of each worker in a distributed computing environment.
620
+ message (str): A message associated with the current state of the optimization process.
621
+ seed (int or None): The random seed used for random number generation during the optimization process.
622
+ timeout (float or int or None): The maximum time allowed for each iteration of the optimization process. If exceeded, it will be interrupted and terminated early.
623
+ n_trials (int or None): The maximum number of trials allowed for each iteration of the optimization process. If exceeded, it will be interrupted and terminated early.
624
+ is_cluster (bool): Flag indicating if running on a distributed computing cluster.
625
+
626
+ """
609
627
 
610
628
  def __init__(self):
611
629
  self.fem = None
@@ -624,6 +642,7 @@ class AbstractOptimizer(ABC):
624
642
  self.is_cluster = False
625
643
 
626
644
  def f(self, x):
645
+ """Get x, update fem analysis, return objectives (and constraints)."""
627
646
  # interruption の実装は具象クラスに任せる
628
647
 
629
648
  # x の更新
@@ -657,6 +676,7 @@ class AbstractOptimizer(ABC):
657
676
  return np.array(y), np.array(_y), np.array(c)
658
677
 
659
678
  def set_fem(self, skip_reconstruct=False):
679
+ """Reconstruct FEMInterface in a subprocess."""
660
680
  # restore fem
661
681
  if not skip_reconstruct:
662
682
  self.fem = self.fem_class(**self.fem_kwargs)
@@ -692,7 +712,8 @@ class AbstractOptimizer(ABC):
692
712
  else:
693
713
  raise ValueError('get_parameter() got invalid format: {format}')
694
714
 
695
- def check_interruption(self):
715
+ def _check_interruption(self):
716
+ """"""
696
717
  if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
697
718
  self.worker_status.set(OptimizationStatus.INTERRUPTING)
698
719
  self.finalize()
@@ -701,6 +722,7 @@ class AbstractOptimizer(ABC):
701
722
  return False
702
723
 
703
724
  def finalize(self):
725
+ """Destruct fem and set worker status."""
704
726
  del self.fem
705
727
  self.worker_status.set(OptimizationStatus.TERMINATED)
706
728
 
@@ -716,25 +738,26 @@ class AbstractOptimizer(ABC):
716
738
  self.worker_status = worker_status_list[subprocess_idx]
717
739
  self.worker_status.set(OptimizationStatus.LAUNCHING_FEM)
718
740
 
719
- if self.check_interruption():
741
+ if self._check_interruption():
720
742
  return None
721
743
 
722
744
  # set_fem をはじめ、終了したらそれを示す
723
745
  if not skip_set_fem: # なくても動く??
724
746
  self.set_fem()
747
+ self.fem.setup_after_parallel()
725
748
  self.worker_status.set(OptimizationStatus.WAIT_OTHER_WORKERS)
726
749
 
727
750
  # wait_setup or not
728
751
  if wait_setup:
729
752
  while True:
730
- if self.check_interruption():
753
+ if self._check_interruption():
731
754
  return None
732
755
  # 他のすべての worker_status が wait 以上になったら break
733
756
  if all([ws.get() >= OptimizationStatus.WAIT_OTHER_WORKERS for ws in worker_status_list]):
734
757
  break
735
758
  sleep(1)
736
759
  else:
737
- if self.check_interruption():
760
+ if self._check_interruption():
738
761
  return None
739
762
 
740
763
  # set status running
@@ -752,12 +775,12 @@ class AbstractOptimizer(ABC):
752
775
 
753
776
  @abstractmethod
754
777
  def main(self, subprocess_idx: int = 0) -> None:
755
- """Called from dask worker."""
778
+ """Start calcuration using optimization library."""
756
779
  pass
757
780
 
758
781
  @abstractmethod
759
782
  def setup_before_parallel(self, *args, **kwargs):
760
- """Preprocessing for dask parallel workers."""
783
+ """Setup before parallel processes are launched."""
761
784
  pass
762
785
 
763
786
 
@@ -779,7 +802,6 @@ class OptunaOptimizer(AbstractOptimizer):
779
802
  self.additional_initial_parameter = []
780
803
  self.additional_initial_methods = add_init_method if hasattr(add_init_method, '__iter__') else [add_init_method]
781
804
 
782
-
783
805
  def _objective(self, trial):
784
806
 
785
807
  # 中断の確認 (FAIL loop に陥る対策)
@@ -820,7 +842,7 @@ class OptunaOptimizer(AbstractOptimizer):
820
842
  try:
821
843
  _, _y, c = self.f(x)
822
844
  except (ModelError, MeshError, SolveError) as e:
823
- logger.info(str(type(e)).split("'")[1].split('.')[-1], e)
845
+ logger.info(str(type(e)))
824
846
  logger.info('以下の変数で FEM 解析に失敗しました。')
825
847
  print(self.get_parameter('dict'))
826
848
 
@@ -855,7 +877,7 @@ class OptunaOptimizer(AbstractOptimizer):
855
877
  return trial.user_attrs['constraint'] if 'constraint' in trial.user_attrs.keys() else (1,) # infeasible
856
878
 
857
879
  def setup_before_parallel(self):
858
- """Main process から呼ばれる関数"""
880
+ """Create storage, study and set initial parameter."""
859
881
 
860
882
  # create storage
861
883
  self.study_name = os.path.basename(self.history.path)
@@ -951,7 +973,7 @@ class OptunaOptimizer(AbstractOptimizer):
951
973
  self.additional_initial_parameter.append([parameter, name])
952
974
 
953
975
  def main(self, subprocess_idx=0):
954
- """Dask worker から呼ばれる関数"""
976
+ """Set random seed, sampler, study and run study.optimize()."""
955
977
 
956
978
  # (re)set random seed
957
979
  seed = self.seed
@@ -1167,7 +1189,8 @@ class FEMOpt:
1167
1189
  lower_bound (float or Non, optional): The lower bound of the constraint. Defaults to None.
1168
1190
  upper_bound (float or Non, optional): The upper bound of the constraint. Defaults to None.
1169
1191
  strict (bool, optional): Flag indicating if it is a strict constraint. Defaults to True.
1170
- args (tuple or None, optional): Additional arguments for the constraint function. Defaults toNone.
1192
+ args (tuple or None, optional): Additional arguments for the constraint function. Defaults to None.
1193
+ kwargs (dict): Additional arguments for the constraint function. Defaults to None.
1171
1194
 
1172
1195
  Note:
1173
1196
  If the FEMInterface is FemtetInterface, the 1st argument of fun should be Femtet (IPyDispatch) object.
@@ -1229,10 +1252,13 @@ class FEMOpt:
1229
1252
  port (int or None, optional): The port number of the monitor server. If None, ``8080`` will be used. Defaults to None.
1230
1253
 
1231
1254
  Tip:
1232
- If host is ``0.0.0.0``, a server will be set up
1233
- that is visible from the local network.
1234
- Start a browser on another machine
1235
- and type ``<ip_address_or_hostname>:<port>`` in the address bar.
1255
+ If you do not know the host IP address
1256
+ for connecting to the local network,
1257
+ use the ``ipconfig`` command to find out.
1258
+
1259
+ Alternatively, you can specify host as 0.0.0.0
1260
+ to access the monitor server through all network interfaces
1261
+ used by that computer.
1236
1262
 
1237
1263
  However, please note that in this case,
1238
1264
  it will be visible to all users on the local network.
@@ -1262,15 +1288,14 @@ class FEMOpt:
1262
1288
  If setup_monitor_server() is not executed, a local server for monitoring will be started at localhost:8080.
1263
1289
 
1264
1290
  Note:
1265
- If ``n_trials`` and ``timeout`` are both None, it will calculate repeatedly until interrupting by the user.
1291
+ If ``n_trials`` and ``timeout`` are both None, it runs forever until interrupting by the user.
1266
1292
 
1267
1293
  Note:
1268
1294
  If ``n_parallel`` >= 2, depending on the end timing, ``n_trials`` may be exceeded by up to ``n_parallel-1`` times.
1269
1295
 
1270
- Note:
1271
- Currently, supported methods are 'TPE' and 'botorch'.
1272
- For detail, see https://optuna.readthedocs.io/en/stable/reference/samplers/index.html.
1273
-
1296
+ Warning:
1297
+ If ``n_parallel`` >= 2 and ``fem`` is a subclass of ``FemtetInterface``, the ``strictly_pid_specify`` of subprocess is set to ``False``.
1298
+ So **it is recommended to close all other Femtet processes before running main().**
1274
1299
 
1275
1300
  """
1276
1301
 
@@ -1296,7 +1321,8 @@ class FEMOpt:
1296
1321
  if not self.opt.is_cluster:
1297
1322
  subprocess_indices = subprocess_indices[1:]
1298
1323
  worker_addresses = list(self.client.nthreads().keys())
1299
- assert max(subprocess_indices) <= len(worker_addresses)-1, f'コア数{len(worker_addresses)}は不足しています。'
1324
+ if len(subprocess_indices)>0:
1325
+ assert max(subprocess_indices) <= len(worker_addresses)-1, f'コア数{len(worker_addresses)}は不足しています。'
1300
1326
  worker_addresses = worker_addresses[:len(range(n_parallel))] # TODO: ノードごとに適度に振り分ける
1301
1327
  if not self.opt.is_cluster:
1302
1328
  worker_addresses[0] = 'Main'
@@ -1322,19 +1348,9 @@ class FEMOpt:
1322
1348
  list(self.opt.constraints.keys()),
1323
1349
  )
1324
1350
 
1325
- # fem
1326
- self.fem.setup_before_parallel(self.client)
1327
-
1328
- # opt
1329
- self.opt.fem_class = type(self.fem)
1330
- self.opt.fem_kwargs = self.fem.kwargs
1331
- self.opt.entire_status = self.status
1332
- self.opt.history = self.history
1333
- self.opt.setup_before_parallel()
1334
-
1335
1351
  # launch monitor
1336
1352
  self.monitor_process_future = self.client.submit(
1337
- start_monitor_server_forever,
1353
+ start_monitor_server,
1338
1354
  self.history,
1339
1355
  self.status,
1340
1356
  worker_addresses,
@@ -1344,6 +1360,16 @@ class FEMOpt:
1344
1360
  allow_other_workers=False
1345
1361
  )
1346
1362
 
1363
+ # fem
1364
+ self.fem.setup_before_parallel(self.client)
1365
+
1366
+ # opt
1367
+ self.opt.fem_class = type(self.fem)
1368
+ self.opt.fem_kwargs = self.fem.kwargs
1369
+ self.opt.entire_status = self.status
1370
+ self.opt.history = self.history
1371
+ self.opt.setup_before_parallel()
1372
+
1347
1373
  # クラスターでの計算開始
1348
1374
  self.status.set(OptimizationStatus.LAUNCHING_FEM)
1349
1375
  start = time()
@@ -1406,6 +1432,11 @@ class FEMOpt:
1406
1432
  logger.info(f'結果は{self.history.path}を確認してください.')
1407
1433
 
1408
1434
  def terminate_all(self):
1435
+ """Try to terminate all launched processes.
1436
+
1437
+ If distributed computing, Scheduler and Workers will NOT be terminated.
1438
+
1439
+ """
1409
1440
 
1410
1441
  # monitor が terminated 状態で少なくとも一度更新されなければ running のまま固まる
1411
1442
  sleep(1)
@@ -1452,7 +1483,7 @@ class FEMOpt:
1452
1483
  sleep(3)
1453
1484
 
1454
1485
 
1455
- def start_monitor_server_forever(history, status, worker_addresses, worker_status_list, host='localhost', port=8080):
1486
+ def start_monitor_server(history, status, worker_addresses, worker_status_list, host='localhost', port=8080):
1456
1487
  monitor = Monitor(history, status, worker_addresses, worker_status_list)
1457
1488
  monitor.start_server(worker_addresses, worker_status_list, host, port)
1458
1489
  return 'Exit monitor server process gracefully'
@@ -1,16 +1,19 @@
1
1
  import os
2
+ import re
2
3
  import sys
3
4
  from time import sleep, time
4
5
  import json
5
6
  import subprocess
6
7
  import signal
8
+ from abc import ABC, abstractmethod
7
9
 
8
10
  import pandas as pd
9
11
  import psutil
10
12
  from pywintypes import com_error
11
13
  from pythoncom import CoInitialize, CoUninitialize
12
- from win32com.client import constants, Dispatch
14
+ from win32com.client import constants, DispatchEx
13
15
  from dask.distributed import get_worker
16
+ from tqdm import trange
14
17
  from femtetutils import util
15
18
 
16
19
  from ..core import (
@@ -34,7 +37,7 @@ logger.setLevel(logging.INFO)
34
37
  here, me = os.path.split(__file__)
35
38
 
36
39
 
37
- class FEMInterface:
40
+ class FEMInterface(ABC):
38
41
  """Abstract base class for the interface with FEM software."""
39
42
 
40
43
  def __init__(
@@ -46,59 +49,28 @@ class FEMInterface:
46
49
  The concrete class should call super().__init__() with the desired arguments when restoring.
47
50
 
48
51
  Args:
49
- subprocess_idx (int or None, optional): The index of the subprocess. Defaults to None.
50
- subprocess_settings ('Any' or None, optional): Additional settings for the subprocess. Defaults to None.
51
- **kwargs: keyword arguments for FEMInterface constructor.
52
+ **kwargs: keyword arguments for FEMInterface (re)constructor.
52
53
 
53
54
  """
54
55
  # restore のための情報保管
55
56
  self.kwargs = kwargs
56
57
 
57
- def check_param_value(self, param_name) -> float:
58
- """Checks the value of a parameter in the FEM model.
59
-
60
- Args:
61
- param_name (str): The name of the parameter.
62
-
63
- Raises:
64
- Exception: If param_name does not exist in the FEM model.
65
-
66
- Returns:
67
- float: The value of the parameter.
68
-
69
- """
70
- return 0.0
71
-
58
+ @abstractmethod
72
59
  def update(self, parameters: pd.DataFrame) -> None:
73
- """Updates the FEM analysis based on the proposed parameters.
74
-
75
- Args:
76
- parameters (pd.DataFrame): The parameters to be updated. Must have at least 'name' and 'value'.
60
+ """Updates the FEM analysis based on the proposed parameters."""
61
+ raise NotImplementedError('update() must be implemented.')
77
62
 
78
- Raises:
79
- ModelError, MeshError, SolveError: If there are errors in updating or analyzing the model.
80
-
81
- Returns:
82
- None
83
-
84
- """
85
- pass
63
+ def check_param_value(self, param_name) -> float or None:
64
+ """Checks the value of a parameter in the FEM model (if implemented in concrete class)."""
65
+ if False:
66
+ raise RuntimeError(f"{param_name} doesn't exist on FEM model.")
86
67
 
87
68
  def update_parameter(self, parameters: pd.DataFrame) -> None:
88
- """Updates only FEM variables that can be obtained with Femtet.GetVariableValue and similar functions.
89
-
90
- If users define a function to be passed to add_objective etc. to take OptimizerBase as an argument,
91
- and access the variable by calling OptimizerBase.get_parameter() within the function,
92
- there is no need to implement this method in the concrete class of FEMInterface.
93
-
94
- Args:
95
- parameters (pd.DataFrame): The parameters to be updated.
96
-
97
- """
69
+ """Updates only FEM variables (if implemented in concrete class)."""
98
70
  pass
99
71
 
100
72
  def setup_before_parallel(self, client) -> None:
101
- """Preprocessing before launching a dask worker.
73
+ """Preprocessing before launching a dask worker (if implemented in concrete class).
102
74
 
103
75
  Args:
104
76
  client: dask client.
@@ -110,29 +82,42 @@ class FEMInterface:
110
82
  """
111
83
  pass
112
84
 
85
+ def setup_after_parallel(self):
86
+ """Preprocessing after launching a dask worker and before run optimization (if implemented in concrete class)."""
87
+ pass
113
88
 
114
- class FemtetInterface(FEMInterface):
115
- """Concrete class for the interface with Femtet software."""
116
-
117
- def __init__(
118
- self,
119
- femprj_path=None,
120
- model_name=None,
121
- connect_method='auto',
122
- strict_pid_specify=False,
123
- **kwargs
124
- ):
125
- """Initializes the FemtetInterface.
126
89
 
90
+ class FemtetInterface(FEMInterface):
91
+ """Concrete class for the interface with Femtet software.
92
+
127
93
  Args:
128
94
  femprj_path (str or None, optional): The path to the .femprj file. Defaults to None.
129
95
  model_name (str or None, optional): The name of the analysis model. Defaults to None.
130
96
  connect_method (str, optional): The connection method to use. Can be 'new', 'existing', or 'auto'. Defaults to 'auto'.
97
+ strictly_pid_specify (bool, optional): If True and connect_method=='new', search launched Femtet process strictly based on its process id.
98
+
99
+ Warning:
100
+ Even if you specify ``strictly_pid_specify=True`` on the constructor,
101
+ **the connection behavior is like** ``strictly_pid_specify=False`` **in parallel processing**
102
+ because of its large overhead.
103
+ So you should close all Femtet processes before running FEMOpt.main()
104
+ if ``n_parallel`` >= 2.
105
+
106
+
131
107
 
132
108
  Tip:
133
109
  If you search for information about the method to connect python and Femtet, see :func:`connect_femtet`.
110
+
111
+ """
134
112
 
135
- """
113
+ def __init__(
114
+ self,
115
+ femprj_path=None,
116
+ model_name=None,
117
+ connect_method='auto',
118
+ strictly_pid_specify=True,
119
+ **kwargs # 継承されたクラスからの引数
120
+ ):
136
121
 
137
122
  # win32com の初期化
138
123
  CoInitialize()
@@ -151,7 +136,7 @@ class FemtetInterface(FEMInterface):
151
136
  self.connected_method = 'unconnected'
152
137
  self.parameters = None
153
138
  self.max_api_retry = 3
154
- self.strict_pid_specify = strict_pid_specify
139
+ self.strictly_pid_specify = strictly_pid_specify
155
140
 
156
141
  # dask サブプロセスのときは femprj を更新し connect_method を new にする
157
142
  try:
@@ -160,6 +145,7 @@ class FemtetInterface(FEMInterface):
160
145
  # worker なら femprj_path が None でないはず
161
146
  self.femprj_path = os.path.join(space, os.path.basename(self.femprj_path))
162
147
  self.connect_method = 'new'
148
+ self.strictly_pid_specify = False
163
149
  except ValueError: # get_worker に失敗した場合
164
150
  pass
165
151
 
@@ -170,7 +156,7 @@ class FemtetInterface(FEMInterface):
170
156
  # 接続した Femtet の種類に応じて del 時に quit するかどうか決める
171
157
  self.quit_when_destruct = self.connected_method == 'new'
172
158
 
173
- # restore するための情報保管
159
+ # subprocess で restore するための情報保管
174
160
  # パスなどは connect_and_open_femtet での処理結果を反映し
175
161
  # メインで開いた解析モデルが確実に開かれるようにする
176
162
  super().__init__(
@@ -201,7 +187,7 @@ class FemtetInterface(FEMInterface):
201
187
  def _connect_new_femtet(self):
202
188
  logger.info('└ Try to launch and connect new Femtet process.')
203
189
 
204
- self.Femtet, _ = launch_and_dispatch_femtet(strict_pid_specify=self.strict_pid_specify)
190
+ self.Femtet, _ = launch_and_dispatch_femtet(strictly_pid_specify=self.strictly_pid_specify)
205
191
 
206
192
  self.connected_method = 'new'
207
193
 
@@ -217,21 +203,25 @@ class FemtetInterface(FEMInterface):
217
203
 
218
204
  def connect_femtet(self, connect_method: str = 'auto', pid: int or None = None):
219
205
  """Connects to a Femtet process.
206
+
220
207
  Args:
221
208
  connect_method (str, optional): The connection method. Can be 'new', 'existing', or 'auto'. Defaults to 'new'.
222
209
  pid (int or None, optional): The process ID of an existing Femtet process and wanted to connect.
223
210
 
224
- When connect_method is 'new', starts a new Femtet process and connects to it.
225
- **`pid` will be ignored.**
211
+ Note:
212
+ When connect_method is 'new', starts a new Femtet process and connects to it.
213
+ **`pid` will be ignored.**
226
214
 
227
- When 'existing', connect to an existing Femtet process.
228
- However, if there are no Femtets to which it can connect
229
- (i.e. already connected to another Python or Excel process),
230
- it throws an exception.
215
+ Note:
216
+ When 'existing', connect to an existing Femtet process.
217
+ However, if there are no Femtets to which it can connect
218
+ (i.e. already connected to another Python or Excel process),
219
+ it throws an exception.
231
220
 
232
- When set to 'auto', first tries 'existing', and if that fails, connects with 'new'.
233
- If `pid` is specified and failed to connect,
234
- it will not try existing another Femtet process.
221
+ Note:
222
+ When set to 'auto', first tries 'existing', and if that fails, connects with 'new'.
223
+ If `pid` is specified and failed to connect,
224
+ it will not try existing another Femtet process.
235
225
 
236
226
  """
237
227
 
@@ -398,6 +388,7 @@ class FemtetInterface(FEMInterface):
398
388
  )
399
389
 
400
390
  def femtet_is_alive(self) -> bool:
391
+ """Returns connected femtet process is exsiting or not."""
401
392
  return _get_pid(self.Femtet.hWnd) > 0
402
393
 
403
394
  def open(self, femprj_path: str, model_name: str or None = None) -> None:
@@ -596,12 +587,21 @@ class FemtetInterface(FEMInterface):
596
587
 
597
588
  class NoFEM(FEMInterface):
598
589
  """Interface with no FEM for debug."""
599
- pass
590
+ def update(self, parameters: pd.DataFrame) -> None:
591
+ pass
600
592
 
601
593
 
602
594
  class FemtetWithNXInterface(FemtetInterface):
595
+ """Femtet with NX interface class.
596
+
597
+ Args:
598
+ prt_path: The path to the prt file.
599
+
600
+ For details of The other arguments, see ``FemtetInterface``.
601
+
602
+ """
603
603
 
604
- JOURNAL_PATH = os.path.abspath(os.path.join(here, '_FemtetWithNX/update_model.py'))
604
+ _JOURNAL_PATH = os.path.abspath(os.path.join(here, '_FemtetWithNX/update_model.py'))
605
605
 
606
606
  def __init__(
607
607
  self,
@@ -609,21 +609,8 @@ class FemtetWithNXInterface(FemtetInterface):
609
609
  femprj_path=None,
610
610
  model_name=None,
611
611
  connect_method='auto',
612
+ strictly_pid_specify=True,
612
613
  ):
613
- """
614
- Initializes the FemtetWithNXInterface class.
615
-
616
- Args:
617
- prt_path: The path to the prt file.
618
- femprj_path: The path to the femprj file (optional).
619
- model_name: The name of the model (optional).
620
- connect_method: The connection method (default 'auto').
621
- subprocess_idx: The subprocess index (optional).
622
- subprocess_settings: The subprocess settings (optional).
623
-
624
- Returns:
625
- None
626
- """
627
614
 
628
615
  # check NX installation
629
616
  self.run_journal_path = os.path.join(os.environ.get('UGII_BASE_DIR'), 'NXBIN', 'run_journal.exe')
@@ -633,33 +620,44 @@ class FemtetWithNXInterface(FemtetInterface):
633
620
  # 引数の処理
634
621
  self.prt_path = os.path.abspath(prt_path)
635
622
 
623
+ # dask サブプロセスのときは prt_path を worker space から取るようにする
624
+ try:
625
+ worker = get_worker()
626
+ space = worker.local_directory
627
+ # worker なら femprj_path が None でないはず
628
+ self.prt_path = os.path.join(space, os.path.basename(self.prt_path))
629
+ except ValueError: # get_worker に失敗した場合
630
+ pass
631
+
636
632
  # FemtetInterface の設定 (femprj_path, model_name の更新など)
637
633
  # + restore 情報の上書き
638
634
  super().__init__(
639
635
  femprj_path=femprj_path,
640
636
  model_name=model_name,
641
637
  connect_method=connect_method,
642
- prt_path=os.path.basename(prt_path) # upload_file でアップロードされたファイルへのパスになる
638
+ strictly_pid_specify=strictly_pid_specify,
639
+ prt_path=self.prt_path,
643
640
  )
644
641
 
645
642
 
646
643
  def check_param_value(self, name):
647
- # desable FemtetInterface.check_param_value()
648
- # because the parameter can be registered to not only .femprj but also .prt.
649
- return None
644
+ """Override FemtetInterface.check_param_value().
645
+
646
+ Do nothing because the parameter can be registered
647
+ to not only .femprj but also .prt.
648
+
649
+ """
650
+ pass
650
651
 
651
652
  def setup_before_parallel(self, client):
652
- client.upload_file(
653
- self.kwargs['femprj_path'],
654
- False
655
- )
656
653
  client.upload_file(
657
654
  self.kwargs['prt_path'],
658
655
  False
659
656
  )
657
+ super().setup_before_parallel(client)
660
658
 
661
659
  def update_model(self, parameters: 'pd.DataFrame') -> None:
662
- """Update .x_t file before FemtetInterface.update_model()"""
660
+ """Update .x_t"""
663
661
 
664
662
  self.parameters = parameters.copy()
665
663
 
@@ -679,7 +677,7 @@ class FemtetWithNXInterface(FemtetInterface):
679
677
  # NX journal を使ってモデルを編集する
680
678
  env = os.environ.copy()
681
679
  subprocess.run(
682
- [self.run_journal_path, self.JOURNAL_PATH, '-args', self.prt_path, str_json, x_t_path],
680
+ [self.run_journal_path, self._JOURNAL_PATH, '-args', self.prt_path, str_json, x_t_path],
683
681
  env=env,
684
682
  shell=True,
685
683
  cwd=os.path.dirname(self.prt_path)
@@ -711,7 +709,167 @@ class FemtetWithNXInterface(FemtetInterface):
711
709
  super().update_model(parameters)
712
710
 
713
711
 
712
+ class FemtetWithSolidworksInterface(FemtetInterface):
714
713
 
715
- # TODO: SW-Femtet 再実装
714
+ # 定数の宣言
715
+ swThisConfiguration = 1 # https://help.solidworks.com/2023/english/api/swconst/SOLIDWORKS.Interop.swconst~SOLIDWORKS.Interop.swconst.swInConfigurationOpts_e.html
716
+ swAllConfiguration = 2
717
+ swSpecifyConfiguration = 3 # use with ConfigName argument
718
+ swSaveAsCurrentVersion = 0
719
+ swSaveAsOptions_Copy = 2 #
720
+ swSaveAsOptions_Silent = 1 # https://help.solidworks.com/2021/english/api/swconst/solidworks.interop.swconst~solidworks.interop.swconst.swsaveasoptions_e.html
721
+ swSaveWithReferencesOptions_None = 0 # https://help-solidworks-com.translate.goog/2023/english/api/swconst/SolidWorks.Interop.swconst~SolidWorks.Interop.swconst.swSaveWithReferencesOptions_e.html?_x_tr_sl=auto&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=wapp
722
+ swDocPART = 1 # https://help.solidworks.com/2023/english/api/swconst/SOLIDWORKS.Interop.swconst~SOLIDWORKS.Interop.swconst.swDocumentTypes_e.html
716
723
 
724
+ def __init__(
725
+ self,
726
+ sldprt_path,
727
+ femprj_path=None,
728
+ model_name=None,
729
+ connect_method='auto',
730
+ strictly_pid_specify=True,
731
+ ):
732
+ # 引数の処理
733
+ self.sldprt_path = os.path.abspath(sldprt_path)
717
734
 
735
+ # dask サブプロセスのときは prt_path を worker space から取るようにする
736
+ try:
737
+ worker = get_worker()
738
+ space = worker.local_directory
739
+ # worker なら femprj_path が None でないはず
740
+ self.prt_path = os.path.join(space, os.path.basename(self.prt_path))
741
+ except ValueError: # get_worker に失敗した場合
742
+ pass
743
+
744
+ # FemtetInterface の設定 (femprj_path, model_name の更新など)
745
+ # + restore 情報の上書き
746
+ super().__init__(
747
+ femprj_path=femprj_path,
748
+ model_name=model_name,
749
+ connect_method=connect_method,
750
+ strictly_pid_specify=strictly_pid_specify,
751
+ sldprt_path=self.sldprt_path,
752
+ )
753
+
754
+ def initialize_sldworks_connection(self):
755
+ # SolidWorks を捕まえ、ファイルを開く
756
+ self.swApp = DispatchEx('SLDWORKS.Application')
757
+ self.swApp.Visible = True
758
+
759
+ # open model
760
+ self.swApp.OpenDoc(self.sldprt_path, self.swDocPART)
761
+ self.swModel = self.swApp.ActiveDoc
762
+ self.swEqnMgr = self.swModel.GetEquationMgr
763
+ self.nEquation = self.swEqnMgr.GetCount
764
+
765
+ def check_param_value(self, param_name):
766
+ """Override FemtetInterface.check_param_value().
767
+
768
+ Do nothing because the parameter can be registered
769
+ to not only .femprj but also .SLDPRT.
770
+
771
+ """
772
+ pass
773
+
774
+ def setup_before_parallel(self, client):
775
+ client.upload_file(
776
+ self.kwargs['sldprt_path'],
777
+ False
778
+ )
779
+ super().setup_before_parallel(client)
780
+
781
+ def setup_after_parallel(self):
782
+ CoInitialize()
783
+ self.initialize_sldworks_connection()
784
+
785
+ def update_model(self, parameters: pd.DataFrame):
786
+ """Update .x_t"""
787
+
788
+ self.parameters = parameters.copy()
789
+
790
+ # Femtet が参照している x_t パスを取得する
791
+ x_t_path = self.Femtet.Gaudi.LastXTPath
792
+
793
+ # 前のが存在するならば消しておく
794
+ if os.path.isfile(x_t_path):
795
+ os.remove(x_t_path)
796
+
797
+ # solidworks のモデルの更新
798
+ self.update_sw_model(parameters)
799
+
800
+ # export as x_t
801
+ self.swModel.SaveAs(x_t_path)
802
+
803
+ # 30 秒待っても x_t ができてなければエラー(COM なので)
804
+ timeout = 30
805
+ start = time()
806
+ while True:
807
+ if os.path.isfile(x_t_path):
808
+ break
809
+ if time()-start > timeout:
810
+ raise ModelError('モデル再構築に失敗しました')
811
+ sleep(1)
812
+
813
+ # モデルの再インポート
814
+ self._call_femtet_api(
815
+ self.Femtet.Gaudi.ReExecute,
816
+ False,
817
+ ModelError, # 生きてるのに失敗した場合
818
+ error_message=f'モデル再構築に失敗しました.',
819
+ is_Gaudi_method=True,
820
+ )
821
+
822
+ # 処理を確定
823
+ self._call_femtet_api(
824
+ self.Femtet.Redraw,
825
+ False, # 戻り値は常に None なのでこの変数に意味はなく None 以外なら何でもいい
826
+ ModelError, # 生きてるのに失敗した場合
827
+ error_message=f'モデル再構築に失敗しました.',
828
+ is_Gaudi_method=True,
829
+ )
830
+
831
+ # femprj モデルの変数も更新
832
+ super().update_model(parameters)
833
+
834
+ def update_sw_model(self, parameters: pd.DataFrame):
835
+ """Update .sldprt"""
836
+ # df を dict に変換
837
+ user_param_dict = {}
838
+ for i, row in parameters.iterrows():
839
+ user_param_dict[row['name']] = row['value']
840
+
841
+ # プロパティを退避
842
+ buffer_aso = self.swEqnMgr.AutomaticSolveOrder
843
+ buffer_ar = self.swEqnMgr.AutomaticRebuild
844
+ self.swEqnMgr.AutomaticSolveOrder = False
845
+ self.swEqnMgr.AutomaticRebuild = False
846
+
847
+ for i in range(self.nEquation):
848
+ # name, equation の取得
849
+ current_equation = self.swEqnMgr.Equation(i)
850
+ current_name = self._get_name_from_equation(current_equation)
851
+ # 対象なら処理
852
+ if current_name in list(user_param_dict.keys()):
853
+ new_equation = f'"{current_name}" = {user_param_dict[current_name]}'
854
+ self.swEqnMgr.Equation(i, new_equation)
855
+
856
+ # 式の計算
857
+ # noinspection PyStatementEffect
858
+ self.swEqnMgr.EvaluateAll # always returns -1
859
+
860
+ # プロパティをもとに戻す
861
+ self.swEqnMgr.AutomaticSolveOrder = buffer_aso
862
+ self.swEqnMgr.AutomaticRebuild = buffer_ar
863
+
864
+ # 更新する(ここで失敗はしうる)
865
+ result = self.swModel.EditRebuild3 # モデル再構築
866
+ if not result:
867
+ raise ModelError('モデル再構築に失敗しました')
868
+
869
+ def _get_name_from_equation(self, equation:str):
870
+ pattern = r'^\s*"(.+?)"\s*$'
871
+ matched = re.match(pattern, equation.split('=')[0])
872
+ if matched:
873
+ return matched.group(1)
874
+ else:
875
+ return None
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pyfemtet"
3
- version = "0.3.6" # ignored by versioning plugin
3
+ version = "0.3.8" # ignored by versioning plugin
4
4
  description = "Design parameter optimization using Femtet."
5
5
  authors = ["kazuma.naito <kazuma.naito@murata.com>"]
6
6
  readme = "README.md"
@@ -1 +0,0 @@
1
- __version__ = "0.3.6"
@@ -1,163 +0,0 @@
1
- import re
2
- import os
3
- from time import sleep
4
-
5
- import pandas as pd
6
- from win32com.client import DispatchEx, Dispatch
7
-
8
- from .core import Femtet
9
-
10
- # 定数の宣言
11
- swThisConfiguration = 1 # https://help.solidworks.com/2023/english/api/swconst/SOLIDWORKS.Interop.swconst~SOLIDWORKS.Interop.swconst.swInConfigurationOpts_e.html
12
- swAllConfiguration = 2
13
- swSpecifyConfiguration = 3 # use with ConfigName argument
14
- swSaveAsCurrentVersion = 0
15
- swSaveAsOptions_Copy = 2 #
16
- swSaveAsOptions_Silent = 1 # https://help.solidworks.com/2021/english/api/swconst/solidworks.interop.swconst~solidworks.interop.swconst.swsaveasoptions_e.html
17
- swSaveWithReferencesOptions_None = 0 # https://help-solidworks-com.translate.goog/2023/english/api/swconst/SolidWorks.Interop.swconst~SolidWorks.Interop.swconst.swSaveWithReferencesOptions_e.html?_x_tr_sl=auto&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=wapp
18
- swDocPART = 1 # https://help.solidworks.com/2023/english/api/swconst/SOLIDWORKS.Interop.swconst~SOLIDWORKS.Interop.swconst.swDocumentTypes_e.html
19
-
20
- class SolidWorksAutomationError(Exception):
21
- pass
22
-
23
-
24
- class SW_Femtet(Femtet):
25
-
26
- def __init__(self, path_sldprt):
27
- # 引数の処理
28
- self.path_sldprt = os.path.abspath(path_sldprt)
29
-
30
- # SolidWorks を捕まえ、ファイルを開く
31
- self.swApp = DispatchEx('SLDWORKS.Application')
32
- self.swApp.Visible = True
33
-
34
- # open model
35
- self.swApp.OpenDoc(self.path_sldprt, swDocPART)
36
- self.swModel = self.swApp.ActiveDoc
37
- self.swEqnMgr = self.swModel.GetEquationMgr
38
- self.nEquation = self.swEqnMgr.GetCount
39
-
40
- # 引数の保存
41
- super().__init__(self.path_sldprt)
42
-
43
-
44
- def update(self, df:pd.DataFrame) ->None:
45
- """Update_model_via_SW と run を実行します.
46
-
47
- Parameters
48
- ----------
49
- df
50
-
51
- Returns
52
- -------
53
-
54
- """
55
-
56
- # df を dict に変換
57
- user_param_dict = {}
58
- for i, row in df.iterrows():
59
- user_param_dict[row['name']] = row['value']
60
-
61
- # 変数の存在チェック
62
- self.check_parameter(user_param_dict)
63
-
64
- # Femtet 解析モデルを更新
65
- self.update_model_via_SW(user_param_dict)
66
-
67
- # 解析を実行する
68
- super().run()
69
-
70
-
71
- def check_parameter(self, user_param_dict):
72
- """与えられた変数セットが .sldprt ファイルに存在するか確認します."""
73
- swEqnMgr = self.swModel.GetEquationMgr
74
-
75
- # 指定された変数が SW に存在するかどうかをチェック
76
- for user_param_name in user_param_dict.keys():
77
- is_in_model = False
78
- # global 変数に存在するかどうか
79
- nEquation = swEqnMgr.GetCount
80
- global_param_names = [self.get_name(swEqnMgr.Equation(i)) for i in range(nEquation) if swEqnMgr.GlobalVariable(i)]
81
- is_in_model = is_in_model or (user_param_name in global_param_names)
82
- # 寸法に存在するかどうか
83
- swParam = self.swModel.Parameter(user_param_name)
84
- is_in_model = is_in_model or (swParam is not None)
85
- # is_in_model が False ならエラー
86
- if not is_in_model:
87
- raise Exception(f'no such parameter in SW:{user_param_name}')
88
-
89
-
90
- def update_model_via_SW(self, user_param_dict):
91
- """SW を使って parasolid を作り, Femtet 解析モデルを更新します."""
92
-
93
- # Femtet が参照している x_t パスを取得する
94
- path_x_t = self.Femtet.Gaudi.LastXTPath
95
-
96
- # 先にそれを消しておく
97
- if os.path.isfile(path_x_t):
98
- os.remove(path_x_t)
99
-
100
- # sldprt モデルの変数の更新
101
- self.update_SW_parameter(user_param_dict)
102
-
103
- # export parasolid
104
- self.swModel.SaveAs(path_x_t)
105
-
106
- # 30 秒待っても parasolid ができてなければエラー(COM なので)
107
- timeout = 30
108
- waiting = 0
109
- while True:
110
- sleep(1)
111
- waiting += 1
112
- if os.path.isfile(path_x_t):
113
- break
114
- else:
115
- if waiting<timeout:
116
- continue
117
- else:
118
- # from .core import ModelError
119
- # 再構築できていれば保存はできているはず
120
- raise SolidWorksAutomationError('parasolid 書き出しに失敗しました')
121
-
122
- # Femtet モデルの更新 by ReExecute Femtet History
123
- self.Femtet.Gaudi.ReExecute()
124
- self.Femtet.Redraw()
125
-
126
-
127
- def update_SW_parameter(self, user_param_dict):
128
- # プロパティを退避
129
- buffer_aso = self.swEqnMgr.AutomaticSolveOrder
130
- buffer_ar = self.swEqnMgr.AutomaticRebuild
131
- self.swEqnMgr.AutomaticSolveOrder = False
132
- self.swEqnMgr.AutomaticRebuild = False
133
-
134
- from tqdm import trange
135
- for i in trange(self.nEquation):
136
- # name, equation の取得
137
- current_equation = self.swEqnMgr.Equation(i)
138
- current_name = self.get_name(current_equation)
139
- # 対象なら処理
140
- if current_name in list(user_param_dict.keys()):
141
- new_equation = f'"{current_name}" = {user_param_dict[current_name]}'
142
- self.swEqnMgr.Equation(i, new_equation)
143
-
144
- # 式の計算
145
- self.swEqnMgr.EvaluateAll # always returns -1
146
-
147
- # プロパティをもとに戻す
148
- self.swEqnMgr.AutomaticSolveOrder = buffer_aso
149
- self.swEqnMgr.AutomaticRebuild = buffer_ar
150
-
151
- # 更新する(ここで失敗はしうる)
152
- result = self.swModel.EditRebuild3 # モデル再構築
153
- if result==False:
154
- from .core import ModelError
155
- raise ModelError('モデル再構築に失敗しました')
156
-
157
- def get_name(self, equation:str):
158
- pattern = r'^\s*"(.+?)"\s*$'
159
- matched = re.match(pattern, equation.split('=')[0])
160
- if matched:
161
- return matched.group(1)
162
- else:
163
- return None
@@ -1,2 +0,0 @@
1
- from .base import FEMOpt, OptunaOptimizer
2
- from .interface import FemtetInterface, NoFEM
File without changes
File without changes
File without changes
File without changes