Skip to content

Commit c3b86f7

Browse files
authored
Merge pull request #4 from glotzerlab/include-any
Evaluate any/all group include conditions.
2 parents b82e6f2 + baf7252 commit c3b86f7

File tree

16 files changed

+203
-102
lines changed

16 files changed

+203
-102
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,9 @@ Resolves #???
1313

1414
<!--- Please describe how you tested your changes. -->
1515

16-
## Change log
17-
18-
<!-- Propose a change log entry. -->
19-
```
20-
21-
```
22-
2316
## Checklist:
2417

2518
- [ ] I have reviewed the [**Contributor Guidelines**](https://github.com/glotzerlab/row/blob/trunk/doc/src/developers/contributing.md).
2619
- [ ] I agree with the terms of the [**Row Contributor Agreement**](https://github.com/glotzerlab/row/blob/trunk/ContributorAgreement.md).
2720
- [ ] My name is on the list of contributors (`doc/src/contributors.md`) in the pull request source branch.
21+
- [ ] I have added a change log entry to `doc/src/release-notes.md`.

.github/workflows/pre-commit.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88
pull_request:
99
push:
1010
branches:
11-
- "*"
11+
- trunk
1212

1313
workflow_dispatch:
1414

.github/workflows/release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ on:
1212

1313
push:
1414
branches:
15-
- "*"
15+
- trunk
1616
tags:
1717
- "*"
1818

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88
pull_request:
99
push:
1010
branches:
11-
- "*"
11+
- trunk
1212

1313
workflow_dispatch:
1414

DESIGN.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,4 +295,3 @@ status may take a long time, so it should display a progress bar.
295295
- **workspace**: The location on the file system that contains **directories**.
296296

297297
# TODO: logo
298-
# TODO: Expand include to apply any to the array and allow condition or all elements.

doc/src/developers/contributing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,6 @@ When applicable, update or write a new tutorial or how-to guide.
8888
Update the contributors documentation to name each developer that has contributed to the
8989
code.
9090

91-
### Propose a change log entry
91+
### Add a change log entry
9292

93-
Propose a short concise entry describing the change in the pull request description.
93+
Add a short concise entry describing the change in `doc/src/release-notes.md`.

doc/src/guide/howto/same.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
You can submit the same action to different groups and resources. To do so,
44
create multiple elements in the action array *with the same name*. Each must use
5-
[`group.include`](../../workflow/action/group.md#include) to select *non-overlapping
6-
subsets*. You can use [`action.from`](../../workflow/action/index.md#from) to copy all
7-
fields from one action and selectively override others.
5+
[`action.group.include`] to select *non-overlapping subsets*. You can use
6+
[`action.from`] to copy all fields from one action and selectively override others.
87

98
For example, this `workflow.toml` uses 4 processors on directories with small *N* and 8
109
those with a large *N*.
@@ -20,11 +19,16 @@ products = ["results.out"]
2019
walltime.per_submission = "12:00:00"
2120
processes.per_directory = 4
2221
[action.group]
23-
include = [["/N", "<=", "4096"]]
2422
maximum_size = 32
23+
[[action.group.include]]
24+
condition = ["/N", "<=", "4096"]
2525

2626
[[action]]
2727
from = "compute"
2828
resources.processes.per_directory = 8
29-
group.include = [["/N", ">", "4096"]]
29+
[[action.group.include]]
30+
condition = ["/N", ">", "4096"]
3031
```
32+
33+
[`action.group.include`]: ../../workflow/action/group.md#include
34+
[`action.from`]: ../../workflow/action/index.md#from

doc/src/guide/tutorial/group-workflow2.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ value_file = "value.json"
44
[[action]]
55
name = "process_point"
66
command = "echo {directory}"
7-
[action.group]
8-
include = [["/type", "==", "point"]]
7+
[[action.group.include]]
8+
condition = ["/type", "==", "point"]
99

1010
[[action]]
1111
name = "process_letter"
1212
command = "echo {directory}"
13-
[action.group]
14-
include = [["/type", "==", "letter"]]
13+
[[action.group.include]]
14+
condition = ["/type", "==", "letter"]

doc/src/guide/tutorial/group-workflow3.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ value_file = "value.json"
55
name = "process_point"
66
command = "echo {directory}"
77
[action.group]
8-
include = [["/type", "==", "point"]]
98
sort_by = ["/x"]
9+
[[action.group.include]]
10+
condition = ["/type", "==", "point"]
1011

1112
[[action]]
1213
name = "process_letter"
1314
command = "echo {directory}"
14-
[action.group]
15-
include = [["/type", "==", "letter"]]
15+
[[action.group.include]]
16+
condition = ["/type", "==", "letter"]

doc/src/guide/tutorial/group-workflow4.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ value_file = "value.json"
55
name = "process_point"
66
command = "echo {directory}"
77
[action.group]
8-
include = [["/type", "==", "point"]]
98
sort_by = ["/x"]
109
split_by_sort_key = true
10+
[[action.group.include]]
11+
condition = ["/type", "==", "point"]
1112

1213
[[action]]
1314
name = "process_letter"
1415
command = "echo {directory}"
15-
[action.group]
16-
include = [["/type", "==", "letter"]]
16+
[[action.group.include]]
17+
condition = ["/type", "==", "letter"]

doc/src/guide/tutorial/group-workflow5.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ value_file = "value.json"
55
name = "process_point"
66
command = "echo {directory}"
77
[action.group]
8-
include = [["/type", "==", "point"]]
98
sort_by = ["/x"]
109
maximum_size = 4
10+
[[action.group.include]]
11+
condition = ["/type", "==", "point"]
1112

1213
[[action]]
1314
name = "process_letter"
1415
command = "echo {directory}"
15-
[action.group]
16-
include = [["/type", "==", "letter"]]
16+
[[action.group.include]]
17+
condition = ["/type", "==", "letter"]

doc/src/guide/tutorial/group.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,15 @@ This workflow will apply the `process_point` action to the directories where
5353
`value/type == "point"` and the `process_letter` action to the directories where
5454
`value/type == "letter"`.
5555

56-
`include` is an array. Each element is a length 3 array with the contents: `[JSON
57-
pointer, operator, operand]`. Think of each element as an expression. The [*JSON
58-
pointer*](../concepts/json-pointers.md) is a string that reads a particular value
56+
`condition` is a length 3 array with the contents: `[JSON pointer, operator, operand]`.
57+
Think of each element as an expression. The
58+
[*JSON pointer*](../concepts/json-pointers.md) is a string that reads a particular value
5959
from the directory's **value**. The *operator* is a comparison operator: `"<"`, `"<="`,
6060
`"=="`, `">="`, or `">"`. The *operand* is the value to compare to. Together, these 3
6161
elements make a *condition*.
6262

63-
**Row** applies these *conditions* to all directories in the workspace. When all
64-
*conditions* are true, the directory is included in the action's **groups**.
63+
**Row** applies the *condition* to all directories in the workspace. When the
64+
*condition* is true, the directory is included in the action's **groups**.
6565

6666
> Note: This implies that every JSON pointer used in an `include` condition **MUST**
6767
> be present in every value file.

doc/src/workflow/action/group.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ that it submits.
66
Example:
77
```toml
88
[action.group]
9-
include = [["/subproject", "==", "project_one"]]
109
sort_by = ["/value"]
1110
split_by_sort_key = true
1211
maximum_size = 16
1312
submit_whole = true
1413
reverse_sort = true
14+
[[action.group.include]]
15+
condition = ["/subproject", "==", "project_one"]
1516
```
1617

1718
> Note: You may omit `[action.group]` entirely.
@@ -21,27 +22,36 @@ groups of directories included in a given action.
2122

2223
## include
2324

24-
`action.group.include`: **array** of **arrays** - Define a set of conditions that must
25-
all be true for a directory to be included in this group. Each condition is an **array**
26-
of three elements: The *JSON pointer*, *the operator*, and the *operand*. The [JSON
27-
pointer](../../guide/concepts/json-pointers.md) points to a specific element
28-
from the directory's value. The operator may be `"<"`, `"<="`, `"=="`, `">="`, or `">"`.
25+
`action.group.include`: **array** of **tables** - Define a set of selectors, *any* of
26+
which may be true for a directory to be included in this group.
27+
28+
Each selector is a **table** with only one of the following keys:
29+
* `condition`: An array of three elements: The *JSON pointer*, *the operator*, and the
30+
*operand*. The [JSON pointer](../../guide/concepts/json-pointers.md) points to a
31+
specific element from the directory's value. The operator may be `"<"`, `"<="`,
32+
`"=="`, `">="`, or `">"`.
33+
* `all`: Array of conditions (see above). All conditions must be true for this selector
34+
to be true.
2935

3036
For example, select all directories where a value is in the given range:
3137
```toml
32-
include = [["/value", ">", 0.2], ["/value", "<", 0.9]]
38+
[[action.group.include]]
39+
all = [["/value", ">", 0.2], ["/value", "<", 0.9]]
3340
```
3441
Choose directories where an array element is equal to a specific value:
3542
```toml
36-
include = [["/array/1", "==", 12]]
43+
[[action.group.include]]
44+
condition = ["/array/1", "==", 12]
3745
```
3846
Match against strings:
3947
```toml
40-
include = [["/map/name", "==", "string"]]
48+
[[action.group.include]]
49+
condition = ["/map/name", "==", "string"]
4150
```
4251
Compare by array:
4352
```toml
44-
include = [["/array", "==", [1, "string", 14.0]]]
53+
[[action.group.include]]
54+
condition = ["/array", "==", [1, "string", 14.0]
4555
```
4656

4757
Both operands **must** have the same data type. The JSON pointer must be present in the

doc/src/workflow/action/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ action. The name may be set by [from](#from).
3232
> the same name. All elements with the same name **must** have identical
3333
> [`products`](#products) and [`previous_actions`](#previous_actions). All elements
3434
> with the same name **must also** select non-intersecting subsets of directories with
35-
> [`group.include`](group.md#include).
35+
> [`action.group.include`](group.md#include).
3636
3737
## command
3838

src/project.rs

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::scheduler::bash::Bash;
1717
use crate::scheduler::slurm::Slurm;
1818
use crate::scheduler::Scheduler;
1919
use crate::state::State;
20-
use crate::workflow::{Action, Workflow};
20+
use crate::workflow::{Action, Selector, Workflow};
2121
use crate::{Error, MultiProgressContainer};
2222

2323
/// Encapsulate the workflow, state, and scheduler into a project.
@@ -184,23 +184,55 @@ impl Project {
184184

185185
'outer: for name in directories {
186186
if let Some(value) = self.state.values().get(&name) {
187-
for (include, comparison, expected) in action.group.include() {
188-
let actual = value
189-
.pointer(include)
190-
.ok_or_else(|| Error::JSONPointerNotFound(name.clone(), include.clone()))?;
191-
if !expr::evaluate_json_comparison(comparison, actual, expected).ok_or_else(
192-
|| {
193-
Error::CannotCompareInclude(
194-
actual.clone(),
195-
expected.clone(),
196-
name.clone(),
197-
)
198-
},
199-
)? {
200-
continue 'outer;
187+
if action.group.include().is_empty() {
188+
matching_directories.push(name);
189+
} else {
190+
for selector in action.group.include() {
191+
let result = match selector {
192+
Selector::Condition((include, comparison, expected)) => {
193+
let actual = value.pointer(include).ok_or_else(|| {
194+
Error::JSONPointerNotFound(name.clone(), include.clone())
195+
})?;
196+
197+
expr::evaluate_json_comparison(comparison, actual, expected)
198+
.ok_or_else(|| {
199+
Error::CannotCompareInclude(
200+
actual.clone(),
201+
expected.clone(),
202+
name.clone(),
203+
)
204+
})
205+
}
206+
207+
Selector::All(conditions) => {
208+
let mut matches = 0;
209+
for (include, comparison, expected) in conditions {
210+
let actual = value.pointer(include).ok_or_else(|| {
211+
Error::JSONPointerNotFound(name.clone(), include.clone())
212+
})?;
213+
214+
if expr::evaluate_json_comparison(comparison, actual, expected)
215+
.ok_or_else(|| {
216+
Error::CannotCompareInclude(
217+
actual.clone(),
218+
expected.clone(),
219+
name.clone(),
220+
)
221+
})?
222+
{
223+
matches += 1;
224+
}
225+
}
226+
Ok(matches == conditions.len())
227+
}
228+
};
229+
230+
if result? {
231+
matching_directories.push(name);
232+
continue 'outer;
233+
}
201234
}
202235
}
203-
matching_directories.push(name);
204236
} else {
205237
warn!("Directory '{}' not found in workspace.", name.display());
206238
}
@@ -424,7 +456,8 @@ products = ["one"]
424456
name = "two"
425457
command = "c"
426458
products = ["two"]
427-
group.include = [["/i", "<", {}]]
459+
[[action.group.include]]
460+
condition = ["/i", "<", {}]
428461
429462
[[action]]
430463
name = "three"
@@ -464,15 +497,43 @@ previous_actions = ["two"]
464497
all_directories[0..6]
465498
);
466499

500+
// Check all conditions.
467501
let mut action = project.workflow.action[1].clone();
468502
let include = action.group.include.as_mut().unwrap();
469-
include.push(("/i".into(), Comparison::GreaterThan, Value::from(4)));
503+
include.clear();
504+
include.push(Selector::All(vec![
505+
("/i".into(), Comparison::GreaterThan, Value::from(4)),
506+
("/i".into(), Comparison::LessThan, Value::from(6)),
507+
]));
470508
assert_eq!(
471509
project
472510
.find_matching_directories(&action, all_directories.clone())
473511
.unwrap(),
474512
vec![PathBuf::from("dir5")]
475513
);
514+
515+
// Check any conditions.
516+
let mut action = project.workflow.action[1].clone();
517+
let include = action.group.include.as_mut().unwrap();
518+
include.clear();
519+
include.push(Selector::Condition((
520+
"/i".into(),
521+
Comparison::LessThan,
522+
Value::from(1),
523+
)));
524+
525+
include.push(Selector::Condition((
526+
"/i".into(),
527+
Comparison::GreaterThan,
528+
Value::from(6),
529+
)));
530+
531+
assert_eq!(
532+
project
533+
.find_matching_directories(&action, all_directories.clone())
534+
.unwrap(),
535+
vec![PathBuf::from("dir0"), PathBuf::from("dir7")]
536+
);
476537
}
477538

478539
#[test]

0 commit comments

Comments
 (0)