aces/
atom.rs

1use std::{
2    fmt, hash,
3    collections::{BTreeMap, BTreeSet, HashMap},
4};
5use crate::{
6    Face, ID, NodeID, Context, Contextual, ExclusivelyContextual, InContext, AcesError,
7    AcesErrorKind, sat,
8};
9
10/// An abstract structural identifier serving as the common base of
11/// [`PortID`], [`LinkID`], [`ForkID`] and [`JoinID`].
12///
13/// Since this is a numeric identifier, which is serial and one-based,
14/// it trivially maps into numeric codes of variables in the DIMACS
15/// SAT format.
16///
17/// See [`ID`] for more details.
18pub type AtomID = ID;
19
20/// An identifier of a [`Port`], a type derived from [`AtomID`].
21///
22/// There is a trivial bijection between values of this type and
23/// numeric codes of DIMACS variables.  This mapping simplifies the
24/// construction of SAT queries and interpretation of solutions.
25#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
26#[repr(transparent)]
27pub struct PortID(pub(crate) AtomID);
28
29impl PortID {
30    #[inline]
31    pub const fn get(self) -> AtomID {
32        self.0
33    }
34}
35
36impl From<AtomID> for PortID {
37    #[inline]
38    fn from(id: AtomID) -> Self {
39        PortID(id)
40    }
41}
42
43impl From<PortID> for AtomID {
44    #[inline]
45    fn from(id: PortID) -> Self {
46        id.0
47    }
48}
49
50impl ExclusivelyContextual for PortID {
51    fn format_locked(&self, ctx: &Context) -> Result<String, AcesError> {
52        let port = ctx
53            .get_port(*self)
54            .ok_or_else(|| AcesError::from(AcesErrorKind::PortMissingForID(*self)))?;
55        port.format_locked(ctx)
56    }
57}
58
59/// An identifier of a [`Link`], a type derived from [`AtomID`].
60///
61/// There is a trivial bijection between values of this type and
62/// numeric codes of DIMACS variables.  This mapping simplifies the
63/// construction of SAT queries and interpretation of solutions.
64#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
65#[repr(transparent)]
66pub struct LinkID(pub(crate) AtomID);
67
68impl LinkID {
69    #[inline]
70    pub const fn get(self) -> AtomID {
71        self.0
72    }
73}
74
75impl From<AtomID> for LinkID {
76    #[inline]
77    fn from(id: AtomID) -> Self {
78        LinkID(id)
79    }
80}
81
82impl From<LinkID> for AtomID {
83    #[inline]
84    fn from(id: LinkID) -> Self {
85        id.0
86    }
87}
88
89impl ExclusivelyContextual for LinkID {
90    fn format_locked(&self, ctx: &Context) -> Result<String, AcesError> {
91        let link = ctx
92            .get_link(*self)
93            .ok_or_else(|| AcesError::from(AcesErrorKind::LinkMissingForID(*self)))?;
94        link.format_locked(ctx)
95    }
96}
97
98/// An identifier of a [`Fork`], a type derived from [`AtomID`].
99///
100/// There is a trivial bijection between values of this type and
101/// numeric codes of DIMACS variables.  This mapping simplifies the
102/// construction of SAT queries and interpretation of solutions.
103#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
104#[repr(transparent)]
105pub struct ForkID(pub(crate) AtomID);
106
107impl ForkID {
108    #[inline]
109    pub const fn get(self) -> AtomID {
110        self.0
111    }
112}
113
114impl From<AtomID> for ForkID {
115    #[inline]
116    fn from(id: AtomID) -> Self {
117        ForkID(id)
118    }
119}
120
121impl From<ForkID> for AtomID {
122    #[inline]
123    fn from(id: ForkID) -> Self {
124        id.0
125    }
126}
127
128impl ExclusivelyContextual for ForkID {
129    fn format_locked(&self, ctx: &Context) -> Result<String, AcesError> {
130        let fork = ctx
131            .get_fork(*self)
132            .ok_or_else(|| AcesError::from(AcesErrorKind::ForkMissingForID(*self)))?;
133        fork.format_locked(ctx)
134    }
135}
136
137/// An identifier of a [`Join`], a type derived from [`AtomID`].
138///
139/// There is a trivial bijection between values of this type and
140/// numeric codes of DIMACS variables.  This mapping simplifies the
141/// construction of SAT queries and interpretation of solutions.
142#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
143#[repr(transparent)]
144pub struct JoinID(pub(crate) AtomID);
145
146impl JoinID {
147    #[inline]
148    pub const fn get(self) -> AtomID {
149        self.0
150    }
151}
152
153impl From<AtomID> for JoinID {
154    #[inline]
155    fn from(id: AtomID) -> Self {
156        JoinID(id)
157    }
158}
159
160impl From<JoinID> for AtomID {
161    #[inline]
162    fn from(id: JoinID) -> Self {
163        id.0
164    }
165}
166
167impl ExclusivelyContextual for JoinID {
168    fn format_locked(&self, ctx: &Context) -> Result<String, AcesError> {
169        let join = ctx
170            .get_join(*self)
171            .ok_or_else(|| AcesError::from(AcesErrorKind::JoinMissingForID(*self)))?;
172        join.format_locked(ctx)
173    }
174}
175
176/// A collection of [`Atom`]s: [`Port`]s, [`Link`]s, [`Fork`]s and
177/// [`Join`]s.
178///
179/// [`AtomSpace`] maintains a mapping from [`Atom`]s to [`AtomID`]s,
180/// its inverse, and a mapping from [`NodeID`]s to [`PortID`]s.  For
181/// the reverse mapping, from [`PortID`]s to [`NodeID`]s, call
182/// [`AtomSpace::get_port()`] followed by [`Port::get_node_id()`].
183#[derive(Clone, Debug)]
184pub(crate) struct AtomSpace {
185    atoms:          Vec<Atom>,
186    atom_ids:       HashMap<Atom, AtomID>,
187    source_nodes:   BTreeMap<NodeID, PortID>,
188    sink_nodes:     BTreeMap<NodeID, PortID>,
189    internal_nodes: BTreeMap<NodeID, (PortID, PortID)>,
190}
191
192impl Default for AtomSpace {
193    fn default() -> Self {
194        Self {
195            atoms:          vec![Atom::Bottom],
196            atom_ids:       Default::default(),
197            source_nodes:   Default::default(),
198            sink_nodes:     Default::default(),
199            internal_nodes: Default::default(),
200        }
201    }
202}
203
204impl AtomSpace {
205    fn do_share_atom(&mut self, mut new_atom: Atom) -> AtomID {
206        if let Some(old_atom_id) = self.get_atom_id(&new_atom) {
207            if new_atom.get_atom_id().is_none() {
208                trace!("Resharing: {:?}", new_atom);
209
210                old_atom_id
211            } else {
212                panic!("Attempt to reset ID of atom {:?}", new_atom);
213            }
214        } else {
215            let atom_id = unsafe { AtomID::new_unchecked(self.atoms.len()) };
216            new_atom.set_atom_id(atom_id);
217
218            trace!("New share: {:?}", new_atom);
219
220            self.atoms.push(new_atom.clone());
221            self.atom_ids.insert(new_atom, atom_id);
222
223            atom_id
224        }
225    }
226
227    pub(crate) fn share_port(&mut self, port: &mut Port) -> PortID {
228        let host = port.node_id;
229
230        match port.face {
231            Face::Tx => {
232                let atom_id = self.do_share_atom(Atom::Tx(port.clone()));
233
234                port.atom_id = Some(atom_id);
235
236                let pid = PortID(atom_id);
237
238                if let Some(&rx_id) = self.sink_nodes.get(&host) {
239                    self.sink_nodes.remove(&host);
240                    self.internal_nodes.insert(host, (pid, rx_id));
241                } else {
242                    self.source_nodes.insert(host, pid);
243                }
244
245                pid
246            }
247            Face::Rx => {
248                let atom_id = self.do_share_atom(Atom::Rx(port.clone()));
249
250                port.atom_id = Some(atom_id);
251
252                let pid = PortID(atom_id);
253
254                if let Some(&tx_id) = self.source_nodes.get(&host) {
255                    self.source_nodes.remove(&host);
256                    self.internal_nodes.insert(host, (tx_id, pid));
257                } else {
258                    self.sink_nodes.insert(host, pid);
259                }
260
261                pid
262            }
263        }
264    }
265
266    #[inline]
267    pub(crate) fn share_link(&mut self, link: &mut Link) -> LinkID {
268        let atom_id = self.do_share_atom(Atom::Link(link.clone()));
269
270        link.atom_id = Some(atom_id);
271
272        LinkID(atom_id)
273    }
274
275    #[inline]
276    pub(crate) fn share_fork(&mut self, fork: &mut Fork) -> ForkID {
277        let atom_id = self.do_share_atom(Atom::Fork(fork.clone()));
278
279        fork.atom_id = Some(atom_id);
280
281        ForkID(atom_id)
282    }
283
284    #[inline]
285    pub(crate) fn share_join(&mut self, join: &mut Join) -> JoinID {
286        let atom_id = self.do_share_atom(Atom::Join(join.clone()));
287
288        join.atom_id = Some(atom_id);
289
290        JoinID(atom_id)
291    }
292
293    #[inline]
294    pub(crate) fn get_atom(&self, atom_id: AtomID) -> Option<&Atom> {
295        self.atoms.get(atom_id.get())
296    }
297
298    #[inline]
299    pub(crate) fn get_atom_id(&self, atom: &Atom) -> Option<AtomID> {
300        self.atom_ids.get(atom).copied()
301    }
302
303    #[inline]
304    pub(crate) fn is_port(&self, atom_id: AtomID) -> bool {
305        match self.get_atom(atom_id) {
306            Some(Atom::Tx(_)) | Some(Atom::Rx(_)) => true,
307            _ => false,
308        }
309    }
310
311    #[inline]
312    pub(crate) fn get_port(&self, pid: PortID) -> Option<&Port> {
313        match self.get_atom(pid.into()) {
314            Some(Atom::Tx(a)) => Some(a),
315            Some(Atom::Rx(a)) => Some(a),
316            _ => None,
317        }
318    }
319
320    #[inline]
321    pub(crate) fn is_link(&self, atom_id: AtomID) -> bool {
322        match self.get_atom(atom_id) {
323            Some(Atom::Link(_)) => true,
324            _ => false,
325        }
326    }
327
328    #[inline]
329    pub(crate) fn get_link(&self, lid: LinkID) -> Option<&Link> {
330        match self.get_atom(lid.into()) {
331            Some(Atom::Link(a)) => Some(a),
332            _ => None,
333        }
334    }
335
336    #[inline]
337    pub(crate) fn is_harc(&self, atom_id: AtomID) -> bool {
338        match self.get_atom(atom_id) {
339            Some(Atom::Fork(_)) | Some(Atom::Join(_)) => true,
340            _ => false,
341        }
342    }
343
344    #[inline]
345    pub(crate) fn get_harc(&self, aid: AtomID) -> Option<&Harc> {
346        match self.get_atom(aid) {
347            Some(Atom::Fork(a)) => Some(a),
348            Some(Atom::Join(a)) => Some(a),
349            _ => None,
350        }
351    }
352
353    #[inline]
354    pub(crate) fn is_fork(&self, atom_id: AtomID) -> bool {
355        match self.get_atom(atom_id) {
356            Some(Atom::Fork(_)) => true,
357            _ => false,
358        }
359    }
360
361    #[inline]
362    pub(crate) fn get_fork(&self, fid: ForkID) -> Option<&Fork> {
363        match self.get_atom(fid.into()) {
364            Some(Atom::Fork(a)) => Some(a),
365            _ => None,
366        }
367    }
368
369    #[inline]
370    pub(crate) fn is_join(&self, atom_id: AtomID) -> bool {
371        match self.get_atom(atom_id) {
372            Some(Atom::Join(_)) => true,
373            _ => false,
374        }
375    }
376
377    #[inline]
378    pub(crate) fn get_join(&self, jid: JoinID) -> Option<&Join> {
379        match self.get_atom(jid.into()) {
380            Some(Atom::Join(a)) => Some(a),
381            _ => None,
382        }
383    }
384
385    pub fn get_antiport_id(&self, pid: PortID) -> Option<PortID> {
386        if let Some(port) = self.get_port(pid) {
387            if let Some(&(tx_id, rx_id)) = self.internal_nodes.get(&port.node_id) {
388                match port.face {
389                    Face::Tx => {
390                        if tx_id == pid {
391                            return Some(rx_id)
392                        } else {
393                            panic!("Corrupt atom space")
394                        }
395                    }
396                    Face::Rx => {
397                        if rx_id == pid {
398                            return Some(tx_id)
399                        } else {
400                            panic!("Corrupt atom space")
401                        }
402                    }
403                }
404            }
405        }
406
407        None
408    }
409}
410
411#[derive(Clone, Eq, Debug)]
412pub(crate) enum Atom {
413    Tx(Port),
414    Rx(Port),
415    Link(Link),
416    Fork(Fork),
417    Join(Join),
418    Bottom,
419}
420
421impl Atom {
422    fn set_atom_id(&mut self, atom_id: AtomID) {
423        use Atom::*;
424
425        let prev_id = match self {
426            Tx(p) => &mut p.atom_id,
427            Rx(p) => &mut p.atom_id,
428            Link(l) => &mut l.atom_id,
429            Fork(f) => &mut f.atom_id,
430            Join(j) => &mut j.atom_id,
431            Bottom => panic!("Attempt to set ID of the bottom atom"),
432        };
433
434        if *prev_id == None {
435            *prev_id = Some(atom_id);
436        } else {
437            panic!("Attempt to reset ID of atom {:?}", self);
438        }
439    }
440
441    fn get_atom_id(&self) -> Option<AtomID> {
442        use Atom::*;
443
444        match self {
445            Tx(p) => p.atom_id,
446            Rx(p) => p.atom_id,
447            Link(l) => l.atom_id,
448            Fork(f) => f.atom_id,
449            Join(j) => j.atom_id,
450            Bottom => panic!("Attempt to get ID of the bottom atom"),
451        }
452    }
453}
454
455impl PartialEq for Atom {
456    #[rustfmt::skip]
457    fn eq(&self, other: &Self) -> bool {
458        use Atom::*;
459
460        match self {
461            Tx(p) => if let Tx(o) = other { p == o } else { false },
462            Rx(p) => if let Rx(o) = other { p == o } else { false },
463            Link(l) => if let Link(o) = other { l == o } else { false },
464            Fork(f) => if let Fork(o) = other { f == o } else { false },
465            Join(j) => if let Join(o) = other { j == o } else { false },
466            Bottom => panic!("Attempt to access the bottom atom"),
467        }
468    }
469}
470
471impl hash::Hash for Atom {
472    fn hash<H: hash::Hasher>(&self, state: &mut H) {
473        use Atom::*;
474
475        match self {
476            Tx(p) | Rx(p) => p.hash(state),
477            Link(l) => l.hash(state),
478            Fork(f) => f.hash(state),
479            Join(j) => j.hash(state),
480            Bottom => panic!("Attempt to access the bottom atom"),
481        }
482    }
483}
484
485/// Representation of a port.
486///
487/// This is one of the two [`Face`]s of a node.
488#[derive(Clone, Eq, Debug)]
489pub struct Port {
490    face:    Face,
491    atom_id: Option<AtomID>,
492    node_id: NodeID,
493}
494
495impl Port {
496    pub(crate) fn new(face: Face, node_id: NodeID) -> Self {
497        Self { face, atom_id: None, node_id }
498    }
499
500    pub(crate) fn get_face(&self) -> Face {
501        self.face
502    }
503
504    pub fn get_atom_id(&self) -> AtomID {
505        self.atom_id.expect("Attempt to access an uninitialized port")
506    }
507
508    pub fn get_node_id(&self) -> NodeID {
509        self.node_id
510    }
511}
512
513impl PartialEq for Port {
514    fn eq(&self, other: &Self) -> bool {
515        self.node_id == other.node_id
516    }
517}
518
519impl hash::Hash for Port {
520    fn hash<H: hash::Hasher>(&self, state: &mut H) {
521        self.face.hash(state);
522        self.node_id.hash(state);
523    }
524}
525
526impl ExclusivelyContextual for Port {
527    fn format_locked(&self, ctx: &Context) -> Result<String, AcesError> {
528        let node_name = ctx
529            .get_node_name(self.get_node_id())
530            .ok_or_else(|| AcesError::from(AcesErrorKind::NodeMissingForPort(self.get_face())))?;
531
532        Ok(format!("[{} {}]", node_name, self.get_face()))
533    }
534}
535
536/// Representation of a link.
537///
538/// This is a fat link, if used on its own, or a thin link, if paired
539/// with a [`Face`].  See [`CEStructure`]'s private field `links`, or
540/// the implementation of [`CEStructure::check_coherence()`].
541///
542/// [`CEStructure`]: crate::CEStructure
543/// [`CEStructure::check_coherence()`]: crate::CEStructure::check_coherence()
544#[derive(Clone, Eq, Debug)]
545pub struct Link {
546    atom_id:    Option<AtomID>,
547    tx_port_id: PortID,
548    tx_node_id: NodeID,
549    rx_port_id: PortID,
550    rx_node_id: NodeID,
551}
552
553impl Link {
554    pub fn new(
555        tx_port_id: PortID,
556        tx_node_id: NodeID,
557        rx_port_id: PortID,
558        rx_node_id: NodeID,
559    ) -> Self {
560        Self { atom_id: None, tx_port_id, tx_node_id, rx_port_id, rx_node_id }
561    }
562
563    pub fn get_atom_id(&self) -> AtomID {
564        self.atom_id.expect("Attempt to access an uninitialized link")
565    }
566
567    pub fn get_link_id(&self) -> LinkID {
568        LinkID(self.get_atom_id())
569    }
570
571    pub fn get_port_id(&self, face: Face) -> PortID {
572        if face == Face::Rx {
573            self.rx_port_id
574        } else {
575            self.tx_port_id
576        }
577    }
578
579    pub fn get_node_id(&self, face: Face) -> NodeID {
580        if face == Face::Rx {
581            self.rx_node_id
582        } else {
583            self.tx_node_id
584        }
585    }
586
587    pub fn get_tx_port_id(&self) -> PortID {
588        self.tx_port_id
589    }
590
591    pub fn get_tx_node_id(&self) -> NodeID {
592        self.tx_node_id
593    }
594
595    pub fn get_rx_port_id(&self) -> PortID {
596        self.rx_port_id
597    }
598
599    pub fn get_rx_node_id(&self) -> NodeID {
600        self.rx_node_id
601    }
602}
603
604impl PartialEq for Link {
605    fn eq(&self, other: &Self) -> bool {
606        self.tx_port_id == other.tx_port_id && self.rx_node_id == other.rx_node_id
607    }
608}
609
610impl hash::Hash for Link {
611    fn hash<H: hash::Hasher>(&self, state: &mut H) {
612        self.tx_port_id.hash(state);
613        self.tx_node_id.hash(state);
614        self.rx_port_id.hash(state);
615        self.rx_node_id.hash(state);
616    }
617}
618
619impl ExclusivelyContextual for Link {
620    fn format_locked(&self, ctx: &Context) -> Result<String, AcesError> {
621        let tx_node_name = ctx
622            .get_node_name(self.get_tx_node_id())
623            .ok_or_else(|| AcesError::from(AcesErrorKind::NodeMissingForLink(Face::Tx)))?;
624        let rx_node_name = ctx
625            .get_node_name(self.get_rx_node_id())
626            .ok_or_else(|| AcesError::from(AcesErrorKind::NodeMissingForLink(Face::Rx)))?;
627
628        Ok(format!("({} > {})", tx_node_name, rx_node_name))
629    }
630}
631
632/// A common type of one-to-many and many-to-one arcs of the
633/// BF-hypergraph representation of c-e structures.
634///
635/// A hyperarc represents a monomial attached to a node.  There are
636/// two possible interpretations of a `Harc`: a [`Join`] is a B-arc
637/// which represents causes and a [`Fork`] is an F-arc representing
638/// effects.
639#[derive(Clone, Eq)]
640pub struct Harc {
641    atom_id:  Option<AtomID>,
642    face:     Face,
643    host_id:  NodeID,
644    suit_ids: Vec<NodeID>,
645}
646
647impl Harc {
648    fn new_unchecked(face: Face, host_id: NodeID, suit_ids: Vec<NodeID>) -> Self {
649        if cfg!(debug_assertions) {
650            let mut sit = suit_ids.iter();
651
652            if let Some(nid) = sit.next() {
653                let mut prev_nid = *nid;
654
655                for &nid in sit {
656                    assert!(prev_nid < nid, "Unordered suit");
657                    prev_nid = nid;
658                }
659            } else {
660                panic!("Empty suit")
661            }
662        }
663        Harc { atom_id: None, face, host_id, suit_ids }
664    }
665
666    /// [`Fork`]'s constructor.
667    ///
668    /// See also  [`Harc::new_fork_unchecked()`].
669    pub fn new_fork<I>(host_id: NodeID, suit_ids: I) -> Self
670    where
671        I: IntoIterator<Item = NodeID>,
672    {
673        let suit_ids: BTreeSet<_> = suit_ids.into_iter().collect();
674
675        if suit_ids.is_empty() {
676            // FIXME
677        }
678
679        Self::new_fork_unchecked(host_id, suit_ids)
680    }
681
682    /// [`Join`]'s constructor.
683    ///
684    /// See also  [`Harc::new_join_unchecked()`].
685    pub fn new_join<I>(host_id: NodeID, suit_ids: I) -> Self
686    where
687        I: IntoIterator<Item = NodeID>,
688    {
689        let suit_ids: BTreeSet<_> = suit_ids.into_iter().collect();
690
691        if suit_ids.is_empty() {
692            // FIXME
693        }
694
695        Self::new_join_unchecked(host_id, suit_ids)
696    }
697
698    /// A more efficient variant of [`Harc::new_fork()`].
699    ///
700    /// Note: new [`Fork`] is created under the assumption that
701    /// `suit_ids` are nonempty and listed in ascending order.  If the
702    /// caller fails to provide an ordered suit, the library may panic
703    /// in some other call (the constructor itself panics immediately
704    /// in debug mode).
705    pub fn new_fork_unchecked<I>(host_id: NodeID, suit_ids: I) -> Self
706    where
707        I: IntoIterator<Item = NodeID>,
708    {
709        let suit_ids: Vec<_> = suit_ids.into_iter().collect();
710        trace!("New fork: {:?} -> {:?}", host_id, suit_ids);
711        Harc::new_unchecked(Face::Tx, host_id, suit_ids)
712    }
713
714    /// A more efficient variant of [`Harc::new_join()`].
715    ///
716    /// Note: new [`Join`] is created under the assumption that
717    /// `suit_ids` are nonempty and listed in ascending order.  If the
718    /// caller fails to provide an ordered suit, the library may panic
719    /// in some other call (the constructor itself panics immediately
720    /// in debug mode).
721    pub fn new_join_unchecked<I>(host_id: NodeID, suit_ids: I) -> Self
722    where
723        I: IntoIterator<Item = NodeID>,
724    {
725        let suit_ids: Vec<_> = suit_ids.into_iter().collect();
726        trace!("New join: {:?} <- {:?}", host_id, suit_ids);
727        Harc::new_unchecked(Face::Rx, host_id, suit_ids)
728    }
729
730    pub fn get_atom_id(&self) -> AtomID {
731        match self.face {
732            Face::Tx => self.atom_id.expect("Attempt to access an uninitialized fork"),
733            Face::Rx => self.atom_id.expect("Attempt to access an uninitialized join"),
734        }
735    }
736
737    pub fn get_fork_id(&self) -> Option<ForkID> {
738        match self.face {
739            Face::Tx => Some(ForkID(self.get_atom_id())),
740            Face::Rx => None,
741        }
742    }
743
744    pub fn get_join_id(&self) -> Option<JoinID> {
745        match self.face {
746            Face::Tx => None,
747            Face::Rx => Some(JoinID(self.get_atom_id())),
748        }
749    }
750
751    pub fn get_host_id(&self) -> NodeID {
752        self.host_id
753    }
754
755    pub fn get_suit_ids(&self) -> &[NodeID] {
756        self.suit_ids.as_slice()
757    }
758}
759
760impl fmt::Debug for Harc {
761    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
762        write!(
763            f,
764            "{} {{ atom_id: ",
765            match self.face {
766                Face::Tx => "Fork",
767                Face::Rx => "Join",
768            }
769        )?;
770        self.atom_id.fmt(f)?;
771        write!(f, ", host_id: ")?;
772        self.host_id.fmt(f)?;
773        write!(f, ", suit_ids: ")?;
774        self.suit_ids.fmt(f)?;
775        write!(f, " }}")
776    }
777}
778
779impl PartialEq for Harc {
780    #[inline]
781    fn eq(&self, other: &Self) -> bool {
782        self.face == other.face && self.host_id == other.host_id && self.suit_ids == other.suit_ids
783    }
784}
785
786impl hash::Hash for Harc {
787    fn hash<H: hash::Hasher>(&self, state: &mut H) {
788        self.face.hash(state);
789        self.host_id.hash(state);
790        self.suit_ids.hash(state);
791    }
792}
793
794impl ExclusivelyContextual for Harc {
795    fn format_locked(&self, ctx: &Context) -> Result<String, AcesError> {
796        let host_name = ctx.get_node_name(self.get_host_id()).ok_or(match self.face {
797            Face::Tx => AcesError::from(AcesErrorKind::NodeMissingForFork(Face::Tx)),
798            Face::Rx => AcesError::from(AcesErrorKind::NodeMissingForJoin(Face::Rx)),
799        })?;
800
801        let suit_names: Result<Vec<_>, AcesError> = self
802            .get_suit_ids()
803            .iter()
804            .map(|&node_id| {
805                ctx.get_node_name(node_id).ok_or(match self.face {
806                    Face::Tx => AcesError::from(AcesErrorKind::NodeMissingForFork(Face::Rx)),
807                    Face::Rx => AcesError::from(AcesErrorKind::NodeMissingForJoin(Face::Tx)),
808                })
809            })
810            .collect();
811
812        match self.face {
813            Face::Tx => Ok(format!("({} > {:?})", host_name, suit_names?)),
814            Face::Rx => Ok(format!("({:?} > {})", suit_names?, host_name)),
815        }
816    }
817}
818
819/// Forward hyperarc representation of effects.
820pub type Fork = Harc;
821
822/// Backward hyperarc representation of causes.
823pub type Join = Harc;
824
825/// A trait of an identifier convertible into [`NodeID`] and into
826/// [`sat::Literal`].
827pub trait Atomic:
828    From<AtomID> + Into<AtomID> + Contextual + Copy + PartialEq + Eq + PartialOrd + Ord
829{
830    fn into_node_id(this: InContext<Self>) -> Option<NodeID>;
831
832    fn into_node_id_docked(this: InContext<Self>, _dock: Face) -> Option<NodeID> {
833        Self::into_node_id(this)
834    }
835
836    fn into_sat_literal(self, negated: bool) -> sat::Literal;
837}
838
839impl Atomic for PortID {
840    fn into_node_id(this: InContext<Self>) -> Option<NodeID> {
841        this.using_context(|pid, ctx| ctx.get_port(*pid).map(|port| port.get_node_id()))
842    }
843
844    #[inline]
845    fn into_sat_literal(self, negated: bool) -> sat::Literal {
846        sat::Literal::from_atom_id(self.get(), negated)
847    }
848}
849
850impl Atomic for LinkID {
851    fn into_node_id(_this: InContext<Self>) -> Option<NodeID> {
852        None
853    }
854
855    fn into_node_id_docked(this: InContext<Self>, dock: Face) -> Option<NodeID> {
856        this.using_context(|lid, ctx| ctx.get_link(*lid).map(|link| link.get_node_id(dock)))
857    }
858
859    #[inline]
860    fn into_sat_literal(self, negated: bool) -> sat::Literal {
861        sat::Literal::from_atom_id(self.get(), negated)
862    }
863}
864
865impl Atomic for ForkID {
866    fn into_node_id(this: InContext<Self>) -> Option<NodeID> {
867        this.using_context(|fid, ctx| ctx.get_fork(*fid).map(|fork| fork.get_host_id()))
868    }
869
870    #[inline]
871    fn into_sat_literal(self, negated: bool) -> sat::Literal {
872        sat::Literal::from_atom_id(self.get(), negated)
873    }
874}
875
876impl Atomic for JoinID {
877    fn into_node_id(this: InContext<Self>) -> Option<NodeID> {
878        this.using_context(|jid, ctx| ctx.get_join(*jid).map(|join| join.get_host_id()))
879    }
880
881    #[inline]
882    fn into_sat_literal(self, negated: bool) -> sat::Literal {
883        sat::Literal::from_atom_id(self.get(), negated)
884    }
885}
886
887#[cfg(test)]
888mod tests {
889    use super::*;
890
891    fn new_tx_port(id: usize) -> Port {
892        Port::new(Face::Tx, NodeID(unsafe { ID::new_unchecked(id) }))
893    }
894
895    #[test]
896    #[should_panic(expected = "uninitialized")]
897    fn test_atom_uninitialized() {
898        let atom = Atom::Tx(new_tx_port(1));
899        let _ = atom.get_atom_id().expect("uninitialized");
900    }
901
902    #[test]
903    #[should_panic(expected = "bottom")]
904    fn test_atom_bottom() {
905        let mut atoms = AtomSpace::default();
906        let atom = Atom::Bottom;
907        let _ = atoms.do_share_atom(atom);
908    }
909
910    #[test]
911    #[should_panic(expected = "reset")]
912    fn test_atom_reset_id() {
913        let mut atoms = AtomSpace::default();
914        let mut atom = Atom::Tx(new_tx_port(1));
915        atom.set_atom_id(unsafe { AtomID::new_unchecked(1) });
916        let _ = atoms.do_share_atom(atom);
917    }
918
919    #[test]
920    fn test_atom_id() {
921        let mut atoms = AtomSpace::default();
922        let atom = Atom::Tx(new_tx_port(1));
923        let atom_id = atoms.do_share_atom(atom);
924        let atom = atoms.get_atom(atom_id).unwrap();
925        assert_eq!(atom.get_atom_id().unwrap(), atom_id);
926    }
927}