1use std::{
2 io::{Write, stderr},
3 sync::Arc,
4};
5
6use algebra::{
7 AdemAlgebra, Algebra, GeneratedAlgebra, module::FDModule, steenrod_evaluator::SteenrodEvaluator,
8};
9use anyhow::anyhow;
10use bivec::BiVec;
11use fp::{
12 prime::{Prime, ValidPrime},
13 vector::FpVector,
14};
15use rustc_hash::FxHashMap as HashMap;
16use serde_json::{Value, json};
17
18pub fn get_gens() -> anyhow::Result<BiVec<Vec<String>>> {
19 ext::utils::init_logging()?;
20
21 eprintln!("Input generators. Press return to finish.");
23 stderr().flush()?;
24
25 let mut gens: BiVec<Vec<_>> = BiVec::new(0);
26 loop {
27 let gen_deg: Option<i32> = query::optional("Generator degree", str::parse);
28 if gen_deg.is_none() {
29 eprintln!("This is the list of generators and degrees:");
30 for (i, deg_i_gens) in gens.iter_enum() {
31 for g in deg_i_gens.iter() {
32 eprint!("({i}, {g}) ");
33 }
34 }
35 eprintln!();
36 if query::yes_no("Is it okay?") {
37 break;
38 } else {
39 if query::yes_no("Start over?") {
40 gens = BiVec::new(0);
41 }
42 continue;
43 }
44 }
45
46 let gen_deg = gen_deg.unwrap();
47
48 gens.extend_negative(gen_deg, Vec::new());
49 gens.extend_with(gen_deg, |_| Vec::new());
50
51 let gen_name = query::with_default(
52 "Generator name",
53 &format!("x{gen_deg}{}", gens[gen_deg].len()).replace('-', "_"),
54 |x| {
55 match x.chars().next() {
56 Some(a) => {
57 if !a.is_alphabetic() {
58 return Err("variable name must start with a letter".to_string());
59 }
60 }
61 None => return Err("Variable name cannot be empty".to_string()),
62 };
63 for c in x.chars() {
64 if !c.is_alphanumeric() && c != '_' {
65 return Err(format!(
66 "Variable name cannot contain {c}. Should be alphanumeric and '_'"
67 ));
68 }
69 }
70 Ok(x.to_string())
71 },
72 );
73 gens[gen_deg].push(gen_name);
74 }
75 Ok(gens)
76}
77
78pub fn gens_to_json(gens: &BiVec<Vec<String>>) -> serde_json::Value {
79 let mut gens_json = json!({});
80 for (i, deg_i_gens) in gens.iter_enum() {
81 for g in deg_i_gens {
82 gens_json[g] = json!(i);
83 }
84 }
85 gens_json
86}
87
88pub fn interactive_module_define_fdmodule(
89 output_json: &mut Value,
90 p: ValidPrime,
91) -> anyhow::Result<()> {
92 output_json["p"] = Value::from(p.as_u32());
93 let algebra = Arc::new(AdemAlgebra::new(p, false));
94
95 let gens = get_gens()?;
96 let min_degree = gens.min_degree();
97 let max_degree = gens.len();
98
99 algebra.compute_basis(max_degree - min_degree);
100
101 let mut graded_dim = BiVec::with_capacity(min_degree, max_degree);
102 for i in gens.iter().map(Vec::len) {
103 graded_dim.push(i);
104 }
105
106 let mut module = FDModule::new(Arc::clone(&algebra), String::new(), graded_dim);
107
108 for (i, deg_i_gens) in gens.iter_enum() {
109 for (j, g) in deg_i_gens.iter().enumerate() {
110 module.set_basis_element_name(i, j, g.to_string());
111 }
112 }
113
114 eprintln!(
115 "Input actions. Write the value of the action in the form 'a x0 + b x1 + ...' where a, b \
116 are non-negative integers and x0, x1 are names of the generators. The coefficient can be \
117 omitted if it is 1"
118 );
119
120 let len = gens.len();
121 for input_deg in gens.range().rev() {
122 for output_deg in (input_deg + 1)..len {
123 let op_deg = output_deg - input_deg;
124 if gens[output_deg].is_empty() {
125 continue;
126 }
127 for op_idx in algebra.generators(op_deg) {
128 for input_idx in 0..gens[input_deg].len() {
129 let output = query::raw(
130 &format!(
131 "{} {}",
132 algebra.basis_element_to_string(op_deg, op_idx),
133 gens[input_deg][input_idx]
134 ),
135 |expr| {
136 let mut result = vec![0; gens[output_deg].len()];
137 if expr == "0" {
138 return Ok(result);
139 }
140 for term in expr.split('+') {
141 let term = term.trim();
142 let (coef, g) = match term.split_once(' ') {
143 Some((coef, g)) => (str::parse::<u32>(coef)?, g),
144 None => (1, term),
145 };
146
147 if let Some(gen_idx) = gens[output_deg].iter().position(|d| d == g)
148 {
149 result[gen_idx] += coef;
150 } else {
151 return Err(anyhow!("No generator {g} in degree {output_deg}"));
152 }
153 }
154
155 Ok(result)
156 },
157 );
158
159 module.set_action(op_deg, op_idx, input_deg, input_idx, &output);
160 }
161 }
162 module.extend_actions(input_deg, output_deg);
163 module.check_validity(input_deg, output_deg)?;
164 }
165 }
166
167 module.to_json(output_json);
168 Ok(())
169}
170
171fn replace(algebra_elt: &str, g: &str) -> String {
174 algebra_elt.replace('+', &format!("{g} +")) + " " + g
175}
176
177pub fn interactive_module_define_fpmodule(
178 output_json: &mut Value,
179 p: ValidPrime,
180) -> anyhow::Result<()> {
181 let gens = get_gens()?;
182 let min_degree = gens.min_degree();
183 let max_degree = gens.len();
184
185 let ev = SteenrodEvaluator::new(p);
186
187 let mut graded_dim = BiVec::with_capacity(min_degree, max_degree);
188 for i in gens.iter().map(Vec::len) {
189 graded_dim.push(i);
190 }
191
192 eprintln!("Input relations");
193 match p.as_u32() {
194 2 => eprintln!("Write relations in the form 'Sq6 * Sq2 * x + Sq7 * y'"),
195 _ => eprintln!(
196 "Write relations in the form 'Q5 * P(5) * x + 2 * P(1, 3) * Q2 * y', where P(...) and \
197 Qi are Milnor basis elements."
198 ),
199 }
200
201 let mut degree_lookup = HashMap::default();
202 for (i, deg_i_gens) in gens.iter_enum() {
203 for g in deg_i_gens.iter() {
204 degree_lookup.insert(g.clone(), i);
205 }
206 }
207
208 let mut adem_relations = Vec::new();
209 let mut milnor_relations = Vec::new();
210
211 loop {
212 let relation = query::raw("Enter relation", |rel| {
213 let result = ev.evaluate_module_adem(rel)?;
214
215 if result.is_empty() {
216 return Ok(result);
217 }
218
219 let degrees = result
221 .iter()
222 .map(|(g, (op_deg, _))| {
223 degree_lookup
224 .get(g)
225 .map(|d| d + op_deg)
226 .ok_or_else(|| anyhow!("Unknown generator: {g}"))
227 })
228 .collect::<Result<Vec<_>, _>>()?;
229 for window in degrees.windows(2) {
230 if window[0] != window[1] {
231 return Err(anyhow!(
232 "Relation terms have different degrees: {} and {}",
233 window[0],
234 window[1],
235 ));
236 }
237 }
238
239 Ok(result)
240 });
241
242 if relation.is_empty() {
243 break;
244 }
245
246 let mut adem_relation = String::new();
247 let mut milnor_relation = String::new();
248
249 let mut milnor_op = FpVector::new(p, 0);
250 for (g, (op_deg, adem_op)) in relation.iter() {
251 if adem_op.is_zero() {
252 continue;
253 }
254 if !adem_relation.is_empty() {
255 adem_relation += " + ";
256 milnor_relation += " + ";
257 }
258 milnor_op.set_scratch_vector_size(adem_op.len());
259 ev.adem_to_milnor(&mut milnor_op, 1, *op_deg, adem_op);
260
261 adem_relation += &replace(&ev.adem.element_to_string(*op_deg, adem_op.as_slice()), g);
262 milnor_relation += &replace(
263 &ev.milnor.element_to_string(*op_deg, milnor_op.as_slice()),
264 g,
265 );
266 }
267 if !adem_relation.is_empty() {
268 adem_relations.push(Value::String(adem_relation));
269 milnor_relations.push(Value::String(milnor_relation));
270 }
271 }
272
273 output_json["p"] = Value::from(p.as_u32());
274 output_json["type"] = Value::String("finitely presented module".to_owned());
275 for (i, deg_i_gens) in gens.iter_enum() {
276 for g in deg_i_gens {
277 output_json["gens"][g] = Value::from(i);
278 }
279 }
280 output_json["adem_relations"] = Value::Array(adem_relations);
281 output_json["milnor_relations"] = Value::Array(milnor_relations);
282 Ok(())
283}
284
285fn main() -> anyhow::Result<()> {
286 let module_type = query::with_default(
287 "Input module type (default 'finite dimensional module'):\n (fd) - finite dimensional \
288 module \n (fp) - finitely presented module\n",
289 "fd",
290 |x| match x {
291 "fd" | "fp" => Ok(x.to_string()),
292 _ => Err(format!("Invalid type '{x}'. Type must be 'fd' or 'fp'")),
293 },
294 );
295
296 let p: ValidPrime = query::with_default("p", "2", str::parse);
297 let mut output_json = json!({});
298
299 eprintln!("module_type: {module_type}");
300 match &*module_type {
301 "fd" => interactive_module_define_fdmodule(&mut output_json, p)?,
302 "fp" => interactive_module_define_fpmodule(&mut output_json, p)?,
303 _ => unreachable!(),
304 }
305 println!("{output_json}");
306 Ok(())
307}