def get_limits_graphs(hist: history.DB, top_nodes, sub_graphs: SubGraphs): """Make a dict of ax to x and y limits""" # x limits come from the column graph if sub_graphs.any_with_common_x(): column_data = list(hist.get_all(history.ColumnResult)) x_vals = [cd.x for cd in column_data] x_lims = min(x_vals), max(x_vals) raw_y_range = {} if sub_graphs.surface_absolute: raw_y_range[sub_graphs.surface_absolute] = _get_overall_limits( False, top_nodes, hist, x_lims) if sub_graphs.surface_deviation: raw_y_range[sub_graphs.surface_deviation] = _get_overall_limits( True, top_nodes, hist, x_lims) if sub_graphs.columns_strain_x: raw_y_range[ sub_graphs.columns_strain_x] = hist.get_column_result_range( history.ContourKey.total_strain_x) out_d = { ax: (x_lims, _add_margin(y_range)) for ax, y_range in raw_y_range.items() } return out_d
def graph_force_disp(hist: history.DB, db_res_case, ax: plt.Axes): ax.clear() fd_data = list(hist.get_all(history.LoadDisplacementPoint)) # Put marks on the final increment of each load step res_case_data = list(hist.get_all(history.ResultCase)) res_case_data.sort() maj_to_max_res_case = dict() for res_case in res_case_data: maj_to_max_res_case[res_case.major_inc] = res_case.num final_minor_inc_cases = set(maj_to_max_res_case.values()) def sort_key(ld_point: history.LoadDisplacementPoint): return ld_point.node_num, ld_point.load_text, ld_point.disp_text fd_data.sort(key=sort_key) for (node_num, load_text, disp_text), fd_points in itertools.groupby(fd_data, sort_key): fd_points = list(fd_points) x = [p.disp_val for p in fd_points] y = [p.load_val for p in fd_points] show_marker = [ p.result_case_num in final_minor_inc_cases for p in fd_points ] ax.plot(x, y, marker='x', markevery=show_marker, label=f"{node_num} {disp_text} vs {load_text}") # Hacky getting DoF dof_iter = (dof for dof in st7.DoF if dof.rx_mz_text == load_text) dof = next(dof_iter) ax.set_xlabel(dof.disp_or_rotation_text) ax.set_ylabel(dof.force_or_moment_text) # Point to show current db res case x_one = [ p.disp_val for p in fd_points if p.result_case_num == db_res_case ] y_one = [ p.load_val for p in fd_points if p.result_case_num == db_res_case ] ax.plot(x_one, y_one, marker='o', markeredgecolor='tab:orange', markerfacecolor='tab:orange') ax.legend()
def make_dashboard(db: history.DB): cases = db.get_all(history.ResultCase) all_views = [ ContourView(db_case_num=case.num, contour_key=history.ContourKey.prestrain_mag) for case in cases ]
def _get_overall_limits( deviation_only: bool, top_nodes: typing.FrozenSet[int], hist: history.DB, x_lims: typing.Tuple[float, float]) -> typing.Tuple[float, float]: all_y_points = [] for db_res_case in sorted(hist.get_all(history.ResultCase)): for line_data in _get_graph_surface_profile_data( deviation_only, top_nodes, hist, db_res_case.num): these_y_points = line_data.y_within_bounds(x_lims) all_y_points.extend(these_y_points) return min(all_y_points), max(all_y_points)
def _node_of_top_line(hist: history.DB) -> typing.FrozenSet[int]: row_skeleton = history.NodePosition._all_nones()._replace( result_case_num=1) node_first_increment = list(hist.get_all_matching(row_skeleton)) if not node_first_increment: return frozenset() top_y = max(n.y for n in node_first_increment) EPS = 1e-6 top_row = [n for n in node_first_increment if abs(n.y - top_y) <= EPS] return frozenset(n.node_num for n in top_row)
def graph_frame(hist: history.DB, db_res_case: int, contour_key: history.ContourKey, ax: plt.Axes): row_skeleton = history.ColumnResult._all_nones()._replace( result_case_num=db_res_case) column_data = list(hist.get_all_matching(row_skeleton)) col_data_graph = [ cd for cd in column_data if cd.contour_key == contour_key ] ax.clear() graphed_something = False for yielded, base_col in ( (False, 'tab:blue'), (True, 'tab:orange'), ): # Fall back to NaNs, only override with the real data. x_to_cd = { cd.x: history.ColumnResult._nans_at(cd.x) for cd in col_data_graph } # Override with the real thing for cd in col_data_graph: if cd.yielded == yielded: x_to_cd[cd.x] = cd graphed_something = True res_to_plot = [cd for x, cd in sorted(x_to_cd.items())] x = [cd.x for cd in res_to_plot] y_min = [cd.minimum for cd in res_to_plot] y_mean = [cd.mean for cd in res_to_plot] y_max = [cd.maximum for cd in res_to_plot] ax.fill_between(x, y_min, y_max, color=base_col, alpha=0.2) yield_text = "Dilated" if yielded else "Undilated" ax.plot(x, y_mean, color=base_col, label=f"{contour_key.name} ({yield_text})") ax.legend() return graphed_something
def _get_graph_surface_profile_data( deviation_only: bool, top_nodes: typing.FrozenSet[int], hist: history.DB, db_res_case: int) -> typing.Iterable[LineData]: row_skeleton = history.NodePosition._all_nones()._replace( result_case_num=db_res_case) nodes_this_increment = list(hist.get_all_matching(row_skeleton)) nodes_top_line = [ n for n in nodes_this_increment if n.node_num in top_nodes ] top_curve = _get_surface_curve(nodes_top_line, hist, db_res_case) def sort_key(n): return n.x nodes_top_line.sort(key=sort_key) x_data = [n.x for n in nodes_top_line] if deviation_only: y_deviation = [n.y - top_curve(n.x) for n in nodes_top_line] yield LineData( x_data, y_deviation, 'black', f"Top Surface Deviation from {CurveCoefficients.curve_name()}", '-') else: y_data = [n.y for n in nodes_top_line] y_fit = [top_curve(x) for x in x_data] yield LineData( x_data, y_fit, 'tab:gray', f"{CurveCoefficients.curve_name()} best fit: {top_curve.make_nice_name()}", '--') yield LineData(x_data, y_data, 'black', f"Top Surface", '-')
def make_quadmesh( db: history.DB, contour_view: ContourView, ) -> holoviews.QuadMesh: """Make a single Quadmesh representation""" struct_data = get_regular_mesh_data(db) node_skeleton = history.NodePosition._all_nones()._replace( result_case_num=contour_view.db_case_num) node_coords = { node_pos.node_num: node_pos for node_pos in db.get_all_matching(node_skeleton) } # Node positions X = numpy.zeros(shape=struct_data.node_indices.shape) Y = numpy.zeros(shape=struct_data.node_indices.shape) for idx_x, along_y in enumerate(struct_data.node_indices): for idx_y, node_num in enumerate(along_y): X[idx_x, idx_y] = node_coords[node_num].x Y[idx_x, idx_y] = node_coords[node_num].y # The values are element centred. Z = numpy.zeros(shape=struct_data.elem_indices.shape) contour_val_skeleton = history.ContourValue._all_nones()._replace( result_case_num=contour_view.db_case_num, contour_key_num=contour_view.contour_key.value) contour_vals = { contour_val.elem_num: contour_val.value for contour_val in db.get_all_matching(contour_val_skeleton) } # TEMP - to get rasterize to work, need to make this node-centred. Z_nodal_shape = (4, ) + struct_data.node_indices.shape Z_nodal = numpy.empty(shape=Z_nodal_shape) Z_nodal[:] = numpy.nan for idx_x, along_y in enumerate(struct_data.elem_indices): for idx_y, elem_num in enumerate(along_y): c_val = contour_vals.get(elem_num, 0.0) Z[idx_x, idx_y] = c_val for layer, (z_nodal_idx_x, z_nodal_idx_y) in enumerate( itertools.product((idx_x, idx_x + 1), (idx_y, idx_y + 1))): Z_nodal[layer, z_nodal_idx_x, z_nodal_idx_y] = c_val Z_nodal_flat = numpy.nanmean(Z_nodal, axis=0) qmesh = holoviews.QuadMesh((X, Y, Z_nodal_flat), vdims='level', group=contour_view.title) qmesh.options(cmap='viridis') qmesh.opts(aspect='equal', line_width=0.1, padding=0.1, colorbar=True, width=FIG_SIZE[0], height=FIG_SIZE[1]) qmesh.data.hvplot.image('x', 'y', label=contour_view.title).options( width=FIG_SIZE[0], height=FIG_SIZE[1]) return qmesh
def get_regular_mesh_data(db: history.DB) -> StructuredMeshData: """Returns a numpy array of node indices for a structured mesh.""" elem_conn = db.get_element_connections() # Step 1 - get all the edges of plates. all_edges = [] min_elem_num = math.inf for elem_num, nodes in elem_conn.items(): if len(nodes) != 4: raise ValueError("Quad4 only at the moment") min_elem_num = min(min_elem_num, elem_num) for edge_idx in range(4): node_idx_1 = edge_idx node_idx_2 = (edge_idx + 1) % 4 one_edge = QuadPlateEdge( plate_num=elem_num, edge_idx=edge_idx, node_num_1=nodes[node_idx_1], node_num_2=nodes[node_idx_2], ) all_edges.append(one_edge) # Step 2 - orientationA and orientationB are perpendicular - one if horizontal and one is vert but we don't know which is which yet. sorted_global_node_to_edge = collections.defaultdict(set) for one_edge in all_edges: sorted_global_node_to_edge[one_edge.global_sorted_nodes].add(one_edge) elem_to_edges = collections.defaultdict(set) for one_edge in all_edges: elem_to_edges[one_edge.plate_num].add(one_edge) # These are perpendicular - one if horizontal and one is vert but we don't know which is which yet. ORI_A = 0 ORI_B = 1 edge_to_ori = dict() # Step 3 - populate the known edges by working a "frontier" def discard_edge_from_working_set(one_edge): sorted_global_node_to_edge[one_edge.global_sorted_nodes].discard( one_edge) elem_to_edges[one_edge.plate_num].discard(one_edge) # Start off arbitrary_edge = elem_to_edges[min_elem_num].pop() edge_to_ori[arbitrary_edge] = ORI_A discard_edge_from_working_set(arbitrary_edge) frontier_edges = { arbitrary_edge, } while frontier_edges: new_frontier = set() for one_edge in frontier_edges: # We can tell a relative orientation from the edge index. new_edges_from_elem = elem_to_edges[one_edge.plate_num] for one_new_edge in new_edges_from_elem: same_ori = one_new_edge.edge_idx % 2 == one_edge.edge_idx % 2 if same_ori: this_ori = edge_to_ori[one_edge] else: this_ori = (edge_to_ori[one_edge] + 1) % 2 edge_to_ori[one_new_edge] = this_ori new_frontier.add(one_new_edge) # We can also tell when an edge has the same orientation if it has the same global nodes. new_edges_from_nodes = sorted_global_node_to_edge[ one_edge.global_sorted_nodes] for one_new_edge in new_edges_from_nodes: this_ori = edge_to_ori[one_edge] edge_to_ori[one_new_edge] = this_ori new_frontier.add(one_new_edge) for one_new_edge in new_frontier: discard_edge_from_working_set(one_new_edge) frontier_edges.clear() frontier_edges.update(new_frontier) # Step 4 - find a corner nodes_on_edges = collections.defaultdict(set) for one_edge in all_edges: for n in [one_edge.node_num_1, one_edge.node_num_2]: nodes_on_edges[n].add(one_edge) corner_nodes = [ node_num for node_num, edges in nodes_on_edges.items() if len(edges) == 2 ] # Step 5 - send out "streamers" from the corner def make_streamer(starting_node: int, ori) -> typing.List[int]: """Get the nodes in a line.""" streamer = [starting_node] still_going = True while still_going: frontier_node = streamer[-1] if len(streamer) > 1: backwards_node = streamer[-2] else: backwards_node = None next_edges = [ edge for edge in nodes_on_edges[frontier_node] if edge_to_ori[edge] == ori and backwards_node not in edge.global_sorted_nodes ] if not next_edges: still_going = False else: next_edge = next_edges.pop() streamer.append(next_edge.opposing_node(frontier_node)) return streamer arbitrary_corner_node = min(corner_nodes) ori_to_streamer = { ori: make_streamer(arbitrary_corner_node, ori) for ori in [ORI_A, ORI_B] } len_to_ori = { len(streamer): ori for ori, streamer in ori_to_streamer.items() } X_ORI = max(len_to_ori.items())[1] Y_ORI = (X_ORI + 1) % 2 # Step 6 - Make the node indices for the structured mesh node_indices = numpy.zeros(shape=(len(ori_to_streamer[X_ORI]), len(ori_to_streamer[Y_ORI])), dtype=int) for y_idx, y_starting_node in enumerate(ori_to_streamer[Y_ORI]): x_streamer = make_streamer(y_starting_node, X_ORI) node_indices[:, y_idx] = x_streamer # Step 7 - element indices are determined based on the 2x2 patch and the nodes. sorted_nodes_to_elem = { tuple(sorted(conn)): elem_num for elem_num, conn in elem_conn.items() } elem_shape = (node_indices.shape[0] - 1, node_indices.shape[1] - 1) elem_indices = numpy.zeros(shape=elem_shape, dtype=int) x_idxs = range(node_indices.shape[0] - 1) y_idxs = range(node_indices.shape[1] - 1) for x_node_idx, y_node_idx in itertools.product(x_idxs, y_idxs): window = node_indices[x_node_idx:x_node_idx + 2, y_node_idx:y_node_idx + 2] sorted_nodes = tuple(sorted(window.flatten())) elem_num = sorted_nodes_to_elem[sorted_nodes] elem_indices[x_node_idx, y_node_idx] = elem_num return StructuredMeshData( node_indices=node_indices, elem_indices=elem_indices, )