opt_einsum_path/
parser.rs1use crate::*;
2
3pub fn get_symbol(i: u32) -> char {
15 match i {
16 0..26 => char::from(b'a' + i as u8),
17 26..52 => char::from(b'A' + (i - 26) as u8),
18 52..55296 => char::from_u32(i + 140).unwrap(),
19 55296.. => char::from_u32(i + 2048).unwrap(),
20 }
21}
22
23pub fn gen_unused_symbols(used: &str, n: usize) -> String {
34 let mut unused = Vec::with_capacity(n);
35 let mut i = 0;
36 while unused.len() < n {
37 let c = get_symbol(i);
38 if !used.contains(c) {
39 unused.push(c);
40 }
41 i += 1;
42 }
43 unused.into_iter().collect()
44}
45
46pub fn find_output_str(subscripts: &str) -> String {
58 let tmp_subscripts: String = subscripts.split(',').collect();
59 let mut unique_chars = BTreeSet::new();
60 let mut repeated_chars = BTreeSet::new();
61
62 for c in tmp_subscripts.chars() {
64 if !unique_chars.insert(c) {
65 repeated_chars.insert(c);
66 }
67 }
68
69 (&unique_chars - &repeated_chars).into_iter().collect()
71}
72
73pub fn find_output_shape(inputs: &[&str], shapes: &[TensorShapeType], output: &str) -> TensorShapeType {
85 output
86 .chars()
87 .map(|c| {
88 shapes
89 .iter()
90 .zip(inputs.iter())
91 .filter_map(|(shape, input)| input.chars().position(|x| x == c).map(|loc| shape.get(loc).copied()))
92 .flatten()
93 .max()
94 .unwrap() })
96 .collect()
97}
98
99pub fn convert_subscripts(old_sub: &[&str], symbol_map: &BTreeMap<&str, &str>) -> String {
112 old_sub.iter().map(|&s| if s == "..." { "..." } else { symbol_map[s] }).collect()
113}
114
115pub fn parse_einsum_input(
149 subscripts: &str,
150 operands: &[TensorShapeType],
151) -> Result<(String, String, Vec<TensorShapeType>), String> {
152 let einsum_str = subscripts.split_whitespace().collect::<String>();
154
155 if (einsum_str.contains('-') || einsum_str.contains('>'))
157 && (einsum_str.matches('-').count() > 1
158 || einsum_str.matches('>').count() > 1
159 || einsum_str.matches("->").count() != 1)
160 {
161 return Err("Subscripts can only contain one '->'.".to_string());
162 }
163
164 let mut subscripts = einsum_str.to_string();
166 if einsum_str.contains('.') {
167 let used: String = einsum_str.replace(".", "").replace(",", "").replace("->", "");
168 let ellipse_inds = gen_unused_symbols(&used, operands.iter().map(|s| s.len()).max().unwrap_or(0));
169 let mut longest = 0;
170
171 let (split_subscripts, out_sub) = if einsum_str.contains("->") {
173 let parts = einsum_str.split("->").collect_vec();
174 let input_tmp = parts[0];
175 (input_tmp.split(',').collect_vec(), true)
176 } else {
177 (einsum_str.split(',').collect_vec(), false)
178 };
179
180 let mut processed_subscripts = Vec::new();
181 for (num, sub) in split_subscripts.iter().enumerate() {
182 if sub.contains('.') {
183 if sub.matches('.').count() != 3 || !sub.contains("...") {
184 return Err("Invalid Ellipses.".to_string());
185 }
186
187 let ellipse_count = if operands[num].is_empty() {
189 0
190 } else if operands[num].len() >= sub.len() - 3 {
191 operands[num].len() - (sub.len() - 3)
192 } else {
193 return Err("Ellipses lengths do not match.".to_string());
194 };
195 longest = longest.max(ellipse_count);
196
197 if ellipse_count == 0 {
198 processed_subscripts.push(sub.replace("...", ""));
199 } else {
200 let replacement = &ellipse_inds[ellipse_inds.len() - ellipse_count..];
201 processed_subscripts.push(sub.replace("...", replacement));
202 }
203 } else {
204 processed_subscripts.push(sub.to_string());
205 }
206 }
207
208 subscripts = processed_subscripts.join(",");
209
210 let out_ellipse = if longest == 0 { "" } else { &ellipse_inds[ellipse_inds.len() - longest..] };
212
213 subscripts = if out_sub {
214 let parts: Vec<&str> = einsum_str.split("->").collect();
215 let output_sub = parts[1];
216 format!("{subscripts}->{}", output_sub.replace("...", out_ellipse))
217 } else {
218 let output_subscript = find_output_str(&subscripts);
220 let normal_inds: String = output_subscript.chars().filter(|c| !out_ellipse.contains(*c)).collect();
221 format!("{subscripts}->{out_ellipse}{normal_inds}")
222 };
223 }
224
225 let (input_subscripts, output_subscript) = if subscripts.contains("->") {
227 let parts: Vec<&str> = subscripts.split("->").collect();
228 (parts[0].to_string(), parts[1].to_string())
229 } else {
230 (subscripts.clone(), find_output_str(&subscripts))
231 };
232
233 for char in output_subscript.chars() {
235 if output_subscript.matches(char).count() != 1 {
236 return Err(format!("Output character '{char}' appeared more than once in the output."));
237 }
238 if !input_subscripts.contains(char) {
239 return Err(format!("Output character '{char}' did not appear in the input"));
240 }
241 }
242
243 let input_count = input_subscripts.split(',').count();
245 if input_count != operands.len() {
246 return Err(format!(
247 "Number of einsum subscripts, {input_count}, must be equal to the number of operands, {}.",
248 operands.len()
249 ));
250 }
251
252 Ok((input_subscripts, output_subscript, operands.to_vec()))
253}
254
255#[test]
256fn playground() {
257 for i in 0..500 {
258 println!("{} -> '{:?}'", i, char::from_u32(i));
259 }
260}