Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

int parameters can now be formatted by %d #2431

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 41 additions & 14 deletions doc/src/cylc-user-guide/cug.tex
Original file line number Diff line number Diff line change
Expand Up @@ -4498,12 +4498,28 @@ \subsubsection{Parameter Expansion}
\begin{lstlisting}
[cylc]
[[parameters]]
# parameters: "ship", "buoy", "plane"
# default task suffixes: _ship, _buoy, _plane
obs = ship, buoy, plane
run = 1..5 # 1, 2, 3, 4, 5
idx = 1..9..2 # 1, 3, 5, 7, 9
i = 1..5..2, 10, 11..13 # 1, 3, 5, 10, 11, 12, 13
item = 0, 1, e, pi, i # "0", "1", "e", "pi", "i"
p = one, two, 3..5 # ERROR

# parameters: 1, 2, 3, 4, 5
# default task suffixes: _run1, _run2, _run3, _run4, _run5
run = 1..5

# parameters: 1, 3, 5, 7, 9
# default task suffixes: _idx1, _idx3, _idx5, _idx7, _idx9
idx = 1..9..2

# parameters: 1, 3, 5, 10, 11, 12, 13
# default task suffixes: _i01, _i03, _i05, _i10, _i11, _i12, _i13
i = 1..5..2, 10, 11..13

# parameters: "0", "1", "e", "pi", "i"
# default task suffixes: _0, _1, _e, _pi, _i
item = 0, 1, e, pi, i

# ERROR: mix strings with int range
p = one, two, 3..5
\end{lstlisting}
Then angle brackets denote use of these parameters throughout the suite
definition. For the values above, this parameterized name:
Expand Down Expand Up @@ -4604,13 +4620,24 @@ \subsubsection{Parameter Expansion}

\paragraph{Zero-Padded Integer Values}

Integer parameter values are zero-padded according to the size of their
largest value, so \lstinline@foo<p>@ for \lstinline@p = 9..10@ expands to
Integer parameter values are given a default template for generating task
suffixes that are zero-padded according to the size of their largest value.
For example, the default template for \lstinline@p = 9..10@ would be
\lstinline@_p%(p)02d@, so that \lstinline@foo<p>@ would become
\lstinline@foo_p09, foo_p10@.

To get thicker padding, prepend extra zeroes to the upper range value:
\lstinline@foo<p>@ for \lstinline@p = 9..010@ expands to
\lstinline@foo_p009, foo_p010@.
To get thicker padding and/or alternate suffixes, use a template. E.g.:

\begin{lstlisting}
[cylc]
[[parameters]]
i = 1..9
p = 3..14
[[parameter templates]]
i = _i%(i)02d # suffixes = _i01, _i02, ..., _i09
# A double-percent gives a literal percent character
p = %%p%(p)03d # suffixes = %p003, %p004, ..., %p013, %p014
\end{lstlisting}


\subsubsection{Passing Parameter Values To Tasks}
Expand Down Expand Up @@ -4658,11 +4685,11 @@ \subsubsection{Selecting Partial Parameter Ranges}
\begin{lstlisting}
[cylc]
[[parameters]]
run = 1..10 # 01, 02, ..., 10
runx = 1..03 # 01, 02, 03 (note `03' to get correct padding)
run = 1..10 # 1, 2, ..., 10
runx = 1..3 # 1, 2, 3
[[parameter templates]]
run = _R%(run)s
runx = _R%(runx)s
run = _R%(run)02d # _R01, _R02, ..., _R10
runx = _R%(runx)02d # _R01, _R02, _R03
[scheduling]
[[dependencies]]
graph = """model<run> => post<run>
Expand Down
13 changes: 6 additions & 7 deletions doc/src/cylc-user-guide/suiterc.tex
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,12 @@ \subsection{[cylc]}

\begin{myitemize}
\item {\em type:} a Python-style string template
\item {\em default} for an integer-valued parameter \lstinline=p=:
\lstinline=_p%(p)s= \\
\item {\em default} for integer parameters \lstinline=p=:
\lstinline=_p%(p)0Nd= \\
where N is the number of digits of the maximum integer value,
e.g.\ \lstinline=foo<run>= becomes \lstinline=foo_run3= for
\lstinline@run@ value \lstinline@3@.
\item {\em default} for a non integer-valued parameter \lstinline=p=:
\item {\em default} for non-integer parameters \lstinline=p=:
\lstinline=_%(p)s= \\
e.g.\ \lstinline=foo<run>= becomes \lstinline=foo_top= for
\lstinline@run@ value \lstinline@top@.
Expand All @@ -309,10 +310,8 @@ \subsection{[cylc]}
\end{myitemize}

Note that the values of a parameter named \lstinline=p= are substituted for
\lstinline=%(p)s=. The \lstinline=s= indicates a string value and should
be used even for integer-valued parameters, because cylc converts integer
parameter values to strings, with (depending on size) zero-padding. In
\lstinline=_run%(run)s= the first ``run'' is a string literal, and the second
\lstinline=%(p)s=.
In \lstinline=_run%(run)s= the first ``run'' is a string literal, and the second
gets substituted with each value of the parameter.

\subsubsection[{[[}events{]]}]{[cylc] \textrightarrow [[events]]}
Expand Down
4 changes: 1 addition & 3 deletions lib/cylc/cfgspec/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,7 @@ def _coerce_parameter_list(value, keys, _):
not str(item).isdigit() for item in items):
return items
else:
items = [int(item) for item in items]
n_digits = len(str(max(items)))
return [str(item).zfill(n_digits) for item in sorted(items)]
return [int(item) for item in items]

coercers['cycletime'] = _coerce_cycletime
coercers['cycletime_format'] = _coerce_cycletime_format
Expand Down
15 changes: 6 additions & 9 deletions lib/cylc/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,16 +248,13 @@ def __init__(self, suite, fpath, template_vars=None,

# Set default parameter expansion templates if necessary.
for pname, pvalues in parameter_values.items():
if pname not in parameter_templates:
try:
[int(i) for i in pvalues]
except ValueError:
# Don't prefix string values with the parameter name.
parameter_templates[pname] = "_%(" + pname + ")s"
if pvalues and pname not in parameter_templates:
if all(isinstance(pvalue, int) for pvalue in pvalues):
parameter_templates[pname] = r'_%s%%(%s)0%dd' % (
pname, pname, len(str(max(pvalues))))
else:
# All int values, prefix values with the parameter name.
parameter_templates[pname] = (
"_" + pname + "%(" + pname + ")s")
# Don't prefix string values with the parameter name.
parameter_templates[pname] = r'_%%(%s)s' % pname

# Expand parameters in 'special task' lists.
if 'special tasks' in self.cfg['scheduling']:
Expand Down
103 changes: 50 additions & 53 deletions lib/cylc/param_expand.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import re
import unittest
from copy import copy
from task_id import TaskID
from parsec.OrderedDict import OrderedDictWithDefaults

"""Parameter expansion for runtime namespace names and graph strings.

Uses recursion to achieve nested looping over any number of parameters. In its
Expand Down Expand Up @@ -65,6 +58,12 @@ def expand(template, params, results, values=None):
#------------------------------------------------------------------------------
"""

import re
import unittest

from cylc.task_id import TaskID
from parsec.OrderedDict import OrderedDictWithDefaults

# To split runtime heading name lists.
REC_NAMES = re.compile(r'(?:[^,<]|\<[^>]*\>)+')
# To extract 'name', '<parameters>', and 'other' from
Expand Down Expand Up @@ -154,13 +153,10 @@ def expand(self, runtime_heading):
elif sval.startswith('='):
# Check that specific parameter values exist.
val = sval[1:].strip()
# Pad integer values here.
try:
int(val)
nval = int(val)
except ValueError:
nval = val
else:
nval = val.zfill(len(self.param_cfg[pname][0]))
if not item_in_iterable(nval, self.param_cfg[pname]):
raise ParamExpandError(
"ERROR, parameter %s out of range: %s" % (
Expand Down Expand Up @@ -196,7 +192,7 @@ def _expand_name(self, str_tmpl, param_list, results, spec_vals=None):
spec_vals = {}
if not param_list:
# Inner loop.
current_values = copy(spec_vals)
current_values = dict(spec_vals)
try:
results.append((str_tmpl % current_values, current_values))
except KeyError as exc:
Expand Down Expand Up @@ -236,6 +232,9 @@ def replace_params(self, name_in, param_values, origin):
class GraphExpander(object):
"""Handle parameter expansion of graph string lines."""

_REMOVE = -32768
_REMOVE_REC = re.compile(r'^.*' + str(_REMOVE) + r'.*?=>\s*?')

def __init__(self, parameters):
"""Initialize the parameterized task name expander.

Expand Down Expand Up @@ -290,16 +289,10 @@ def expand(self, line):
elif offs.startswith('='):
# Check that specific parameter values exist.
val = offs[1:]
# Pad integer values here.
try:
int(val)
nval = int(val)
except ValueError:
nval = val
else:
nval = val.zfill(len(self.param_cfg[pname][0]))
if nval != val:
line = re.sub(item,
'%s=%s' % (pname, nval), line)
if not item_in_iterable(nval, self.param_cfg[pname]):
raise ParamExpandError(
"ERROR, parameter %s out of range: %s" % (
Expand Down Expand Up @@ -332,14 +325,18 @@ def _expand_graph(self, line, all_params,
param_values[pname] = values[pname]
elif offs.startswith('='):
# Specific value.
param_values[pname] = offs[1:]
try:
# Template may require an integer
param_values[pname] = int(offs[1:])
except ValueError:
param_values[pname] = offs[1:]
else:
# Index offset.
plist = all_params[pname]
cur_idx = plist.index(values[pname])
off_idx = cur_idx + int(offs)
if off_idx < 0:
offval = "--<REMOVE>--"
offval = self._REMOVE
else:
offval = plist[off_idx]
param_values[pname] = offval
Expand All @@ -352,7 +349,7 @@ def _expand_graph(self, line, all_params,
'defined.' % str(exc.args[0]))
line = re.sub('<' + p_group + '>', repl, line)
# Remove out-of-range nodes to first arrow.
line = re.sub('^.*--<REMOVE>--.*?=>\s*?', '', line)
line = self._REMOVE_REC.sub('', line)
line_set.add(line)
else:
# Recurse through index ranges.
Expand All @@ -367,9 +364,9 @@ class TestParamExpand(unittest.TestCase):

def setUp(self):
"""Create some parameters and templates for use in tests."""
ivals = [str(i) for i in range(2)]
jvals = [str(j) for j in range(3)]
kvals = [str(k) for k in range(2)]
ivals = list(range(2))
jvals = list(range(3))
kvals = list(range(2))
params_map = {'i': ivals, 'j': jvals, 'k': kvals}
templates = {'i': '_i%(i)s',
'j': '_j%(j)s',
Expand All @@ -381,56 +378,56 @@ def test_name_one_param(self):
"""Test name expansion and returned value for a single parameter."""
self.assertEqual(
self.name_expander.expand('foo<j>'),
[('foo_j0', {'j': '0'}),
('foo_j1', {'j': '1'}),
('foo_j2', {'j': '2'})]
[('foo_j0', {'j': 0}),
('foo_j1', {'j': 1}),
('foo_j2', {'j': 2})]
)

def test_name_two_params(self):
"""Test name expansion and returned values for two parameters."""
self.assertEqual(
self.name_expander.expand('foo<i,j>'),
[('foo_i0_j0', {'i': '0', 'j': '0'}),
('foo_i0_j1', {'i': '0', 'j': '1'}),
('foo_i0_j2', {'i': '0', 'j': '2'}),
('foo_i1_j0', {'i': '1', 'j': '0'}),
('foo_i1_j1', {'i': '1', 'j': '1'}),
('foo_i1_j2', {'i': '1', 'j': '2'})]
[('foo_i0_j0', {'i': 0, 'j': 0}),
('foo_i0_j1', {'i': 0, 'j': 1}),
('foo_i0_j2', {'i': 0, 'j': 2}),
('foo_i1_j0', {'i': 1, 'j': 0}),
('foo_i1_j1', {'i': 1, 'j': 1}),
('foo_i1_j2', {'i': 1, 'j': 2})]
)

def test_name_two_names(self):
"""Test name expansion for two names."""
self.assertEqual(
self.name_expander.expand('foo<i>, bar<j>'),
[('foo_i0', {'i': '0'}),
('foo_i1', {'i': '1'}),
('bar_j0', {'j': '0'}),
('bar_j1', {'j': '1'}),
('bar_j2', {'j': '2'})]
[('foo_i0', {'i': 0}),
('foo_i1', {'i': 1}),
('bar_j0', {'j': 0}),
('bar_j1', {'j': 1}),
('bar_j2', {'j': 2})]
)

def test_name_specific_val_1(self):
"""Test singling out a specific value, in name expansion."""
self.assertEqual(
self.name_expander.expand('foo<i=0>'),
[('foo_i0', {'i': '0'})]
[('foo_i0', {'i': 0})]
)

def test_name_specific_val_2(self):
"""Test specific value in the first parameter of a pair."""
self.assertEqual(
self.name_expander.expand('foo<i=0,j>'),
[('foo_i0_j0', {'i': '0', 'j': '0'}),
('foo_i0_j1', {'i': '0', 'j': '1'}),
('foo_i0_j2', {'i': '0', 'j': '2'})]
[('foo_i0_j0', {'i': 0, 'j': 0}),
('foo_i0_j1', {'i': 0, 'j': 1}),
('foo_i0_j2', {'i': 0, 'j': 2})]
)

def test_name_specific_val_3(self):
"""Test specific value in the second parameter of a pair."""
self.assertEqual(
self.name_expander.expand('foo<i,j=1>'),
[('foo_i0_j1', {'i': '0', 'j': '1'}),
('foo_i1_j1', {'i': '1', 'j': '1'})]
[('foo_i0_j1', {'i': 0, 'j': 1}),
('foo_i1_j1', {'i': 1, 'j': 1})]
)

def test_name_fail_bare_value(self):
Expand Down Expand Up @@ -458,14 +455,14 @@ def test_name_multiple(self):
"""Test expansion of two names, with one and two parameters."""
self.assertEqual(
self.name_expander.expand('foo<i>, bar<i,j>'),
[('foo_i0', {'i': '0'}),
('foo_i1', {'i': '1'}),
('bar_i0_j0', {'i': '0', 'j': '0'}),
('bar_i0_j1', {'i': '0', 'j': '1'}),
('bar_i0_j2', {'i': '0', 'j': '2'}),
('bar_i1_j0', {'i': '1', 'j': '0'}),
('bar_i1_j1', {'i': '1', 'j': '1'}),
('bar_i1_j2', {'i': '1', 'j': '2'})]
[('foo_i0', {'i': 0}),
('foo_i1', {'i': 1}),
('bar_i0_j0', {'i': 0, 'j': 0}),
('bar_i0_j1', {'i': 0, 'j': 1}),
('bar_i0_j2', {'i': 0, 'j': 2}),
('bar_i1_j0', {'i': 1, 'j': 0}),
('bar_i1_j1', {'i': 1, 'j': 1}),
('bar_i1_j2', {'i': 1, 'j': 2})]
)

def test_graph_expand_1(self):
Expand Down
Loading