fix cross border flow coloring, country color are more contrasted, and update the config documentations

This commit is contained in:
virio-andreyana 2023-07-31 01:27:15 +02:00
parent 269a08006f
commit 6bf6ec603c
8 changed files with 1193 additions and 95 deletions

View File

@ -1,17 +1,18 @@
,Unit,Values,Description
simplify_network,,,
-- to_substations,bool,"{'true','false'}","Aggregates all nodes without power injection (positive or negative, i.e. demand or generation) to electrically closest ones"
-- algorithm,str,"One of {kmeans, hac, modularity}",
-- feature,str,"Str in the format carrier1+carrier2+...+carrierN-X, where CarrierI can be from {solar, onwind, offwind, ror} and X is one of {cap, time}.",
-- exclude_carriers,list,"List of Str like [ 'solar', 'onwind'] or empy list []","List of carriers which will not be aggregated. If empty, all carriers will be aggregated."
-- remove stubs,bool,"true/false","Controls whether radial parts of the network should be recursively aggregated. Defaults to true."
-- remove_stubs_across_borders,bool,"true/false","Controls whether radial parts of the network should be recursively aggregated across borders. Defaults to true."
cluster_network,,,
-- algorithm,str,"One of {kmeans, hac}",
-- feature,str,"Str in the format carrier1+carrier2+...+carrierN-X, where CarrierI can be from {solar, onwind, offwind, ror} and X is one of {cap, time}.",
-- exclude_carriers,list,"List of Str like [ 'solar', 'onwind'] or empy list []","List of carriers which will not be aggregated. If empty, all carriers will be aggregated."
aggregation_strategies,,,
-- generators,,,
-- -- {key},str,"{key} can be any of the component of the generator (str). Its value can be any that can be converted to pandas.Series using getattr(). For example one of {min, max, sum}.","Aggregates the component according to the given strategy. For example, if sum, then all values within each cluster are summed to represent the new generator."
-- buses,,,
-- -- {key},str,"{key} can be any of the component of the bus (str). Its value can be any that can be converted to pandas.Series using getattr(). For example one of {min, max, sum}.","Aggregates the component according to the given strategy. For example, if sum, then all values within each cluster are summed to represent the new bus."
,Unit,Values,Description
simplify_network,,,
-- to_substations,bool,"{'true','false'}","Aggregates all nodes without power injection (positive or negative, i.e. demand or generation) to electrically closest ones"
-- algorithm,str,"One of {kmeans, hac, modularity}",
-- feature,str,"Str in the format carrier1+carrier2+...+carrierN-X, where CarrierI can be from {solar, onwind, offwind, ror} and X is one of {cap, time}.",
-- exclude_carriers,list,"List of Str like [ 'solar', 'onwind'] or empy list []","List of carriers which will not be aggregated. If empty, all carriers will be aggregated."
-- remove stubs,bool,"{'true','false'}",Controls whether radial parts of the network should be recursively aggregated. Defaults to true.
-- remove_stubs_across_borders,bool,"{'true','false'}",Controls whether radial parts of the network should be recursively aggregated across borders. Defaults to true.
cluster_network,,,
-- algorithm,str,"One of {kmeans, hac}",
-- feature,str,"Str in the format carrier1+carrier2+...+carrierN-X, where CarrierI can be from {solar, onwind, offwind, ror} and X is one of {cap, time}.",
-- exclude_carriers,list,"List of Str like [ 'solar', 'onwind'] or empy list []","List of carriers which will not be aggregated. If empty, all carriers will be aggregated."
-- consider_efficiency_classes,bool,"{'true','false'}","Aggregated each carriers into the top 10-quantile (high), the bottom 90-quantile (low), and everything in between (medium)."
aggregation_strategies,,,
-- generators,,,
-- -- {key},str,"{key} can be any of the component of the generator (str). Its value can be any that can be converted to pandas.Series using getattr(). For example one of {min, max, sum}.","Aggregates the component according to the given strategy. For example, if sum, then all values within each cluster are summed to represent the new generator."
-- buses,,,
-- -- {key},str,"{key} can be any of the component of the bus (str). Its value can be any that can be converted to pandas.Series using getattr(). For example one of {min, max, sum}.","Aggregates the component according to the given strategy. For example, if sum, then all values within each cluster are summed to represent the new bus."

1 Unit Values Description
2 simplify_network
3 -- to_substations bool {'true','false'} Aggregates all nodes without power injection (positive or negative, i.e. demand or generation) to electrically closest ones
4 -- algorithm str One of {‘kmeans’, ‘hac’, ‘modularity‘}
5 -- feature str Str in the format ‘carrier1+carrier2+...+carrierN-X’, where CarrierI can be from {‘solar’, ‘onwind’, ‘offwind’, ‘ror’} and X is one of {‘cap’, ‘time’}.
6 -- exclude_carriers list List of Str like [ 'solar', 'onwind'] or empy list [] List of carriers which will not be aggregated. If empty, all carriers will be aggregated.
7 -- remove stubs bool true/false {'true','false'} Controls whether radial parts of the network should be recursively aggregated. Defaults to true.
8 -- remove_stubs_across_borders bool true/false {'true','false'} Controls whether radial parts of the network should be recursively aggregated across borders. Defaults to true.
9 cluster_network
10 -- algorithm str One of {‘kmeans’, ‘hac’}
11 -- feature str Str in the format ‘carrier1+carrier2+...+carrierN-X’, where CarrierI can be from {‘solar’, ‘onwind’, ‘offwind’, ‘ror’} and X is one of {‘cap’, ‘time’}.
12 -- exclude_carriers list List of Str like [ 'solar', 'onwind'] or empy list [] List of carriers which will not be aggregated. If empty, all carriers will be aggregated.
13 aggregation_strategies -- consider_efficiency_classes bool {'true','false'} Aggregated each carriers into the top 10-quantile (high), the bottom 90-quantile (low), and everything in between (medium).
14 -- generators aggregation_strategies
15 -- -- {key} -- generators str {key} can be any of the component of the generator (str). It’s value can be any that can be converted to pandas.Series using getattr(). For example one of {min, max, sum}. Aggregates the component according to the given strategy. For example, if sum, then all values within each cluster are summed to represent the new generator.
16 -- buses -- -- {key} str {key} can be any of the component of the generator (str). It’s value can be any that can be converted to pandas.Series using getattr(). For example one of {min, max, sum}. Aggregates the component according to the given strategy. For example, if sum, then all values within each cluster are summed to represent the new generator.
17 -- -- {key} -- buses str {key} can be any of the component of the bus (str). It’s value can be any that can be converted to pandas.Series using getattr(). For example one of {min, max, sum}. Aggregates the component according to the given strategy. For example, if sum, then all values within each cluster are summed to represent the new bus.
18 -- -- {key} str {key} can be any of the component of the bus (str). It’s value can be any that can be converted to pandas.Series using getattr(). For example one of {min, max, sum}. Aggregates the component according to the given strategy. For example, if sum, then all values within each cluster are summed to represent the new bus.

View File

@ -1,3 +1,7 @@
,Unit,Values,Description
{name},--,"string","For any carrier/technology overwrite attributes as listed below."
-- {attribute},--,"string or float","For any attribute, can specify a float or reference to a file path to a CSV file giving floats for each country (2-letter code)."
,Unit,Values,Description
{name},--,string,For any carrier/technology overwrite attributes as listed below.
-- {attribute},--,string or float,"For any attribute, can specify a float or reference to a file path to a CSV file giving floats for each country (2-letter code)."
unit_commitment ,bool,"{true, false}","Allow the overwrite of ramp_limit_up, ramp_limit_start_up, ramp_limit_shut_down, p_min_pu, min_up_time, min_down_time, and start_up_cost of conventional generators. Refer to the CSV file „unit_commitment.csv“."
dynamic_fuel_price ,bool,"{true, false}","Consider the monthly fluctuating fuel prices for each conventional generator. Refer to the CSV file ""data/validation/monthly_fuel_price.csv""."
nuclear ,,,
-- p_max_pu,--,string or float ,Specify a float or reference to a file path to a CSV file the maximum dispatch for nuclear power plants in each country (2-letter code)

1 Unit Values Description
2 {name} -- string For any carrier/technology overwrite attributes as listed below.
3 -- {attribute} -- string or float For any attribute, can specify a float or reference to a file path to a CSV file giving floats for each country (2-letter code).
4 unit_commitment bool {true, false} Allow the overwrite of ramp_limit_up, ramp_limit_start_up, ramp_limit_shut_down, p_min_pu, min_up_time, min_down_time, and start_up_cost of conventional generators. Refer to the CSV file „unit_commitment.csv“.
5 dynamic_fuel_price bool {true, false} Consider the monthly fluctuating fuel prices for each conventional generator. Refer to the CSV file "data/validation/monthly_fuel_price.csv".
6 nuclear
7 -- p_max_pu -- string or float Specify a float or reference to a file path to a CSV file the maximum dispatch for nuclear power plants in each country (2-letter code)

View File

@ -1,6 +1,7 @@
,Unit,Values,Description
cutout,--,"Must be 'europe-2013-era5'","Specifies the directory where the relevant weather data ist stored."
carriers,--,"Any subset of {'ror', 'PHS', 'hydro'}","Specifies the types of hydro power plants to build per-unit availability time series for. 'ror' stands for run-of-river plants, 'PHS' represents pumped-hydro storage, and 'hydro' stands for hydroelectric dams."
PHS_max_hours,h,float,"Maximum state of charge capacity of the pumped-hydro storage (PHS) in terms of hours at full output capacity ``p_nom``. Cf. `PyPSA documentation <https://pypsa.readthedocs.io/en/latest/components.html#storage-unit>`_."
hydro_max_hours,h,"Any of {float, 'energy_capacity_totals_by_country', 'estimate_by_large_installations'}","Maximum state of charge capacity of the pumped-hydro storage (PHS) in terms of hours at full output capacity ``p_nom`` or heuristically determined. Cf. `PyPSA documentation <https://pypsa.readthedocs.io/en/latest/components.html#storage-unit>`_."
clip_min_inflow,MW,float,"To avoid too small values in the inflow time series, values below this threshold are set to zero."
,Unit,Values,Description
cutout,--,Must be 'europe-2013-era5',Specifies the directory where the relevant weather data ist stored.
carriers,--,"Any subset of {'ror', 'PHS', 'hydro'}","Specifies the types of hydro power plants to build per-unit availability time series for. 'ror' stands for run-of-river plants, 'PHS' represents pumped-hydro storage, and 'hydro' stands for hydroelectric dams."
PHS_max_hours,h,float,Maximum state of charge capacity of the pumped-hydro storage (PHS) in terms of hours at full output capacity ``p_nom``. Cf. `PyPSA documentation <https://pypsa.readthedocs.io/en/latest/components.html#storage-unit>`_.
hydro_max_hours,h,"Any of {float, 'energy_capacity_totals_by_country', 'estimate_by_large_installations'}",Maximum state of charge capacity of the pumped-hydro storage (PHS) in terms of hours at full output capacity ``p_nom`` or heuristically determined. Cf. `PyPSA documentation <https://pypsa.readthedocs.io/en/latest/components.html#storage-unit>`_.
flatten_dispatch,bool or float,"{true, false, float}","Enables the flattening of the hydro-dispatch. If bool is specified, it represents the buffer value where the maximum hydro dispatch is equal to the average capacity factor plus the buffer with a maximum value of 1. If true is specified, the buffer value is 0.2. Otherwise, the maximum hydro dispatch remains equal to 1."
clip_min_inflow,MW,float,"To avoid too small values in the inflow time series, values below this threshold are set to zero."

1 Unit Values Description
2 cutout -- Must be 'europe-2013-era5' Specifies the directory where the relevant weather data ist stored.
3 carriers -- Any subset of {'ror', 'PHS', 'hydro'} Specifies the types of hydro power plants to build per-unit availability time series for. 'ror' stands for run-of-river plants, 'PHS' represents pumped-hydro storage, and 'hydro' stands for hydroelectric dams.
4 PHS_max_hours h float Maximum state of charge capacity of the pumped-hydro storage (PHS) in terms of hours at full output capacity ``p_nom``. Cf. `PyPSA documentation <https://pypsa.readthedocs.io/en/latest/components.html#storage-unit>`_.
5 hydro_max_hours h Any of {float, 'energy_capacity_totals_by_country', 'estimate_by_large_installations'} Maximum state of charge capacity of the pumped-hydro storage (PHS) in terms of hours at full output capacity ``p_nom`` or heuristically determined. Cf. `PyPSA documentation <https://pypsa.readthedocs.io/en/latest/components.html#storage-unit>`_.
6 clip_min_inflow flatten_dispatch MW bool or float float {true, false, float} To avoid too small values in the inflow time series, values below this threshold are set to zero. Enables the flattening of the hydro-dispatch. If bool is specified, it represents the buffer value where the maximum hydro dispatch is equal to the average capacity factor plus the buffer with a maximum value of 1. If true is specified, the buffer value is 0.2. Otherwise, the maximum hydro dispatch remains equal to 1.
7 clip_min_inflow MW float To avoid too small values in the inflow time series, values below this threshold are set to zero.

View File

@ -1,17 +1,19 @@
,Unit,Values,Description
options,,,
-- load_shedding,bool/float,"{'true','false', float}","Add generators with very high marginal cost to simulate load shedding and avoid problem infeasibilities. If load shedding is a float, it denotes the marginal cost in EUR/kWh."
-- transmission_losses,int,"[0-9]","Add piecewise linear approximation of transmission losses based on n tangents. Defaults to 0, which means losses are ignored."
-- noisy_costs,bool,"{'true','false'}","Add random noise to marginal cost of generators by :math:`\mathcal{U}(0.009,0,011)` and capital cost of lines and links by :math:`\mathcal{U}(0.09,0,11)`."
-- min_iterations,--,int,"Minimum number of solving iterations in between which resistance and reactence (``x/r``) are updated for branches according to ``s_nom_opt`` of the previous run."
-- max_iterations,--,int,"Maximum number of solving iterations in between which resistance and reactence (``x/r``) are updated for branches according to ``s_nom_opt`` of the previous run."
-- nhours,--,int,"Specifies the :math:`n` first snapshots to take into account. Must be less than the total number of snapshots. Rather recommended only for debugging."
-- clip_p_max_pu,p.u.,float,"To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero."
-- skip_iterations,bool,"{'true','false'}","Skip iterating, do not update impedances of branches. Defaults to true."
-- track_iterations,bool,"{'true','false'}","Flag whether to store the intermediate branch capacities and objective function values are recorded for each iteration in ``network.lines['s_nom_opt_X']`` (where ``X`` labels the iteration)"
-- seed,--,int,"Random seed for increased deterministic behaviour."
solver,,,
-- name,--,"One of {'gurobi', 'cplex', 'cbc', 'glpk', 'ipopt'}; potentially more possible","Solver to use for optimisation problems in the workflow; e.g. clustering and linear optimal power flow."
-- options,--,"Key listed under ``solver_options``.","Link to specific parameter settings."
solver_options,,"dict","Dictionaries with solver-specific parameter settings."
mem,MB,"int","Estimated maximum memory requirement for solving networks."
,Unit,Values,Description
options,,,
-- clip_p_max_pu,p.u.,float,To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero.
-- load_shedding,bool/float,"{'true','false', float}","Add generators with very high marginal cost to simulate load shedding and avoid problem infeasibilities. If load shedding is a float, it denotes the marginal cost in EUR/kWh."
-- noisy_costs,bool,"{'true','false'}","Add random noise to marginal cost of generators by :math:`\mathcal{U}(0.009,0,011)` and capital cost of lines and links by :math:`\mathcal{U}(0.09,0,11)`."
-- skip_iterations,bool,"{'true','false'}","Skip iterating, do not update impedances of branches. Defaults to true."
-- rolling_horizon,bool,"{'true','false'}",
-- seed,--,int,Random seed for increased deterministic behaviour.
-- track_iterations,bool,"{'true','false'}",Flag whether to store the intermediate branch capacities and objective function values are recorded for each iteration in ``network.lines['s_nom_opt_X']`` (where ``X`` labels the iteration)
-- min_iterations,--,int,Minimum number of solving iterations in between which resistance and reactence (``x/r``) are updated for branches according to ``s_nom_opt`` of the previous run.
-- max_iterations,--,int,Maximum number of solving iterations in between which resistance and reactence (``x/r``) are updated for branches according to ``s_nom_opt`` of the previous run.
-- transmission_losses,int,[0-9],"Add piecewise linear approximation of transmission losses based on n tangents. Defaults to 0, which means losses are ignored."
-- linearized_unit_commitment,bool,"{'true','false'}",
-- horizon,--,int,
solver,,,
-- name,--,"One of {'gurobi', 'cplex', 'cbc', 'glpk', 'ipopt'}; potentially more possible",Solver to use for optimisation problems in the workflow; e.g. clustering and linear optimal power flow.
-- options,--,Key listed under ``solver_options``.,Link to specific parameter settings.
solver_options,,dict,Dictionaries with solver-specific parameter settings.
mem,MB,int,Estimated maximum memory requirement for solving networks.

1 Unit Values Description
2 options
3 -- load_shedding -- clip_p_max_pu bool/float p.u. {'true','false', float} float Add generators with very high marginal cost to simulate load shedding and avoid problem infeasibilities. If load shedding is a float, it denotes the marginal cost in EUR/kWh. To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero.
4 -- transmission_losses -- load_shedding int bool/float [0-9] {'true','false', float} Add piecewise linear approximation of transmission losses based on n tangents. Defaults to 0, which means losses are ignored. Add generators with very high marginal cost to simulate load shedding and avoid problem infeasibilities. If load shedding is a float, it denotes the marginal cost in EUR/kWh.
5 -- noisy_costs bool {'true','false'} Add random noise to marginal cost of generators by :math:`\mathcal{U}(0.009,0,011)` and capital cost of lines and links by :math:`\mathcal{U}(0.09,0,11)`.
6 -- min_iterations -- skip_iterations -- bool int {'true','false'} Minimum number of solving iterations in between which resistance and reactence (``x/r``) are updated for branches according to ``s_nom_opt`` of the previous run. Skip iterating, do not update impedances of branches. Defaults to true.
7 -- max_iterations -- rolling_horizon -- bool int {'true','false'} Maximum number of solving iterations in between which resistance and reactence (``x/r``) are updated for branches according to ``s_nom_opt`` of the previous run.
8 -- nhours -- seed -- int Specifies the :math:`n` first snapshots to take into account. Must be less than the total number of snapshots. Rather recommended only for debugging. Random seed for increased deterministic behaviour.
9 -- clip_p_max_pu -- track_iterations p.u. bool float {'true','false'} To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero. Flag whether to store the intermediate branch capacities and objective function values are recorded for each iteration in ``network.lines['s_nom_opt_X']`` (where ``X`` labels the iteration)
10 -- skip_iterations -- min_iterations bool -- {'true','false'} int Skip iterating, do not update impedances of branches. Defaults to true. Minimum number of solving iterations in between which resistance and reactence (``x/r``) are updated for branches according to ``s_nom_opt`` of the previous run.
11 -- track_iterations -- max_iterations bool -- {'true','false'} int Flag whether to store the intermediate branch capacities and objective function values are recorded for each iteration in ``network.lines['s_nom_opt_X']`` (where ``X`` labels the iteration) Maximum number of solving iterations in between which resistance and reactence (``x/r``) are updated for branches according to ``s_nom_opt`` of the previous run.
12 -- seed -- transmission_losses -- int int [0-9] Random seed for increased deterministic behaviour. Add piecewise linear approximation of transmission losses based on n tangents. Defaults to 0, which means losses are ignored.
13 solver -- linearized_unit_commitment bool {'true','false'}
14 -- name -- horizon -- One of {'gurobi', 'cplex', 'cbc', 'glpk', 'ipopt'}; potentially more possible int Solver to use for optimisation problems in the workflow; e.g. clustering and linear optimal power flow.
15 -- options solver -- Key listed under ``solver_options``. Link to specific parameter settings.
16 solver_options -- name -- dict One of {'gurobi', 'cplex', 'cbc', 'glpk', 'ipopt'}; potentially more possible Dictionaries with solver-specific parameter settings. Solver to use for optimisation problems in the workflow; e.g. clustering and linear optimal power flow.
17 mem -- options MB -- int Key listed under ``solver_options``. Estimated maximum memory requirement for solving networks. Link to specific parameter settings.
18 solver_options dict Dictionaries with solver-specific parameter settings.
19 mem MB int Estimated maximum memory requirement for solving networks.

View File

@ -1,6 +1,12 @@
,Unit,Values,Description
version,--,0.x.x,"Version of PyPSA-Eur. Descriptive only."
tutorial,bool,"{true, false}","Switch to retrieve the tutorial data set instead of the full data set."
logging,,,
-- level,--,"Any of {'INFO', 'WARNING', 'ERROR'}","Restrict console outputs to all infos, warning or errors only"
-- format,--,"","Custom format for log messages. See `LogRecord <https://docs.python.org/3/library/logging.html#logging.LogRecord>`_ attributes."
,Unit,Values,Description
version,--,0.x.x,Version of PyPSA-Eur. Descriptive only.
tutorial,bool,"{true, false}",Switch to retrieve the tutorial data set instead of the full data set.
logging,,,
-- level,--,"Any of {'INFO', 'WARNING', 'ERROR'}","Restrict console outputs to all infos, warning or errors only"
-- format,--,,Custom format for log messages. See `LogRecord <https://docs.python.org/3/library/logging.html#logging.LogRecord>`_ attributes.
private,,,
-- keys,,,
-- -- entsoe_api,--,,Specify the ENTSO-E API key. See the guidelines to get `ENTSO-E API key <https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html>`_
remote,,,
-- ssh,--,,Specify the SSH of a remote cluster to be synchronized.
-- path,--,,Specify the file path within the remote cluster to be synchronized.

1 Unit Values Description
2 version -- 0.x.x Version of PyPSA-Eur. Descriptive only.
3 tutorial bool {true, false} Switch to retrieve the tutorial data set instead of the full data set.
4 logging
5 -- level -- Any of {'INFO', 'WARNING', 'ERROR'} Restrict console outputs to all infos, warning or errors only
6 -- format -- Custom format for log messages. See `LogRecord <https://docs.python.org/3/library/logging.html#logging.LogRecord>`_ attributes.
7 private
8 -- keys
9 -- -- entsoe_api -- Specify the ENTSO-E API key. See the guidelines to get `ENTSO-E API key <https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html>`_
10 remote
11 -- ssh -- Specify the SSH of a remote cluster to be synchronized.
12 -- path -- Specify the file path within the remote cluster to be synchronized.

View File

@ -16,12 +16,13 @@ PyPSA-Eur has several configuration options which are documented in this section
Top-level configuration
=======================
"Private" refers to local, machine-specific settings or data meant for personal use, not to be shared. "Remote" indicates the address of a server used for data exchange, often for clusters and data pushing/pulling.
.. literalinclude:: ../config/config.default.yaml
:language: yaml
:start-at: version:
:end-before: # docs
.. csv-table::
:header-rows: 1
:widths: 22,7,22,33

File diff suppressed because one or more lines are too long

View File

@ -40,18 +40,17 @@ color_country = {
"LV": "#f3758d",
"ME": "#f37685",
"MK": "#f37b7c",
"NL": "#f28774",
"NO": "#f1976b",
"PL": "#efaa63",
"PT": "#ebb160",
"RO": "#e6c260",
"RS": "#e2d75e",
"SE": "#dedc5b",
"SI": "#d9e35a",
"SK": "#d3e75a",
"NL": "#FF6666",
"NO": "#FF3333",
"PL": "#eb0000",
"PT": "#d70000",
"RO": "#c00000",
"RS": "#a50000",
"SE": "#8a0000",
"SI": "#6f0000",
"SK": "#550000",
}
def sort_one_country(country, df):
indices = [link for link in df.columns if country in link]
df_country = df[indices].copy()
@ -142,7 +141,6 @@ def cross_border_bar(countries, data):
else:
title = "Optimized"
color = [color_country[link[5:]] for link in df_country.columns] + color
df_positive_new = pd.DataFrame(data=df_pos.sum()).T.rename(
{0: title + " " + cc.convert(country, to="name_short")}
)
@ -155,6 +153,8 @@ def cross_border_bar(countries, data):
order = order + 1
color = [color_country[link[5:]] for link in df_positive.columns]
fig, ax = plt.subplots(figsize=(15, 60))
df_positive.plot.barh(ax=ax, stacked=True, color=color, zorder=2)