pyfemtet 0.8.4__py3-none-any.whl → 0.8.6__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.

pyfemtet/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.8.4"
1
+ __version__ = "0.8.6"
pyfemtet/opt/_femopt.py CHANGED
@@ -27,6 +27,7 @@ from pyfemtet.opt._femopt_core import (
27
27
  History,
28
28
  OptimizationStatus,
29
29
  logger,
30
+ MonitorHostRecord,
30
31
  )
31
32
  from pyfemtet._message import Msg, encoding
32
33
  from pyfemtet.opt.optimizer.parameter import Parameter, Expression
@@ -135,6 +136,7 @@ class FEMOpt:
135
136
  self.monitor_process_future = None
136
137
  self.monitor_server_kwargs = dict()
137
138
  self.monitor_process_worker_name = None
139
+ self.monitor_host_record = None
138
140
  self._hv_reference = None
139
141
  self._extra_space_dir = None
140
142
 
@@ -403,7 +405,7 @@ class FEMOpt:
403
405
 
404
406
  if names is not None:
405
407
  if isinstance(names, str):
406
- names = [f'name_{i}' for i in range(n_return)]
408
+ names = [f'{names}_{i}' for i in range(n_return)]
407
409
  else:
408
410
  # names = names
409
411
  pass
@@ -679,11 +681,11 @@ class FEMOpt:
679
681
  # ===== fem の設定 ==--=
680
682
 
681
683
  # Femtet 特有の処理
682
- metadata = None
684
+ extra_data = dict()
683
685
  if isinstance(self.fem, FemtetInterface):
684
686
 
685
687
  # 結果 csv に記載する femprj に関する情報の作成
686
- metadata = json.dumps(
688
+ extra_data.update(
687
689
  dict(
688
690
  femprj_path=self.fem.original_femprj_path,
689
691
  model_name=self.fem.model_name
@@ -774,23 +776,25 @@ class FEMOpt:
774
776
 
775
777
  with self.client.cluster as _cluster, self.client as _client:
776
778
 
777
- # actor の設定
779
+ # ===== status actor の設定 =====
778
780
  self.status = OptimizationStatus(_client, worker_address=self.monitor_process_worker_name)
779
781
  self.worker_status_list = [OptimizationStatus(_client, worker_address=self.monitor_process_worker_name, name=name) for name in worker_addresses] # tqdm 検討
780
782
  self.status.set(OptimizationStatus.SETTING_UP)
783
+ self.monitor_host_record = MonitorHostRecord(_client, self.monitor_process_worker_name)
784
+ logger.info('Status Actor initialized successfully.')
785
+
786
+ # ===== initialize history =====
781
787
  self.history = History(
782
788
  self.history_path,
783
789
  self.opt.variables.get_parameter_names(),
784
790
  list(self.opt.objectives.keys()),
785
791
  list(self.opt.constraints.keys()),
786
792
  _client,
787
- metadata,
788
793
  self._hv_reference
789
794
  )
790
- logger.info('Status Actor initialized successfully.')
791
795
 
796
+ # ===== launch monitor =====
792
797
  # launch monitor
793
- # noinspection PyTypeChecker
794
798
  self.monitor_process_future = _client.submit(
795
799
  # func
796
800
  _start_monitor_server,
@@ -799,6 +803,7 @@ class FEMOpt:
799
803
  self.status,
800
804
  worker_addresses,
801
805
  self.worker_status_list,
806
+ self.monitor_host_record,
802
807
  # kwargs
803
808
  **self.monitor_server_kwargs,
804
809
  # kwargs of submit
@@ -807,6 +812,16 @@ class FEMOpt:
807
812
  )
808
813
  logger.info('Process monitor initialized successfully.')
809
814
 
815
+ # update extra_data of history to notify
816
+ # how to emit interruption signal by
817
+ # external processes
818
+ start = time()
819
+ while len(self.monitor_host_record.get()) == 0:
820
+ sleep(0.1)
821
+ extra_data.update(self.monitor_host_record.get())
822
+ self.history.extra_data.update(extra_data)
823
+
824
+ # ===== setup fem and opt before parallelization =====
810
825
  # fem
811
826
  self.fem._setup_before_parallel(_client)
812
827
 
@@ -817,6 +832,11 @@ class FEMOpt:
817
832
  self.opt.history = self.history
818
833
  self.opt._setup_before_parallel()
819
834
 
835
+ # ===== 最適化ループ開始 =====
836
+ # opt から non-serializable な com を
837
+ # 有している可能性のある fem を削除
838
+ # ただし main process の sub thread では
839
+ # これをそのまま使うので buff に退避
820
840
  buff = self.opt.fem
821
841
  del self.opt.fem
822
842
 
@@ -832,8 +852,12 @@ class FEMOpt:
832
852
  allow_other_workers=False,
833
853
  )
834
854
 
855
+ # 退避した fem を戻す
835
856
  self.opt.fem = buff
836
857
 
858
+ # リモートクラスタではない場合
859
+ # main process の sub thread で
860
+ # 計算開始
837
861
  t_main = None
838
862
  if not self.opt.is_cluster:
839
863
  # ローカルプロセスでの計算(opt._run 相当の処理)
@@ -857,7 +881,7 @@ class FEMOpt:
857
881
  )
858
882
  t_main.start()
859
883
 
860
- # save history
884
+ # ===== save history during optimization =====
861
885
  def save_history():
862
886
  while True:
863
887
  sleep(2)
@@ -872,7 +896,6 @@ class FEMOpt:
872
896
  t_save_history.start()
873
897
 
874
898
  # ===== 終了 =====
875
-
876
899
  # クラスターの Unexpected Exception のリストを取得
877
900
  opt_exceptions: list[Exception or None] = _client.gather(calc_futures) # gather() で終了待ちも兼ねる
878
901
 
@@ -884,7 +907,7 @@ class FEMOpt:
884
907
  local_opt_exception = self.opt._exception # Exception を取得
885
908
  opt_exceptions.append(local_opt_exception)
886
909
 
887
- # 終了
910
+ # 終了シグナルを送る
888
911
  self.status.set(OptimizationStatus.TERMINATED)
889
912
  end = time()
890
913
 
@@ -910,6 +933,8 @@ class FEMOpt:
910
933
  print()
911
934
 
912
935
  # monitor worker を残してユーザーが結果を確認できるようにする
936
+ # with 文を抜けると monitor worker が終了して
937
+ # daemon thread である run_forever が終了する
913
938
  if confirm_before_exit:
914
939
  print()
915
940
  print('='*len(Msg.CONFIRM_BEFORE_EXIT))
@@ -945,6 +970,7 @@ def _start_monitor_server(
945
970
  status,
946
971
  worker_addresses,
947
972
  worker_status_list,
973
+ host_record,
948
974
  host=None,
949
975
  port=None,
950
976
  ):
@@ -955,5 +981,6 @@ def _start_monitor_server(
955
981
  worker_status_list,
956
982
  host,
957
983
  port,
984
+ host_record,
958
985
  )
959
986
  return 'Exit monitor server process gracefully'
@@ -1,4 +1,5 @@
1
1
  # typing
2
+ import json
2
3
  from typing import List, TYPE_CHECKING
3
4
 
4
5
  # built-in
@@ -524,7 +525,6 @@ class History:
524
525
  obj_names (List[str], optional): The names of objectives. Defaults to None.
525
526
  cns_names (List[str], optional): The names of constraints. Defaults to None.
526
527
  client (dask.distributed.Client): Dask client.
527
- additional_metadata (str, optional): metadata of optimization process.
528
528
  hv_reference (str or list[float or np.ndarray, optional):
529
529
  The method to calculate hypervolume or
530
530
  the reference point itself.
@@ -550,7 +550,6 @@ class History:
550
550
  obj_names=None,
551
551
  cns_names=None,
552
552
  client=None,
553
- additional_metadata=None,
554
553
  hv_reference=None,
555
554
  ):
556
555
  # hypervolume 計算メソッド
@@ -561,7 +560,8 @@ class History:
561
560
  self.prm_names = prm_names
562
561
  self.obj_names = obj_names
563
562
  self.cns_names = cns_names
564
- self.additional_metadata = additional_metadata or ''
563
+ self.extra_data = dict()
564
+ self.meta_columns = None
565
565
  self.__scheduler_address = client.scheduler.address if client is not None else None
566
566
 
567
567
  # 最適化実行中かどうか
@@ -575,15 +575,15 @@ class History:
575
575
 
576
576
  # 続きからなら df を読み込んで df にコピー
577
577
  if self.is_restart:
578
- self.load()
578
+ self.load() # 中で meta_columns を読む
579
579
 
580
580
  # そうでなければ df を初期化
581
581
  else:
582
- columns, metadata = self.create_df_columns()
582
+ columns, meta_columns = self.create_df_columns()
583
583
  df = pd.DataFrame()
584
584
  for c in columns:
585
585
  df[c] = None
586
- self.metadata = metadata
586
+ self.meta_columns = meta_columns
587
587
  self.set_df(df)
588
588
 
589
589
  # 一時ファイルに書き込みを試み、UnicodeEncodeError が出ないかチェック
@@ -609,16 +609,16 @@ class History:
609
609
  # df を読み込む
610
610
  df = pd.read_csv(self.path, encoding=self.ENCODING, header=self.HEADER_ROW)
611
611
 
612
- # metadata を読み込む
612
+ # meta_columns を読み込む
613
613
  with open(self.path, mode='r', encoding=self.ENCODING, newline='\n') as f:
614
614
  reader = csv.reader(f, delimiter=',')
615
- self.metadata = reader.__next__()
615
+ self.meta_columns = reader.__next__()
616
616
 
617
617
  # 最適化問題を読み込む
618
618
  columns = df.columns
619
- prm_names = [column for i, column in enumerate(columns) if self.metadata[i] == 'prm']
620
- obj_names = [column for i, column in enumerate(columns) if self.metadata[i] == 'obj']
621
- cns_names = [column for i, column in enumerate(columns) if self.metadata[i] == 'cns']
619
+ prm_names = [column for i, column in enumerate(columns) if self.meta_columns[i] == 'prm']
620
+ obj_names = [column for i, column in enumerate(columns) if self.meta_columns[i] == 'obj']
621
+ cns_names = [column for i, column in enumerate(columns) if self.meta_columns[i] == 'cns']
622
622
 
623
623
  # is_restart の場合、読み込んだ names と引数の names が一致するか確認しておく
624
624
  if self.is_restart:
@@ -662,7 +662,7 @@ class History:
662
662
  logger.debug('Access df of History before it is initialized.')
663
663
  return pd.DataFrame()
664
664
  except OSError:
665
- logger.error('Scheduler is already dead. Most frequent reasen to show this message is that the pyfemtet monitor UI is not refreshed even if the main optimization process is terminated.')
665
+ logger.error('Scheduler is already dead. Most frequent reason to show this message is that the pyfemtet monitor UI is not refreshed even if the main optimization process is terminated.')
666
666
  return pd.DataFrame()
667
667
 
668
668
  def set_df(self, df: pd.DataFrame):
@@ -687,46 +687,46 @@ class History:
687
687
  columns = list()
688
688
 
689
689
  # columns のメタデータを作成
690
- metadata = list()
690
+ meta_columns = list()
691
691
 
692
692
  # trial
693
693
  columns.append('trial') # index
694
- metadata.append(self.additional_metadata)
694
+ meta_columns.append('') # extra_data. save 時に中身を記入する。
695
695
 
696
696
  # parameter
697
697
  for prm_name in self.prm_names:
698
698
  columns.extend([prm_name, prm_name + '_lower_bound', prm_name + '_upper_bound'])
699
- metadata.extend(['prm', 'prm_lb', 'prm_ub'])
699
+ meta_columns.extend(['prm', 'prm_lb', 'prm_ub'])
700
700
 
701
701
  # objective relative
702
702
  for name in self.obj_names:
703
703
  columns.append(name)
704
- metadata.append('obj')
704
+ meta_columns.append('obj')
705
705
  columns.append(name + '_direction')
706
- metadata.append('obj_direction')
706
+ meta_columns.append('obj_direction')
707
707
  columns.append('non_domi')
708
- metadata.append('')
708
+ meta_columns.append('')
709
709
 
710
710
  # constraint relative
711
711
  for name in self.cns_names:
712
712
  columns.append(name)
713
- metadata.append('cns')
713
+ meta_columns.append('cns')
714
714
  columns.append(name + '_lower_bound')
715
- metadata.append('cns_lb')
715
+ meta_columns.append('cns_lb')
716
716
  columns.append(name + '_upper_bound')
717
- metadata.append('cns_ub')
717
+ meta_columns.append('cns_ub')
718
718
  columns.append('feasible')
719
- metadata.append('')
719
+ meta_columns.append('')
720
720
 
721
721
  # the others
722
722
  columns.append('hypervolume')
723
- metadata.append('')
723
+ meta_columns.append('')
724
724
  columns.append('message')
725
- metadata.append('')
725
+ meta_columns.append('')
726
726
  columns.append('time')
727
- metadata.append('')
727
+ meta_columns.append('')
728
728
 
729
- return columns, metadata
729
+ return columns, meta_columns
730
730
 
731
731
  def record(
732
732
  self,
@@ -973,13 +973,16 @@ class History:
973
973
 
974
974
  df = self.get_df()
975
975
 
976
+ # extra_data の更新
977
+ self.meta_columns[0] = json.dumps(self.extra_data)
978
+
976
979
  if _f is None:
977
980
  # save df with columns with prefix
978
981
  with open(self.path, 'w', encoding=self.ENCODING) as f:
979
982
  writer = csv.writer(f, delimiter=',', lineterminator="\n")
980
- writer.writerow(self.metadata)
983
+ writer.writerow(self.meta_columns)
981
984
  for i in range(self.HEADER_ROW-1):
982
- writer.writerow([''] * len(self.metadata))
985
+ writer.writerow([''] * len(self.meta_columns))
983
986
  df.to_csv(f, index=None, encoding=self.ENCODING, lineterminator='\n')
984
987
  else: # test
985
988
  df.to_csv(_f, index=None, encoding=self.ENCODING, lineterminator='\n')
@@ -1088,3 +1091,35 @@ class OptimizationStatus:
1088
1091
  def get_text(self) -> str:
1089
1092
  """Get optimization status message."""
1090
1093
  return self._actor.status
1094
+
1095
+
1096
+ class _MonitorHostRecordActor:
1097
+ host = None
1098
+ port = None
1099
+
1100
+ def set(self, host, port):
1101
+ self.host = host
1102
+ self.port = port
1103
+
1104
+
1105
+ class MonitorHostRecord:
1106
+
1107
+ def __init__(self, client, worker_name):
1108
+ self._future = client.submit(
1109
+ _MonitorHostRecordActor,
1110
+ actor=True,
1111
+ workers=(worker_name,),
1112
+ allow_other_workers=False,
1113
+ )
1114
+ self._actor = self._future.result()
1115
+
1116
+ def set(self, host, port):
1117
+ self._actor.set(host, port).result()
1118
+
1119
+ def get(self):
1120
+ host = self._actor.host
1121
+ port = self._actor.port
1122
+ if host is None and port is None:
1123
+ return dict()
1124
+ else:
1125
+ return dict(host=host, port=port)
@@ -10,7 +10,7 @@ from pyfemtet.opt import FEMOpt
10
10
  from pyfemtet._message import encoding as ENCODING
11
11
 
12
12
 
13
- def remove_femprj_metadata_from_csv(csv_path, encoding=ENCODING):
13
+ def remove_extra_data_from_csv(csv_path, encoding=ENCODING):
14
14
 
15
15
  with open(csv_path, mode="r", encoding=encoding, newline="\n") as f:
16
16
  reader = csv.reader(f, delimiter=",")
@@ -59,32 +59,39 @@ def _get_obj_from_csv(csv_path, encoding=ENCODING):
59
59
  reader = csv.reader(f, delimiter=",")
60
60
  meta = reader.__next__()
61
61
  obj_indices = np.where(np.array(meta) == "obj")[0]
62
- out = df.iloc[:, obj_indices]
62
+ out: pd.DataFrame = df.iloc[:, obj_indices]
63
+ out = out.dropna(axis=0)
63
64
  return out, columns
64
65
 
65
66
 
66
- def is_equal_result(ref_path, dif_path, log_path):
67
+ def is_equal_result(ref_path, dif_path, log_path=None, threashold=0.05):
67
68
  """Check the equality of two result csv files."""
68
69
  ref_df, ref_columns = _get_obj_from_csv(ref_path)
69
70
  dif_df, dif_columns = _get_obj_from_csv(dif_path)
70
71
 
71
- with open(log_path, "a", newline="\n", encoding=ENCODING) as f:
72
- f.write("\n\n===== 結果の分析 =====\n\n")
73
- f.write(f" \tref\tdif\n")
74
- f.write(f"---------------------\n")
75
- f.write(f"len(col)\t{len(ref_columns)}\t{len(dif_columns)}\n")
76
- f.write(f"len(df) \t{len(ref_df)}\t{len(dif_df)}\n")
77
- try:
78
- difference = (
72
+ if log_path is not None:
73
+ with open(log_path, "a", newline="\n", encoding=ENCODING) as f:
74
+ f.write("\n\n===== 結果の分析 =====\n\n")
75
+ f.write(f" \tref\tdif\n")
76
+ f.write(f"---------------------\n")
77
+ f.write(f"len(col)\t{len(ref_columns)}\t{len(dif_columns)}\n")
78
+ f.write(f"len(df) \t{len(ref_df)}\t{len(dif_df)}\n")
79
+ try:
80
+ difference = (
81
+ np.abs(ref_df.values - dif_df.values) / np.abs(dif_df.values)
82
+ ).mean()
83
+ f.write(f"diff \t{int(difference*100)}%\n")
84
+ except Exception:
85
+ f.write(f"diff \tcannot calc\n")
86
+
87
+ else:
88
+ difference = (
79
89
  np.abs(ref_df.values - dif_df.values) / np.abs(dif_df.values)
80
- ).mean()
81
- f.write(f"diff \t{int(difference*100)}%\n")
82
- except Exception:
83
- f.write(f"diff \tcannot calc\n")
90
+ ).mean()
84
91
 
85
92
  assert len(ref_columns) == len(dif_columns), "結果 csv の column 数が異なります。"
86
93
  assert len(ref_df) == len(dif_df), "結果 csv の row 数が異なります。"
87
- assert difference <= 0.05, "前回の結果との平均差異が 5% を超えています。"
94
+ assert difference <= threashold*100, f"前回の結果との平均差異が {int(difference)}% で {int(threashold*100)}% を超えています。"
88
95
 
89
96
 
90
97
  def _get_simplified_df_values(csv_path, exclude_columns=None):
@@ -21,7 +21,9 @@ else:
21
21
  FemtetWithNXInterface = type('FemtetWithNXInterface', (FemtetInterface,), {})
22
22
  ExcelInterface = type('FemtetInterface', (NotAvailableForWindows,), {})
23
23
 
24
- from pyfemtet.opt.interface._surrogate import PoFBoTorchInterface
24
+ from pyfemtet.opt.interface._surrogate._base import SurrogateModelInterfaceBase
25
+ from pyfemtet.opt.interface._surrogate._singletaskgp import PoFBoTorchInterface
26
+
25
27
 
26
28
  __all__ =[
27
29
  'FEMInterface',
@@ -30,5 +32,6 @@ __all__ =[
30
32
  'FemtetWithSolidworksInterface',
31
33
  'FemtetWithNXInterface',
32
34
  'ExcelInterface',
35
+ 'SurrogateModelInterfaceBase',
33
36
  'PoFBoTorchInterface',
34
37
  ]
@@ -17,6 +17,7 @@ class SurrogateModelInterfaceBase(FEMInterface, ABC):
17
17
  self,
18
18
  history_path: str = None,
19
19
  history: History = None,
20
+ override_objective: bool = True,
20
21
  ):
21
22
 
22
23
  self.history: History
@@ -25,6 +26,7 @@ class SurrogateModelInterfaceBase(FEMInterface, ABC):
25
26
  self.obj: dict[str, float] = dict()
26
27
  self.df_prm: pd.DataFrame
27
28
  self.df_obj: pd.DataFrame
29
+ self.override_objective: bool = override_objective
28
30
 
29
31
  # history_path が与えられた場合、history をコンストラクトする
30
32
  if history_path is not None:
@@ -57,27 +59,25 @@ class SurrogateModelInterfaceBase(FEMInterface, ABC):
57
59
  FEMInterface.__init__(
58
60
  self,
59
61
  history=history, # コンストラクト済み history を渡せば並列計算時も何もしなくてよい
62
+ override_objective=self.override_objective
60
63
  )
61
64
 
62
65
  def filter_feasible(self, x: np.ndarray, y: np.ndarray, return_feasibility=False):
63
66
  feasible_idx = np.where(~np.isnan(y.sum(axis=1)))
64
67
  if return_feasibility:
65
68
  # calculated or not
66
- y = np.zeros_like(y)
67
- y[feasible_idx] = 1.
68
- # satisfy weak feasibility or not
69
- infeasible_idx = np.where(~self.history.get_df()['feasible'].values)
70
- y[infeasible_idx] = .0
71
- return x, y.reshape((-1, 1))
69
+ feas = np.zeros((len(y), 1), dtype=float)
70
+ feas[feasible_idx] = 1.
71
+ return x, feas
72
72
  else:
73
73
  return x[feasible_idx], y[feasible_idx]
74
74
 
75
75
  def _setup_after_parallel(self, *args, **kwargs):
76
-
77
- opt: AbstractOptimizer = kwargs['opt']
78
- obj: Objective
79
- for obj_name, obj in opt.objectives.items():
80
- obj.fun = lambda: self.obj[obj_name]
76
+ if self.override_objective:
77
+ opt: AbstractOptimizer = kwargs['opt']
78
+ obj: Objective
79
+ for obj_name, obj in opt.objectives.items():
80
+ obj.fun = lambda obj_name_=obj_name: self.obj[obj_name_]
81
81
 
82
82
  def update_parameter(self, parameters: pd.DataFrame, with_warning=False) -> Optional[List[str]]:
83
83
  for i, row in parameters.iterrows():
@@ -27,8 +27,8 @@ class PoFBoTorchInterface(SurrogateModelInterfaceBase):
27
27
  def train_f(self):
28
28
  # df そのまま用いて training する
29
29
  x, y = self.filter_feasible(self.df_prm.values, self.df_obj.values, return_feasibility=True)
30
- if y.min() == 1:
31
- self.model_f.predict = lambda *args, **kwargs: (1., 0.001)
30
+ if y.min() == 1: # feasible values only
31
+ self.model_f.predict = lambda *args, **kwargs: (1., 0.001) # mean, std
32
32
  self.model_f.fit(x, y)
33
33
 
34
34
  def _setup_after_parallel(self, *args, **kwargs):
@@ -64,7 +64,8 @@ class PoFBoTorchInterface(SurrogateModelInterfaceBase):
64
64
  raise SolveError(Msg.INFO_POF_IS_LESS_THAN_THRESHOLD)
65
65
 
66
66
  # 実際の計算(mean は history.obj_names 順)
67
- mean, _ = self.model.predict(np.array([x]))
67
+ _mean, _std = self.model.predict(np.array([x]))
68
+ mean = _mean[0]
68
69
 
69
70
  # 目的関数の更新
70
71
  self.obj = {obj_name: value for obj_name, value in zip(self.history.obj_names, mean)}
@@ -42,16 +42,16 @@ class SingleTaskGPModel(PredictionModelBase):
42
42
  def set_bounds_from_history(self, history, df=None):
43
43
  from pyfemtet.opt._femopt_core import History
44
44
  history: History
45
- metadata: str
45
+ meta_column: str
46
46
 
47
47
  if df is None:
48
48
  df = history.get_df()
49
49
 
50
50
  columns = df.columns
51
- metadata_columns = history.metadata
51
+
52
52
  target_columns = [
53
- col for col, metadata in zip(columns, metadata_columns)
54
- if metadata == 'prm_lb' or metadata == 'prm_ub'
53
+ col for col, meta_column in zip(columns, history.meta_columns)
54
+ if meta_column == 'prm_lb' or meta_column == 'prm_ub'
55
55
  ]
56
56
 
57
57
  bounds_buff = df.iloc[0][target_columns].values # 2*len(prm_names) array
@@ -85,6 +85,8 @@ class SingleTaskGPModel(PredictionModelBase):
85
85
  fit_gpytorch_mll(mll)
86
86
 
87
87
  def predict(self, x: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
88
+ assert len(x.shape) >= 2
89
+
88
90
  X = tensor(x)
89
91
 
90
92
  post = self.gp.posterior(X)
@@ -2,6 +2,7 @@
2
2
  from dash.development.base_component import Component
3
3
 
4
4
  # application
5
+ from flask import Flask
5
6
  from dash import Dash
6
7
  import webbrowser
7
8
 
@@ -20,6 +21,7 @@ from pyfemtet.opt.visualization._wrapped_components import html, dcc, dbc
20
21
  from abc import ABC, abstractmethod
21
22
  import logging
22
23
  import psutil
24
+ import json
23
25
  from pyfemtet.logger import get_module_logger, get_dash_logger
24
26
 
25
27
  logger = get_module_logger('opt.monitor', __name__)
@@ -132,15 +134,19 @@ class SidebarApplicationBase:
132
134
  # define app
133
135
  self.title = title if title is not None else 'App'
134
136
  self.subtitle = subtitle if title is not None else ''
137
+ self.server = Flask(__name__)
135
138
  self.app = Dash(
136
139
  __name__,
137
140
  external_stylesheets=[dash_bootstrap_components.themes.BOOTSTRAP],
138
141
  title=title,
139
142
  update_title=None,
143
+ server=self.server,
140
144
  )
141
145
  self.pages = dict()
142
146
  self.nav_links = dict()
143
147
  self.page_objects = []
148
+ self.host = None
149
+ self.port = None
144
150
 
145
151
  def add_page(self, page: AbstractPage, order: int = None):
146
152
  page.set_application(self)
@@ -191,7 +197,7 @@ class SidebarApplicationBase:
191
197
  className="p-3 bg-light rounded-3",
192
198
  )
193
199
 
194
- def run(self, host='localhost', port=None, debug=False):
200
+ def run(self, host='localhost', port=None, debug=False, host_record=None):
195
201
  self._setup_layout()
196
202
  port = port or self.DEFAULT_PORT
197
203
  # port を検証
@@ -201,6 +207,11 @@ class SidebarApplicationBase:
201
207
  webbrowser.open(f'http://localhost:{str(port)}')
202
208
  else:
203
209
  webbrowser.open(f'http://{host}:{str(port)}')
210
+ self.host = host
211
+ self.port = port
212
+ if host_record is not None:
213
+ host_record.set(self.host, self.port)
214
+
204
215
  self.app.run(debug=debug, host=host, port=port)
205
216
 
206
217
 
@@ -138,18 +138,18 @@ class FemtetControl(AbstractPage):
138
138
 
139
139
  # check holding history
140
140
  if self.application.history is None:
141
- return kwargs, Msg.ERR_HISTORY_CSV_NOT_READ
141
+ return kwargs, Msg.WARN_HISTORY_CSV_NOT_READ
142
142
 
143
- # get metadata
144
- additional_metadata = self.application.history.metadata[0]
143
+ # get extra_data
144
+ extra_data = self.application.history.meta_columns[0]
145
145
 
146
- # check metadata exists
147
- if additional_metadata == '':
146
+ # check extra_data exists
147
+ if extra_data == '':
148
148
  return kwargs, Msg.WARN_INVALID_METADATA
149
149
 
150
- # check the metadata is valid json
150
+ # check the extra_data is valid json
151
151
  try:
152
- d = json.loads(additional_metadata)
152
+ d = json.loads(extra_data)
153
153
  femprj_path = os.path.abspath(d['femprj_path'])
154
154
  except (TypeError, json.decoder.JSONDecodeError, KeyError):
155
155
  return kwargs, Msg.WARN_INVALID_METADATA
@@ -447,17 +447,50 @@ class MainGraph(AbstractPage):
447
447
  Output(self.tooltip.id, "show"),
448
448
  Output(self.tooltip.id, "bbox"),
449
449
  Output(self.tooltip.id, "children"),
450
- Input(self.graph.id, "hoverData"),)
451
- def show_hover(hoverData):
450
+ Output(self.tooltip.id, "direction"),
451
+ Input(self.graph.id, "hoverData"),
452
+ State(self.graph.id, "figure"),
453
+ )
454
+ def show_hover(hoverData, figure):
452
455
  if hoverData is None:
453
- return False, no_update, no_update
456
+ return False, no_update, no_update, no_update
454
457
 
455
458
  if self.application.history is None:
456
459
  raise PreventUpdate
457
460
 
458
- # get hover location
461
+ # pt = {'curveNumber': 1, 'pointNumber': 0, 'pointIndex': 0, 'x': 1, 'y': 2369132.929996869, 'bbox': {'x0': 341.5, 'x1': 350.5, 'y0': 235, 'y1': 244}, 'customdata': [1]}
462
+ # bbox = {'x0': 341.5, 'x1': 350.5, 'y0': 235, 'y1': 244} # point の大きさ?
463
+ # figure = {'data': [{'customdata': [[1], [2], [3], [4], [5]], 'marker': {'color': '#6c757d', 'size': 6}, 'mode': 'markers', 'name': 'すべての解', 'x': [1, 2, 3, 4, 5], 'y': [2369132.929996866, 5797829.746579617, 1977631.590419346, 2083867.2403273676, 1539203.6452652698], 'type': 'scatter', 'hoverinfo': 'none'}, {'customdata': [[1], [3], [5]], 'legendgroup': 'optimal', 'line': {'color': '#6c757d', 'width': 1}, 'marker': {'color': '#007bff', 'size': 9}, 'mode': 'markers+lines', 'name': '最適解の推移', 'x': [1, 3, 5], 'y': [2369132.929996866, 1977631.590419346, 1539203.6452652698], 'type': 'scatter', 'hoverinfo': 'none'}, {'legendgroup': 'optimal', 'line': {'color': '#6c757d', 'dash': 'dash', 'width': 0.5}, 'mode': 'lines', 'showlegend': False, 'x': [5, 5], 'y': [1539203.6452652698, 1539203.6452652698], 'type': 'scatter', 'hoverinfo': 'none'}], 'layout': {'template': {'data': {'histogram2dcontour': [{'type': 'histogram2dcontour', 'colorbar': {'outlinewidth': 0, 'ticks': ''}, 'colorscale': [[0, '#0d0887'], [0.1111111111111111, '#46039f'], [0.2222222222222222, '#7201a8'], [0.3333333333333333, '#9c179e'], [0.4444444444444444, '#bd3786'], [0.5555555555555556, '#d8576b'], [0.6666666666666666, '#ed7953'], [0.7777777777777778, '#fb9f3a'], [0.8888888888888888, '#fdca26'], [1, '#f0f921']]}], 'choropleth': [{'type': 'choropleth', 'colorbar': {'outlinewidth': 0, 'ticks': ''}}], 'histogram2d': [{'type': 'histogram2d', 'colorbar': {'outlinewidth': 0, 'ticks': ''}, 'colorscale': [[0, '#0d0887'], [0.1111111111111111, '#46039f'], [0.2222222222222222, '#7201a8'], [0.3333333333333333, '#9c179e'], [0.4444444444444444, '#bd3786'], [0.5555555555555556, '#d8576b'], [0.6666666666666666, '#ed7953'], [0.7777777777777778, '#fb9f3a'], [0.8888888888888888, '#fdca26'], [1, '#f0f921']]}], 'heatmap': [{'type': 'heatmap', 'colorbar': {'outlinewidth': 0, 'ticks': ''}, 'colorscale': [[0, '#0d0887'], [0.1111111111111111, '#46039f'], [0.2222222222222222, '#7201a8'], [0.3333333333333333, '#9c179e'], [0.4444444444444444, '#bd3786'], [0.5555555555555556, '#d8576b'], [0.6666666666666666, '#ed7953'], [0.7777777777777778, '#fb9f3a'], [0.8888888888888888, '#fdca26'], [1, '#f0f921']]}], 'heatmapgl': [{'type': 'heatmapgl', 'colorbar': {'outlinewidth': 0, 'ticks': ''}, 'colorscale': [[0, '#0d0887'], [0.1111111111111111, '#46039f'], [0.2222222222222222, '#7201a8'], [0.3333333333333333, '#9c179e'], [0.4444444444444444, '#bd3786'], [0.5555555555555556, '#d8576b'], [0.6666666666666666, '#ed7953'], [0.7777777777777778, '#fb9f3a'], [0.8888888888888888, '#fdca26'], [1, '#f0f921']]}], 'contourcarpet': [{'type': 'contourcarpet', 'colorbar': {'outlinewidth': 0, 'ticks': ''}}], 'contour': [{'type': 'contour', 'colorbar': {'outlinewidth': 0, 'ticks': ''}, 'colorscale': [[0, '#0d0887'], [0.1111111111111111, '#46039f'], [0.2222222222222222, '#7201a8'], [0.3333333333333333, '#9c179e'], [0.4444444444444444, '#bd3786'], [0.5555555555555556, '#d8576b'], [0.6666666666666666, '#ed7953'], [0.7777777777777778, '#fb9f3a'], [0.8888888888888888, '#fdca26'], [1, '#f0f921']]}], 'surface': [{'type': 'surface', 'colorbar': {'outlinewidth': 0, 'ticks': ''}, 'colorscale': [[0, '#0d0887'], [0.1111111111111111, '#46039f'], [0.2222222222222222, '#7201a8'], [0.3333333333333333, '#9c179e'], [0.4444444444444444, '#bd3786'], [0.5555555555555556, '#d8576b'], [0.6666666666666666, '#ed7953'], [0.7777777777777778, '#fb9f3a'], [0.8888888888888888, '#fdca26'], [1, '#f0f921']]}], 'mesh3d': [{'type': 'mesh3d', 'colorbar': {'outlinewidth': 0, 'ticks': ''}}], 'scatter': [{'fillpattern': {'fillmode': 'overlay', 'size': 10, 'solidity': 0.2}, 'type': 'scatter'}], 'parcoords': [{'type': 'parcoords', 'line': {'colorbar': {'outlinewidth': 0, 'ticks': ''}}}], 'scatterpolargl': [{'type': 'scatterpolargl', 'marker': {'colorbar': {'outlinewidth': 0, 'ticks': ''}}}], 'bar': [{'error_x': {'color': '#2a3f5f'}, 'error_y': {'color': '#2a3f5f'}, 'marker': {'line': {'color': '#E5ECF6', 'width': 0.5}, 'pattern': {'fillmode': 'overlay', 'size': 10, 'solidity': 0.2}}, 'type': 'bar'}], 'scattergeo': [{'type': 'scattergeo', 'marker': {'colorbar': {'outlinewidth': 0, 'ticks': ''}}}], 'scatterpolar': [{'type': 'scatterpolar', 'marker': {'colorbar': {'outlinewidth': 0, 'ticks': ''}}}], 'histogram': [{'marker': {'pattern': {'fillmode': 'overlay', 'size': 10, 'solidity': 0.2}}, 'type': 'histogram'}], 'scattergl': [{'type': 'scattergl', 'marker': {'colorbar': {'outlinewidth': 0, 'ticks': ''}}}], 'scatter3d': [{'type': 'scatter3d', 'line': {'colorbar': {'outlinewidth': 0, 'ticks': ''}}, 'marker': {'colorbar': {'outlinewidth': 0, 'ticks': ''}}}], 'scattermapbox': [{'type': 'scattermapbox', 'marker': {'colorbar': {'outlinewidth': 0, 'ticks': ''}}}], 'scatterternary': [{'type': 'scatterternary', 'marker': {'colorbar': {'outlinewidth': 0, 'ticks': ''}}}], 'scattercarpet': [{'type': 'scattercarpet', 'marker': {'colorbar': {'outlinewidth': 0, 'ticks': ''}}}], 'carpet': [{'aaxis': {'endlinecolor': '#2a3f5f', 'gridcolor': 'white', 'linecolor': 'white', 'minorgridcolor': 'white', 'startlinecolor': '#2a3f5f'}, 'baxis': {'endlinecolor': '#2a3f5f', 'gridcolor': 'white', 'linecolor': 'white', 'minorgridcolor': 'white', 'startlinecolor': '#2a3f5f'}, 'type': 'carpet'}], 'table': [{'cells': {'fill': {'color': '#EBF0F8'}, 'line': {'color': 'white'}}, 'header': {'fill': {'color': '#C8D4E3'}, 'line': {'color': 'white'}}, 'type': 'table'}], 'barpolar': [{'marker': {'line': {'color': '#E5ECF6', 'width': 0.5}, 'pattern': {'fillmode': 'overlay', 'size': 10, 'solidity': 0.2}}, 'type': 'barpolar'}], 'pie': [{'automargin': True, 'type': 'pie'}]}, 'layout': {'autotypenumbers': 'strict', 'colorway': ['#636efa', '#EF553B', '#00cc96', '#ab63fa', '#FFA15A', '#19d3f3', '#FF6692', '#B6E880', '#FF97FF', '#FECB52'], 'font': {'color': '#2a3f5f'}, 'hovermode': 'closest', 'hoverlabel': {'align': 'left'}, 'paper_bgcolor': 'white', 'plot_bgcolor': '#E5ECF6', 'polar': {'bgcolor': '#E5ECF6', 'angularaxis': {'gridcolor': 'white', 'linecolor': 'white', 'ticks': ''}, 'radialaxis': {'gridcolor': 'white', 'linecolor': 'white', 'ticks': ''}}, 'ternary': {'bgcolor': '#E5ECF6', 'aaxis': {'gridcolor': 'white', 'linecolor': 'white', 'ticks': ''}, 'baxis': {'gridcolor': 'white', 'linecolor': 'white', 'ticks': ''}, 'caxis': {'gridcolor': 'white', 'linecolor': 'white', 'ticks': ''}}, 'coloraxis': {'colorbar': {'outlinewidth': 0, 'ticks': ''}}, 'colorscale': {'sequential': [[0, '#0d0887'], [0.1111111111111111, '#46039f'], [0.2222222222222222, '#7201a8'], [0.3333333333333333, '#9c179e'], [0.4444444444444444, '#bd3786'], [0.5555555555555556, '#d8576b'], [0.6666666666666666, '#ed7953'], [0.7777777777777778, '#fb9f3a'], [0.8888888888888888, '#fdca26'], [1, '#f0f921']], 'sequentialminus': [[0, '#0d0887'], [0.1111111111111111, '#46039f'], [0.2222222222222222, '#7201a8'], [0.3333333333333333, '#9c179e'], [0.4444444444444444, '#bd3786'], [0.5555555555555556, '#d8576b'], [0.6666666666666666, '#ed7953'], [0.7777777777777778, '#fb9f3a'], [0.8888888888888888, '#fdca26'], [1, '#f0f921']], 'diverging': [[0, '#8e0152'], [0.1, '#c51b7d'], [0.2, '#de77ae'], [0.3, '#f1b6da'], [0.4, '#fde0ef'], [0.5, '#f7f7f7'], [0.6, '#e6f5d0'], [0.7, '#b8e186'], [0.8, '#7fbc41'], [0.9, '#4d9221'], [1, '#276419']]}, 'xaxis': {'gridcolor': 'white', 'linecolor': 'white', 'ticks': '', 'title': {'standoff': 15}, 'zerolinecolor': 'white', 'automargin': True, 'zerolinewidth': 2}, 'yaxis': {'gridcolor': 'white', 'linecolor': 'white', 'ticks': '', 'title': {'standoff': 15}, 'zerolinecolor': 'white', 'automargin': True, 'zerolinewidth': 2}, 'scene': {'xaxis': {'backgroundcolor': '#E5ECF6', 'gridcolor': 'white', 'linecolor': 'white', 'showbackground': True, 'ticks': '', 'zerolinecolor': 'white', 'gridwidth': 2}, 'yaxis': {'backgroundcolor': '#E5ECF6', 'gridcolor': 'white', 'linecolor': 'white', 'showbackground': True, 'ticks': '', 'zerolinecolor': 'white', 'gridwidth': 2}, 'zaxis': {'backgroundcolor': '#E5ECF6', 'gridcolor': 'white', 'linecolor': 'white', 'showbackground': True, 'ticks': '', 'zerolinecolor': 'white', 'gridwidth': 2}}, 'shapedefaults': {'line': {'color': '#2a3f5f'}}, 'annotationdefaults': {'arrowcolor': '#2a3f5f', 'arrowhead': 0, 'arrowwidth': 1}, 'geo': {'bgcolor': 'white', 'landcolor': '#E5ECF6', 'subunitcolor': 'white', 'showland': True, 'showlakes': True, 'lakecolor': 'white'}, 'title': {'x': 0.05}, 'mapbox': {'style': 'light'}}}, 'title': {'text': '目的関数プロット'}, 'xaxis': {'title': {'text': '成功した解析数'}, 'type': 'linear', 'range': [0.7173255954539959, 5.282674404546004], 'autorange': False}, 'yaxis': {'title': {'text': 'Mises Stress'}, 'type': 'linear', 'range': [1194338.1744483153, 6109662.126322151], 'autorange': False}, 'transition': {'duration': 1000}, 'clickmode': 'event+select'}}
464
+
465
+ # get point location
459
466
  pt = hoverData["points"][0]
460
- bbox = pt["bbox"]
467
+
468
+ # get the bounding box of target
469
+ bbox = pt['bbox']
470
+
471
+ # get relative location of point
472
+ xrange = figure['layout']['xaxis']['range']
473
+ # yrange = figure['layout']['yaxis']['range']
474
+
475
+ is_left = pt['x'] < np.mean(xrange)
476
+
477
+ # デフォルトでは Hover が Point に重なり、
478
+ # Hover した瞬間に Un-hover する場合があるので
479
+ # offset を追加
480
+ if is_left:
481
+ direction = 'right'
482
+ bbox['x0'] = bbox['x0'] + 40
483
+ bbox['x1'] = bbox['x1'] + 40
484
+
485
+ else:
486
+ direction = 'left'
487
+ bbox['x0'] = bbox['x0'] + 15
488
+ bbox['x1'] = bbox['x1'] + 15
489
+
490
+ # ついでに縦方向も適当に調整
491
+ bbox['y0'] = bbox['y0'] + 80
492
+ bbox['y1'] = bbox['y1'] + 80
493
+
461
494
 
462
495
  # get row of the history from customdata defined in main_figure
463
496
  if 'customdata' not in pt.keys():
@@ -482,18 +515,20 @@ class MainGraph(AbstractPage):
482
515
  title_component,
483
516
  tbl_component_prm,
484
517
  ])
485
- tooltip_layout = html.Div([
486
- html.Div(img_component, style={'display': 'inline-block', 'margin-right': '10px', 'vertical-align': 'top'}),
487
- html.Div(description, style={'display': 'inline-block', 'margin-right': '10px'}),
488
- html.Div(tbl_component_obj, style={'display': 'inline-block', 'margin-right': '10px'}),
489
- ])
518
+ tooltip_layout = html.Div(
519
+ children=[
520
+ html.Div(img_component, style={'display': 'inline-block', 'margin-right': '10px'}),
521
+ html.Div(description, style={'display': 'inline-block', 'margin-right': '10px'}),
522
+ html.Div(tbl_component_obj, style={'display': 'inline-block', 'margin-right': '10px'}),
523
+ ],
524
+ )
490
525
 
491
- return True, bbox, tooltip_layout
526
+ return True, bbox, tooltip_layout, direction
492
527
 
493
528
  def create_formatted_parameter(self, row) -> Component:
494
- metadata = self.application.history.metadata
529
+ meta_columns = self.application.history.meta_columns
495
530
  pd.options.display.float_format = '{:.4e}'.format
496
- parameters = row.iloc[:, np.where(np.array(metadata) == 'prm')[0]]
531
+ parameters = row.iloc[:, np.where(np.array(meta_columns) == 'prm')[0]]
497
532
  names = parameters.columns
498
533
  values = [f'{value:.3e}' for value in parameters.values.ravel()]
499
534
  data = pd.DataFrame(dict(
@@ -506,9 +541,9 @@ class MainGraph(AbstractPage):
506
541
  return table
507
542
 
508
543
  def create_formatted_objective(self, row) -> Component:
509
- metadata = self.application.history.metadata
544
+ meta_columns = self.application.history.meta_columns
510
545
  pd.options.display.float_format = '{:.4e}'.format
511
- objectives = row.iloc[:, np.where(np.array(metadata) == 'obj')[0]]
546
+ objectives = row.iloc[:, np.where(np.array(meta_columns) == 'obj')[0]]
512
547
  names = objectives.columns
513
548
  values = [f'{value:.3e}' for value in objectives.values.ravel()]
514
549
  data = pd.DataFrame(dict(
@@ -522,20 +557,21 @@ class MainGraph(AbstractPage):
522
557
 
523
558
  def create_image_content_if_femtet(self, trial) -> Component:
524
559
  img_url = None
525
- metadata = self.application.history.metadata
526
- if metadata[0] != '':
527
- # get img path
528
- d = json.loads(metadata[0])
529
- femprj_path = d['femprj_path']
530
- model_name = d['model_name']
531
- femprj_result_dir = femprj_path.replace('.femprj', '.Results')
532
- img_path = os.path.join(femprj_result_dir, f'{model_name}_trial{trial}.jpg')
533
- if os.path.exists(img_path):
534
- # create encoded image
535
- with open(img_path, 'rb') as f:
536
- content = f.read()
537
- encoded_image = base64.b64encode(content).decode('utf-8')
538
- img_url = 'data:image/jpeg;base64, ' + encoded_image
560
+ meta_columns = self.application.history.meta_columns
561
+ if meta_columns[0] != '':
562
+ extra_data = json.loads(meta_columns[0])
563
+ if 'femprj_path' in extra_data.keys():
564
+ # get img path
565
+ femprj_path = extra_data['femprj_path']
566
+ model_name = extra_data['model_name']
567
+ femprj_result_dir = femprj_path.replace('.femprj', '.Results')
568
+ img_path = os.path.join(femprj_result_dir, f'{model_name}_trial{trial}.jpg')
569
+ if os.path.exists(img_path):
570
+ # create encoded image
571
+ with open(img_path, 'rb') as f:
572
+ content = f.read()
573
+ encoded_image = base64.b64encode(content).decode('utf-8')
574
+ img_url = 'data:image/jpeg;base64, ' + encoded_image
539
575
  return html.Img(src=img_url, style={"width": "200px"}) if img_url is not None else html.Div()
540
576
 
541
577
  def get_fig_by_tab_id(self, tab_id, with_length=False, kwargs: dict = None):
@@ -547,9 +547,9 @@ class PredictionModelGraph(AbstractPage):
547
547
  return tuple(ret.values())
548
548
 
549
549
  def create_formatted_parameter(self, row) -> Component:
550
- metadata = self.application.history.metadata
550
+ meta_columns = self.application.history.meta_columns
551
551
  pd.options.display.float_format = '{:.4e}'.format
552
- parameters = row.iloc[:, np.where(np.array(metadata) == 'prm')[0]]
552
+ parameters = row.iloc[:, np.where(np.array(meta_columns) == 'prm')[0]]
553
553
  names = parameters.columns
554
554
  values = [f'{value:.3e}' for value in parameters.values.ravel()]
555
555
  data = pd.DataFrame(dict(
@@ -563,20 +563,21 @@ class PredictionModelGraph(AbstractPage):
563
563
 
564
564
  def create_image_content_if_femtet(self, trial) -> Component:
565
565
  img_url = None
566
- metadata = self.application.history.metadata
567
- if metadata[0] != '':
568
- # get img path
569
- d = json.loads(metadata[0])
570
- femprj_path = d['femprj_path']
571
- model_name = d['model_name']
572
- femprj_result_dir = femprj_path.replace('.femprj', '.Results')
573
- img_path = os.path.join(femprj_result_dir, f'{model_name}_trial{trial}.jpg')
574
- if os.path.exists(img_path):
575
- # create encoded image
576
- with open(img_path, 'rb') as f:
577
- content = f.read()
578
- encoded_image = base64.b64encode(content).decode('utf-8')
579
- img_url = 'data:image/jpeg;base64, ' + encoded_image
566
+ meta_columns = self.application.history.meta_columns
567
+ if meta_columns[0] != '':
568
+ extra_data = json.loads(meta_columns[0])
569
+ if 'femprj_path' in extra_data:
570
+ # get img path
571
+ femprj_path = extra_data['femprj_path']
572
+ model_name = extra_data['model_name']
573
+ femprj_result_dir = femprj_path.replace('.femprj', '.Results')
574
+ img_path = os.path.join(femprj_result_dir, f'{model_name}_trial{trial}.jpg')
575
+ if os.path.exists(img_path):
576
+ # create encoded image
577
+ with open(img_path, 'rb') as f:
578
+ content = f.read()
579
+ encoded_image = base64.b64encode(content).decode('utf-8')
580
+ img_url = 'data:image/jpeg;base64, ' + encoded_image
580
581
  return html.Img(src=img_url, style={"width": "200px"}) if img_url is not None else html.Div()
581
582
 
582
583
  # def get_fig_by_tab_id(self, tab_id, with_length=False):
@@ -3,6 +3,7 @@ from time import sleep
3
3
  from threading import Thread
4
4
 
5
5
  import pandas as pd
6
+ from flask import jsonify
6
7
 
7
8
  from pyfemtet.opt.visualization._base import PyFemtetApplicationBase, logger
8
9
  from pyfemtet.opt.visualization._process_monitor.pages import HomePage, WorkerPage, PredictionModelPage, OptunaVisualizerPage
@@ -80,7 +81,22 @@ class ProcessMonitorApplication(PyFemtetApplicationBase):
80
81
  if not debug:
81
82
  super().setup_callback()
82
83
 
83
- def start_server(self, host=None, port=None):
84
+ from pyfemtet.opt._femopt_core import OptimizationStatus
85
+
86
+ @self.server.route('/interrupt')
87
+ def some_command():
88
+
89
+ # If the entire_state < INTERRUPTING, set INTERRUPTING
90
+ if self.local_entire_status_int < OptimizationStatus.INTERRUPTING:
91
+ self.local_entire_status_int = OptimizationStatus.INTERRUPTING
92
+ result = {"message": "Interrupting signal emitted successfully."}
93
+
94
+ else:
95
+ result = {"message": "Interrupting signal is already emitted."}
96
+
97
+ return jsonify(result)
98
+
99
+ def start_server(self, host=None, port=None, host_record=None):
84
100
  """Callback の中で使いたい Actor のデータを Application クラスのメンバーとやり取りしつつ、server を落とす関数"""
85
101
 
86
102
  self._should_get_actor_data = True
@@ -95,6 +111,7 @@ class ProcessMonitorApplication(PyFemtetApplicationBase):
95
111
  server_thread = Thread(
96
112
  target=self.run,
97
113
  args=(host, port,),
114
+ kwargs=dict(host_record=host_record),
98
115
  daemon=True,
99
116
  )
100
117
  server_thread.start()
@@ -188,7 +205,7 @@ def g_debug():
188
205
  g_application.run(debug=False)
189
206
 
190
207
 
191
- def main(history, status, worker_addresses, worker_status_list, host=None, port=None):
208
+ def main(history, status, worker_addresses, worker_status_list, host=None, port=None, host_record=None):
192
209
  g_application = ProcessMonitorApplication(history, status, worker_addresses, worker_status_list)
193
210
 
194
211
  g_home_page = HomePage(Msg.PAGE_TITLE_PROGRESS)
@@ -202,7 +219,7 @@ def main(history, status, worker_addresses, worker_status_list, host=None, port=
202
219
  g_application.add_page(g_worker_page, 3)
203
220
  g_application.setup_callback()
204
221
 
205
- g_application.start_server(host, port)
222
+ g_application.start_server(host, port, host_record)
206
223
 
207
224
 
208
225
  if __name__ == '__main__':
@@ -209,11 +209,11 @@ class HomePage(AbstractPage):
209
209
  Input('debug-button-2', 'n_clicks'),
210
210
  prevent_initial_call=True)
211
211
  def add_data(*_):
212
- metadata = self.application.history.metadata
212
+ meta_columns = self.application.history.meta_columns
213
213
  df = self.application.local_data
214
214
 
215
215
  new_row = df.iloc[-2:]
216
- obj_index = np.where(np.array(metadata) == 'obj')[0]
216
+ obj_index = np.where(np.array(meta_columns) == 'obj')[0]
217
217
  for idx in obj_index:
218
218
  new_row.iloc[:, idx] = np.random.rand(len(new_row))
219
219
 
@@ -183,11 +183,11 @@ class HomePage(AbstractPage):
183
183
  return alerts
184
184
 
185
185
  # get femprj in history csv
186
- kwargs = self.femtet_control.create_femtet_interface_args()[0] # read metadata from history.
186
+ kwargs = self.femtet_control.create_femtet_interface_args()[0] # read extra_data from history.
187
187
  femprj_path = kwargs['femprj_path']
188
188
  model_name = kwargs['model_name']
189
189
 
190
- # check metadata is valid
190
+ # check extra_data is valid
191
191
  if femprj_path is None:
192
192
  msg = Msg.ERR_FEMPRJ_IN_CSV_NOT_FOUND
193
193
  alerts = self.alert_region.create_alerts(msg, color='danger')
@@ -283,8 +283,8 @@ class HomePage(AbstractPage):
283
283
  # get parameter and update model
284
284
  df = self.application.history.get_df(valid_only=True)
285
285
  row = df[df['trial'] == trial]
286
- metadata = np.array(self.application.history.metadata)
287
- idx = np.where(metadata == 'prm')[0]
286
+ meta_columns = np.array(self.application.history.meta_columns)
287
+ idx = np.where(meta_columns == 'prm')[0]
288
288
 
289
289
  names = np.array(row.columns)[idx]
290
290
  values = np.array(row.iloc[:, idx]).ravel()
@@ -346,7 +346,7 @@ class HomePage(AbstractPage):
346
346
 
347
347
  # check the corresponding between history and Femtet
348
348
  # ├ history-side
349
- kwargs = self.femtet_control.create_femtet_interface_args()[0] # read metadata from history.
349
+ kwargs = self.femtet_control.create_femtet_interface_args()[0] # read extra_data from history.
350
350
  femprj_path_history_on_history: str or None = kwargs['femprj_path']
351
351
  model_name_on_history: str or None = kwargs['model_name']
352
352
  # ├ Femtet-side
@@ -673,7 +673,7 @@ class Tutorial(AbstractPage):
673
673
  destination_folder = source_folder.replace('wat_ex14_parametric', 'tutorial')
674
674
  shutil.copytree(source_folder, destination_folder, dirs_exist_ok=True)
675
675
 
676
- self.application.history.metadata[0] = json.dumps(
676
+ self.application.history.meta_columns[0] = json.dumps(
677
677
  dict(
678
678
  femprj_path=destination_file,
679
679
  model_name='Ex14',
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pyfemtet
3
- Version: 0.8.4
3
+ Version: 0.8.6
4
4
  Summary: Design parameter optimization using Femtet.
5
5
  License: BSD-3-Clause
6
6
  Author: kazuma.naito
@@ -1,4 +1,4 @@
1
- pyfemtet/__init__.py,sha256=oR63M0ef9dMrWZnMjTb9xk69cW2iS6rluqpyJ5_0C84,21
1
+ pyfemtet/__init__.py,sha256=B72_7DUBCOMnhKCvBwcwiuuCre398QDqqytHa0lXG-Q,21
2
2
  pyfemtet/_femtet_config_util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  pyfemtet/_femtet_config_util/autosave.py,sha256=dNirA9XGuFehas8_Jkj2BW9GOzMbPyhnt1WHcH_ObSU,2070
4
4
  pyfemtet/_femtet_config_util/exit.py,sha256=0BWID-tjOkmZwmgPFkcJMkWW39voccz5ARIBWvZbHaw,1877
@@ -24,12 +24,12 @@ pyfemtet/dispatch_extensions/_impl.py,sha256=yH_yeAnQ-Xi9GfjX-FQt9u3yHnrLYIteRb6
24
24
  pyfemtet/logger/__init__.py,sha256=UOJ9n_U2xwdTrp0Xgg-N6geySxNzKqTBQlXsaH0kW_w,420
25
25
  pyfemtet/logger/_impl.py,sha256=rsAd0HpmveOaLS39ucp3U2OcDhQMWjC5fnVGhbJtWVw,6375
26
26
  pyfemtet/opt/__init__.py,sha256=wRR8LbEhb5I6MUgmnCgjB6-tqHlOVxDIo7yPkq0QbBs,758
27
- pyfemtet/opt/_femopt.py,sha256=vqLUGMMHn0lp0bRE_FEV0sOoUcVAbohy9_8OLYw3ZrU,39217
28
- pyfemtet/opt/_femopt_core.py,sha256=mA2wQ5h_mmbTQ9ilhDHLUyN-jsWFDpJE2r5guUWlS10,38083
27
+ pyfemtet/opt/_femopt.py,sha256=ZhwOK1QYwo7Xk67qEOm4biKXzdIW2AUHYvgklBREDm8,40587
28
+ pyfemtet/opt/_femopt_core.py,sha256=kxKh4_TdXR4LBgv8WibPiZ5Pe9apJd5ArBCfnhBwcCQ,38969
29
29
  pyfemtet/opt/_test_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  pyfemtet/opt/_test_utils/control_femtet.py,sha256=8oAl9y5V2n8Nnsgx_ebcZVzwFt1eI3swkdiKg6pg3-M,1085
31
31
  pyfemtet/opt/_test_utils/hyper_sphere.py,sha256=nQhw8EIY0DwvcTqrbKhkxiITLZifr4-nG77E-_6ggmA,700
32
- pyfemtet/opt/_test_utils/record_history.py,sha256=JCNJLZMCNTpJ6VT7iwEt2DIbwmsuQmgC0ClQSfcatj4,3915
32
+ pyfemtet/opt/_test_utils/record_history.py,sha256=zsa1w73K7NLBqbj7yuv0fWVJvZtWdiI0eCaUoAn5Bjg,4239
33
33
  pyfemtet/opt/advanced_samples/excel_ui/(ref) original_project.femprj,sha256=5OqZfynTpVCrgEIOBOMYuDGaMvepi5lojVNFr1jAsEI,157489
34
34
  pyfemtet/opt/advanced_samples/excel_ui/femtet-macro.xlsm,sha256=ckF0SQ0f3IWSW6QoH1IPJdwUUlR7O_AiGC5fi8SI3jA,133137
35
35
  pyfemtet/opt/advanced_samples/excel_ui/pyfemtet-core.py,sha256=aF2TWXdbt7dnkeBqqVO6GvIExozjFp0mxx3BX8rpYNc,9879
@@ -39,7 +39,7 @@ pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_create_training_data_jp.p
39
39
  pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_optimize_with_surrogate.py,sha256=s0b31wuN3iXjb78dt0ro0ZjxHa8uLIH94jRfEuj1EVY,3090
40
40
  pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_optimize_with_surrogate_jp.py,sha256=OAOpHKyMMo1StSqNMqx4saYDn4hiGOKDypyK6uhTILQ,3215
41
41
  pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_parametric.femprj,sha256=iIHH1X-wWBqEYj4cFJXco73LCJXSrYBsSKOD0HxYu60,87599
42
- pyfemtet/opt/interface/__init__.py,sha256=dkQ8PoIzYJVRtckNN2VgL7FEwci9RbYM3owOvqnPm80,1219
42
+ pyfemtet/opt/interface/__init__.py,sha256=na6-elI9-karOqoSxT9LfLQpjBPm1lrUWjow0NYYRP4,1349
43
43
  pyfemtet/opt/interface/_base.py,sha256=y0uQ5jdsWbgt5odyqPin7NXcK_IbUwPDcrrkV_JhpRw,2722
44
44
  pyfemtet/opt/interface/_excel_interface.py,sha256=s103vePTPXXYiPwGdAEUFgtpvGXtu1nSljDtP4HsmcY,40355
45
45
  pyfemtet/opt/interface/_femtet.py,sha256=teALmp66aJ_rrmtEOjCGDG1jGLTZr2AmvMFmuuXRQkw,34634
@@ -49,9 +49,9 @@ pyfemtet/opt/interface/_femtet_with_nx/_interface.py,sha256=LkaODUSpBLq05uz5Jf-J
49
49
  pyfemtet/opt/interface/_femtet_with_nx/update_model.py,sha256=P7VH0i_o-X9OUe6AGaLF1fACPeHNrMjcrOBCA3MMrI4,3092
50
50
  pyfemtet/opt/interface/_femtet_with_sldworks.py,sha256=rjEgebuP1w1eAFVWw4eRJUq3lsyBcmXlkMjZKIpD0kw,11019
51
51
  pyfemtet/opt/interface/_surrogate/__init__.py,sha256=2UT5NuBylyWQJNjg1zsBRCV-MzNCUswTUt6ZuSrYFUM,120
52
- pyfemtet/opt/interface/_surrogate/_base.py,sha256=-k02jRywxaSKMDzGitPE_qBj5nUxC7OL6guF4y6F1Zw,2923
52
+ pyfemtet/opt/interface/_surrogate/_base.py,sha256=bQMoztVq1b-3BW5Z1V-dSROplMHutrblDI289j0cC-E,3001
53
53
  pyfemtet/opt/interface/_surrogate/_chaospy.py,sha256=gL72bCgs1AY_EZdJtcifSC-apwsZzp4zsWYxcpVKvtw,1969
54
- pyfemtet/opt/interface/_surrogate/_singletaskgp.py,sha256=YBRm-8MRwK26qg6T5LKaAhwPfF3jLcKQV-fycP6dnlA,2406
54
+ pyfemtet/opt/interface/_surrogate/_singletaskgp.py,sha256=ojZHsxGxSc8ZJqJQ_uMHvpK98TPUsHzXP0q4tmM0YPQ,2471
55
55
  pyfemtet/opt/optimizer/__init__.py,sha256=Ia6viowECkG0IFXtFef0tJ4jDKsoDzJLqMJ9xLFH2LQ,543
56
56
  pyfemtet/opt/optimizer/_base.py,sha256=j8aQc3fGehZTJT9ETf9cr3VWYs2FYk1F8fO3f7QyKAU,13099
57
57
  pyfemtet/opt/optimizer/_optuna/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -64,7 +64,7 @@ pyfemtet/opt/optimizer/_scipy_scalar.py,sha256=rGvrLjrgfYzxK9GA0-r2Hhoaqt6A0TQsT
64
64
  pyfemtet/opt/optimizer/parameter.py,sha256=YLE9lmYRaZA8isnTPJnbYXpUn6zsJFW4xg03QaSWey8,3950
65
65
  pyfemtet/opt/prediction/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
66
  pyfemtet/opt/prediction/_base.py,sha256=dEyEur3IntNokYK8NhPndHb2pWY_A4C1SjEejOTCUGw,2048
67
- pyfemtet/opt/prediction/single_task_gp.py,sha256=6NMSjTtzXJWaW7NEqfqOjz_37mbHpXh9Oo5_KjivRU0,3922
67
+ pyfemtet/opt/prediction/single_task_gp.py,sha256=t4Vby0Llh7ZcVQ6M5zYqwmmo-NwSXwWRExLtqzmPcu8,3929
68
68
  pyfemtet/opt/samples/femprj_sample/ParametricIF.femprj,sha256=9BtDHmc3cdom0Zq33DTdZ0mDAsIUY6i8SRkkg-n7GO0,442090
69
69
  pyfemtet/opt/samples/femprj_sample/ParametricIF.py,sha256=oXzchBZEbH69xacDht5HDnbZzKwapXsn6bp9qihY17Y,707
70
70
  pyfemtet/opt/samples/femprj_sample/ParametricIF_test_result.reccsv,sha256=TiOAqEDMub6SCGYClBv1JvQxphDOY3iIdr_pMmGgJ9M,2859
@@ -116,18 +116,18 @@ pyfemtet/opt/samples/femprj_sample_jp/wat_ex14_parametric_jp.femprj,sha256=dMwQM
116
116
  pyfemtet/opt/samples/femprj_sample_jp/wat_ex14_parametric_jp.py,sha256=vMy-KUP1wEMV9Rt6yXjkE40Fcs1t1cpQK-nQJK8hHao,2284
117
117
  pyfemtet/opt/samples/femprj_sample_jp/wat_ex14_parametric_parallel_jp.py,sha256=4X0cl3YWpYarcNBCH79mrlyFuKUYSqvnGzokEbv9ILk,2335
118
118
  pyfemtet/opt/visualization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
- pyfemtet/opt/visualization/_base.py,sha256=xh6yIkoyBrV670JhAnR9rRewpH7P25wz0pnr0JH0pvc,7623
119
+ pyfemtet/opt/visualization/_base.py,sha256=b1oWPsCTFmoHRjFWhysmpE3VmaB7e-FNq6V9eMTZRz8,7934
120
120
  pyfemtet/opt/visualization/_complex_components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
121
121
  pyfemtet/opt/visualization/_complex_components/alert_region.py,sha256=sX8xqT4NqhACagK4YgumF4ResrTqhOKQ8dN4q58shI8,2106
122
- pyfemtet/opt/visualization/_complex_components/control_femtet.py,sha256=LcMoh_MQQ1-hiz7nMGOmxSSoJLOX8viVxZB6uIggg_g,6243
122
+ pyfemtet/opt/visualization/_complex_components/control_femtet.py,sha256=sY0YH56MgDWtfFfEkFsfzpr0VbP8dRT0KrhhGzeRiRE,6227
123
123
  pyfemtet/opt/visualization/_complex_components/main_figure_creator.py,sha256=Wt_aL6srMNW-84LeZ86_OtljzmFoF9v0yklVpPAgNDE,9480
124
- pyfemtet/opt/visualization/_complex_components/main_graph.py,sha256=qcofnuWfItYQJWs16zY1p7L4vE_rr5mHEtj9poU0i5I,21711
125
- pyfemtet/opt/visualization/_complex_components/pm_graph.py,sha256=eh9rQYUcEefwN3eb7AnXXKFyknl_P_3xt4kl3Y4woI8,25010
124
+ pyfemtet/opt/visualization/_complex_components/main_graph.py,sha256=WbV0oW6nUS734688Zd4H1OpDrBBWJEu6u4u7lqoqnSQ,31975
125
+ pyfemtet/opt/visualization/_complex_components/pm_graph.py,sha256=FnxerXoddflukSj_BdhjK7jBl83qSDFsTUcQzs8Nij8,25153
126
126
  pyfemtet/opt/visualization/_complex_components/pm_graph_creator.py,sha256=f-ikYAPChazqyRQ0Y-tKrYrMBHzFHJJ4uV6QXBEBRKI,7304
127
127
  pyfemtet/opt/visualization/_create_wrapped_components.py,sha256=9AltJHr1DM6imZfpNp867rC-uAYqQ-emdgTLChKDrl8,2513
128
128
  pyfemtet/opt/visualization/_process_monitor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
129
- pyfemtet/opt/visualization/_process_monitor/application.py,sha256=l9Z1SS4r1BXH6JlccjDyJNanG2JTu4OALbEdZje0XrY,7955
130
- pyfemtet/opt/visualization/_process_monitor/pages.py,sha256=VEyP7cRyVjLOgKD6KUHu3AivbwbmjTucQWv-obH-HY0,15135
129
+ pyfemtet/opt/visualization/_process_monitor/application.py,sha256=8ShNMPWrD_1IHyPz2a63tlzENQg7by3kg4pdXSuv0_4,8659
130
+ pyfemtet/opt/visualization/_process_monitor/pages.py,sha256=-G-zNvYS6HDXrwX0lQlInlJn3rZPr1-Rh4AAAOudmuY,15147
131
131
  pyfemtet/opt/visualization/_wrapped_components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
132
132
  pyfemtet/opt/visualization/_wrapped_components/dbc.py,sha256=iSh4QRmLIQMfiAWowG1ThXLPhmKluRYOYPcdDFVI0t0,42162
133
133
  pyfemtet/opt/visualization/_wrapped_components/dcc.py,sha256=-Iw6MjFQmvJ__KcddPhFDqui6lk2ixB2U2tZH_Il5pA,17500
@@ -136,9 +136,9 @@ pyfemtet/opt/visualization/_wrapped_components/str_enum.py,sha256=NZqbh2jNEAckvJ
136
136
  pyfemtet/opt/visualization/result_viewer/.gitignore,sha256=ryvb4aqbbsHireHWlPQfxxqDHQJo6YkVYhE9imKt0b8,6
137
137
  pyfemtet/opt/visualization/result_viewer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
138
138
  pyfemtet/opt/visualization/result_viewer/application.py,sha256=WcHBx_J5eNLKSaprpk9BGifwhO04oN8FiNGYTWorrXA,1691
139
- pyfemtet/opt/visualization/result_viewer/pages.py,sha256=zcsRmVpVK7xbmOpnKkSypNPsRyHcV3ingfNmuqln6nw,32171
140
- pyfemtet-0.8.4.dist-info/LICENSE,sha256=sVQBhyoglGJUu65-BP3iR6ujORI6YgEU2Qm-V4fGlOA,1485
141
- pyfemtet-0.8.4.dist-info/METADATA,sha256=gm8_jNbFddbgs4cCz1XchxJDMSfcTlBn-GHxCu0ikrE,3509
142
- pyfemtet-0.8.4.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
143
- pyfemtet-0.8.4.dist-info/entry_points.txt,sha256=ZfYqRaoiPtuWqFi2_msccyrVF0LurMn-IHlYamAegZo,104
144
- pyfemtet-0.8.4.dist-info/RECORD,,
139
+ pyfemtet/opt/visualization/result_viewer/pages.py,sha256=MZAjzbuq0toZrR-iJhElM3A12_jHVCTt65gz1kdNPbw,32193
140
+ pyfemtet-0.8.6.dist-info/LICENSE,sha256=sVQBhyoglGJUu65-BP3iR6ujORI6YgEU2Qm-V4fGlOA,1485
141
+ pyfemtet-0.8.6.dist-info/METADATA,sha256=Nygc04MFehj-ZvJ5e3KuWq3CL2C94_tbTezHQdp5ubg,3509
142
+ pyfemtet-0.8.6.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
143
+ pyfemtet-0.8.6.dist-info/entry_points.txt,sha256=ZfYqRaoiPtuWqFi2_msccyrVF0LurMn-IHlYamAegZo,104
144
+ pyfemtet-0.8.6.dist-info/RECORD,,