pyfemtet 1.3.2__py3-none-any.whl → 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyfemtet might be problematic. Click here for more details.
- pyfemtet/opt/interface/_femtet_interface/femtet_interface.py +8 -2
- pyfemtet/opt/optimizer/_base_optimizer.py +1 -1
- pyfemtet/opt/optimizer/scipy_optimizer/_scipy_optimizer.py +88 -47
- pyfemtet/opt/visualization/plotter/main_figure_creator.py +6 -0
- {pyfemtet-1.3.2.dist-info → pyfemtet-1.4.0.dist-info}/METADATA +3 -2
- {pyfemtet-1.3.2.dist-info → pyfemtet-1.4.0.dist-info}/RECORD +10 -10
- {pyfemtet-1.3.2.dist-info → pyfemtet-1.4.0.dist-info}/WHEEL +0 -0
- {pyfemtet-1.3.2.dist-info → pyfemtet-1.4.0.dist-info}/entry_points.txt +0 -0
- {pyfemtet-1.3.2.dist-info → pyfemtet-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {pyfemtet-1.3.2.dist-info → pyfemtet-1.4.0.dist-info}/licenses/LICENSE_THIRD_PARTY.txt +0 -0
|
@@ -425,12 +425,18 @@ class FemtetInterface(COMInterface):
|
|
|
425
425
|
# 開く
|
|
426
426
|
if self.model_name is None:
|
|
427
427
|
result = self.Femtet.LoadProject(self.femprj_path, True)
|
|
428
|
+
if not result:
|
|
429
|
+
self.Femtet.ShowLastError()
|
|
428
430
|
else:
|
|
431
|
+
result = self.Femtet.LoadProject(self.femprj_path, True)
|
|
432
|
+
if not result:
|
|
433
|
+
self.Femtet.ShowLastError()
|
|
434
|
+
|
|
429
435
|
result = self.Femtet.LoadProjectAndAnalysisModel(
|
|
430
436
|
self.femprj_path, self.model_name, True
|
|
431
437
|
)
|
|
432
|
-
|
|
433
|
-
|
|
438
|
+
if not result:
|
|
439
|
+
self.Femtet.ShowLastError()
|
|
434
440
|
|
|
435
441
|
def _connect_and_open_femtet(self):
|
|
436
442
|
"""Connects to a Femtet process and open the femprj.
|
|
@@ -2,7 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Callable
|
|
4
4
|
|
|
5
|
-
from contextlib import suppress
|
|
5
|
+
from contextlib import suppress, contextmanager
|
|
6
|
+
from time import time
|
|
6
7
|
|
|
7
8
|
import numpy as np
|
|
8
9
|
from scipy.optimize import minimize, OptimizeResult
|
|
@@ -10,9 +11,11 @@ from scipy.optimize import NonlinearConstraint
|
|
|
10
11
|
|
|
11
12
|
from pyfemtet._i18n import Msg, _
|
|
12
13
|
from pyfemtet._util.closing import closing
|
|
14
|
+
from pyfemtet._util.dask_util import get_client, Lock
|
|
13
15
|
from pyfemtet.opt.problem.variable_manager import *
|
|
14
16
|
from pyfemtet.opt.problem.problem import *
|
|
15
17
|
from pyfemtet.opt.exceptions import *
|
|
18
|
+
from pyfemtet.opt.history import MAIN_FILTER
|
|
16
19
|
from pyfemtet.logger import get_module_logger
|
|
17
20
|
|
|
18
21
|
from pyfemtet.opt.optimizer._base_optimizer import *
|
|
@@ -26,6 +29,10 @@ __all__ = [
|
|
|
26
29
|
logger = get_module_logger('opt.optimizer', False)
|
|
27
30
|
|
|
28
31
|
|
|
32
|
+
class InterruptScipyMinimize(Exception):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
29
36
|
class _ScipyCallback:
|
|
30
37
|
|
|
31
38
|
def __init__(self, opt: ScipyOptimizer):
|
|
@@ -56,9 +63,6 @@ class ScipyOptimizer(AbstractOptimizer):
|
|
|
56
63
|
|
|
57
64
|
"""
|
|
58
65
|
|
|
59
|
-
_timeout: None = None
|
|
60
|
-
_n_trials: None = None
|
|
61
|
-
|
|
62
66
|
def __init__(self, method: str = None, tol=None):
|
|
63
67
|
super().__init__()
|
|
64
68
|
|
|
@@ -67,30 +71,42 @@ class ScipyOptimizer(AbstractOptimizer):
|
|
|
67
71
|
self.options = {}
|
|
68
72
|
self.constraint_enhancement = 0.001
|
|
69
73
|
self.constraint_scaling = 1.
|
|
74
|
+
self._scheduler_address: str | None = None
|
|
75
|
+
self.timeout = None
|
|
76
|
+
self._time_start = None
|
|
77
|
+
self.__n_succeeded_trials = None
|
|
70
78
|
|
|
71
79
|
@property
|
|
72
|
-
def
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
@
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
80
|
+
def _n_succeeded_trials_in_current_optimization(self) -> int | None: # オーバーライド不可
|
|
81
|
+
out = None
|
|
82
|
+
client = get_client(self._scheduler_address)
|
|
83
|
+
if client is not None:
|
|
84
|
+
raise NotImplementedError(
|
|
85
|
+
"ScipyOptimizer does not support parallel computing."
|
|
86
|
+
)
|
|
87
|
+
if self.__n_succeeded_trials is None:
|
|
88
|
+
self.__n_succeeded_trials = 0
|
|
89
|
+
return self.__n_succeeded_trials
|
|
90
|
+
|
|
91
|
+
@_n_succeeded_trials_in_current_optimization.setter
|
|
92
|
+
def _n_succeeded_trials_in_current_optimization(self, value: int | None): # オーバーライド不可
|
|
93
|
+
client = get_client(self._scheduler_address)
|
|
94
|
+
with Lock('scipy_n_succeeded_trials'):
|
|
95
|
+
if client is not None:
|
|
96
|
+
raise NotImplementedError('ScipyOptimizer does not support parallel computing.')
|
|
97
|
+
self.__n_succeeded_trials = value
|
|
98
|
+
|
|
99
|
+
def _per_solve_callback(self):
|
|
100
|
+
df = self.history.get_df(equality_filters=MAIN_FILTER)
|
|
101
|
+
self._n_succeeded_trials_in_current_optimization = (
|
|
102
|
+
1 + self._n_succeeded_trials_in_current_optimization
|
|
103
|
+
)
|
|
104
|
+
if self.n_trials is not None:
|
|
105
|
+
if self._n_succeeded_trials_in_current_optimization >= self.n_trials:
|
|
106
|
+
raise InterruptScipyMinimize
|
|
107
|
+
if self.timeout is not None:
|
|
108
|
+
if time() - self._time_start >= self.timeout:
|
|
109
|
+
raise InterruptScipyMinimize
|
|
94
110
|
|
|
95
111
|
def add_trial(self, parameters: dict[str, SupportedVariableTypes]):
|
|
96
112
|
raise NotImplementedError(_(
|
|
@@ -98,8 +114,7 @@ class ScipyOptimizer(AbstractOptimizer):
|
|
|
98
114
|
jp_message='`ScipyOptimizer` では `add_trial()` は使えません。',
|
|
99
115
|
))
|
|
100
116
|
|
|
101
|
-
def _get_x0(self) -> np.ndarray:
|
|
102
|
-
|
|
117
|
+
def _get_x0(self, after_finalizing_history=False) -> np.ndarray:
|
|
103
118
|
# params を取得
|
|
104
119
|
params: dict[str, Parameter] = self.variable_manager.get_variables(
|
|
105
120
|
filter='parameter', format='raw'
|
|
@@ -112,7 +127,17 @@ class ScipyOptimizer(AbstractOptimizer):
|
|
|
112
127
|
jp_message='Scipy では数値パラメータのみ最適化できます。'
|
|
113
128
|
))
|
|
114
129
|
|
|
115
|
-
#
|
|
130
|
+
# リスタートならば最適値で初期値を上書きする
|
|
131
|
+
if after_finalizing_history:
|
|
132
|
+
df = self.history.get_df()
|
|
133
|
+
optimal_trials = df[df["optimality"]]
|
|
134
|
+
if len(optimal_trials) > 0:
|
|
135
|
+
optimal_trial = optimal_trials.iloc[-1]
|
|
136
|
+
for prm_name in params.keys():
|
|
137
|
+
if prm_name in optimal_trial:
|
|
138
|
+
params[prm_name].value = optimal_trial[prm_name]
|
|
139
|
+
|
|
140
|
+
# fix == True のものを除いて初期値を作成
|
|
116
141
|
x0 = np.array([p.value for p in params.values() if not p.properties.get('fix', False)])
|
|
117
142
|
|
|
118
143
|
return x0
|
|
@@ -133,8 +158,13 @@ class ScipyOptimizer(AbstractOptimizer):
|
|
|
133
158
|
logger.warning(Msg.WARN_SCIPY_NELDER_MEAD_BOUND)
|
|
134
159
|
|
|
135
160
|
def _setup_before_parallel(self):
|
|
136
|
-
|
|
137
161
|
if not self._done_setup_before_parallel:
|
|
162
|
+
# 並列プロセスは起動のラグがあるので
|
|
163
|
+
# すべての並列プロセスで起算時刻を同じにしたい
|
|
164
|
+
self._time_start = time()
|
|
165
|
+
# 別のプロセスが計算を始めてから自分のプロセスが上書きし内容
|
|
166
|
+
# ここで初期化
|
|
167
|
+
self._n_succeeded_trials_in_current_optimization = 0
|
|
138
168
|
|
|
139
169
|
super()._setup_before_parallel() # flag inside
|
|
140
170
|
|
|
@@ -343,9 +373,7 @@ class ScipyOptimizer(AbstractOptimizer):
|
|
|
343
373
|
) from e
|
|
344
374
|
|
|
345
375
|
def _objective(self, xk: np.ndarray) -> float:
|
|
346
|
-
|
|
347
376
|
with self._logging():
|
|
348
|
-
|
|
349
377
|
vm = self.variable_manager
|
|
350
378
|
|
|
351
379
|
# parameter suggestion
|
|
@@ -357,6 +385,7 @@ class ScipyOptimizer(AbstractOptimizer):
|
|
|
357
385
|
# process main fidelity model
|
|
358
386
|
solve_set = self._get_solve_set()
|
|
359
387
|
f_return = solve_set.solve(x)
|
|
388
|
+
self._per_solve_callback()
|
|
360
389
|
assert f_return is not None
|
|
361
390
|
dict_y_internal = f_return[1]
|
|
362
391
|
y_internal: float = tuple(dict_y_internal.values())[0] # type: ignore
|
|
@@ -364,26 +393,38 @@ class ScipyOptimizer(AbstractOptimizer):
|
|
|
364
393
|
return y_internal
|
|
365
394
|
|
|
366
395
|
def run(self):
|
|
367
|
-
|
|
368
396
|
# ===== finalize =====
|
|
369
397
|
self._finalize()
|
|
370
398
|
|
|
371
399
|
# ===== construct x0 =====
|
|
372
|
-
x0 = self._get_x0()
|
|
400
|
+
x0 = self._get_x0(after_finalizing_history=True)
|
|
401
|
+
|
|
402
|
+
# ===== 終了条件 =====
|
|
403
|
+
# In case that setup_before_parallel is not called
|
|
404
|
+
if self._time_start is None:
|
|
405
|
+
self._time_start = time()
|
|
406
|
+
if self._n_succeeded_trials_in_current_optimization is None:
|
|
407
|
+
self._n_succeeded_trials_in_current_optimization = 0
|
|
373
408
|
|
|
374
409
|
# ===== run =====
|
|
375
410
|
with closing(self.fem):
|
|
376
411
|
|
|
377
|
-
with self._setting_status(),
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
412
|
+
with self._setting_status(), \
|
|
413
|
+
suppress(InterruptOptimization), \
|
|
414
|
+
suppress(InterruptScipyMinimize):
|
|
415
|
+
|
|
416
|
+
try:
|
|
417
|
+
minimize(
|
|
418
|
+
self._objective,
|
|
419
|
+
x0,
|
|
420
|
+
args=(),
|
|
421
|
+
method=self.method,
|
|
422
|
+
bounds=self._get_scipy_bounds(),
|
|
423
|
+
constraints=self._get_scipy_constraints(),
|
|
424
|
+
tol=self.tol,
|
|
425
|
+
callback=self._get_scipy_callback(),
|
|
426
|
+
options=self.options,
|
|
427
|
+
)
|
|
428
|
+
finally:
|
|
429
|
+
self._time_start = None
|
|
430
|
+
self._n_succeeded_trials_in_current_optimization = None
|
|
@@ -98,6 +98,12 @@ def get_hypervolume_plot(_: History, df: pd.DataFrame) -> go.Figure:
|
|
|
98
98
|
# メインデータを抽出
|
|
99
99
|
df = get_partial_df(df, equality_filters=MAIN_FILTER)
|
|
100
100
|
|
|
101
|
+
# 成功した試行のみを抽出
|
|
102
|
+
df = df[df['feasibility']]
|
|
103
|
+
|
|
104
|
+
# 番号を振り直し
|
|
105
|
+
df['trial'] = range(1, len(df) + 1)
|
|
106
|
+
|
|
101
107
|
# create figure
|
|
102
108
|
fig = px.line(
|
|
103
109
|
df,
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyfemtet
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Design parameter optimization using Femtet.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
License-File: LICENSE_THIRD_PARTY.txt
|
|
8
8
|
Author: pyfemtet
|
|
9
9
|
Author-email: 148934231+pyfemtet@users.noreply.github.com
|
|
10
|
-
Requires-Python: >= 3.10, < 3.
|
|
10
|
+
Requires-Python: >= 3.10, < 3.15
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
17
|
Requires-Dist: babel (>=2.15.0,<3)
|
|
17
18
|
Requires-Dist: botorch (>=0.12.0,<0.15.0)
|
|
18
19
|
Requires-Dist: colorlog (>=6.8.0,<7)
|
|
@@ -44,7 +44,7 @@ pyfemtet/opt/interface/_excel_interface/debug-excel-interface.xlsm,sha256=TM1CEO
|
|
|
44
44
|
pyfemtet/opt/interface/_excel_interface/excel_interface.py,sha256=Sghavj7bcslQ5ue9xJzV6DdBfhv6XKn-XxKD-T39KEo,39854
|
|
45
45
|
pyfemtet/opt/interface/_femtet_interface/__init__.py,sha256=snQruC8Rl_5rFeVmiqw9lmzdJ5mL42HpIlHIn5ytd8s,77
|
|
46
46
|
pyfemtet/opt/interface/_femtet_interface/_femtet_parametric.py,sha256=Om2S1hd90nQSuOXf1ylUJiILcxVhLiCOL4dc34lJzdM,10946
|
|
47
|
-
pyfemtet/opt/interface/_femtet_interface/femtet_interface.py,sha256=
|
|
47
|
+
pyfemtet/opt/interface/_femtet_interface/femtet_interface.py,sha256=KAJ3ff9oGNlsHz8Im4EJTYg6AIB3LDvFi8nhv9bPWuc,45985
|
|
48
48
|
pyfemtet/opt/interface/_femtet_with_nx_interface/__init__.py,sha256=ppeoWVSmVsTmDNKpuFRVTnhjcoefQVEog3-FRiKpEe4,104
|
|
49
49
|
pyfemtet/opt/interface/_femtet_with_nx_interface/femtet_with_nx_interface.py,sha256=IPIoNMtcJmtiv7eM3yDMWDOynTf0mXd-438LoDVqZCU,8628
|
|
50
50
|
pyfemtet/opt/interface/_femtet_with_nx_interface/model1.prt,sha256=cYVw2izr4_9PCPHOpi46XmDVOuNZ5ksuwKqzBtCZfNA,104833
|
|
@@ -67,7 +67,7 @@ pyfemtet/opt/meta_script/__main__.py,sha256=9-QM6eZOLpZ_CxERpRu3RAMqpudorSJdPCiK
|
|
|
67
67
|
pyfemtet/opt/meta_script/sample/sample.bas,sha256=2iuSYMgPDyAdiSDVGxRu3avjcZYnULz0l8e25YBa7SQ,27966
|
|
68
68
|
pyfemtet/opt/meta_script/sample/sample.femprj,sha256=6_0ywhgXxZjdzZzQFog8mgMUEjKNCFVNlEgAWoptovk,292885
|
|
69
69
|
pyfemtet/opt/optimizer/__init__.py,sha256=A4QYeF0KHEFdwoxLfkDND7ikDQ186Ryy3oXEGdakFSg,463
|
|
70
|
-
pyfemtet/opt/optimizer/_base_optimizer.py,sha256=
|
|
70
|
+
pyfemtet/opt/optimizer/_base_optimizer.py,sha256=L4-WUXBkLVUarWvHyoBLe9EdOlOOmYB1GNB4guctNAk,37657
|
|
71
71
|
pyfemtet/opt/optimizer/_trial_queue.py,sha256=Yv6JlfVCYOiCukllfxk79xU4_utmxwRA3gcCWpdyG9k,2919
|
|
72
72
|
pyfemtet/opt/optimizer/optuna_optimizer/__init__.py,sha256=u2Bwc79tkZTU5dMbhzzrPQi0RlFg22UgXc-m9K9G6wQ,242
|
|
73
73
|
pyfemtet/opt/optimizer/optuna_optimizer/_optuna_attribute.py,sha256=7eZsruVCGgMlcnf3a9Vf55FOEE-D7V777MJQajI12Cw,1842
|
|
@@ -78,7 +78,7 @@ pyfemtet/opt/optimizer/optuna_optimizer/_pof_botorch/enable_nonlinear_constraint
|
|
|
78
78
|
pyfemtet/opt/optimizer/optuna_optimizer/_pof_botorch/pof_botorch_sampler.py,sha256=qOAQOiC7xW1aGZ7JbMPYVjc9FEHi3hYffOfcXpy4rhI,48642
|
|
79
79
|
pyfemtet/opt/optimizer/optuna_optimizer/wat_ex14_parametric_jp.femprj,sha256=-M54MTNrV7muZWPm9Tjptd6HDdtgUFBsRroC6ytyqa0,180970
|
|
80
80
|
pyfemtet/opt/optimizer/scipy_optimizer/__init__.py,sha256=oXx2JAVLvgz0WwIXAknuV4p2MupaiutYYvjI8hXcFwc,45
|
|
81
|
-
pyfemtet/opt/optimizer/scipy_optimizer/_scipy_optimizer.py,sha256=
|
|
81
|
+
pyfemtet/opt/optimizer/scipy_optimizer/_scipy_optimizer.py,sha256=mszpNNxzkyA62aheA-e1M5gW47BI_VcwZ4JLdLcpskE,15641
|
|
82
82
|
pyfemtet/opt/prediction/__init__.py,sha256=-XYo-l5YFjExMtqMKj1YUAhmGSQq_0YITS0qizj2Xbs,104
|
|
83
83
|
pyfemtet/opt/prediction/_botorch_utils.py,sha256=cXQlQ_oC6cQaI5kGIYANlqDJPdkGEA_zK2gtJpluJNE,6001
|
|
84
84
|
pyfemtet/opt/prediction/_gpytorch_modules_extension.py,sha256=B_qUtFn06dQENOmUOObbCpkeASUKI5JpXROx8zYeaq0,5224
|
|
@@ -169,14 +169,14 @@ pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_
|
|
|
169
169
|
pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.femprj,sha256=uPqOara56FAt_T4x2P6tvcIXnISw_fX7RECbVoJlAFs,280214
|
|
170
170
|
pyfemtet/opt/visualization/plotter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
171
171
|
pyfemtet/opt/visualization/plotter/contour_creator.py,sha256=bvGGWNBLZawHq0fAIHfZOUAXw0f9GTlNROiJThoXZ2A,3043
|
|
172
|
-
pyfemtet/opt/visualization/plotter/main_figure_creator.py,sha256=
|
|
172
|
+
pyfemtet/opt/visualization/plotter/main_figure_creator.py,sha256=ENJa30ua-e_R5amjRPN8ihBavcl1XagE8CK8E7YjDLo,17778
|
|
173
173
|
pyfemtet/opt/visualization/plotter/parallel_plot_creator.py,sha256=VRhT0CUG1mCHDoVO8e6LR_-FiD0QB3GsB95016iXmYc,802
|
|
174
174
|
pyfemtet/opt/visualization/plotter/pm_graph_creator.py,sha256=7EwmoJlnHwDrpw65NchiA63FIjgGTLq6vTcpTzrSnJo,11841
|
|
175
175
|
pyfemtet/opt/wat_ex14_parametric_jp.femprj,sha256=dMwQMt6yok_PbZLyxPYdmg5wJQwgQDZ4RhS76zdGLGk,177944
|
|
176
176
|
pyfemtet/opt/worker_status.py,sha256=simvPa1AkO1idmPXrF5WjYVEBx3tO7hLhbM3J1rFjdo,3824
|
|
177
|
-
pyfemtet-1.
|
|
178
|
-
pyfemtet-1.
|
|
179
|
-
pyfemtet-1.
|
|
180
|
-
pyfemtet-1.
|
|
181
|
-
pyfemtet-1.
|
|
182
|
-
pyfemtet-1.
|
|
177
|
+
pyfemtet-1.4.0.dist-info/METADATA,sha256=ldGF__xZvXssltC13o-LXUUR-ait8WW5-Rmoid5H75w,3461
|
|
178
|
+
pyfemtet-1.4.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
179
|
+
pyfemtet-1.4.0.dist-info/entry_points.txt,sha256=Tsb_l_8Z6pyyq2tRfuKiwfJUV3nq_cHoLS61foALtsg,134
|
|
180
|
+
pyfemtet-1.4.0.dist-info/licenses/LICENSE,sha256=LWUL5LlMGjSRTvsalS8_fFuwS4VMw18fJSNWFwDK8pc,1060
|
|
181
|
+
pyfemtet-1.4.0.dist-info/licenses/LICENSE_THIRD_PARTY.txt,sha256=8_9-cgzTpmeuCqItPZb9-lyAZcH2Qp9sZTU_hYuOZIQ,191
|
|
182
|
+
pyfemtet-1.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|