use energyml_core::value::Reflect;
use crate::error::{Error, Result};
use crate::geometry::{Geometry, Patch, Topology};
use crate::points::Point;
use crate::source::ValueSource;
use crate::values::read_integers;
const ALTERNATE_INDEX: &[&str] = &["OriginalCellIndex"];
const GRIDS: &[&str] = &["Grid"];
const INTERPRETATIONS: &[&str] = &["ConnectionInterpretations"];
const CELL_PAIRS: &[&str] = &["CellIndexPairs"];
const GRID_PAIRS: &[&str] = &["GridIndexPairs"];
const FACE_PAIRS: &[&str] = &["LocalFacePerCellIndexPairs"];
pub(crate) fn read(representation: &dyn Reflect, source: &dyn ValueSource) -> Result<Vec<Patch>> {
Ok(build(representation, source)?.0)
}
fn build(representation: &dyn Reflect, source: &dyn ValueSource) -> Result<(Vec<Patch>, Vec<u32>)> {
let Some(pairs) = integers(representation, CELL_PAIRS, source)? else {
return Err(Error::malformed(
"GridConnectionSetRepresentation",
"no `CellIndexPairs`: the set states no connection",
));
};
if pairs.is_empty() {
return Err(Error::malformed(
"GridConnectionSetRepresentation",
"`CellIndexPairs` is empty: the set states no connection",
));
}
if pairs.len() % 2 != 0 {
return Err(Error::malformed(
"GridConnectionSetRepresentation.CellIndexPairs",
format!("{} indices cannot be read as pairs", pairs.len()),
));
}
let connections = pairs.len() / 2;
let grids = read_grids(representation, source)?;
let faces = integers(representation, FACE_PAIRS, source)?;
if let Some(found) = &faces
&& found.len() != pairs.len()
{
return Err(Error::malformed(
"GridConnectionSetRepresentation.LocalFacePerCellIndexPairs",
format!(
"{} face indices against {} cell indices: the schema pairs them one for one",
found.len(),
pairs.len()
),
));
}
let which = integers(representation, GRID_PAIRS, source)?;
if let Some(found) = &which
&& found.len() != pairs.len()
{
return Err(Error::malformed(
"GridConnectionSetRepresentation.GridIndexPairs",
format!(
"{} grid indices against {} cell indices: the schema pairs them one for one",
found.len(),
pairs.len()
),
));
}
let mut points: Vec<Point> = Vec::new();
let mut rings: Vec<Vec<u32>> = Vec::new();
let mut segments: Vec<[u32; 2]> = Vec::new();
let mut origin: Vec<u32> = Vec::new();
let mut unplaced = 0usize;
for connection in 0..connections {
let mut centres: [Option<Point>; 2] = [None, None];
for (side, centre) in centres.iter_mut().enumerate() {
let at = connection * 2 + side;
let Some(cell) = pairs[at] else {
continue;
};
let grid = which.as_ref().and_then(|list| list[at]).unwrap_or(0) as usize;
let Some(shape) = grids.get(grid) else {
return Err(Error::malformed(
"GridConnectionSetRepresentation.GridIndexPairs",
format!("grid index {grid} names no grid: the set lists {}", grids.len()),
));
};
let face = match faces.as_ref() {
None => None,
Some(list) => match list[at] {
Some(index) => Some(index as usize),
None => continue,
},
};
match face {
Some(local) => match face_of(shape, cell, local)? {
Some(face) => {
let base = points.len() as u32;
rings.push((0..face.len() as u32).map(|at| base + at).collect());
points.extend(face);
origin.push(connection as u32);
}
None => unplaced += 1,
},
None => match centre_of(shape, cell) {
Some(found) => *centre = Some(found),
None => unplaced += 1,
},
}
}
if let [Some(a), Some(b)] = centres {
let base = points.len() as u32;
points.push(a);
points.push(b);
segments.push([base, base + 1]);
origin.push(connection as u32);
}
}
if points.is_empty() {
return Err(Error::malformed(
"GridConnectionSetRepresentation",
format!(
"none of the {connections} connection(s) names a cell the grids place \
({unplaced} side(s) unplaced)"
),
));
}
Ok((
vec![Patch {
index: 0,
node_offset: 0,
points,
topology: if faces.is_some() {
Topology::Polygons(rings)
} else {
Topology::Segments(segments)
},
closed: Vec::new(),
}],
origin,
))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConnectionFeature {
pub uuid: String,
pub title: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Interpretations {
pub features: Vec<ConnectionFeature>,
pub per_entry: Vec<Vec<u32>>,
}
pub fn read_with_interpretations(
representation: &dyn Reflect,
source: &dyn ValueSource,
) -> Result<(Vec<Patch>, Option<Interpretations>)> {
let (patches, origin) = build(representation, source)?;
let Some(element) = INTERPRETATIONS
.iter()
.find_map(|field| crate::geometry::field_object(representation, field))
else {
return Ok((patches, None));
};
let features: Vec<ConnectionFeature> = crate::geometry::field_list(element, "FeatureInterpretation")
.into_iter()
.map(|reference| ConnectionFeature {
uuid: energyml_core::identity::uuid_of_reflect(reference)
.unwrap_or_default()
.to_string(),
title: text_of(reference, "Title").unwrap_or_default(),
})
.collect();
if features.is_empty() {
return Err(Error::malformed(
"GridConnectionSetRepresentation.ConnectionInterpretations",
"interpretation indices with no `FeatureInterpretation` to index",
));
}
let Some(array) = crate::geometry::field_object(element, "InterpretationIndices") else {
return Err(Error::malformed(
"GridConnectionSetRepresentation.ConnectionInterpretations",
"no `InterpretationIndices`: nothing says which connection is which",
));
};
let rows = energyml_domain::values::jagged(
array,
source,
"GridConnectionSetRepresentation.ConnectionInterpretations.InterpretationIndices",
)?;
let connections = origin.iter().copied().max().map_or(0, |last| last as usize + 1);
if rows.len() < connections {
return Err(Error::malformed(
"GridConnectionSetRepresentation.ConnectionInterpretations.InterpretationIndices",
format!(
"{} row(s) for {connections} connection(s): the schema gives one row per connection",
rows.len()
),
));
}
let mut per_entry = Vec::with_capacity(origin.len());
for connection in &origin {
let Some(values) = rows[*connection as usize].integer() else {
return Err(Error::malformed(
"GridConnectionSetRepresentation.ConnectionInterpretations.InterpretationIndices",
"the indices are not integers",
));
};
let mut row = Vec::with_capacity(values.len());
for value in values {
if *value < 0 || *value as usize >= features.len() {
return Err(Error::malformed(
"GridConnectionSetRepresentation.ConnectionInterpretations.InterpretationIndices",
format!(
"the index {value} names no interpretation: the set lists {}",
features.len()
),
));
}
row.push(*value as u32);
}
per_entry.push(row);
}
Ok((patches, Some(Interpretations { features, per_entry })))
}
fn text_of(object: &dyn Reflect, field: &str) -> Option<String> {
match object.field(field)?.as_scalar()? {
energyml_core::value::Scalar::Str(found) if !found.is_empty() => Some(found.to_string()),
_ => None,
}
}
fn read_grids(representation: &dyn Reflect, source: &dyn ValueSource) -> Result<Vec<Geometry>> {
let mut out = Vec::new();
for field in GRIDS {
for reference in crate::geometry::field_list(representation, field) {
let uri = crate::follow::address_of(reference).ok_or_else(|| {
Error::malformed(
"GridConnectionSetRepresentation.Grid",
"a grid reference with no uuid",
)
})?;
let object = source
.object_at(&uri)
.ok_or_else(|| Error::Missing(uri.clone()))?;
let grid = object.as_reflect();
if ALTERNATE_INDEX
.iter()
.any(|field| crate::geometry::field_object(grid, field).is_some())
{
return Err(Error::unsupported(
"AlternateCellIndex",
format!(
"the grid {uri} declares an alternate cell indexing, which is then the \
only one its connection set's indices may be read against"
),
));
}
out.push(crate::geometry::of(grid, source)?);
}
if !out.is_empty() {
break;
}
}
if out.is_empty() {
return Err(Error::malformed(
"GridConnectionSetRepresentation",
"the set names no grid, so its cell indices name nothing",
));
}
Ok(out)
}
fn face_of(shape: &Geometry, cell: u64, face: usize) -> Result<Option<Vec<Point>>> {
for patch in &shape.patches {
let Some(position) = patch.topology.position_of(cell as u32) else {
continue;
};
return match &patch.topology {
Topology::Hexahedra { cells, .. } => {
let corners = cells[position];
let quad: [usize; 4] = match face {
0 => [0, 1, 2, 3],
1 => [4, 7, 6, 5],
2 => [0, 4, 5, 1],
3 => [1, 5, 6, 2],
4 => [2, 6, 7, 3],
5 => [3, 7, 4, 0],
_ => {
return Err(Error::malformed(
"GridConnectionSetRepresentation.LocalFacePerCellIndexPairs",
format!("local face {face} on an IJK cell, which has six (0-5)"),
));
}
};
Ok(Some(
quad.iter()
.map(|at| patch.points[corners[*at] as usize])
.collect(),
))
}
Topology::Polyhedra(cells) => {
let listed: Vec<usize> = cells.cell(position).map(|(face, _)| face).collect();
let Some(global) = listed.get(face) else {
return Err(Error::malformed(
"GridConnectionSetRepresentation.LocalFacePerCellIndexPairs",
format!(
"local face {face} on a cell that has {} (0-{})",
listed.len(),
listed.len().saturating_sub(1)
),
));
};
Ok(Some(
cells
.face(*global)
.iter()
.map(|node| patch.points[*node as usize])
.collect(),
))
}
other => Err(Error::Shape {
topology: crate::geometry::leak(name_of(other)),
wanted: "a grid whose cells have faces",
}),
};
}
Ok(None)
}
fn centre_of(shape: &Geometry, cell: u64) -> Option<Point> {
for patch in &shape.patches {
let Some(position) = patch.topology.position_of(cell as u32) else {
continue;
};
let nodes: Vec<u32> = match &patch.topology {
Topology::Hexahedra { cells, .. } => cells[position].to_vec(),
Topology::Polyhedra(cells) => cells
.cell(position)
.flat_map(|(face, _)| cells.face(face).to_vec())
.collect(),
_ => return None,
};
if nodes.is_empty() {
return None;
}
let mut sum = [0.0f64; 3];
for node in &nodes {
let point = patch.points[*node as usize];
for axis in 0..3 {
sum[axis] += point[axis];
}
}
let count = nodes.len() as f64;
return Some([sum[0] / count, sum[1] / count, sum[2] / count]);
}
None
}
fn name_of(topology: &Topology) -> &'static str {
match topology {
Topology::Points => "a set of points",
Topology::Triangles(_) => "a set of triangles",
Topology::Quads(_) => "a set of quadrilaterals",
Topology::Polygons(_) => "a set of polygons",
Topology::Polylines(_) => "a set of polylines",
Topology::Segments(_) => "a set of segments",
Topology::Grid { .. } => "a lattice",
Topology::Hexahedra { .. } => "an IJK grid",
Topology::Polyhedra(_) => "an unstructured grid",
}
}
fn integers(
object: &dyn Reflect,
fields: &[&str],
source: &dyn ValueSource,
) -> Result<Option<Vec<Option<u64>>>> {
for field in fields {
let Some(array) = crate::geometry::field_object(object, field) else {
continue;
};
let null = crate::values::scalar_i64(array, "NullValue");
let read = read_integers(array, source, field)?;
let mut out = Vec::with_capacity(read.len());
for value in read {
if Some(value) == null {
out.push(None);
continue;
}
let Ok(index) = u64::try_from(value) else {
return Err(Error::malformed(
format!("GridConnectionSetRepresentation.{field}"),
format!("{value} is not an index: the schema makes these positive"),
));
};
out.push(Some(index));
}
return Ok(Some(out));
}
Ok(None)
}