Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
nni
Commits
ae7a72bc
"tools/nni_trial_tool/constants.py" did not exist on "ff390b4d5457cd5dec9e5c1c3a98892e5f2aed2a"
Commit
ae7a72bc
authored
Jun 19, 2019
by
Hongarc
Committed by
Chi Song
Jun 19, 2019
Browse files
Remove all whitespace at end of line (#1162)
parent
14c1b31c
Changes
176
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
78 additions
and
78 deletions
+78
-78
src/sdk/pynni/nni/common.py
src/sdk/pynni/nni/common.py
+1
-1
src/sdk/pynni/nni/curvefitting_assessor/curvefitting_assessor.py
.../pynni/nni/curvefitting_assessor/curvefitting_assessor.py
+2
-2
src/sdk/pynni/nni/curvefitting_assessor/curvefunctions.py
src/sdk/pynni/nni/curvefitting_assessor/curvefunctions.py
+3
-3
src/sdk/pynni/nni/curvefitting_assessor/model_factory.py
src/sdk/pynni/nni/curvefitting_assessor/model_factory.py
+13
-13
src/sdk/pynni/nni/evolution_tuner/evolution_tuner.py
src/sdk/pynni/nni/evolution_tuner/evolution_tuner.py
+2
-2
src/sdk/pynni/nni/hyperband_advisor/hyperband_advisor.py
src/sdk/pynni/nni/hyperband_advisor/hyperband_advisor.py
+11
-11
src/sdk/pynni/nni/medianstop_assessor/medianstop_assessor.py
src/sdk/pynni/nni/medianstop_assessor/medianstop_assessor.py
+5
-5
src/sdk/pynni/nni/metis_tuner/lib_acquisition_function.py
src/sdk/pynni/nni/metis_tuner/lib_acquisition_function.py
+1
-1
src/sdk/pynni/nni/networkmorphism_tuner/networkmorphism_tuner.py
.../pynni/nni/networkmorphism_tuner/networkmorphism_tuner.py
+4
-4
src/sdk/pynni/nni/platform/local.py
src/sdk/pynni/nni/platform/local.py
+3
-3
src/sdk/pynni/nni/smac_tuner/convert_ss_to_scenario.py
src/sdk/pynni/nni/smac_tuner/convert_ss_to_scenario.py
+15
-15
src/sdk/pynni/nni/smac_tuner/smac_tuner.py
src/sdk/pynni/nni/smac_tuner/smac_tuner.py
+8
-8
src/sdk/pynni/tests/test_multi_phase_tuner.py
src/sdk/pynni/tests/test_multi_phase_tuner.py
+1
-1
src/sdk/pynni/tests/test_trial.py
src/sdk/pynni/tests/test_trial.py
+1
-1
src/webui/src/App.css
src/webui/src/App.css
+1
-1
src/webui/src/components/TrialsDetail.tsx
src/webui/src/components/TrialsDetail.tsx
+1
-1
src/webui/src/components/overview/Progress.tsx
src/webui/src/components/overview/Progress.tsx
+1
-1
src/webui/src/components/public-child/TrialLog.tsx
src/webui/src/components/public-child/TrialLog.tsx
+1
-1
src/webui/src/index.css
src/webui/src/index.css
+3
-3
src/webui/src/static/function.ts
src/webui/src/static/function.ts
+1
-1
No files found.
src/sdk/pynni/nni/common.py
View file @
ae7a72bc
...
...
@@ -34,7 +34,7 @@ log_level_map = {
}
_time_format
=
'%m/%d/%Y, %I:%M:%S %p'
class
_LoggerFileWrapper
(
TextIOBase
):
def
__init__
(
self
,
logger_file
):
self
.
file
=
logger_file
...
...
src/sdk/pynni/nni/curvefitting_assessor/curvefitting_assessor.py
View file @
ae7a72bc
...
...
@@ -67,7 +67,7 @@ class CurvefittingAssessor(Assessor):
def
trial_end
(
self
,
trial_job_id
,
success
):
"""update the best performance of completed trial job
Parameters
----------
trial_job_id: int
...
...
@@ -112,7 +112,7 @@ class CurvefittingAssessor(Assessor):
curr_step
=
len
(
trial_history
)
if
curr_step
<
self
.
start_step
:
return
AssessResult
.
Good
if
trial_job_id
in
self
.
last_judgment_num
.
keys
()
and
curr_step
-
self
.
last_judgment_num
[
trial_job_id
]
<
self
.
gap
:
return
AssessResult
.
Good
self
.
last_judgment_num
[
trial_job_id
]
=
curr_step
...
...
src/sdk/pynni/nni/curvefitting_assessor/curvefunctions.py
View file @
ae7a72bc
...
...
@@ -26,7 +26,7 @@ curve_combination_models = ['vap', 'pow3', 'linear', 'logx_linear', 'dr_hill_zer
def
vap
(
x
,
a
,
b
,
c
):
"""Vapor pressure model
Parameters
----------
x: int
...
...
@@ -109,7 +109,7 @@ model_para_num['logx_linear'] = 2
def
dr_hill_zero_background
(
x
,
theta
,
eta
,
kappa
):
"""dr hill zero background
Parameters
----------
x: int
...
...
@@ -261,7 +261,7 @@ model_para_num['weibull'] = 4
def
janoschek
(
x
,
a
,
beta
,
k
,
delta
):
"""http://www.pisces-conservation.com/growthhelp/janoschek.htm
Parameters
----------
x: int
...
...
src/sdk/pynni/nni/curvefitting_assessor/model_factory.py
View file @
ae7a72bc
...
...
@@ -35,7 +35,7 @@ logger = logging.getLogger('curvefitting_Assessor')
class
CurveModel
(
object
):
"""Build a Curve Model to predict the performance
Algorithm: https://github.com/Microsoft/nni/blob/master/src/sdk/pynni/nni/curvefitting_assessor/README.md
Parameters
...
...
@@ -53,7 +53,7 @@ class CurveModel(object):
def
fit_theta
(
self
):
"""use least squares to fit all default curves parameter seperately
Returns
-------
None
...
...
@@ -87,7 +87,7 @@ class CurveModel(object):
def
filter_curve
(
self
):
"""filter the poor performing curve
Returns
-------
None
...
...
@@ -117,7 +117,7 @@ class CurveModel(object):
def
predict_y
(
self
,
model
,
pos
):
"""return the predict y of 'model' when epoch = pos
Parameters
----------
model: string
...
...
@@ -162,7 +162,7 @@ class CurveModel(object):
def
normalize_weight
(
self
,
samples
):
"""normalize weight
Parameters
----------
samples: list
...
...
@@ -184,7 +184,7 @@ class CurveModel(object):
def
sigma_sq
(
self
,
sample
):
"""returns the value of sigma square, given the weight's sample
Parameters
----------
sample: list
...
...
@@ -203,7 +203,7 @@ class CurveModel(object):
def
normal_distribution
(
self
,
pos
,
sample
):
"""returns the value of normal distribution, given the weight's sample and target position
Parameters
----------
pos: int
...
...
@@ -227,7 +227,7 @@ class CurveModel(object):
----------
sample: list
sample is a (1 * NUM_OF_FUNCTIONS) matrix, representing{w1, w2, ... wk}
Returns
-------
float
...
...
@@ -241,13 +241,13 @@ class CurveModel(object):
def
prior
(
self
,
samples
):
"""priori distribution
Parameters
----------
samples: list
a collection of sample, it's a (NUM_OF_INSTANCE * NUM_OF_FUNCTIONS) matrix,
representing{{w11, w12, ..., w1k}, {w21, w22, ... w2k}, ...{wk1, wk2,..., wkk}}
Returns
-------
float
...
...
@@ -264,13 +264,13 @@ class CurveModel(object):
def
target_distribution
(
self
,
samples
):
"""posterior probability
Parameters
----------
samples: list
a collection of sample, it's a (NUM_OF_INSTANCE * NUM_OF_FUNCTIONS) matrix,
representing{{w11, w12, ..., w1k}, {w21, w22, ... w2k}, ...{wk1, wk2,..., wkk}}
Returns
-------
float
...
...
@@ -319,7 +319,7 @@ class CurveModel(object):
def
predict
(
self
,
trial_history
):
"""predict the value of target position
Parameters
----------
trial_history: list
...
...
src/sdk/pynni/nni/evolution_tuner/evolution_tuner.py
View file @
ae7a72bc
...
...
@@ -167,7 +167,7 @@ class EvolutionTuner(Tuner):
self
.
space
=
None
def
update_search_space
(
self
,
search_space
):
"""Update search space.
"""Update search space.
Search_space contains the information that user pre-defined.
Parameters
...
...
@@ -194,7 +194,7 @@ class EvolutionTuner(Tuner):
Parameters
----------
parameter_id : int
Returns
-------
config : dict
...
...
src/sdk/pynni/nni/hyperband_advisor/hyperband_advisor.py
View file @
ae7a72bc
...
...
@@ -43,7 +43,7 @@ _epsilon = 1e-6
def
create_parameter_id
():
"""Create an id
Returns
-------
int
...
...
@@ -55,7 +55,7 @@ def create_parameter_id():
def
create_bracket_parameter_id
(
brackets_id
,
brackets_curr_decay
,
increased_id
=-
1
):
"""Create a full id for a specific bracket's hyperparameter configuration
Parameters
----------
brackets_id: int
...
...
@@ -79,7 +79,7 @@ def create_bracket_parameter_id(brackets_id, brackets_curr_decay, increased_id=-
def
json2parameter
(
ss_spec
,
random_state
):
"""Randomly generate values for hyperparameters from hyperparameter space i.e., x.
Parameters
----------
ss_spec:
...
...
@@ -116,7 +116,7 @@ def json2parameter(ss_spec, random_state):
class
Bracket
():
"""A bracket in Hyperband, all the information of a bracket is managed by an instance of this class
Parameters
----------
s: int
...
...
@@ -132,7 +132,7 @@ class Bracket():
optimize_mode: str
optimize mode, 'maximize' or 'minimize'
"""
def
__init__
(
self
,
s
,
s_max
,
eta
,
R
,
optimize_mode
):
self
.
bracket_id
=
s
self
.
s_max
=
s_max
...
...
@@ -163,7 +163,7 @@ class Bracket():
def
set_config_perf
(
self
,
i
,
parameter_id
,
seq
,
value
):
"""update trial's latest result with its sequence number, e.g., epoch number or batch number
Parameters
----------
i: int
...
...
@@ -184,7 +184,7 @@ class Bracket():
self
.
configs_perf
[
i
][
parameter_id
]
=
[
seq
,
value
]
else
:
self
.
configs_perf
[
i
][
parameter_id
]
=
[
seq
,
value
]
def
inform_trial_end
(
self
,
i
):
"""If the trial is finished and the corresponding round (i.e., i) has all its trials finished,
...
...
@@ -230,7 +230,7 @@ class Bracket():
----------
num: int
the number of hyperparameter configurations
Returns
-------
list
...
...
@@ -350,7 +350,7 @@ class Hyperband(MsgDispatcherBase):
def
handle_update_search_space
(
self
,
data
):
"""data: JSON object, which is search space
Parameters
----------
data: int
...
...
@@ -392,9 +392,9 @@ class Hyperband(MsgDispatcherBase):
"""
Parameters
----------
data:
data:
it is an object which has keys 'parameter_id', 'value', 'trial_job_id', 'type', 'sequence'.
Raises
------
ValueError
...
...
src/sdk/pynni/nni/medianstop_assessor/medianstop_assessor.py
View file @
ae7a72bc
...
...
@@ -21,10 +21,10 @@ from nni.assessor import Assessor, AssessResult
logger
=
logging
.
getLogger
(
'medianstop_Assessor'
)
class
MedianstopAssessor
(
Assessor
):
"""MedianstopAssessor is The median stopping rule stops a pending trial X at step S
if the trial’s best objective value by step S is strictly worse than the median value
"""MedianstopAssessor is The median stopping rule stops a pending trial X at step S
if the trial’s best objective value by step S is strictly worse than the median value
of the running averages of all completed trials’ objectives reported up to step S
Parameters
----------
optimize_mode: str
...
...
@@ -60,7 +60,7 @@ class MedianstopAssessor(Assessor):
def
trial_end
(
self
,
trial_job_id
,
success
):
"""trial_end
Parameters
----------
trial_job_id: int
...
...
@@ -83,7 +83,7 @@ class MedianstopAssessor(Assessor):
def
assess_trial
(
self
,
trial_job_id
,
trial_history
):
"""assess_trial
Parameters
----------
trial_job_id: int
...
...
src/sdk/pynni/nni/metis_tuner/lib_acquisition_function.py
View file @
ae7a72bc
...
...
@@ -27,7 +27,7 @@ from scipy.optimize import minimize
import
nni.metis_tuner.lib_data
as
lib_data
def
next_hyperparameter_expected_improvement
(
fun_prediction
,
def
next_hyperparameter_expected_improvement
(
fun_prediction
,
fun_prediction_args
,
x_bounds
,
x_types
,
samples_y_aggregation
,
...
...
src/sdk/pynni/nni/networkmorphism_tuner/networkmorphism_tuner.py
View file @
ae7a72bc
...
...
@@ -69,7 +69,7 @@ class NetworkMorphismTuner(Tuner):
optimize_mode : str
optimize mode "minimize" or "maximize" (default: {"minimize"})
path : str
default mode path to save the model file (default: {"model_path"})
default mode path to save the model file (default: {"model_path"})
verbose : bool
verbose to print the log (default: {True})
beta : float
...
...
@@ -154,7 +154,7 @@ class NetworkMorphismTuner(Tuner):
def
receive_trial_result
(
self
,
parameter_id
,
parameters
,
value
):
""" Record an observation of the objective function.
Parameters
----------
parameter_id : int
...
...
@@ -267,7 +267,7 @@ class NetworkMorphismTuner(Tuner):
----------
model_id : int
model index
Returns
-------
load_model : Graph
...
...
@@ -297,7 +297,7 @@ class NetworkMorphismTuner(Tuner):
----------
model_id : int
model index
Returns
-------
float
...
...
src/sdk/pynni/nni/platform/local.py
View file @
ae7a72bc
...
...
@@ -67,7 +67,7 @@ def get_next_parameter():
params_file_name
=
'parameter.cfg'
else
:
raise
AssertionError
(
'_param_index value ({}) should >=0'
.
format
(
_param_index
))
params_filepath
=
os
.
path
.
join
(
_sysdir
,
params_file_name
)
if
not
os
.
path
.
isfile
(
params_filepath
):
request_next_parameter
()
...
...
@@ -81,11 +81,11 @@ def get_next_parameter():
def
send_metric
(
string
):
if
_nni_platform
!=
'local'
:
data
=
(
string
).
encode
(
'utf8'
)
assert
len
(
data
)
<
1000000
,
'Metric too long'
assert
len
(
data
)
<
1000000
,
'Metric too long'
print
(
'NNISDK_ME%s'
%
(
data
),
flush
=
True
)
else
:
data
=
(
string
+
'
\n
'
).
encode
(
'utf8'
)
assert
len
(
data
)
<
1000000
,
'Metric too long'
assert
len
(
data
)
<
1000000
,
'Metric too long'
_metric_file
.
write
(
b
'ME%06d%b'
%
(
len
(
data
),
data
))
_metric_file
.
flush
()
if
sys
.
platform
==
"win32"
:
...
...
src/sdk/pynni/nni/smac_tuner/convert_ss_to_scenario.py
View file @
ae7a72bc
...
...
@@ -24,12 +24,12 @@ import numpy as np
def
get_json_content
(
file_path
):
"""Load json file content
Parameters
----------
file_path:
path to the file
Raises
------
TypeError
...
...
@@ -43,9 +43,9 @@ def get_json_content(file_path):
return
None
def
generate_pcs
(
nni_search_space_content
):
"""Generate the Parameter Configuration Space (PCS) which defines the
"""Generate the Parameter Configuration Space (PCS) which defines the
legal ranges of the parameters to be optimized and their default values.
Generally, the format is:
# parameter_name categorical {value_1, ..., value_N} [default value]
# parameter_name ordinal {value_1, ..., value_N} [default value]
...
...
@@ -53,14 +53,14 @@ def generate_pcs(nni_search_space_content):
# parameter_name integer [min_value, max_value] [default value] log
# parameter_name real [min_value, max_value] [default value]
# parameter_name real [min_value, max_value] [default value] log
Reference: https://automl.github.io/SMAC3/stable/options.html
Parameters
----------
nni_search_space_content: search_space
The search space in this experiment in nni
Returns
-------
Parameter Configuration Space (PCS)
...
...
@@ -81,8 +81,8 @@ def generate_pcs(nni_search_space_content):
if
search_space
[
key
][
'_type'
]
==
'choice'
:
choice_len
=
len
(
search_space
[
key
][
'_value'
])
pcs_fd
.
write
(
'%s categorical {%s} [%s]
\n
'
%
(
key
,
json
.
dumps
(
list
(
range
(
choice_len
)))[
1
:
-
1
],
key
,
json
.
dumps
(
list
(
range
(
choice_len
)))[
1
:
-
1
],
json
.
dumps
(
0
)))
if
key
in
categorical_dict
:
raise
RuntimeError
(
'%s has already existed, please make sure search space has no duplicate key.'
%
key
)
...
...
@@ -90,19 +90,19 @@ def generate_pcs(nni_search_space_content):
elif
search_space
[
key
][
'_type'
]
==
'randint'
:
# TODO: support lower bound in randint
pcs_fd
.
write
(
'%s integer [0, %d] [%d]
\n
'
%
(
key
,
search_space
[
key
][
'_value'
][
0
],
key
,
search_space
[
key
][
'_value'
][
0
],
search_space
[
key
][
'_value'
][
0
]))
elif
search_space
[
key
][
'_type'
]
==
'uniform'
:
pcs_fd
.
write
(
'%s real %s [%s]
\n
'
%
(
key
,
key
,
json
.
dumps
(
search_space
[
key
][
'_value'
]),
json
.
dumps
(
search_space
[
key
][
'_value'
][
0
])))
elif
search_space
[
key
][
'_type'
]
==
'loguniform'
:
# use np.round here to ensure that the rounded defaut value is in the range, which will be rounded in configure_space package
search_space
[
key
][
'_value'
]
=
list
(
np
.
round
(
np
.
log
(
search_space
[
key
][
'_value'
]),
10
))
pcs_fd
.
write
(
'%s real %s [%s]
\n
'
%
(
key
,
key
,
json
.
dumps
(
search_space
[
key
][
'_value'
]),
json
.
dumps
(
search_space
[
key
][
'_value'
][
0
])))
elif
search_space
[
key
][
'_type'
]
==
'quniform'
\
...
...
@@ -122,9 +122,9 @@ def generate_pcs(nni_search_space_content):
return
None
def
generate_scenario
(
ss_content
):
"""Generate the scenario. The scenario-object (smac.scenario.scenario.Scenario) is used to configure SMAC and
"""Generate the scenario. The scenario-object (smac.scenario.scenario.Scenario) is used to configure SMAC and
can be constructed either by providing an actual scenario-object, or by specifing the options in a scenario file.
Reference: https://automl.github.io/SMAC3/stable/options.html
The format of the scenario file is one option per line:
...
...
@@ -135,7 +135,7 @@ def generate_scenario(ss_content):
Parameters
----------
abort_on_first_run_crash: bool
If true, SMAC will abort if the first run of the target algorithm crashes. Default: True,
If true, SMAC will abort if the first run of the target algorithm crashes. Default: True,
because trials reported to nni tuner would always in success state
algo: function
Specifies the target algorithm call that SMAC will optimize. Interpreted as a bash-command.
...
...
src/sdk/pynni/nni/smac_tuner/smac_tuner.py
View file @
ae7a72bc
...
...
@@ -64,7 +64,7 @@ class SMACTuner(Tuner):
def
_main_cli
(
self
):
"""Main function of SMAC for CLI interface
Returns
-------
instance
...
...
@@ -153,7 +153,7 @@ class SMACTuner(Tuner):
def
receive_trial_result
(
self
,
parameter_id
,
parameters
,
value
):
"""receive_trial_result
Parameters
----------
parameter_id: int
...
...
@@ -162,7 +162,7 @@ class SMACTuner(Tuner):
parameters
value:
value
Raises
------
RuntimeError
...
...
@@ -185,7 +185,7 @@ class SMACTuner(Tuner):
Also, we convert categorical:
categorical values in search space are changed to list of numbers before,
those original values will be changed back in this function
Parameters
----------
challenger_dict: dict
...
...
@@ -211,12 +211,12 @@ class SMACTuner(Tuner):
def
generate_parameters
(
self
,
parameter_id
):
"""generate one instance of hyperparameters
Parameters
----------
parameter_id: int
parameter id
Returns
-------
list
...
...
@@ -234,12 +234,12 @@ class SMACTuner(Tuner):
def
generate_multiple_parameters
(
self
,
parameter_id_list
):
"""generate mutiple instances of hyperparameters
Parameters
----------
parameter_id_list: list
list of parameter id
Returns
-------
list
...
...
src/sdk/pynni/tests/test_multi_phase_tuner.py
View file @
ae7a72bc
...
...
@@ -32,7 +32,7 @@ from nni.multi_phase.multi_phase_dispatcher import MultiPhaseMsgDispatcher
from
unittest
import
TestCase
,
main
class
NaiveMultiPhaseTuner
(
MultiPhaseTuner
):
'''
'''
supports only choices
'''
def
__init__
(
self
):
...
...
src/sdk/pynni/tests/test_trial.py
View file @
ae7a72bc
...
...
@@ -40,7 +40,7 @@ class TrialTestCase(TestCase):
def
test_get_sequence_id
(
self
):
self
.
assertEqual
(
nni
.
get_sequence_id
(),
0
)
def
test_report_intermediate_result
(
self
):
nni
.
report_intermediate_result
(
123
)
self
.
assertEqual
(
test_platform
.
get_last_metric
(),
{
...
...
src/webui/src/App.css
View file @
ae7a72bc
...
...
@@ -10,7 +10,7 @@
left
:
0
;
top
:
0
;
width
:
100%
;
height
:
56px
;
height
:
56px
;
background
:
#0071BC
;
border-right
:
1px
solid
#ccc
;
z-index
:
1000
;
...
...
src/webui/src/components/TrialsDetail.tsx
View file @
ae7a72bc
...
...
@@ -139,7 +139,7 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
const
items
=
metricSource
[
key
];
if
(
items
.
trialJobId
===
id
)
{
// succeed trial, last intermediate result is final result
// final result format may be object
// final result format may be object
if
(
typeof
JSON
.
parse
(
items
.
data
)
===
'
object
'
)
{
mediate
.
push
(
JSON
.
parse
(
items
.
data
).
default
);
}
else
{
...
...
src/webui/src/components/overview/Progress.tsx
View file @
ae7a72bc
...
...
@@ -78,7 +78,7 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
}).
then
(
res
=>
{
if
(
res
.
status
===
200
)
{
message
.
destroy
();
message
.
success
(
`Update
${
CONTROLTYPE
[
1
].
toLocaleLowerCase
()}
message
.
success
(
`Update
${
CONTROLTYPE
[
1
].
toLocaleLowerCase
()}
successfully`
);
// rerender trial profile message
const
{
updateFile
}
=
this
.
props
;
...
...
src/webui/src/components/public-child/TrialLog.tsx
View file @
ae7a72bc
...
...
@@ -15,7 +15,7 @@ class TrialLog extends React.Component<TrialLogProps, {}> {
render
()
{
const
{
logStr
}
=
this
.
props
;
return
(
<
div
>
<
LogPathChild
...
...
src/webui/src/index.css
View file @
ae7a72bc
...
...
@@ -49,8 +49,8 @@ table {
border-collapse
:
collapse
;
border-spacing
:
0
;
}
@font-face
{
font-family
:
'Segoe'
;
@font-face
{
font-family
:
'Segoe'
;
src
:
url('./static/font/SegoePro-Regular.ttf')
;
}
}
src/webui/src/static/function.ts
View file @
ae7a72bc
...
...
@@ -50,7 +50,7 @@ const getFinalResult = (final: Array<FinalResult>) => {
}
};
// get final result value // acc obj
// get final result value // acc obj
const
getFinal
=
(
final
:
Array
<
FinalResult
>
)
=>
{
let
showDefault
:
FinalType
;
if
(
final
)
{
...
...
Prev
1
…
3
4
5
6
7
8
9
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment