Newer
Older
use std::path::Path;
use crate::analyzer::AnalyzerType;
use crate::get_version;
use crate::nodes::NodeGroup;
use crate::optic_graph::OpticGraph;
use crate::optic_ref::OpticRef;
use crate::optical::{LightResult, Optical};
use crate::properties::{Properties, Proptype};
use chrono::Local;
use petgraph::prelude::NodeIndex;
use serde::de::{self, MapAccess, Visitor};
use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize};
/// All optical elements ([`Optical`]s) have to be added to this structure in order
/// to be considered for an analysis.
///
/// # Example
///
/// ```rust
/// use opossum::OpticScenery;
/// use opossum::nodes::Dummy;
/// use opossum::error::OpmResult;
/// fn main() -> OpmResult<()> {
/// let mut scenery = OpticScenery::new();
/// scenery.set_description("OpticScenery demo");
/// let node1 = scenery.add_node(Dummy::new("dummy1"));
/// let node2 = scenery.add_node(Dummy::new("dummy2"));
/// scenery.connect_nodes(node1, "rear", node2, "front")
/// }
///
/// ```
fn create_default_props() -> Properties {
let mut props = Properties::default();
.create("description", "title of the scenery", None, "".into())
props
}
impl Default for OpticScenery {
fn default() -> Self {
Self {
g: Default::default(),
props: create_default_props(),
}
/// Creates a new (empty) [`OpticScenery`].
/// Add a given [`Optical`] (Source, Detector, Lens, etc.) to the graph of this [`OpticScenery`].
/// This command just adds an [`Optical`] to the graph. It does not connect
/// it to existing nodes in the graph. The given optical element is consumed (owned) by the [`OpticScenery`].
pub fn add_node<T: Optical + 'static>(&mut self, node: T) -> NodeIndex {
/// Connect (already existing) nodes denoted by the respective `NodeIndex`.
/// Both node indices must exist. Otherwise an [`OpossumError::OpticScenery`] is returned. In addition, connections are
/// rejected and an [`OpossumError::OpticScenery`] is returned, if the graph would form a cycle (loop in the graph).
pub fn connect_nodes(
&mut self,
src_node: NodeIndex,
.connect_nodes(src_node, src_port, target_node, target_port)
/// Return a reference to the optical node specified by its node index.
/// This function is mainly useful for setting up a reference node.
///
/// # Errors
///
/// This function will return [`OpossumError::OpticScenery`] if the node does not exist.
pub fn node(&self, node: NodeIndex) -> OpmResult<OpticRef> {
let node = self
.g
.0
.node_weight(node)
.ok_or(OpossumError::OpticScenery(
/// Export the optic graph, including ports, into the `dot` format to be used in combination with the [`graphviz`](https://graphviz.org/) software.
pub fn to_dot(&self, rankdir: &str) -> OpmResult<String> {
//check direction
let rankdir = if rankdir != "LR" { "TB" } else { "LR" };
let mut dot_string = self.add_dot_header(rankdir);
for node_idx in self.g.0.node_indices() {
let node = self.g.0.node_weight(node_idx).unwrap();
let node_name = node.optical_ref.borrow().name().to_owned();
let inverted = node.optical_ref.borrow().inverted();
let ports = node.optical_ref.borrow().ports();
dot_string += &node.optical_ref.borrow().to_dot(
&format!("{}", node_idx.index()),
&node_name,
inverted,
&ports,
"".to_owned(),
rankdir,
)?;
for edge in self.g.0.edge_indices() {
let light: &Light = self.g.0.edge_weight(edge).unwrap();
let end_nodes = self.g.0.edge_endpoints(edge).unwrap();
let src_edge_str =
self.create_node_edge_str(end_nodes.0, light.src_port(), "".to_owned())?;
let target_edge_str =
self.create_node_edge_str(end_nodes.1, light.target_port(), "".to_owned())?;
dot_string.push_str(&format!(" {} -> {} \n", src_edge_str, target_edge_str));
}
dot_string.push_str("}\n");
/// Returns the dot-file header of this [`OpticScenery`] graph.
fn add_dot_header(&self, rankdir: &str) -> String {
let mut dot_string = String::from("digraph {\n\tfontsize = 8\n");
dot_string.push_str("\tcompound = true;\n");
dot_string.push_str(&format!("\trankdir = \"{}\";\n", rankdir));
dot_string.push_str(&format!("\tlabel=\"{}\"\n", self.description()));
dot_string.push_str("\tfontname=\"Helvetica\"\n");
dot_string.push_str("\tnode [fontname=\"Helvetica\" fontsize = 10]\n");
dot_string.push_str("\tedge [fontname=\"Helvetica\"]\n\n");
fn create_node_edge_str(
&self,
end_node: NodeIndex,
light_port: &str,
mut parent_identifier: String,
let node = self.g.0.node_weight(end_node).unwrap().optical_ref.borrow();
parent_identifier = if parent_identifier.is_empty() {
format!("i{}", end_node.index())
} else {
format!("{}_i{}", &parent_identifier, end_node.index())
};
if node.node_type() == "group" {
let group_node: &NodeGroup = node.as_group()?;
Ok(group_node.get_mapped_port_str(light_port, parent_identifier)?)
}
/// Analyze this [`OpticScenery`] based on a given [`AnalyzerType`].
pub fn analyze(&mut self, analyzer_type: &AnalyzerType) -> OpmResult<()> {
.map_err(|_| OpossumError::Analysis("topological sort failed".into()))?;
for idx in sorted {
let node = self.g.0.node_weight(idx).unwrap();
let incoming_edges: HashMap<String, Option<LightData>> = self.incoming_edges(idx);
// paranoia: check if all incoming ports are really input ports of the node to be analyzed
let input_ports = node.optical_ref.borrow().ports().inputs();
if !incoming_edges.iter().all(|e| input_ports.contains(e.0)) {
return Err(OpossumError::Analysis("input light data contains port which is not an input port of the node. Data will be discarded.".into()));
}
//
let node_name = node.optical_ref.borrow().name().to_owned();
let node_type = node.optical_ref.borrow().node_type().to_owned();
.borrow_mut()
.analyze(incoming_edges, analyzer_type)
.map_err(|e| {
format!(
"analysis of node {} <{}> failed: {}",
})?;
for outgoing_edge in outgoing_edges {
self.set_outgoing_edge_data(idx, outgoing_edge.0, outgoing_edge.1)
}
/// Sets the description of this [`OpticScenery`].
pub fn set_description(&mut self, description: &str) {
self.props.set("description", description.into()).unwrap();
}
/// Returns a reference to the description of this [`OpticScenery`].
pub fn description(&self) -> &str {
let prop = self.props.get("description").unwrap();
if let Proptype::String(dsc) = &prop {
dsc
} else {
""
}
fn incoming_edges(&self, idx: NodeIndex) -> LightResult {
let edges = self.g.0.edges_directed(idx, petgraph::Direction::Incoming);
.map(|e| {
(
e.weight().target_port().to_owned(),
e.weight().data().cloned(),
)
})
.collect::<HashMap<String, Option<LightData>>>()
fn set_outgoing_edge_data(&mut self, idx: NodeIndex, port: String, data: Option<LightData>) {
let edges = self.g.0.edges_directed(idx, petgraph::Direction::Outgoing);
let edge_ref = edges
.into_iter()
.filter(|idx| idx.weight().src_port() == port)
.last();
if let Some(edge_ref) = edge_ref {
let edge_idx = edge_ref.id();
let light = self.g.0.edge_weight_mut(edge_idx);
if let Some(light) = light {
} // else outgoing edge not connected
pub fn report(&self, report_dir: &Path) -> serde_json::Value {
let mut report = serde_json::Map::new();
report.insert("opossum version".into(), get_version().into());
report.insert("analysis timestamp".into(), Local::now().to_string().into());
let detector_nodes = self
.g
.filter(|node| node.optical_ref.borrow().is_detector());
let mut detectors: Vec<serde_json::Value> = Vec::new();
detectors.push(node.optical_ref.borrow().report());
node.optical_ref.borrow().export_data(report_dir);
let detector_json = serde_json::Value::Array(detectors);
report.insert("detectors".into(), detector_json);
serde_json::Value::Object(report)
pub fn save_to_file(&self, path: &Path) -> OpmResult<()> {
let serialized = serde_json::to_string_pretty(&self).map_err(|e| {
OpossumError::OpticScenery(format!("deserialization of OpticScenery failed: {}", e))
})?;
let mut output = File::create(path).map_err(|e| {
OpossumError::OpticScenery(format!(
"could not create file path: {}: {}",
path.display(),
e
))
})?;
write!(output, "{}", serialized).map_err(|e| {
OpossumError::OpticScenery(format!(
"writing to file path {} failed: {}",
path.display(),
e
))
})?;
Ok(())
}
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
impl Serialize for OpticScenery {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut scene = serializer.serialize_struct("scenery", 3)?;
scene.serialize_field("opm version", &env!("OPM_FILE_VERSION"))?;
scene.serialize_field("graph", &self.g)?;
scene.serialize_field("properties", &self.props)?;
scene.end()
}
}
impl<'de> Deserialize<'de> for OpticScenery {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
enum Field {
OpmVersion,
Graph,
Properties,
}
const FIELDS: &[&str] = &["opm version", "graph", "properties"];
impl<'de> Deserialize<'de> for Field {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct FieldVisitor;
impl<'de> Visitor<'de> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("`opm version`, `graph`, or `properties`")
}
fn visit_str<E>(self, value: &str) -> std::result::Result<Field, E>
where
E: de::Error,
{
match value {
"opm version" => Ok(Field::OpmVersion),
"graph" => Ok(Field::Graph),
"properties" => Ok(Field::Properties),
_ => Err(de::Error::unknown_field(value, FIELDS)),
}
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
struct OpticSceneryVisitor;
impl<'de> Visitor<'de> for OpticSceneryVisitor {
type Value = OpticScenery;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("an OpticScenery")
}
fn visit_map<A>(self, mut map: A) -> std::result::Result<OpticScenery, A::Error>
where
A: MapAccess<'de>,
{
let mut opm_version: Option<String> = None;
let mut graph: Option<OpticGraph> = None;
let mut properties: Option<Properties> = None;
while let Some(key) = map.next_key()? {
match key {
Field::OpmVersion => {
if opm_version.is_some() {
return Err(de::Error::duplicate_field("opm version"));
}
opm_version = Some(map.next_value()?);
}
Field::Graph => {
if graph.is_some() {
return Err(de::Error::duplicate_field("graph"));
}
graph = Some(map.next_value()?);
}
Field::Properties => {
if properties.is_some() {
return Err(de::Error::duplicate_field("properties"));
}
properties = Some(map.next_value()?);
}
}
}
if let Some(opm_version) = opm_version {
if opm_version != env!("OPM_FILE_VERSION") {
println!(
"\nWarning: version mismatch! File version {}, Appplication version {}",
opm_version,
env!("OPM_FILE_VERSION")
);
}
}
let graph = graph.ok_or_else(|| de::Error::missing_field("graph"))?;
let properties =
properties.ok_or_else(|| de::Error::missing_field("properties"))?;
Ok(OpticScenery {
g: graph,
props: properties,
})
}
}
deserializer.deserialize_struct("OpticScenery", FIELDS, OpticSceneryVisitor)
}
}
use crate::nodes::Metertype;
use super::super::nodes::{BeamSplitter, Dummy, EnergyMeter, Source};
use std::{fs::File, io::Read};
let scenery = OpticScenery::new();
assert_eq!(scenery.description(), "");
assert_eq!(scenery.g.0.edge_count(), 0);
assert_eq!(scenery.g.0.node_count(), 0);
let path = "files_for_testing/dot/to_dot_empty_TB.dot";
let file_content_tb = &mut "".to_owned();
let _ = File::open(path).unwrap().read_to_string(file_content_tb);
let path = "files_for_testing/dot/to_dot_empty_LR.dot";
let file_content_lr = &mut "".to_owned();
let _ = File::open(path).unwrap().read_to_string(file_content_lr);
let mut scenery = OpticScenery::new();
scenery.set_description("Test".into());
let scenery_dot_str_tb = scenery.to_dot("TB").unwrap();
let scenery_dot_str_lr = scenery.to_dot("LR").unwrap();
assert_eq!(file_content_tb.clone(), scenery_dot_str_tb);
assert_eq!(file_content_lr.clone(), scenery_dot_str_lr);
let path = "./files_for_testing/dot/to_dot_w_node_TB.dot";
let file_content_tb = &mut "".to_owned();
let _ = File::open(path).unwrap().read_to_string(file_content_tb);
let path = "./files_for_testing/dot/to_dot_w_node_LR.dot";
let file_content_lr = &mut "".to_owned();
let _ = File::open(path).unwrap().read_to_string(file_content_lr);
let mut scenery = OpticScenery::new();
scenery.set_description("SceneryTest".into());
scenery.add_node(Dummy::new("Test"));
let scenery_dot_str_tb = scenery.to_dot("TB").unwrap();
let scenery_dot_str_lr = scenery.to_dot("LR").unwrap();
assert_eq!(file_content_tb.clone(), scenery_dot_str_tb);
assert_eq!(file_content_lr.clone(), scenery_dot_str_lr);
let path = "files_for_testing/dot/to_dot_full_TB.dot";
let file_content_tb = &mut "".to_owned();
let _ = File::open(path).unwrap().read_to_string(file_content_tb);
let path = "files_for_testing/dot/to_dot_full_LR.dot";
let file_content_lr = &mut "".to_owned();
let _ = File::open(path).unwrap().read_to_string(file_content_lr);
let mut scenery = OpticScenery::new();
scenery.set_description("SceneryTest".into());
let i_s = scenery.add_node(Source::new("Source", LightData::Fourier));
let mut bs = BeamSplitter::new("test", 0.6).unwrap();

Udo Eisenbarth
committed
bs.set_property("name", "Beam splitter".into()).unwrap();
let i_d1 = scenery.add_node(EnergyMeter::new(
"Energy meter 1",
Metertype::IdealEnergyMeter,
));
let i_d2 = scenery.add_node(EnergyMeter::new(
"Energy meter 2",
Metertype::IdealEnergyMeter,
));
scenery.connect_nodes(i_s, "out1", i_bs, "input1").unwrap();
scenery
.connect_nodes(i_bs, "out1_trans1_refl2", i_d1, "in1")
.unwrap();
scenery
.connect_nodes(i_bs, "out2_trans2_refl1", i_d2, "in1")
.unwrap();
let scenery_dot_str_tb = scenery.to_dot("TB").unwrap();
let scenery_dot_str_lr = scenery.to_dot("LR").unwrap();
assert_eq!(file_content_tb.clone(), scenery_dot_str_tb);
assert_eq!(file_content_lr.clone(), scenery_dot_str_lr);
let mut scenery = OpticScenery::new();
scenery.set_description("Test".into());
assert_eq!(scenery.description(), "Test")