qubed-meteo 0.4.11__tar.gz

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.
Files changed (52) hide show
  1. qubed_meteo-0.4.11/Cargo.toml +14 -0
  2. qubed_meteo-0.4.11/PKG-INFO +8 -0
  3. qubed_meteo-0.4.11/pyproject.toml +18 -0
  4. qubed_meteo-0.4.11/qubed/Cargo.toml +28 -0
  5. qubed_meteo-0.4.11/qubed/README.md +42 -0
  6. qubed_meteo-0.4.11/qubed/benches/bench_roaring.rs +316 -0
  7. qubed_meteo-0.4.11/qubed/notes.md +44 -0
  8. qubed_meteo-0.4.11/qubed/src/compress.rs +469 -0
  9. qubed_meteo-0.4.11/qubed/src/coordinates/datetime.rs +282 -0
  10. qubed_meteo-0.4.11/qubed/src/coordinates/floats.rs +324 -0
  11. qubed_meteo-0.4.11/qubed/src/coordinates/integers.rs +190 -0
  12. qubed_meteo-0.4.11/qubed/src/coordinates/mod.rs +903 -0
  13. qubed_meteo-0.4.11/qubed/src/coordinates/ops.rs +292 -0
  14. qubed_meteo-0.4.11/qubed/src/coordinates/setops.rs +0 -0
  15. qubed_meteo-0.4.11/qubed/src/coordinates/strings.rs +175 -0
  16. qubed_meteo-0.4.11/qubed/src/datacube.rs +459 -0
  17. qubed_meteo-0.4.11/qubed/src/difference.rs +251 -0
  18. qubed_meteo-0.4.11/qubed/src/lib.rs +17 -0
  19. qubed_meteo-0.4.11/qubed/src/merge.rs +357 -0
  20. qubed_meteo-0.4.11/qubed/src/metadata.rs +439 -0
  21. qubed_meteo-0.4.11/qubed/src/qube.rs +1618 -0
  22. qubed_meteo-0.4.11/qubed/src/select.rs +569 -0
  23. qubed_meteo-0.4.11/qubed/src/serde/ascii.rs +228 -0
  24. qubed_meteo-0.4.11/qubed/src/serde/json.rs +761 -0
  25. qubed_meteo-0.4.11/qubed/src/serde/mod.rs +2 -0
  26. qubed_meteo-0.4.11/qubed/src/utils/mod.rs +1 -0
  27. qubed_meteo-0.4.11/qubed/src/utils/tiny_ordered_set.rs +365 -0
  28. qubed_meteo-0.4.11/qubed/src/view.rs +138 -0
  29. qubed_meteo-0.4.11/qubed/tests/tdd.rs +99 -0
  30. qubed_meteo-0.4.11/qubed/tests/test_compression.rs +46 -0
  31. qubed_meteo-0.4.11/qubed/tests/test_difference.rs +647 -0
  32. qubed_meteo-0.4.11/qubed/tests/test_hash.rs +193 -0
  33. qubed_meteo-0.4.11/qubed/tests/test_metadata_compress_merge.rs +1525 -0
  34. qubed_meteo-0.4.11/qubed/tests/test_union.rs +142 -0
  35. qubed_meteo-0.4.11/qubed_meteo/Cargo.lock +1701 -0
  36. qubed_meteo-0.4.11/qubed_meteo/Cargo.toml +11 -0
  37. qubed_meteo-0.4.11/qubed_meteo/py_qubed_meteo/Cargo.toml +15 -0
  38. qubed_meteo-0.4.11/qubed_meteo/py_qubed_meteo/src/lib.rs +83 -0
  39. qubed_meteo-0.4.11/qubed_meteo/py_qubed_meteo/tests/test_api.py +58 -0
  40. qubed_meteo-0.4.11/qubed_meteo/py_qubed_meteo/tests/test_qubed_meteo_api.py +21 -0
  41. qubed_meteo-0.4.11/qubed_meteo/qubed_meteo/Cargo.toml +36 -0
  42. qubed_meteo-0.4.11/qubed_meteo/qubed_meteo/bin/fdb_db_reader.rs +53 -0
  43. qubed_meteo-0.4.11/qubed_meteo/qubed_meteo/examples/read_from_dss_constraints.rs +138 -0
  44. qubed_meteo-0.4.11/qubed_meteo/qubed_meteo/examples/read_from_fdb_list.rs +50 -0
  45. qubed_meteo-0.4.11/qubed_meteo/qubed_meteo/examples/read_from_mars_list.rs +28 -0
  46. qubed_meteo-0.4.11/qubed_meteo/qubed_meteo/src/adapters/fdb.rs +137 -0
  47. qubed_meteo-0.4.11/qubed_meteo/qubed_meteo/src/adapters/from_constraints.rs +85 -0
  48. qubed_meteo-0.4.11/qubed_meteo/qubed_meteo/src/adapters/mars_list.rs +246 -0
  49. qubed_meteo-0.4.11/qubed_meteo/qubed_meteo/src/adapters/mod.rs +7 -0
  50. qubed_meteo-0.4.11/qubed_meteo/qubed_meteo/src/adapters/opendata.rs +312 -0
  51. qubed_meteo-0.4.11/qubed_meteo/qubed_meteo/src/adapters/to_constraints.rs +206 -0
  52. qubed_meteo-0.4.11/qubed_meteo/qubed_meteo/src/lib.rs +1 -0
@@ -0,0 +1,14 @@
1
+ [workspace]
2
+ resolver = "3"
3
+
4
+ members = [
5
+ "qubed",
6
+ "py_qubed",
7
+ ]
8
+
9
+ exclude = [
10
+ "qubed_meteo",
11
+ ]
12
+
13
+ [workspace.package]
14
+ version = "0.4.11"
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: qubed-meteo
3
+ Version: 0.4.11
4
+ Classifier: Programming Language :: Python :: 3
5
+ Classifier: Programming Language :: Rust
6
+ Classifier: Programming Language :: Python :: Implementation :: CPython
7
+ Summary: Python bindings for meteorological adapters built on qubed
8
+ Requires-Python: >=3.8
@@ -0,0 +1,18 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.7,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "qubed-meteo"
7
+ dynamic = ["version"]
8
+ description = "Python bindings for meteorological adapters built on qubed"
9
+ requires-python = ">=3.8"
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "Programming Language :: Rust",
13
+ "Programming Language :: Python :: Implementation :: CPython",
14
+ ]
15
+
16
+ [tool.maturin]
17
+ features = []
18
+ manifest-path = "qubed_meteo/py_qubed_meteo/Cargo.toml"
@@ -0,0 +1,28 @@
1
+ [package]
2
+ name = "qubed"
3
+ version.workspace = true
4
+ edition = "2024"
5
+ description = "A new data structure to efficiently represent complex data spaces."
6
+ license = "Apache-2.0"
7
+ readme = "README.md"
8
+ repository = "https://github.com/ecmwf/qubed"
9
+ homepage = "https://github.com/ecmwf/qubed"
10
+ documentation = "https://ecmwf.github.io/qubed/"
11
+
12
+
13
+ [dependencies]
14
+ arrayvec = "0.7.6"
15
+ itertools = "0.14.0"
16
+ lasso = "0.7.3"
17
+ serde_json = "1.0.145"
18
+ serde_yaml = "0.9.34"
19
+ slotmap = "1.0.7"
20
+ smallbitvec = "2.6.0"
21
+ tiny-str = "0.10.0"
22
+ tiny-vec = "0.10.0"
23
+ chrono = "0.4"
24
+ rayon = "1.7"
25
+
26
+ [lib]
27
+ path = "src/lib.rs"
28
+ crate-type = ["rlib"]
@@ -0,0 +1,42 @@
1
+ # <p align="center"><img src="https://raw.githubusercontent.com/ecmwf/qubed/refs/heads/main/docs/banner.svg" width="1000"></p>
2
+ <p align="center">
3
+ <a href="https://github.com/ecmwf/codex/raw/refs/heads/main/Project%20Maturity#emerging">
4
+ <img src="https://github.com/ecmwf/codex/raw/refs/heads/main/Project%20Maturity/emerging_badge.svg" alt="Project Maturity">
5
+ </a>
6
+ <a href="https://ecmwf.github.io/qubed/">
7
+ <img src="https://img.shields.io/badge/Documentation-GitHub%20Pages-blue" alt="Documentation" />
8
+ </a>
9
+ <a href="https://pypi.org/project/qubed/"><img src="https://img.shields.io/pypi/v/qubed.svg" alt='PyPi'></a>
10
+ </p>
11
+
12
+ Qubed provides a data structure primitive for working with trees of Datacubes. If a normal tree looks like this:
13
+ ```
14
+ root
15
+ ├── class=od
16
+ │ ├── expver=0001
17
+ │ │ ├── param=1
18
+ │ │ └── param=2
19
+ │ └── expver=0002
20
+ │ ├── param=1
21
+ │ └── param=2
22
+ └── class=rd
23
+ ├── expver=0001
24
+ │ ├── param=1
25
+ │ ├── param=2
26
+ │ └── param=3
27
+ └── expver=0002
28
+ ├── param=1
29
+ └── param=2
30
+ ```
31
+
32
+ A compressed view of the same set would be:
33
+ ```
34
+ root
35
+ ├── class=od, expver=0001/0002, param=1/2
36
+ └── class=rd
37
+ ├── expver=0001, param=1/2/3
38
+ └── expver=0002, param=1/2
39
+ ```
40
+
41
+ Qubed provides all the algorithms on this data structure you would expect, such as unions, compression, search, filtering etc.
42
+
@@ -0,0 +1,316 @@
1
+ // use roaring::bitmap::RoaringBitmap;
2
+
3
+ // #[test]
4
+ // pub fn benchmark_roaring_vs_sorted_array() {
5
+ // // let mut data1 = vec![
6
+ // // 0, 8, 9, 10, 31, 33, 34, 39, 40, 41, 42, 49, 60, 75, 76, 78, 79, 129, 130, 131, 132,
7
+ // // 133, 134, 135, 136, 137, 139, 141, 142, 144, 146, 147, 148, 151, 157, 159, 164, 165,
8
+ // // 166, 167, 168, 169, 170, 172, 175, 176, 177, 178, 179, 180, 181, 182, 183, 186, 187,
9
+ // // 188, 195, 196, 201, 202, 205, 207, 208, 209, 210, 211, 212, 228, 235, 236, 246, 247,
10
+ // // 248, 3008, 3020, 3062, 3073, 3074, 3075, 140221, 140229, 140230, 140231, 140232,
11
+ // // 174096, 228002, 228021, 228023, 228024, 228029, 228038, 228050, 228051, 228058, 228089,
12
+ // // 228090, 228129, 228141, 228143, 228144, 228164, 228216, 228218, 228219, 228221, 228228,
13
+ // // 228235, 228236, 228246, 228247, 228251, 231001, 231002, 231040, 231041, 231044, 231045,
14
+ // // 231046, 231047, 231048, 231049, 231057, 231058, 231067, 231070, 235015, 235017, 235018,
15
+ // // 235019, 235020, 235021, 235031, 235033, 235034, 235035, 235036, 235037, 235038, 235039,
16
+ // // 235040, 235041, 235042, 235043, 235049, 235050, 235051, 235052, 235053, 235055, 235071,
17
+ // // 237120, 238105, 238382, 260015, 260028, 260046, 260048, 260058, 260109, 260155, 260199,
18
+ // // 260238, 260242, 260259, 260318, 260320, 260360, 260646, 260647, 260654, 260655, 261002,
19
+ // // 263000, 263001, 263002, 263003, 263004, 263008, 263009, 263021, 263022, 263100, 263101,
20
+ // // 263114, 263115, 263121, 263122, 263123, 263124, 263500, 263501, 263505, 263506, 263507,
21
+ // // ];
22
+
23
+ // let mut data1 = vec![
24
+ // 0, 8, 9, 10, 31, 33, 34, 39, 40, 41, 42, 49, 60, 75, 76, 78, 79, 129, 130, 131, 132, 133,
25
+ // 134, 135, 136, 137, 139, 141, 142, 144, 146, 147, 148, 151, 157, 159, 164, 165, 166, 167,
26
+ // 168, 169, 170, 172, 175, 176, 177, 178, 179, 180, 181, 182, 183, 186, 187, 188, 195, 196,
27
+ // 201, 202, 205, 207, 208, 209, 210, 211, 212, 228, 235, 236, 246, 247, 248,
28
+ // ];
29
+
30
+ // // Replicate 5x with offsets
31
+ // let original_len = data1.len() as u32;
32
+
33
+ // for i in 1..100 {
34
+ // let offset = i * 300;
35
+ // for i in 0..original_len {
36
+ // data1.push(data1[i as usize] + offset);
37
+ // }
38
+ // }
39
+
40
+ // data1.sort();
41
+ // data1.dedup();
42
+
43
+ // let mut data2: Vec<u32> = data1
44
+ // .iter()
45
+ // .step_by(2)
46
+ // .copied()
47
+ // .chain(vec![1, 2, 3, 4, 5, 6, 7].into_iter())
48
+ // .collect();
49
+ // data2.sort();
50
+ // data2.dedup();
51
+
52
+ // // println!("Data 1: {:?}", data1);
53
+ // // println!("Data 2: {:?}", data2);
54
+
55
+ // // === Setup Roaring Bitmaps ===
56
+ // let mut bitmap1 = RoaringBitmap::new();
57
+ // for &val in &data1 {
58
+ // bitmap1.insert(val);
59
+ // }
60
+
61
+ // let mut bitmap2 = RoaringBitmap::new();
62
+ // for &val in &data2 {
63
+ // bitmap2.insert(val);
64
+ // }
65
+
66
+ // println!("=== Data Setup ===");
67
+ // println!("Data1 size: {} elements", data1.len());
68
+ // println!("Data2 size: {} elements", data2.len());
69
+ // println!("Bitmap1 cardinality: {}", bitmap1.len());
70
+ // println!("Bitmap2 cardinality: {}", bitmap2.len());
71
+ // println!();
72
+
73
+ // // Helper function for sorted array intersection using two pointers
74
+ // fn array_intersection(a: &[u32], b: &[u32]) -> Vec<u32> {
75
+ // let mut result = Vec::new();
76
+ // let mut i = 0;
77
+ // let mut j = 0;
78
+
79
+ // while i < a.len() && j < b.len() {
80
+ // if a[i] == b[j] {
81
+ // result.push(a[i]);
82
+ // i += 1;
83
+ // j += 1;
84
+ // } else if a[i] < b[j] {
85
+ // i += 1;
86
+ // } else {
87
+ // j += 1;
88
+ // }
89
+ // }
90
+ // result
91
+ // }
92
+
93
+ // // Helper function for sorted array union
94
+ // fn array_union(a: &[u32], b: &[u32]) -> Vec<u32> {
95
+ // let mut result = Vec::new();
96
+ // let mut i = 0;
97
+ // let mut j = 0;
98
+
99
+ // while i < a.len() && j < b.len() {
100
+ // if a[i] == b[j] {
101
+ // result.push(a[i]);
102
+ // i += 1;
103
+ // j += 1;
104
+ // } else if a[i] < b[j] {
105
+ // result.push(a[i]);
106
+ // i += 1;
107
+ // } else {
108
+ // result.push(b[j]);
109
+ // j += 1;
110
+ // }
111
+ // }
112
+ // result.extend_from_slice(&a[i..]);
113
+ // result.extend_from_slice(&b[j..]);
114
+ // result
115
+ // }
116
+
117
+ // // Helper function for sorted array difference
118
+ // fn array_difference(a: &[u32], b: &[u32]) -> Vec<u32> {
119
+ // let mut result = Vec::new();
120
+ // let mut i = 0;
121
+ // let mut j = 0;
122
+
123
+ // while i < a.len() {
124
+ // if j >= b.len() {
125
+ // result.extend_from_slice(&a[i..]);
126
+ // break;
127
+ // }
128
+
129
+ // if a[i] == b[j] {
130
+ // i += 1;
131
+ // j += 1;
132
+ // } else if a[i] < b[j] {
133
+ // result.push(a[i]);
134
+ // i += 1;
135
+ // } else {
136
+ // j += 1;
137
+ // }
138
+ // }
139
+ // result
140
+ // }
141
+
142
+ // // Helper function for sorted array symmetric difference
143
+ // fn array_sym_diff(a: &[u32], b: &[u32]) -> Vec<u32> {
144
+ // let mut result = Vec::new();
145
+ // let mut i = 0;
146
+ // let mut j = 0;
147
+
148
+ // while i < a.len() && j < b.len() {
149
+ // if a[i] == b[j] {
150
+ // i += 1;
151
+ // j += 1;
152
+ // } else if a[i] < b[j] {
153
+ // result.push(a[i]);
154
+ // i += 1;
155
+ // } else {
156
+ // result.push(b[j]);
157
+ // j += 1;
158
+ // }
159
+ // }
160
+ // result.extend_from_slice(&a[i..]);
161
+ // result.extend_from_slice(&b[j..]);
162
+ // result
163
+ // }
164
+
165
+ // println!("=== MEMORY ===");
166
+ // println!(
167
+ // "Roaring bitmap1 serialized size: ~{} bytes",
168
+ // bitmap1.serialized_size()
169
+ // );
170
+ // println!(
171
+ // "Sorted array1 size: {} bytes",
172
+ // data1.len() * std::mem::size_of::<u32>()
173
+ // );
174
+
175
+ // println!("=== INTERSECTION ===");
176
+ // // Roaring intersection
177
+ // let start = std::time::Instant::now();
178
+ // let mut roaring_result = None;
179
+ // for _ in 0..10000 {
180
+ // roaring_result = Some(&bitmap1 & &bitmap2);
181
+ // }
182
+ // let roaring_time = start.elapsed();
183
+ // let roaring_intersection = roaring_result.unwrap();
184
+ // println!(
185
+ // "Roaring (10k iterations): {:?}, result cardinality: {}",
186
+ // roaring_time,
187
+ // roaring_intersection.len()
188
+ // );
189
+
190
+ // // Array intersection
191
+ // let start = std::time::Instant::now();
192
+ // let mut array_result = Vec::new();
193
+ // for _ in 0..10000 {
194
+ // array_result = array_intersection(&data1, &data2);
195
+ // }
196
+ // let array_time = start.elapsed();
197
+ // println!(
198
+ // "Sorted Array (10k iterations): {:?}, result cardinality: {}",
199
+ // array_time,
200
+ // array_result.len()
201
+ // );
202
+ // println!(
203
+ // "Roaring is {:.2}x faster",
204
+ // array_time.as_secs_f64() / roaring_time.as_secs_f64()
205
+ // );
206
+ // println!();
207
+
208
+ // println!("=== UNION ===");
209
+ // // Roaring union
210
+ // let start = std::time::Instant::now();
211
+ // let mut roaring_result = None;
212
+ // for _ in 0..10000 {
213
+ // roaring_result = Some(&bitmap1 | &bitmap2);
214
+ // }
215
+ // let roaring_time = start.elapsed();
216
+ // let roaring_union = roaring_result.unwrap();
217
+ // println!(
218
+ // "Roaring (10k iterations): {:?}, result cardinality: {}",
219
+ // roaring_time,
220
+ // roaring_union.len()
221
+ // );
222
+
223
+ // // Array union
224
+ // let start = std::time::Instant::now();
225
+ // let mut array_result = Vec::new();
226
+ // for _ in 0..10000 {
227
+ // array_result = array_union(&data1, &data2);
228
+ // }
229
+ // let array_time = start.elapsed();
230
+ // println!(
231
+ // "Sorted Array (10k iterations): {:?}, result cardinality: {}",
232
+ // array_time,
233
+ // array_result.len()
234
+ // );
235
+ // println!(
236
+ // "Roaring is {:.2}x faster",
237
+ // array_time.as_secs_f64() / roaring_time.as_secs_f64()
238
+ // );
239
+ // println!();
240
+
241
+ // println!("=== DIFFERENCE ===");
242
+ // // Roaring difference
243
+ // let start = std::time::Instant::now();
244
+ // let mut roaring_result = None;
245
+ // for _ in 0..10000 {
246
+ // roaring_result = Some(&bitmap1 - &bitmap2);
247
+ // }
248
+ // let roaring_time = start.elapsed();
249
+ // let roaring_difference = roaring_result.unwrap();
250
+ // println!(
251
+ // "Roaring (10k iterations): {:?}, result cardinality: {}",
252
+ // roaring_time,
253
+ // roaring_difference.len()
254
+ // );
255
+
256
+ // // Array difference
257
+ // let start = std::time::Instant::now();
258
+ // let mut array_result = Vec::new();
259
+ // for _ in 0..10000 {
260
+ // array_result = array_difference(&data1, &data2);
261
+ // }
262
+ // let array_time = start.elapsed();
263
+ // println!(
264
+ // "Sorted Array (10k iterations): {:?}, result cardinality: {}",
265
+ // array_time,
266
+ // array_result.len()
267
+ // );
268
+ // println!(
269
+ // "Roaring is {:.2}x faster",
270
+ // array_time.as_secs_f64() / roaring_time.as_secs_f64()
271
+ // );
272
+ // println!();
273
+
274
+ // println!("=== SYMMETRIC DIFFERENCE ===");
275
+ // // Roaring symmetric difference
276
+ // let start = std::time::Instant::now();
277
+ // let mut roaring_result = None;
278
+ // for _ in 0..10000 {
279
+ // roaring_result = Some(&bitmap1 ^ &bitmap2);
280
+ // }
281
+ // let roaring_time = start.elapsed();
282
+ // let roaring_sym_diff = roaring_result.unwrap();
283
+ // println!(
284
+ // "Roaring (10k iterations): {:?}, result cardinality: {}",
285
+ // roaring_time,
286
+ // roaring_sym_diff.len()
287
+ // );
288
+
289
+ // // Array symmetric difference
290
+ // let start = std::time::Instant::now();
291
+ // let mut array_result = Vec::new();
292
+ // for _ in 0..10000 {
293
+ // array_result = array_sym_diff(&data1, &data2);
294
+ // }
295
+ // let array_time = start.elapsed();
296
+ // println!(
297
+ // "Sorted Array (10k iterations): {:?}, result cardinality: {}",
298
+ // array_time,
299
+ // array_result.len()
300
+ // );
301
+ // println!(
302
+ // "Roaring is {:.2}x faster",
303
+ // array_time.as_secs_f64() / roaring_time.as_secs_f64()
304
+ // );
305
+ // println!();
306
+
307
+ // println!("=== MEMORY ===");
308
+ // println!(
309
+ // "Roaring bitmap1 serialized size: ~{} bytes",
310
+ // bitmap1.serialized_size()
311
+ // );
312
+ // println!(
313
+ // "Sorted array1 size: {} bytes",
314
+ // data1.len() * std::mem::size_of::<u32>()
315
+ // );
316
+ // }
@@ -0,0 +1,44 @@
1
+ # Arenas
2
+
3
+ Implementing the tree with arena storage makes sense because:
4
+ * it allows us to do multiple linked nodes (link to parent and child) without Arc/RefCell
5
+ * it keeps nodes relatively contiguous in memory, which improves cache performance
6
+ * the general creation/iteration direction of the tree is depth-first. If it was breadth-first then regular vec storage of children might be better.
7
+
8
+
9
+ The choice of arena is complicated.
10
+
11
+ https://donsz.nl/blog/arenas/
12
+
13
+ Those that provide a deref to the stored object reduce indirection, but can never move data around. Their data storage tends to be less efficient.
14
+
15
+ Its not clear if we need an arena which supports deletion.
16
+
17
+ Let's start with SlotMap and see how that goes.
18
+
19
+
20
+ Arguably we don't need a space-reclaiming arena, because the tree could be append-only. Removing nodes can leave them dangling, with a reallocation of the whole arena later, or we
21
+ forbid child deletion.
22
+
23
+ We probably do need a Drop supporting arena, because nodes may own resources, though they could just own indices to other linked arenas.
24
+
25
+ Cannot use any arena which uses deref or pointers, because we want the shared lifetime of the tree nodes.
26
+
27
+ Speed probably doesn't matter much. We are not inserting 1000's of nodes. Main bottleneck will be query comparisons.
28
+ * We should optimise to make the Values as cache-friendly as possible, but they should be anyway.
29
+
30
+
31
+ # Key Interning
32
+
33
+ Keys of the nodes are interned to reduce memory usage.
34
+
35
+ # Value storage
36
+
37
+ Nodes of the Qube store multiple values. The values are an enum of different types which can be compressed differently. Care should be taken to ensure set operations can be done efficiently on different value storage types.
38
+
39
+ # Selects and Unions
40
+
41
+ A union of two Qubes involves recursively iterating the incoming nodes and merging them into the existing tree. For each merge, we need to check all children of the existing node and the children of the incoming node. If the keys are the same, and the structural hash of each pair of children are the same, we do set operations on the values to create three kinds of children:
42
+ * children only in the existing node
43
+ * children only in the incoming node
44
+ * children in both nodes, which need to be merged recursively