geop_ops_model/parameters/instance.rs
1//! Where placed parts are: what the step that places a part (see
2//! [`geop_core_program::placed`]) says of it, as a target of its own
3//! ([`define_instance`]). The program only builds the placed part; where
4//! it goes is a value of the state, or a fixed motion after another
5//! instance, composed outermost first ([`placement`]), and where that puts
6//! it ([`compose`], [`located`]).
7
8use geop_core_math::{
9 geop_error::{GeopError, GeopResult},
10 primitives::Pose,
11 scalars::Number,
12};
13use geop_core_program::{
14 element::{ElementReference, HasElements, Kind, whole},
15 part::StepContext,
16 placed::{INSTANCE_SEPARATOR, Located, OUTSIDE, instance_path},
17 program::ProgramStep,
18 reader::Reader,
19 target::{Target, TargetReference},
20 values::{Settings, StateValue, pose_in},
21};
22
23/// Where an instance is in the part it is in.
24#[derive(Clone, Debug, PartialEq)]
25pub enum Placement {
26 /// Where the pose state `state` of the part puts it: moved by whatever
27 /// solves the state.
28 State(String),
29 /// `motion` after where the instance `of` of the same part is: a copy,
30 /// which goes with what it copies. Only where it is: what is in it is
31 /// run for itself, with its own state.
32 After { of: String, motion: Pose },
33}
34
35/// An instance placed, as its target holds it: where it is, and the file,
36/// the inputs and the steps after the file's own its part was placed from
37/// — what a copy of it places again, and what a list of the parts tells
38/// them apart by. What is in it is its placed part (see
39/// [`geop_core_program::placed::PlacedPart`]).
40#[derive(Clone, Debug, PartialEq)]
41pub struct Instance {
42 pub placement: Placement,
43 /// The file, as the program placing it names it.
44 pub file: String,
45 pub inputs: Settings,
46 /// The steps run after the file's own: a mirrored part's mirror.
47 pub then: Vec<ProgramStep>,
48}
49
50/// Defined once, by the step placing it.
51impl Target for Instance {}
52
53/// Read whole.
54impl Kind for Instance {
55 const NAME: &'static str = "instance";
56}
57
58impl HasElements<Instance> for Instance {
59 type Element<'a> = &'a Instance;
60
61 fn element(&self, name: &str) -> GeopResult<&Instance> {
62 whole(self, name)
63 }
64}
65
66/// What the target of an instance is named, before the instance's name.
67const PREFIX: &str = "instance:";
68
69/// The target of the instance `name`.
70fn instance_target(name: &str) -> TargetReference<Instance> {
71 TargetReference::new(format!("{PREFIX}{name}"))
72}
73
74/// Defines the instance `name` as `instance`. Its part is placed on its
75/// own ([`geop_core_program::placed::place`]).
76pub fn define_instance(ctx: &mut StepContext, name: &str, instance: &Instance) -> GeopResult<()> {
77 ctx.produce(&instance_target(name), instance, |_, instance| {
78 Ok(instance.clone())
79 })
80}
81
82/// The instance `name` placed in the part `reader` reads, the read
83/// recorded.
84pub fn instance<'a>(reader: &Reader<'a>, name: &str) -> GeopResult<&'a Instance> {
85 (reader.retrieve_element(&ElementReference::whole(instance_target(name))))
86 .map_err(|e| e.with_context(format!("the placed part `{name}`")))
87}
88
89/// Every instance placed in the part `reader` reads — not in its
90/// instances' parts — by name, in the order the steps placed their parts
91/// ([`geop_core_program::reader::Reader::instances`]), each read recorded.
92/// An instance whose part is placed must be defined: it is an error if it
93/// is not.
94pub fn instances<'a>(reader: &Reader<'a>) -> GeopResult<Vec<(&'a str, &'a Instance)>> {
95 (reader.instances().into_iter())
96 .map(|name| Ok((name, instance(reader, name)?)))
97 .collect()
98}
99
100/// One factor of where an instance is, as [`placement`] gives it,
101/// outermost first: the instance is where they all are, composed in order.
102#[derive(Clone, Debug, PartialEq)]
103pub enum Factor {
104 /// The value of the state `state` of the part read — named as the part
105 /// read names it, `/asm:b.pose` — which a solve moves, and by which a
106 /// constraint is differentiated.
107 State { state: String, value: StateValue },
108 /// A motion nothing in the part read moves: a copy's after what it
109 /// copies.
110 Fixed(Pose),
111}
112
113/// How deep a chain of copies may go before it is taken for a cycle.
114const COPIES: usize = 1 << 16;
115
116/// Where the instance at `path` — `bolt`, or `asm/bolt` — is in the part
117/// `reader` reads, as factors composed outermost first: the values of the
118/// state that place it and the parts it is in, read and recorded. A state
119/// of an instance's own — what places a part in it — is named as the part
120/// names it, behind the instance's path, and read so ([`Reader::given_state`]):
121/// the part's value where it gives one.
122///
123/// A path through the part `reader`'s part is placed in, read in context
124/// — `../bracket`, a part placed beside it, or `..` itself — is where that
125/// part has it, seen from where it has this one: fixed here, since nothing
126/// this part solves moves it (see [`Reader::outside`]).
127pub fn placement(reader: &Reader, path: &str) -> GeopResult<Vec<Factor>> {
128 let ctx = |e: GeopError| e.with_context(format!("where the placed part {path:?} is"));
129 let outside = (path.strip_prefix(OUTSIDE))
130 .filter(|rest| rest.is_empty() || rest.starts_with(INSTANCE_SEPARATOR));
131 if let Some(rest) = outside {
132 let (outside, me) = reader.outside().map_err(ctx)?;
133 let rest = rest.strip_prefix(INSTANCE_SEPARATOR).unwrap_or(rest);
134 let here = located(&outside, me).map_err(ctx)?;
135 let there = located(&outside, rest).map_err(ctx)?;
136 return Ok(vec![Factor::Fixed(here.inverse().compose(&there))]);
137 }
138 let mut factors = Vec::new();
139 let mut level = reader.clone();
140 let mut outer = String::new();
141 let mut names = path.split(INSTANCE_SEPARATOR).peekable();
142 while let Some(name) = names.next() {
143 factors.append(&mut placed(reader, &level, name, &outer, 0).map_err(ctx)?);
144 outer = instance_path(&outer, name);
145 // Into the part placed, for what is placed in it: where the last
146 // is needs nothing of its own part.
147 if names.peek().is_some() {
148 level = level.placed_part(name).map_err(ctx)?;
149 }
150 }
151 Ok(factors)
152}
153
154/// The factors placing the instance `name` of the part `level` reads — the
155/// part at `path` in the part `outer` reads — in that part: its state's
156/// value, named behind `path` and read so in `outer`, or a copy's motion
157/// after where what it copies is.
158fn placed(
159 outer: &Reader,
160 level: &Reader,
161 name: &str,
162 path: &str,
163 depth: usize,
164) -> GeopResult<Vec<Factor>> {
165 if depth > COPIES {
166 return Err(GeopError::new(format!(
167 "the copies of `{name}` form a cycle"
168 )));
169 }
170 Ok(match &instance(level, name)?.placement {
171 Placement::State(state) => {
172 let state = Located::in_instance(path, state.as_str()).key();
173 vec![Factor::State {
174 value: outer.retrieve_state(&state),
175 state,
176 }]
177 }
178 Placement::After { of, motion } => {
179 let mut factors = vec![Factor::Fixed(*motion)];
180 factors.append(&mut placed(outer, level, of, path, depth + 1)?);
181 factors
182 }
183 })
184}
185
186/// How the pose of each state a placement reads enters it, computed in
187/// `T`: as it is, or moved by a step to differentiate by it. Given the
188/// state's name and the pose it holds.
189pub type PoseOf<'f, T> = dyn FnMut(&str, Pose) -> Pose<T> + 'f;
190
191/// The pose an instance is at whose placement is `factors` (see
192/// [`placement`]), in `T`: theirs,
193/// composed outermost first, each state's entering as `pose_of` has it. A
194/// value that holds no pose — a state before anything solved for it —
195/// is no motion.
196pub fn compose<T: Number>(factors: &[Factor], pose_of: &mut PoseOf<'_, T>) -> Pose<T> {
197 let mut poses = factors.iter().map(|factor| match factor {
198 Factor::State { state, value } => {
199 pose_of(state, pose_in(value).unwrap_or_else(Pose::identity))
200 }
201 Factor::Fixed(motion) => motion.map(T::from_scalar),
202 });
203 // The first as it is: composing it after no motion would round it.
204 let first = poses.next().unwrap_or_else(Pose::identity);
205 poses.fold(first, |world, pose| world.compose(&pose))
206}
207
208/// Where the instance at `path` is in the part `reader` reads: none — no
209/// motion — for the part itself, at the empty path.
210pub fn located(reader: &Reader, path: &str) -> GeopResult<Pose> {
211 if path.is_empty() {
212 return Ok(Pose::identity());
213 }
214 Ok(compose(&placement(reader, path)?, &mut |_, pose| pose))
215}