1use crate::littlewood_richardson::{lr_coefficient, Partition};
20use crate::namespace::{Capability, CapabilityId, Namespace};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum InterfaceDirection {
25 Output,
27 Input,
29}
30
31#[derive(Debug, Clone)]
36pub struct Interface {
37 pub capability_id: CapabilityId,
39 pub direction: InterfaceDirection,
41 pub codimension: usize,
43}
44
45impl Interface {
46 #[must_use]
48 pub fn new(
49 capability_id: CapabilityId,
50 direction: InterfaceDirection,
51 codimension: usize,
52 ) -> Self {
53 Self {
54 capability_id,
55 direction,
56 codimension,
57 }
58 }
59}
60
61#[derive(Debug, Clone)]
66pub struct ComposableNamespace {
67 pub namespace: Namespace,
69 pub interfaces: Vec<Interface>,
71}
72
73impl ComposableNamespace {
74 #[must_use]
76 pub fn new(namespace: Namespace) -> Self {
77 Self {
78 namespace,
79 interfaces: Vec::new(),
80 }
81 }
82
83 pub fn mark_output(&mut self, cap_id: &CapabilityId) -> Result<(), String> {
92 let codim = self
93 .namespace
94 .capabilities
95 .iter()
96 .find(|c| c.id == *cap_id)
97 .map(|c| c.codimension())
98 .ok_or_else(|| format!("Capability {} not found", cap_id))?;
99
100 self.interfaces.push(Interface::new(
101 cap_id.clone(),
102 InterfaceDirection::Output,
103 codim,
104 ));
105 Ok(())
106 }
107
108 pub fn mark_input(&mut self, cap_id: &CapabilityId) -> Result<(), String> {
117 let codim = self
118 .namespace
119 .capabilities
120 .iter()
121 .find(|c| c.id == *cap_id)
122 .map(|c| c.codimension())
123 .ok_or_else(|| format!("Capability {} not found", cap_id))?;
124
125 self.interfaces.push(Interface::new(
126 cap_id.clone(),
127 InterfaceDirection::Input,
128 codim,
129 ));
130 Ok(())
131 }
132
133 #[must_use]
141 pub fn outputs(&self) -> Vec<&Interface> {
142 self.interfaces
143 .iter()
144 .filter(|i| i.direction == InterfaceDirection::Output)
145 .collect()
146 }
147
148 #[must_use]
156 pub fn inputs(&self) -> Vec<&Interface> {
157 self.interfaces
158 .iter()
159 .filter(|i| i.direction == InterfaceDirection::Input)
160 .collect()
161 }
162
163 #[must_use]
172 pub fn effective_capability_count(&self) -> usize {
173 let glued = self.interfaces.len();
174 self.namespace.capabilities.len().saturating_sub(glued)
175 }
176}
177
178#[must_use]
191pub fn interfaces_compatible(output: &Interface, input: &Interface) -> bool {
192 output.direction == InterfaceDirection::Output
193 && input.direction == InterfaceDirection::Input
194 && output.codimension == input.codimension
195}
196
197pub fn compose_namespaces(
211 ns_a: &ComposableNamespace,
212 out_idx: usize,
213 ns_b: &ComposableNamespace,
214 in_idx: usize,
215) -> Result<ComposableNamespace, String> {
216 let outputs = ns_a.outputs();
217 let inputs = ns_b.inputs();
218
219 if out_idx >= outputs.len() {
220 return Err(format!(
221 "Output index {} out of range (have {})",
222 out_idx,
223 outputs.len()
224 ));
225 }
226 if in_idx >= inputs.len() {
227 return Err(format!(
228 "Input index {} out of range (have {})",
229 in_idx,
230 inputs.len()
231 ));
232 }
233
234 let output = outputs[out_idx];
235 let input = inputs[in_idx];
236
237 if !interfaces_compatible(output, input) {
238 return Err(format!(
239 "Interfaces not compatible: output codim={}, input codim={}",
240 output.codimension, input.codimension
241 ));
242 }
243
244 if ns_a.namespace.grassmannian != ns_b.namespace.grassmannian {
245 return Err("Namespaces must be on the same Grassmannian".to_string());
246 }
247
248 let glued_out_id = &output.capability_id;
250 let glued_in_id = &input.capability_id;
251
252 let grassmannian = ns_a.namespace.grassmannian;
254 let name = format!("{} ∘ {}", ns_a.namespace.name, ns_b.namespace.name);
255 let position = ns_a.namespace.position.clone();
256
257 let mut composed = Namespace::new(name, position);
258
259 for cap in &ns_a.namespace.capabilities {
260 if cap.id != *glued_out_id {
261 let new_cap = Capability::new(
263 cap.id.as_str(),
264 &cap.name,
265 cap.schubert_class.partition.clone(),
266 grassmannian,
267 )
268 .map_err(|e| format!("{:?}", e))?;
269 let _ = composed.grant(new_cap);
270 }
271 }
272
273 for cap in &ns_b.namespace.capabilities {
274 if cap.id != *glued_in_id {
275 let new_id = format!("{}_{}", ns_b.namespace.name, cap.id);
277 let new_cap = Capability::new(
278 &new_id,
279 &cap.name,
280 cap.schubert_class.partition.clone(),
281 grassmannian,
282 )
283 .map_err(|e| format!("{:?}", e))?;
284 let _ = composed.grant(new_cap);
285 }
286 }
287
288 let mut result = ComposableNamespace::new(composed);
290 for iface in &ns_a.interfaces {
291 if iface.capability_id != *glued_out_id {
292 result.interfaces.push(iface.clone());
293 }
294 }
295 for iface in &ns_b.interfaces {
296 if iface.capability_id != *glued_in_id {
297 let mut new_iface = iface.clone();
298 new_iface.capability_id =
299 CapabilityId::new(format!("{}_{}", ns_b.namespace.name, iface.capability_id));
300 result.interfaces.push(new_iface);
301 }
302 }
303
304 Ok(result)
305}
306
307pub fn composition_multiplicity(
320 ns_a: &ComposableNamespace,
321 out_idx: usize,
322 ns_b: &ComposableNamespace,
323 in_idx: usize,
324) -> u64 {
325 let outputs = ns_a.outputs();
326 let inputs = ns_b.inputs();
327
328 if out_idx >= outputs.len() || in_idx >= inputs.len() {
329 return 0;
330 }
331
332 let output = outputs[out_idx];
333 let input = inputs[in_idx];
334
335 if !interfaces_compatible(output, input) {
336 return 0;
337 }
338
339 let lambda = Partition::new(vec![output.codimension]);
343 let mu = Partition::new(vec![input.codimension]);
344
345 let (k, n) = ns_a.namespace.grassmannian;
346 let m = n - k;
347
348 let target_size = lambda.size() + mu.size();
352 let target = Partition::new(vec![target_size.min(m)]);
353
354 let coeff = lr_coefficient(&lambda, &mu, &target);
355 if coeff > 0 {
356 coeff
357 } else {
358 1
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366 use crate::namespace::Capability;
367 use crate::schubert::SchubertClass;
368
369 fn make_namespace(name: &str, caps: Vec<(&str, &str, Vec<usize>)>) -> Namespace {
370 let pos = SchubertClass::new(vec![], (2, 4)).unwrap();
371 let mut ns = Namespace::new(name, pos);
372 for (id, cap_name, partition) in caps {
373 let cap = Capability::new(id, cap_name, partition, (2, 4)).unwrap();
374 ns.grant(cap).unwrap();
375 }
376 ns
377 }
378
379 #[test]
380 fn test_interface_compatibility() {
381 let out = Interface::new(CapabilityId::new("a"), InterfaceDirection::Output, 1);
382 let inp = Interface::new(CapabilityId::new("b"), InterfaceDirection::Input, 1);
383 assert!(interfaces_compatible(&out, &inp));
384 }
385
386 #[test]
387 fn test_interface_incompatible_same_direction() {
388 let out1 = Interface::new(CapabilityId::new("a"), InterfaceDirection::Output, 1);
389 let out2 = Interface::new(CapabilityId::new("b"), InterfaceDirection::Output, 1);
390 assert!(!interfaces_compatible(&out1, &out2));
391 }
392
393 #[test]
394 fn test_interface_incompatible_wrong_codim() {
395 let out = Interface::new(CapabilityId::new("a"), InterfaceDirection::Output, 1);
396 let inp = Interface::new(CapabilityId::new("b"), InterfaceDirection::Input, 2);
397 assert!(!interfaces_compatible(&out, &inp));
398 }
399
400 #[test]
401 fn test_compose_namespaces() {
402 let ns_a = make_namespace(
403 "A",
404 vec![
405 ("out_cap", "Output Cap", vec![1]),
406 ("keep_a", "Keep A", vec![1]),
407 ],
408 );
409 let mut comp_a = ComposableNamespace::new(ns_a);
410 comp_a.mark_output(&CapabilityId::new("out_cap")).unwrap();
411
412 let ns_b = make_namespace(
413 "B",
414 vec![
415 ("in_cap", "Input Cap", vec![1]),
416 ("keep_b", "Keep B", vec![1]),
417 ],
418 );
419 let mut comp_b = ComposableNamespace::new(ns_b);
420 comp_b.mark_input(&CapabilityId::new("in_cap")).unwrap();
421
422 let composed = compose_namespaces(&comp_a, 0, &comp_b, 0).unwrap();
423
424 assert_eq!(composed.namespace.capabilities.len(), 2);
426 }
427
428 #[test]
429 fn test_compose_incompatible_fails() {
430 let ns_a = make_namespace("A", vec![("out_cap", "Out", vec![1])]);
431 let mut comp_a = ComposableNamespace::new(ns_a);
432 comp_a.mark_output(&CapabilityId::new("out_cap")).unwrap();
433
434 let ns_b = make_namespace("B", vec![("in_cap", "In", vec![2])]);
435 let mut comp_b = ComposableNamespace::new(ns_b);
436 comp_b.mark_input(&CapabilityId::new("in_cap")).unwrap();
437
438 let result = compose_namespaces(&comp_a, 0, &comp_b, 0);
439 assert!(result.is_err());
440 }
441
442 #[test]
443 fn test_composition_multiplicity() {
444 let ns_a = make_namespace("A", vec![("out_cap", "Out", vec![1])]);
445 let mut comp_a = ComposableNamespace::new(ns_a);
446 comp_a.mark_output(&CapabilityId::new("out_cap")).unwrap();
447
448 let ns_b = make_namespace("B", vec![("in_cap", "In", vec![1])]);
449 let mut comp_b = ComposableNamespace::new(ns_b);
450 comp_b.mark_input(&CapabilityId::new("in_cap")).unwrap();
451
452 let mult = composition_multiplicity(&comp_a, 0, &comp_b, 0);
453 assert!(mult >= 1);
454 }
455
456 #[test]
457 fn test_effective_capability_count() {
458 let ns = make_namespace(
459 "A",
460 vec![
461 ("cap1", "C1", vec![1]),
462 ("cap2", "C2", vec![1]),
463 ("cap3", "C3", vec![1]),
464 ],
465 );
466 let mut comp = ComposableNamespace::new(ns);
467 comp.mark_output(&CapabilityId::new("cap1")).unwrap();
468
469 assert_eq!(comp.effective_capability_count(), 2);
470 }
471
472 #[test]
473 fn test_composable_outputs_inputs() {
474 let ns = make_namespace("A", vec![("cap1", "C1", vec![1]), ("cap2", "C2", vec![1])]);
475 let mut comp = ComposableNamespace::new(ns);
476 comp.mark_output(&CapabilityId::new("cap1")).unwrap();
477 comp.mark_input(&CapabilityId::new("cap2")).unwrap();
478
479 assert_eq!(comp.outputs().len(), 1);
480 assert_eq!(comp.inputs().len(), 1);
481 }
482}