Exemplo n.º 1
0
def make_node_plots(original_c: Config):
    """Make all variations of 3d scatter plots of nodes."""
    for damage_scenario in healthy_and_cracked_scenarios:
        c, sim_params = damage_scenario.use(original_c, SimParams([]))
        for ctx, ctx_name in [
            (BuildContext(add_loads=[Point(x=85, y=0, z=0)]), "refined"),
            (None, "unrefined"),
        ]:
            bridge_nodes = get_bridge_nodes(bridge=c.bridge, ctx=ctx)
            deck_nodes = set(flatten(bridge_nodes[0], Node))
            pier_nodes = set(flatten(bridge_nodes[1], Node))
            all_nodes = set(flatten(bridge_nodes, Node))
            # For each combination of parameters plot the nodes.
            for nodes_name, nodes in [
                ("all", all_nodes),
                ("deck", deck_nodes),
                ("pier", pier_nodes),
            ]:
                node_scatter_3d(nodes=nodes)
                plt.title(f"Nodes of {c.bridge.name}")
                plt.savefig(
                    c.get_image_path(
                        f"geometry/nodes-{ctx_name}",
                        safe_str(f"{nodes_name}") + ".pdf",
                    ))
                plt.close()
Exemplo n.º 2
0
    def load(
        c: Config,
        response_type: ResponseType,
        fem_runner: FEMRunner,
        save_all: bool = True,
    ):
        """Load a DCExpt from disk, running simulations first if necessary.

        Args:
            c: Config, global configuration object.
            response_type: ResponseType, the type of sensor response to load.
            fem_runner: FEMRunner, the FE program to run simulations with.
            save_all: bool, save all response types when running a simulation.

        """
        # id_str = f"dc-{response_type.name()}-{fem_runner.name}"

        # Determine experiment simulation parameters.
        expt_params = [
            SimParams(displacement_ctrl=PierSettlement(c.pd_unit_disp, i))
            for i in range(len(c.bridge.supports))
        ]

        return load_expt_responses(
            c=c,
            expt_params=expt_params,
            response_type=response_type,
        )
Exemplo n.º 3
0
def plot_of_unit_loads(c: Config):
    """Make a contour plot of response at unit load position."""
    fem_runner = OSRunner(c)
    response_type = ResponseType.YTranslation
    X, Z, R = [], [], []
    for x in np.linspace(c.bridge.x_min, c.bridge.x_max, int(c.bridge.length)):
        X.append([])
        Z.append([])
        R.append([])
        for z in np.linspace(c.bridge.z_min, c.bridge.z_max,
                             int(c.bridge.width)):
            pload = PointLoad(x_frac=c.bridge.x_frac(x),
                              z_frac=c.bridge.z_frac(z),
                              kn=100)
            fem_params = SimParams(ploads=[pload],
                                   response_types=[response_type])
            fem_responses = load_fem_responses(
                c=c,
                fem_params=fem_params,
                response_type=response_type,
                fem_runner=fem_runner,
            )
            X[-1].append(x)
            Z[-1].append(z)
            R[-1].append(fem_responses._at(x=x, y=0, z=z))

    cmap = get_cmap("bwr")
    plt.contourf(X, Z, R, levels=50, cmap=cmap)
    plt.show()
Exemplo n.º 4
0
    def use(
        self, c: Config, sim_params: SimParams = SimParams(),
    ) -> Tuple[Config, SimParams]:
        """Modify given Config and SimParams under this Scenario.

        The returned values are deep copies of the given objects.

        Args:
            c: simulation configuration object to maybe modify.
            sim_params: simulation parameters to maybe modify.
        """
        config_copy = copy(c)
        config_copy.bridge = self.mod_bridge(deepcopy(config_copy.bridge))
        sim_params_copy = self.mod_sim_params(deepcopy(sim_params))
        return config_copy, sim_params_copy
Exemplo n.º 5
0
def make_shell_properties_3d(original_c: Config):
    """Make plots of the shells in 3D, coloured by material property."""
    # For each scenarios scenario build the model and extract the shells.
    for damage_scenario in healthy_and_cracked_scenarios:
        c, sim_params = damage_scenario.use(original_c, SimParams([]))
        for ctx, ctx_name in [
            (BuildContext(add_loads=[Point(x=85, y=0, z=0)]), "refined"),
            (None, "unrefined"),
        ]:
            bridge_shells = get_bridge_shells(bridge=c.bridge, ctx=ctx)
            deck_shells = flatten(bridge_shells[0], Shell)
            pier_shells = flatten(bridge_shells[1], Shell)
            all_shells = flatten(bridge_shells, Shell)
            # For each combination of parameters plot the shells.
            for shells_name, shells in [
                ("pier", pier_shells),
                ("all", all_shells),
                ("deck", deck_shells),
            ]:
                for outline, label in itertools.product([True, False],
                                                        [True, False]):
                    for prop_name, prop_units, prop_f in [
                        ("Thickness", "m", lambda s: s.thickness),
                        ("Density", "kg/m", lambda s: s.density),
                        ("Poisson's ratio", "m/m", lambda s: s.poissons),
                        ("Young's modulus", "MPa", lambda s: s.youngs),
                    ]:
                        for cmap in [default_cmap, get_cmap("tab10")]:
                            shell_properties_3d(
                                shells=shells,
                                prop_units=prop_units,
                                prop_f=prop_f,
                                cmap=cmap,
                                outline=outline,
                                label=label,
                                colorbar=not label,
                            )
                            plt.title(f"{prop_name} of {c.bridge.name}")
                            plt.savefig(
                                c.get_image_path(
                                    f"geometry/shells-{ctx_name}-3d",
                                    safe_str(
                                        f"{shells_name}-{prop_name}-outline-{outline}-{cmap.name}"
                                    ) + ".pdf",
                                ))
                            plt.close()
Exemplo n.º 6
0
def cover_photo(c: Config, x: float, deformation_amp: float):
    """

    TODO: SimParams takes any loads iterable, to be flattened.
    TODO: Wrap SimRunner into Config.
    TODO: Ignore response type in SimParams (fill in by load_sim_responses).

    """
    response_type = ResponseType.YTranslation
    sim_responses = load_fem_responses(
        c=c,
        sim_runner=OSRunner(c),
        response_type=response_type,
        sim_params=SimParams(
            response_types=[response_type],
            ploads=list(
                chain.from_iterable(
                    truck1.to_point_loads(
                        bridge=c.bridge,
                        time=truck1.time_at(x=x, bridge=c.bridge),
                    ))),
        ),
    )
    shells = contour_responses_3d(c=c, sim_responses=sim_responses)
    for cmap in [
            parula_cmap,
            get_cmap("jet"),
            get_cmap("coolwarm"),
            get_cmap("viridis"),
    ]:
        contour_responses_3d(
            c=c,
            sim_responses=sim_responses,
            deformation_amp=deformation_amp,
            shells=shells,
            cmap=cmap,
        )
        plt.axis("off")
        plt.grid(False)
        plt.savefig(
            c.get_image_path(
                "cover-photo",
                f"cover-photo-deform-{deformation_amp}"
                f"-cmap-{cmap.name}.pdf",
            ))
        plt.close()
Exemplo n.º 7
0
def responses_to_loads_d(
        c: Config,
        response_type: ResponseType,
        points: List[Point],
        loads: List[List[PointLoad]],
        damage_scenario: Scenario = HealthyScenario(),
):
    """Responses to point-loads via direct simulation (not using superposition).
    """
    if not isinstance(damage_scenario, HealthyScenario):
        raise ValueError("Only HealthyDamage supported in direct simulation")
    expt_responses = load_expt_responses(
        c=c,
        expt_params=[SimParams(ploads=loads_) for loads_ in loads],
        response_type=response_type,
    )
    result = []
    for sim_responses in expt_responses:
        result.append(
            [sim_responses.at_deck(point, interp=True) for point in points])
        print_i("Interpolating fem in responses_from_load_d")
    return np.array(result)
Exemplo n.º 8
0
def load(
    config: LibConfig,
    response_type: ResponseType,
    point_loads: List[PointLoad] = [],
    pier_settlement: List[PierSettlement] = [],
):
    """Responses from a single linear simulation.

    The simulation is only run if results are not found on disk.

    Args:
        config: simulation configuration object.
        response_type: sensor response type to return.
        point_loads: a list of point-loads to apply.
        pier_settlement: a pier settlement to apply.
    """
    return load_fem_responses(
        c=config,
        sim_params=SimParams(ploads=point_loads,
                             pier_settlement=pier_settlement),
        response_type=response_type,
    )
Exemplo n.º 9
0
    def sim_model_path(
        self,
        sim_params: SimParams,
        ext: str,
        append: str = "",
        dirname: Optional[str] = None,
    ) -> str:
        """Deterministic path for a FE model file.

        :param sim_params: simulation parameters.
        :param ext: extension of the output file without the dot.
        :param dirname: directory name of output file. Defaults to FEMRunner.name.
        :param append: append to the filename (before the extension).
        :return: path for the output file.
        """
        param_str = sim_params.id_str()
        append = append if len(append) == 0 else f"-{append}"
        filename = f"{self.c.bridge.id_str()}-params={param_str}{append}"
        if dirname is None:
            dirname = self.name
        dirname = safe_str(dirname)
        return shorten_path(
            self.c,
            safe_str(self.c.get_data_path(dirname, filename)) + f".{ext}")
Exemplo n.º 10
0
 def mod_sim_params(sim_params: SimParams):
     sim_params.axial_delta_temp = self.axial_delta_temp
     sim_params.moment_delta_temp = self.moment_delta_temp
     return sim_params
Exemplo n.º 11
0
 def mod_sim_params(sim_params: SimParams):
     if len(self.pier_disps) > 1:
         raise ValueError("Cannot have SimParams with > 1 pier settlement")
     sim_params.displacement_ctrl = self.pier_disps[0]
     return sim_params
Exemplo n.º 12
0
def number_of_uls_plot(c: Config):
    """Plot error as a function of number of unit load simulations."""
    if not c.shorten_paths:
        raise ValueError("This plot requires --shorten-paths true")
    response_type = ResponseType.YTranslation
    num_ulss = np.arange(100, 2000, 10)
    chosen_uls = 600
    point = Point(x=c.bridge.x_max - (c.bridge.length / 2), y=0, z=-8.4)
    wagen1_time = truck1.time_at(x=point.x, bridge=c.bridge)
    print_i(f"Wagen 1 time at x = {point.x:.3f} is t = {wagen1_time:.3f}")

    # Determine the reference value.
    truck_loads = flatten(
        truck1.to_point_load_pw(time=wagen1_time, bridge=c.bridge), PointLoad)
    print_i(f"Truck loads = {truck_loads}")
    sim_responses = load_fem_responses(
        c=c,
        response_type=response_type,
        sim_runner=OSRunner(c),
        sim_params=SimParams(ploads=truck_loads,
                             response_types=[response_type]),
    )
    ref_value = sim_responses.at_deck(point, interp=True) * 1000
    print_i(f"Reference value = {ref_value}")

    # Collect the data.
    total_load = []
    num_loads = []
    responses = []
    for num_uls in num_ulss:
        c.il_num_loads = num_uls
        # Nested in here because it depends on the setting of 'il_num_loads'.
        truck_loads = flatten(
            truck1.to_wheel_track_loads(c=c, time=wagen1_time), PointLoad)
        num_loads.append(len(truck_loads))
        total_load.append(sum(map(lambda l: l.kn, truck_loads)))
        sim_responses = load_fem_responses(
            c=c,
            response_type=response_type,
            sim_runner=OSRunner(c),
            sim_params=SimParams(ploads=truck_loads,
                                 response_types=[response_type]),
        )
        responses.append(sim_responses.at_deck(point, interp=True) * 1000)

    # Plot the raw fem, then error on the second axis.
    plt.landscape()
    # plt.plot(num_ulss, fem)
    # plt.ylabel(f"{response_type.name().lower()} (mm)")
    plt.xlabel("ULS")
    error = np.abs(np.array(responses) - ref_value).flatten() * 100
    # ax2 = plt.twinx()
    plt.plot(num_ulss, error)
    plt.ylabel("Error (%)")
    plt.title(
        f"Error in {response_type.name()} to Truck 1 as a function of ULS")
    # Plot the chosen number of ULS.
    chosen_error = np.interp([chosen_uls], num_ulss, error)[0]
    plt.axhline(
        chosen_error,
        label=f"At {chosen_uls} ULS, error = {np.around(chosen_error, 2)} %",
        color="black",
    )
    plt.axhline(0,
                color="red",
                label="Response from direct simulation (no wheel tracks)")
    plt.legend()
    plt.tight_layout()
    plt.savefig(c.get_image_path("paramselection", "uls.pdf"))
    plt.close()
    # Additional verification plots.
    plt.plot(num_ulss, total_load)
    plt.savefig(c.get_image_path("paramselection",
                                 "uls-verify-total-load.pdf"))
    plt.close()
    plt.plot(num_ulss, num_loads)
    plt.savefig(c.get_image_path("paramselection", "uls-verify-num-loads.pdf"))
    plt.close()
Exemplo n.º 13
0
    def load_wheel_track(
        c: Config,
        response_type: ResponseType,
        fem_runner: FEMRunner,
        load_z_frac: float,
        run_only: bool,
        indices: Optional[List[int]] = None,
        left_only: bool = False,
        right_only: bool = False,
    ) -> List[Responses]:
        """Load a wheel track from disk, running simulations if necessary.

        NOTE: The result is a generator, not a list.

        Args:
            c: Config, global configuration object.
            response_type: ResponseType, type of sensor response to return.
            fem_runner: FEMRunner, program to run finite element simulations.
            load_z_frac: float, load position as a fraction of the transverse
                direction in [0 1].
            run_only: bool, only run the simulation, do not load results.
            left_only: bool, if true only run the left-hand-side of the wheel
                track. If true, right_only must be false and indices None.
            right_only: bool, if True only run the right-hand-side of the wheel
                track. If true, left_only must be false and indices None.

        """
        wheel_xs = c.bridge.wheel_track_xs(c)
        first_right_index = len(wheel_xs) // 2
        print(f"First right index = {first_right_index}")

        if left_only:
            assert not right_only
            assert indices is None
            wheel_xs = wheel_xs[:first_right_index]
        if right_only:
            assert not left_only
            assert indices is None
            wheel_xs = wheel_xs[first_right_index:]

        assert 0 <= load_z_frac <= 1
        # Determine experiment simulation parameters.
        expt_params = [
            SimParams(
                ploads=[
                    PointLoad(
                        x_frac=c.bridge.x_frac(x),
                        z_frac=load_z_frac,
                        kn=c.il_unit_load_kn,
                    )
                ],
                clean_build=True,
            ) for x in wheel_xs
        ]
        # Filter simulations, only running those in 'indices'.
        if indices is not None:
            expt_params.sim_params = [
                sp for i, sp in enumerate(expt_params.sim_params)
                if i in indices
            ]
        return load_expt_responses(
            c=c,
            expt_params=expt_params,
            response_type=response_type,
            sim_runner=fem_runner,
            run_only=run_only,
        )
Exemplo n.º 14
0
def comparison_plots_705(c: Config, run_only: bool, scatter: bool):
    """Make contour plots for all verification points on bridge 705."""
    # from classify.scenario.bridge import transverse_crack
    # c = transverse_crack().use(c)[0]
    positions = [
        # (52, -8.4, "a"),
        (34.95459, 26.24579 - 16.6, "a"),
        (51.25051, 16.6 - 16.6, "b"),
        (89.98269, 9.445789 - 16.6, "c"),
        (102.5037, 6.954211 - 16.6, "d"),
        # (34.95459, 29.22606 - 16.6, "a"),
        # (51.25051, 16.6 - 16.6, "b"),
        # (92.40638, 12.405 - 16.6, "c"),
        # (101.7649, 3.973938 - 16.6, "d"),
    ]
    diana_values = pd.read_csv("validation/diana-screenshots/min-max.csv")
    response_types = [ResponseType.YTranslation, ResponseType.Strain]
    # For each response type and loading position first create contour plots for
    # OpenSees. Then finally create subplots comparing to Diana.
    cmap = diana_cmap_r
    for load_x, load_z, label in positions:
        for response_type in response_types:
            # Setup the metadata.
            if response_type == ResponseType.YTranslation:
                rt_str = "displa"
                unit_str = "mm"
            elif response_type == ResponseType.Strain:
                rt_str = "strain"
                unit_str = "E-6"
            else:
                raise ValueError("Unsupported response type")
            row = diana_values[diana_values["name"] == f"{label}-{rt_str}"]
            dmin, dmax = float(row["dmin"]), float(row["dmax"])
            omin, omax = float(row["omin"]), float(row["omax"])
            amin, amax = max(dmin, omin), min(dmax, omax)
            levels = np.linspace(amin, amax, 16)

            # Create the OpenSees plot.
            loads = [
                PointLoad(
                    x_frac=c.bridge.x_frac(load_x),
                    z_frac=c.bridge.z_frac(load_z),
                    kn=100,
                )
            ]
            fem_responses = load_fem_responses(
                c=c,
                response_type=response_type,
                sim_runner=OSRunner(c),
                sim_params=SimParams(ploads=loads,
                                     response_types=response_types),
            )
            if run_only:
                continue
            title = (
                f"{response_type.name()} from a {loads[0].kn} kN point load at"
                + f"\nx = {load_x:.3f}m, z = {load_z:.3f}m, with ")
            save = lambda prefix: c.get_image_path(
                "validation/diana-comp",
                safe_str(f"{prefix}{response_type.name()}") + ".pdf",
            )
            top_view_bridge(c.bridge, piers=True, abutments=True)
            fem_responses = fem_responses.resize()
            sci_format = response_type == ResponseType.Strain
            plot_contour_deck(
                c=c,
                responses=fem_responses,
                ploads=loads,
                cmap=cmap,
                levels=levels,
                sci_format=sci_format,
                decimals=4,
                scatter=scatter,
            )
            plt.title(title + "OpenSees")
            plt.tight_layout()
            plt.savefig(save(f"{label}-"))
            plt.close()

            # Finally create label/title the Diana plot.
            if label is not None:
                # First plot and clear, just to have the same colorbar.
                plot_contour_deck(c=c,
                                  responses=fem_responses,
                                  ploads=loads,
                                  cmap=cmap,
                                  levels=levels)
                plt.cla()
                # Then plot the bridge and
                top_view_bridge(c.bridge, piers=True, abutments=True)
                plt.imshow(
                    mpimg.imread(
                        f"validation/diana-screenshots/{label}-{rt_str}.png"),
                    extent=(
                        c.bridge.x_min,
                        c.bridge.x_max,
                        c.bridge.z_min,
                        c.bridge.z_max,
                    ),
                )
                dmin_s = f"{dmin:.4e}" if sci_format else f"{dmin:.4f}"
                dmax_s = f"{dmax:.4e}" if sci_format else f"{dmax:.4f}"
                dabs_s = (f"{abs(dmin - dmax):.4e}"
                          if sci_format else f"{abs(dmin - dmax):.4f}")
                for point, leg_label, color, alpha in [
                    ((load_x, load_z), f"{loads[0].kn} kN load", "r", 1),
                    ((0, 0), f"min = {dmin_s} {fem_responses.units}", "r", 0),
                    ((0, 0), f"max = {dmax_s} {fem_responses.units}", "r", 0),
                    ((0, 0), f"|min-max| = {dabs_s} {fem_responses.units}",
                     "r", 0),
                ]:
                    plt.scatter(
                        [point[0]],
                        [point[1]],
                        label=leg_label,
                        marker="o",
                        color=color,
                        alpha=alpha,
                    )
                plt.legend()
                plt.title(title + "Diana")
                plt.xlabel("X position (m)")
                plt.ylabel("Z position (m)")
                plt.tight_layout()
                plt.savefig(save(f"{label}-diana-"))
                plt.close()
Exemplo n.º 15
0
def piers_displaced(c: Config):
    """Contour plots of pier displacement for the given pier indices."""
    pier_indices = [4, 5]
    response_types = [ResponseType.YTranslation, ResponseType.Strain]
    axis_values = pd.read_csv("validation/axis-screenshots/piers-min-max.csv")
    for r_i, response_type in enumerate(response_types):
        for p in pier_indices:
            # Run the simulation and collect fem.
            sim_responses = load_fem_responses(
                c=c,
                response_type=response_type,
                sim_runner=OSRunner(c),
                sim_params=SimParams(displacement_ctrl=PierSettlement(
                    displacement=c.pd_unit_disp, pier=p), ),
            )

            # In the case of stress we map from kn/m2 to kn/mm2 (E-6) and then
            # divide by 1000, so (E-9).
            assert c.pd_unit_disp == 1
            if response_type == ResponseType.Strain:
                sim_responses.to_stress(c.bridge).map(lambda r: r * 1e-9)

            # Get min and max values for both Axis and OpenSees.
            rt_str = ("displa" if response_type == ResponseType.YTranslation
                      else "stress")
            row = axis_values[axis_values["name"] == f"{p}-{rt_str}"]
            dmin, dmax = float(row["dmin"]), float(row["dmax"])
            omin, omax = float(row["omin"]), float(row["omax"])
            amin, amax = max(dmin, omin), min(dmax, omax)
            levels = np.linspace(amin, amax, 16)

            # Plot and save the image. If plotting strains use Axis values for
            # colour normalization.
            # norm = None
            from plot import axis_cmap_r

            cmap = axis_cmap_r
            top_view_bridge(c.bridge, abutments=True, piers=True)
            plot_contour_deck(c=c,
                              cmap=cmap,
                              responses=sim_responses,
                              levels=levels)
            plt.tight_layout()
            plt.title(
                f"{sim_responses.response_type.name()} from 1mm pier settlement with OpenSees"
            )
            plt.savefig(
                c.get_image_path(
                    "validation/pier-displacement",
                    safe_str(f"pier-{p}-{sim_responses.response_type.name()}")
                    + ".pdf",
                ))
            plt.close()

            # First plot and clear, just to have the same colorbar.
            plot_contour_deck(c=c,
                              responses=sim_responses,
                              cmap=cmap,
                              levels=levels)
            plt.cla()
            # Save the axis plots.
            axis_img = mpimg.imread(
                f"validation/axis-screenshots/{p}-{rt_str}.png")
            top_view_bridge(c.bridge, abutments=True)
            plt.imshow(
                axis_img,
                extent=(
                    c.bridge.x_min,
                    c.bridge.x_max,
                    c.bridge.z_min,
                    c.bridge.z_max,
                ),
            )
            # Plot the load and min, max values.
            for point, leg_label, color in [
                ((0, 0), f"min = {np.around(dmin, 3)} {sim_responses.units}",
                 "r"),
                ((0, 0), f"max = {np.around(dmax, 3)} {sim_responses.units}",
                 "r"),
                (
                    (0, 0),
                    f"|min-max| = {np.around(abs(dmax - dmin), 3)} {sim_responses.units}",
                    "r",
                ),
            ]:
                plt.scatter(
                    [point[0]],
                    [point[1]],
                    label=leg_label,
                    marker="o",
                    color=color,
                    alpha=0,
                )
            if response_type == ResponseType.YTranslation:
                plt.legend()
            # Title and save.
            plt.title(
                f"{response_type.name()} from 1mm pier settlement with AxisVM")
            plt.xlabel("X position (m)")
            plt.ylabel("Z position (m)")
            plt.tight_layout()
            plt.savefig(
                c.get_image_path(
                    "validation/pier-displacement",
                    f"{p}-axis-{rt_str}.pdf",
                ))
            plt.close()
Exemplo n.º 16
0
def point_load_response_plots(c: Config,
                              x: float,
                              z: float,
                              kn: int = 1000,
                              run: bool = False):
    """Response to a point load per scenarios scenario."""
    response_types = [ResponseType.YTranslation, ResponseType.Strain]
    # scenarios = all_scenarios(c)
    damage_scenarios = [HealthyScenario(), transverse_crack()]

    # 10 x 10 grid of points on the bridge deck where to record fem.
    points = [
        Point(x=x, y=0, z=z) for x, z in itertools.product(
            np.linspace(c.bridge.x_min, c.bridge.x_max, 30),
            np.linspace(c.bridge.z_min, c.bridge.z_max, 100),
        )
    ]

    for response_type in response_types:
        all_responses = []
        for damage_scenario in damage_scenarios:
            sim_params = SimParams(
                response_types=[response_type],
                ploads=[
                    PointLoad(x_frac=c.bridge.x_frac(x),
                              z_frac=c.bridge.z_frac(z),
                              kn=kn)
                ],
            )
            use_c, sim_params = damage_scenario.use(c=c, sim_params=sim_params)
            all_responses.append(
                load_fem_responses(
                    c=use_c,
                    sim_params=sim_params,
                    response_type=response_type,
                    sim_runner=OSRunner(use_c),
                    run=run,
                ).resize())
        amin, amax = np.inf, -np.inf
        for sim_responses in all_responses:
            responses = np.array(list(sim_responses.values()))
            amin = min(amin, min(responses))
            amax = max(amax, max(responses))
        for d, damage_scenario in enumerate(damage_scenarios):
            top_view_bridge(c.bridge, abutments=True, piers=True)
            plot_contour_deck(
                c=c,
                responses=all_responses[d],
                levels=100,
                norm=colors.Normalize(vmin=amin, vmax=amax),
                decimals=10,
            )
            plt.title(damage_scenario.name)
            plt.tight_layout()
            plt.savefig(
                c.get_image_path(
                    "contour/point-load",
                    safe_str(
                        f"x-{x:.2f}-z-{z:.2f}-kn-{kn}-{response_type.name()}-{damage_scenario.name}"
                    ) + ".pdf",
                ))
            plt.close()