From 8cdd8ba52a0cb81ac9217bda99b6ea57c2a61260 Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Tue, 8 Apr 2025 10:09:46 -0500 Subject: [PATCH 01/29] refactor of nn opinf from previous branch --- Project.toml | 1 + src/ics_bcs.jl | 73 ++++++++++++++++++++++++++++++++++++++++++++ src/ics_bcs_types.jl | 13 ++++++++ src/model.jl | 48 +++++++++++++++++++++++++++++ src/model_types.jl | 17 +++++++++++ src/solver.jl | 37 ++++++++++++++++------ 6 files changed, 180 insertions(+), 9 deletions(-) diff --git a/Project.toml b/Project.toml index f34651a1..cf52a672 100644 --- a/Project.toml +++ b/Project.toml @@ -20,6 +20,7 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6" +PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" [compat] DelimitedFiles = "1" diff --git a/src/ics_bcs.jl b/src/ics_bcs.jl index adc924f7..41e6920a 100644 --- a/src/ics_bcs.jl +++ b/src/ics_bcs.jl @@ -3,6 +3,8 @@ # the U.S. Government retains certain rights in this software. This software # is released under the BSD license detailed in the file license.txt in the # top-level Norma.jl directory. +using PyCall +import NPZ @variables t, x, y, z D = Differential(t) @@ -20,6 +22,45 @@ function SMDirichletBC(input_mesh::ExodusDatabase, bc_params::Parameters) return SMDirichletBC(node_set_name, offset, node_set_id, node_set_node_indices, disp_num, velo_num, acce_num) end +function SMOpInfDirichletBC(input_mesh::ExodusDatabase, bc_params::Dict{String,Any}) + fom_bc = SMDirichletBC(input_mesh,bc_params) + node_set_name = bc_params["node set"] + expression = bc_params["function"] + offset = component_offset_from_string(bc_params["component"]) + node_set_id = node_set_id_from_name(node_set_name, input_mesh) + node_set_node_indices = Exodus.read_node_set_nodes(input_mesh, node_set_id) + # expression is an arbitrary function of t, x, y, z in the input file + disp_num = eval(Meta.parse(expression)) + velo_num = expand_derivatives(D(disp_num)) + acce_num = expand_derivatives(D(velo_num)) + + opinf_model_file = bc_params["model-file"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + model = py"get_model"(opinf_model_file) + + opinf_model_file = bc_params["model-file"] + basis_file = bc_params["basis-file"] + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + + SMOpInfDirichletBC( + node_set_name, + offset, + node_set_id, + node_set_node_indices, + disp_num, + velo_num, + acce_num, + fom_bc, + model, + basis, + ) +end + function SMDirichletInclined(input_mesh::ExodusDatabase, bc_params::Parameters) node_set_name = bc_params["node set"] expression = bc_params["function"] @@ -267,6 +308,35 @@ function apply_bc(model::OpInfModel, bc::SMDirichletBC) return model.reduced_boundary_forcing[:] += bc_operator[1, :, :] * bc_vector end +function apply_bc(model::NeuralNetworkOpInfRom, bc::SMOpInfDirichletBC) + model.fom_model.time = model.time + apply_bc(model.fom_model,bc.fom_bc) + bc_vector = zeros(0) + for node_index ∈ bc.fom_bc.node_set_node_indices + dof_index = 3 * (node_index - 1) + bc.fom_bc.offset + disp_val = model.fom_model.current[bc.fom_bc.offset,node_index] - model.fom_model.reference[bc.fom_bc.offset, node_index] + push!(bc_vector,disp_val) + end + + py""" + import numpy as np + def setup_inputs(x): + xi = np.zeros((1,x.size)) + xi[0] = x + inputs = torch.tensor(xi) + return inputs + """ + + reduced_bc_vector = bc.basis[1,:,:]' * bc_vector + print(size(reduced_bc_vector),size(bc.basis),size(bc_vector)) + model_inputs = py"setup_inputs"(reduced_bc_vector) + reduced_forcing = bc.nn_model.forward(model_inputs) + reduced_forcing = reduced_forcing.detach().numpy()[1,:] + # SM Dirichlet BC are only defined on a single x,y,z + model.reduced_boundary_forcing[:] += reduced_forcing + end + + function apply_bc(model::SolidMechanics, bc::SMDirichletBC) for node_index in bc.node_set_node_indices values = Dict( @@ -861,6 +931,9 @@ function create_bcs(params::Parameters) if bc_type == "Dirichlet" boundary_condition = SMDirichletBC(input_mesh, bc_setting_params) push!(boundary_conditions, boundary_condition) + elseif bc_type == "OpInf Dirichlet" + boundary_condition = SMOpInfDirichletBC(input_mesh, bc_setting_params) + push!(boundary_conditions, boundary_condition) elseif bc_type == "Neumann" boundary_condition = SMNeumannBC(input_mesh, bc_setting_params) push!(boundary_conditions, boundary_condition) diff --git a/src/ics_bcs_types.jl b/src/ics_bcs_types.jl index d450e14e..8bce0f32 100644 --- a/src/ics_bcs_types.jl +++ b/src/ics_bcs_types.jl @@ -26,6 +26,19 @@ mutable struct SMDirichletBC <: RegularBoundaryCondition acce_num::Num end +mutable struct SMOpInfDirichletBC <: RegularBoundaryCondition + node_set_name::String + offset::Int64 + node_set_id::Int64 + node_set_node_indices::Vector{Int64} + disp_num::Num + velo_num::Num + acce_num::Num + fom_bc::SMDirichletBC + nn_model::Any + basis::Any +end + mutable struct SMDirichletInclined <: RegularBoundaryCondition node_set_name::String node_set_id::Int64 diff --git a/src/model.jl b/src/model.jl index 27aad795..d7492ebc 100644 --- a/src/model.jl +++ b/src/model.jl @@ -10,6 +10,7 @@ include("ics_bcs.jl") using Base.Threads: @threads, threadid, nthreads using NPZ +using PyCall function LinearOpInfRom(params::Parameters) params["mesh smoothing"] = false @@ -81,6 +82,51 @@ function QuadraticOpInfRom(params::Parameters) ) end +function NeuralNetworkOpInfRom(params::Dict{String,Any}) + params["mesh smoothing"] = false + fom_model = SolidMechanics(params) + reference = fom_model.reference + opinf_model_file = params["model"]["model-file"] + + basis_file = params["model"]["basis-file"] + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + model = py"get_model"(opinf_model_file) + num_dofs_per_node,num_nodes_basis,reduced_dim = size(basis) + num_dofs = reduced_dim + + time = 0.0 + failed = false + null_vec = zeros(num_dofs) + + reduced_state = zeros(num_dofs) + reduced_velocity = zeros(num_dofs) + reduced_boundary_forcing = zeros(num_dofs) + free_dofs = trues(num_dofs) + boundary_conditions = Vector{BoundaryCondition}() + NeuralNetworkOpInfRom( + model, + basis, + reduced_state, + reduced_velocity, + reduced_boundary_forcing, + null_vec, + free_dofs, + boundary_conditions, + time, + failed, + fom_model, + reference, + false + ) +end + + function SolidMechanics(params::Parameters) input_mesh = params["input_mesh"] model_params = params["model"] @@ -309,6 +355,8 @@ function create_model(params::Parameters) return LinearOpInfRom(params) elseif model_name == "quadratic opinf rom" return QuadraticOpInfRom(params) + elseif model_name == "neural network opinf rom" + return NeuralNetworkOpInfRom(params) else error("Unknown type of model : ", model_name) diff --git a/src/model_types.jl b/src/model_types.jl index 4cfeb5ca..24ed3026 100644 --- a/src/model_types.jl +++ b/src/model_types.jl @@ -108,3 +108,20 @@ mutable struct LinearOpInfRom <: OpInfModel reference::Matrix{Float64} inclined_support::Bool end + +mutable struct NeuralNetworkOpInfRom <: OpInfModel + nn_model::Any + basis::Array{Float64} + reduced_state::Vector{Float64} + reduced_velocity::Vector{Float64} + reduced_boundary_forcing::Vector{Float64} + #internal_force not used, but include to ease interfacing in Schwarz + internal_force::Vector{Float64} + free_dofs::BitVector + boundary_conditions::Vector{BoundaryCondition} + time::Float64 + failed::Bool + fom_model::SolidMechanics + reference::Matrix{Float64} + inclined_support::Bool +end diff --git a/src/solver.jl b/src/solver.jl index da977716..ebff97d3 100644 --- a/src/solver.jl +++ b/src/solver.jl @@ -410,15 +410,6 @@ function evaluate(integrator::Newmark, solver::HessianMinimizer, model::LinearOp beta = integrator.β gamma = integrator.γ dt = integrator.time_step - - #Ax* = b - # Put in residual format - #e = [x* - x] -> x* = x + e - #Ax + Ae = b - #Ax - b = -Ae - #Ae = r, r = b - Ax - ##M uddot + Ku = f - num_dof = length(model.free_dofs) I = Matrix{Float64}(LinearAlgebra.I, num_dof, num_dof) LHS = I / (dt * dt * beta) + Matrix{Float64}(model.opinf_rom["K"]) @@ -429,6 +420,34 @@ function evaluate(integrator::Newmark, solver::HessianMinimizer, model::LinearOp return solver.gradient[:] = -residual end +### Move to model? +function evaluate(integrator::Newmark, solver::HessianMinimizer, model::NeuralNetworkOpInfRom) + beta = integrator.β + gamma = integrator.γ + dt = integrator.time_step + num_dof, = size(model.free_dofs) + I = Matrix{Float64}(LinearAlgebra.I, num_dof,num_dof) + py""" + import numpy as np + def setup_inputs(x): + xi = np.zeros((1,x.size)) + xi[0] = x + inputs = torch.tensor(xi) + return inputs + """ + model_inputs = py"setup_inputs"(solver.solution) + Kx,K = model.nn_model.forward(model_inputs,return_stiffness=true) + + LHS = I / (dt*dt*beta) - K.detach().numpy()[1,:,:] + RHS = model.reduced_boundary_forcing + 1.0/(dt*dt*beta).*integrator.disp_pre + + residual = RHS - LHS * solver.solution + solver.hessian[:,:] = LHS + solver.gradient[:] = -residual +end + + + function evaluate(integrator::QuasiStatic, solver::HessianMinimizer, model::SolidMechanics) evaluate(integrator, model) if model.failed == true From f44662f8701b50a94a32194731a9cadc975595aa Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Mon, 14 Apr 2025 11:49:58 -0500 Subject: [PATCH 02/29] added NN for Schwarz --- src/ics_bcs.jl | 85 ++++++++++++++++++++++++++++++++++++++++++++ src/ics_bcs_types.jl | 15 ++++++++ 2 files changed, 100 insertions(+) diff --git a/src/ics_bcs.jl b/src/ics_bcs.jl index 41e6920a..a155193b 100644 --- a/src/ics_bcs.jl +++ b/src/ics_bcs.jl @@ -61,6 +61,80 @@ function SMOpInfDirichletBC(input_mesh::ExodusDatabase, bc_params::Dict{String,A ) end + +function SMOpInfCouplingSchwarzBC( + subsim::SingleDomainSimulation, + coupled_subsim::SingleDomainSimulation, + input_mesh::ExodusDatabase, + bc_type::String, + bc_params::Parameters, +) + fom_bc = SMCouplingSchwarzBC(subsim,coupled_subsim,input_mesh,bc_type,bc_params) + side_set_name = bc_params["side set"] + side_set_id = side_set_id_from_name(side_set_name, input_mesh) + _, _, side_set_node_indices = get_side_set_local_from_global_map(input_mesh, side_set_id) + coupled_block_name = bc_params["source block"] + if typeof(coupled_subsim.model) <: RomModel + coupled_mesh = coupled_subsim.model.fom_model.mesh + else + coupled_mesh = coupled_subsim.model.mesh + end + coupled_block_id = block_id_from_name(coupled_block_name, coupled_mesh) + element_type = Exodus.read_block_parameters(coupled_mesh, coupled_block_id)[1] + coupled_side_set_name = bc_params["source side set"] + coupled_side_set_id = side_set_id_from_name(coupled_side_set_name, coupled_mesh) + coupled_nodes_indices = Vector{Vector{Int64}}(undef, 0) + interpolation_function_values = Vector{Vector{Float64}}(undef, 0) + tol = 1.0e-06 + if haskey(bc_params, "search tolerance") == true + tol = bc_params["search tolerance"] + end + side_set_node_indices = unique(side_set_node_indices) + for node_index in side_set_node_indices + point = subsim.model.reference[:, node_index] + node_indices, ξ, found = find_point_in_mesh(point, coupled_subsim.model, coupled_block_id, tol) + if found == false + error("Could not find subdomain ", subsim.name, " point ", point, " in subdomain ", coupled_subsim.name) + end + N = interpolate(element_type, ξ)[1] + push!(coupled_nodes_indices, node_indices) + push!(interpolation_function_values, N) + end + is_dirichlet = true + swap_bcs = false + + opinf_model_file = bc_params["model-file"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + model = py"get_model"(opinf_model_file) + + opinf_model_file = bc_params["model-file"] + basis_file = bc_params["basis-file"] + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + + if bc_type == "OpInf Schwarz overlap" + SMOpInfOverlapSchwarzBC( + side_set_name, + side_set_node_indices, + coupled_nodes_indices, + interpolation_function_values, + coupled_subsim, + subsim, + is_dirichlet, + swap_bcs, + model, + basis + ) + else + error("Unknown boundary condition type : ", bc_type) + end +end + + function SMDirichletInclined(input_mesh::ExodusDatabase, bc_params::Parameters) node_set_name = bc_params["node set"] expression = bc_params["function"] @@ -959,6 +1033,17 @@ function create_bcs(params::Parameters) coupled_subsim = sim.subsims[coupled_subdomain_index] boundary_condition = SMCouplingSchwarzBC(subsim, coupled_subsim, input_mesh, bc_type, bc_setting_params) push!(boundary_conditions, boundary_condition) + elseif bc_type == "OpInf Schwarz overlap" + sim = params["global_simulation"] + subsim_name = params["name"] + subdomain_index = sim.subsim_name_index_map[subsim_name] + subsim = sim.subsims[subdomain_index] + coupled_subsim_name = bc_setting_params["source"] + coupled_subdomain_index = sim.subsim_name_index_map[coupled_subsim_name] + coupled_subsim = sim.subsims[coupled_subdomain_index] + boundary_condition = SMOpInfCouplingSchwarzBC(subsim, coupled_subsim, input_mesh, bc_type, bc_setting_params) + push!(boundary_conditions, boundary_condition) + else error("Unknown boundary condition type : ", bc_type) end diff --git a/src/ics_bcs_types.jl b/src/ics_bcs_types.jl index 8bce0f32..586a1d6e 100644 --- a/src/ics_bcs_types.jl +++ b/src/ics_bcs_types.jl @@ -87,6 +87,21 @@ mutable struct SMOverlapSchwarzBC <: OverlapSchwarzBoundaryCondition swap_bcs::Bool end +mutable struct SMOpInfOverlapSchwarzBC <: OverlapSchwarzBoundaryCondition + side_set_name::String + side_set_node_indices::Vector{Int64} + coupled_nodes_indices::Vector{Vector{Int64}} + interpolation_function_values::Vector{Vector{Float64}} + coupled_subsim::Simulation + subsim::Simulation + is_dirichlet::Bool + swap_bcs::Bool + fom_bc::SMOverlapSchwarzBC + nn_model::Any + basis::Any +end + + mutable struct SMNonOverlapSchwarzBC <: NonOverlapSchwarzBoundaryCondition side_set_id::Int64 side_set_node_indices::Vector{Int64} From 032c46ff7b8c1300c02a6d1df1f2a1bb3df3db9c Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Tue, 15 Apr 2025 16:00:25 -0500 Subject: [PATCH 03/29] switched to ensemble nn formulation --- src/model.jl | 12 ++++++++---- src/model_types.jl | 17 +++++++++++++++++ src/solver.jl | 16 +++++++++++++--- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/model.jl b/src/model.jl index d7492ebc..c76bafef 100644 --- a/src/model.jl +++ b/src/model.jl @@ -86,9 +86,8 @@ function NeuralNetworkOpInfRom(params::Dict{String,Any}) params["mesh smoothing"] = false fom_model = SolidMechanics(params) reference = fom_model.reference - opinf_model_file = params["model"]["model-file"] - - basis_file = params["model"]["basis-file"] + opinf_model_directory = params["model"]["model-directory"] + basis_file = opinf_model_directory * "/nn-opinf-basis.npz" basis = NPZ.npzread(basis_file) basis = basis["basis"] py""" @@ -96,7 +95,12 @@ function NeuralNetworkOpInfRom(params::Dict{String,Any}) def get_model(model_file): return torch.load(model_file) """ - model = py"get_model"(opinf_model_file) + ensemble_size = params["model"]["ensemble-size"] + model = [] + for i in 1:ensemble_size + tmp = py"get_model"(opinf_model_directory * "/stiffness-" * string(i-1) * ".pt") + push!(model,tmp) + end num_dofs_per_node,num_nodes_basis,reduced_dim = size(basis) num_dofs = reduced_dim diff --git a/src/model_types.jl b/src/model_types.jl index 24ed3026..4d41594b 100644 --- a/src/model_types.jl +++ b/src/model_types.jl @@ -92,6 +92,23 @@ mutable struct QuadraticOpInfRom <: OpInfModel inclined_support::Bool end +mutable struct GalerkinRom <: OpInfModel + opinf_rom::Dict{Any,Any} + basis::Array{Float64} + reduced_state::Vector{Float64} + reduced_velocity::Vector{Float64} + reduced_boundary_forcing::Vector{Float64} + #internal_force not used, but include to ease interfacing in Schwarz + internal_force::Vector{Float64} + free_dofs::BitVector + boundary_conditions::Vector{BoundaryCondition} + time::Float64 + failed::Bool + fom_model::SolidMechanics + reference::Matrix{Float64} + inclined_support::Bool +end + mutable struct LinearOpInfRom <: OpInfModel opinf_rom::Dict{Any,Any} basis::Array{Float64} diff --git a/src/solver.jl b/src/solver.jl index ebff97d3..cee53fc7 100644 --- a/src/solver.jl +++ b/src/solver.jl @@ -436,9 +436,19 @@ function evaluate(integrator::Newmark, solver::HessianMinimizer, model::NeuralNe return inputs """ model_inputs = py"setup_inputs"(solver.solution) - Kx,K = model.nn_model.forward(model_inputs,return_stiffness=true) - - LHS = I / (dt*dt*beta) - K.detach().numpy()[1,:,:] + ensemble_size = size(model.nn_model)[1] + stiffness = zeros( num_dof,num_dof ) + #Kx,K = model.nn_model[1].forward(model_inputs,return_stiffness=true) + #K = K.detach().numpy()[1,:,:] + for i in 1:ensemble_size + Kxt,Kt = model.nn_model[i].forward(model_inputs,return_stiffness=true) + #Kx += Kxt + Kt = Kt.detach().numpy()[1,:,:] + stiffness += Kt + + end + stiffness = stiffness./ensemble_size + LHS = I / (dt*dt*beta) - stiffness RHS = model.reduced_boundary_forcing + 1.0/(dt*dt*beta).*integrator.disp_pre residual = RHS - LHS * solver.solution From dc342613efea91621828c16bd3ca9aa9862986b5 Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 17 Apr 2025 14:25:27 -0500 Subject: [PATCH 04/29] refactored opinf code to live in its own directory --- src/ics_bcs.jl | 300 ------------------------- src/model.jl | 122 +--------- src/model_types.jl | 68 ------ src/opinf/opinf_ics_bcs.jl | 370 +++++++++++++++++++++++++++++++ src/opinf/opinf_ics_bcs_types.jl | 47 ++++ src/opinf/opinf_model.jl | 129 +++++++++++ src/opinf/opinf_model_types.jl | 60 +++++ src/simulation.jl | 2 + src/simulation_types.jl | 2 + 9 files changed, 611 insertions(+), 489 deletions(-) create mode 100644 src/opinf/opinf_ics_bcs.jl create mode 100644 src/opinf/opinf_ics_bcs_types.jl create mode 100644 src/opinf/opinf_model.jl create mode 100644 src/opinf/opinf_model_types.jl diff --git a/src/ics_bcs.jl b/src/ics_bcs.jl index a155193b..269ef965 100644 --- a/src/ics_bcs.jl +++ b/src/ics_bcs.jl @@ -22,119 +22,6 @@ function SMDirichletBC(input_mesh::ExodusDatabase, bc_params::Parameters) return SMDirichletBC(node_set_name, offset, node_set_id, node_set_node_indices, disp_num, velo_num, acce_num) end -function SMOpInfDirichletBC(input_mesh::ExodusDatabase, bc_params::Dict{String,Any}) - fom_bc = SMDirichletBC(input_mesh,bc_params) - node_set_name = bc_params["node set"] - expression = bc_params["function"] - offset = component_offset_from_string(bc_params["component"]) - node_set_id = node_set_id_from_name(node_set_name, input_mesh) - node_set_node_indices = Exodus.read_node_set_nodes(input_mesh, node_set_id) - # expression is an arbitrary function of t, x, y, z in the input file - disp_num = eval(Meta.parse(expression)) - velo_num = expand_derivatives(D(disp_num)) - acce_num = expand_derivatives(D(velo_num)) - - opinf_model_file = bc_params["model-file"] - py""" - import torch - def get_model(model_file): - return torch.load(model_file) - """ - model = py"get_model"(opinf_model_file) - - opinf_model_file = bc_params["model-file"] - basis_file = bc_params["basis-file"] - basis = NPZ.npzread(basis_file) - basis = basis["basis"] - - SMOpInfDirichletBC( - node_set_name, - offset, - node_set_id, - node_set_node_indices, - disp_num, - velo_num, - acce_num, - fom_bc, - model, - basis, - ) -end - - -function SMOpInfCouplingSchwarzBC( - subsim::SingleDomainSimulation, - coupled_subsim::SingleDomainSimulation, - input_mesh::ExodusDatabase, - bc_type::String, - bc_params::Parameters, -) - fom_bc = SMCouplingSchwarzBC(subsim,coupled_subsim,input_mesh,bc_type,bc_params) - side_set_name = bc_params["side set"] - side_set_id = side_set_id_from_name(side_set_name, input_mesh) - _, _, side_set_node_indices = get_side_set_local_from_global_map(input_mesh, side_set_id) - coupled_block_name = bc_params["source block"] - if typeof(coupled_subsim.model) <: RomModel - coupled_mesh = coupled_subsim.model.fom_model.mesh - else - coupled_mesh = coupled_subsim.model.mesh - end - coupled_block_id = block_id_from_name(coupled_block_name, coupled_mesh) - element_type = Exodus.read_block_parameters(coupled_mesh, coupled_block_id)[1] - coupled_side_set_name = bc_params["source side set"] - coupled_side_set_id = side_set_id_from_name(coupled_side_set_name, coupled_mesh) - coupled_nodes_indices = Vector{Vector{Int64}}(undef, 0) - interpolation_function_values = Vector{Vector{Float64}}(undef, 0) - tol = 1.0e-06 - if haskey(bc_params, "search tolerance") == true - tol = bc_params["search tolerance"] - end - side_set_node_indices = unique(side_set_node_indices) - for node_index in side_set_node_indices - point = subsim.model.reference[:, node_index] - node_indices, ξ, found = find_point_in_mesh(point, coupled_subsim.model, coupled_block_id, tol) - if found == false - error("Could not find subdomain ", subsim.name, " point ", point, " in subdomain ", coupled_subsim.name) - end - N = interpolate(element_type, ξ)[1] - push!(coupled_nodes_indices, node_indices) - push!(interpolation_function_values, N) - end - is_dirichlet = true - swap_bcs = false - - opinf_model_file = bc_params["model-file"] - py""" - import torch - def get_model(model_file): - return torch.load(model_file) - """ - model = py"get_model"(opinf_model_file) - - opinf_model_file = bc_params["model-file"] - basis_file = bc_params["basis-file"] - basis = NPZ.npzread(basis_file) - basis = basis["basis"] - - if bc_type == "OpInf Schwarz overlap" - SMOpInfOverlapSchwarzBC( - side_set_name, - side_set_node_indices, - coupled_nodes_indices, - interpolation_function_values, - coupled_subsim, - subsim, - is_dirichlet, - swap_bcs, - model, - basis - ) - else - error("Unknown boundary condition type : ", bc_type) - end -end - - function SMDirichletInclined(input_mesh::ExodusDatabase, bc_params::Parameters) node_set_name = bc_params["node set"] expression = bc_params["function"] @@ -356,61 +243,6 @@ function SMCouplingSchwarzBC( end end -function apply_bc(model::OpInfModel, bc::SMDirichletBC) - model.fom_model.time = model.time - apply_bc(model.fom_model, bc) - bc_vector = zeros(0) - for node_index in bc.node_set_node_indices - disp_val = model.fom_model.current[bc.offset, node_index] - model.fom_model.reference[bc.offset, node_index] - push!(bc_vector, disp_val) - end - - offset = bc.offset - if offset == 1 - offset_name = "x" - end - if offset == 2 - offset_name = "y" - end - if offset == 3 - offset_name = "z" - end - - op_name = "B_" * bc.node_set_name * "-" * offset_name - bc_operator = model.opinf_rom[op_name] - # SM Dirichlet BC are only defined on a single x,y,z - return model.reduced_boundary_forcing[:] += bc_operator[1, :, :] * bc_vector -end - -function apply_bc(model::NeuralNetworkOpInfRom, bc::SMOpInfDirichletBC) - model.fom_model.time = model.time - apply_bc(model.fom_model,bc.fom_bc) - bc_vector = zeros(0) - for node_index ∈ bc.fom_bc.node_set_node_indices - dof_index = 3 * (node_index - 1) + bc.fom_bc.offset - disp_val = model.fom_model.current[bc.fom_bc.offset,node_index] - model.fom_model.reference[bc.fom_bc.offset, node_index] - push!(bc_vector,disp_val) - end - - py""" - import numpy as np - def setup_inputs(x): - xi = np.zeros((1,x.size)) - xi[0] = x - inputs = torch.tensor(xi) - return inputs - """ - - reduced_bc_vector = bc.basis[1,:,:]' * bc_vector - print(size(reduced_bc_vector),size(bc.basis),size(bc_vector)) - model_inputs = py"setup_inputs"(reduced_bc_vector) - reduced_forcing = bc.nn_model.forward(model_inputs) - reduced_forcing = reduced_forcing.detach().numpy()[1,:] - # SM Dirichlet BC are only defined on a single x,y,z - model.reduced_boundary_forcing[:] += reduced_forcing - end - - function apply_bc(model::SolidMechanics, bc::SMDirichletBC) for node_index in bc.node_set_node_indices values = Dict( @@ -552,10 +384,6 @@ function find_point_in_mesh(point::Vector{Float64}, model::SolidMechanics, blk_i return node_indices, ξ, found end -function find_point_in_mesh(point::Vector{Float64}, model::RomModel, blk_id::Int, tol::Float64) - node_indices, ξ, found = find_point_in_mesh(point, model.fom_model, blk_id, tol) - return node_indices, ξ, found -end function apply_bc_detail(model::SolidMechanics, bc::SMContactSchwarzBC) if bc.is_dirichlet == true @@ -573,26 +401,7 @@ function apply_bc_detail(model::SolidMechanics, bc::CouplingSchwarzBoundaryCondi end end -function apply_bc_detail(model::OpInfModel, bc::CouplingSchwarzBoundaryCondition) - if (typeof(bc.coupled_subsim.model) == SolidMechanics) - ## Apply BC to the FOM vector - apply_bc_detail(model.fom_model, bc) - # populate our own BC vector - bc_vector = zeros(3, length(bc.side_set_node_indices)) - for i in 1:length(bc.side_set_node_indices) - node_index = bc.side_set_node_indices[i] - bc_vector[:, i] = model.fom_model.current[:, node_index] - model.fom_model.reference[:, node_index] - end - op_name = "B_" * bc.side_set_name - bc_operator = model.opinf_rom[op_name] - for i in 1:3 - model.reduced_boundary_forcing[:] += bc_operator[i, :, :] * bc_vector[i, :] - end - else - throw("ROM-ROM coupling not supported yet") - end -end function apply_sm_schwarz_coupling_dirichlet(model::SolidMechanics, bc::CouplingSchwarzBoundaryCondition) if (typeof(bc.coupled_subsim.model) == SolidMechanics) @@ -708,77 +517,6 @@ function apply_bc(model::SolidMechanics, bc::SchwarzBoundaryCondition) return copy_solution_source_targets(bc.coupled_subsim.integrator, bc.coupled_subsim.solver, bc.coupled_subsim.model) end -function apply_bc(model::RomModel, bc::SchwarzBoundaryCondition) - global_sim = bc.coupled_subsim.params["global_simulation"] - schwarz_controller = global_sim.schwarz_controller - if typeof(bc) == SMContactSchwarzBC && schwarz_controller.active_contact == false - return nothing - end - empty_history = length(global_sim.schwarz_controller.time_hist) == 0 - same_step = schwarz_controller.same_step == true - if empty_history == true - apply_bc_detail(model, bc) - return nothing - end - # Save solution of coupled simulation - saved_disp = bc.coupled_subsim.integrator.displacement - saved_velo = bc.coupled_subsim.integrator.velocity - saved_acce = bc.coupled_subsim.integrator.acceleration - saved_∂Ω_f = bc.coupled_subsim.model.internal_force - time = model.time - coupled_name = bc.coupled_subsim.name - coupled_index = global_sim.subsim_name_index_map[coupled_name] - time_hist = global_sim.schwarz_controller.time_hist[coupled_index] - disp_hist = global_sim.schwarz_controller.disp_hist[coupled_index] - velo_hist = global_sim.schwarz_controller.velo_hist[coupled_index] - acce_hist = global_sim.schwarz_controller.acce_hist[coupled_index] - ∂Ω_f_hist = global_sim.schwarz_controller.∂Ω_f_hist[coupled_index] - interp_disp = same_step == true ? disp_hist[end] : interpolate(time_hist, disp_hist, time) - interp_velo = same_step == true ? velo_hist[end] : interpolate(time_hist, velo_hist, time) - interp_acce = same_step == true ? acce_hist[end] : interpolate(time_hist, acce_hist, time) - interp_∂Ω_f = same_step == true ? ∂Ω_f_hist[end] : interpolate(time_hist, ∂Ω_f_hist, time) - if typeof(bc.coupled_subsim.model) == SolidMechanics - bc.coupled_subsim.model.internal_force = interp_∂Ω_f - elseif typeof(bc.coupled_subsim.model) <: RomModel - bc.coupled_subsim.model.fom_model.internal_force = interp_∂Ω_f - end - - if typeof(bc) == SMContactSchwarzBC || typeof(bc) == SMNonOverlapSchwarzBC - relaxation_parameter = global_sim.schwarz_controller.relaxation_parameter - schwarz_iteration = global_sim.schwarz_controller.iteration_number - if schwarz_iteration == 1 - lambda_dispᵖʳᵉᵛ = zeros(length(interp_disp)) - lambda_veloᵖʳᵉᵛ = zeros(length(interp_velo)) - lambda_acceᵖʳᵉᵛ = zeros(length(interp_acce)) - else - lambda_dispᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_disp[coupled_index] - lambda_veloᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_velo[coupled_index] - lambda_acceᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_acce[coupled_index] - end - bc.coupled_subsim.integrator.displacement = - global_sim.schwarz_controller.lambda_disp[coupled_index] = - relaxation_parameter * interp_disp + (1 - relaxation_parameter) * lambda_dispᵖʳᵉᵛ - bc.coupled_subsim.integrator.velocity = - global_sim.schwarz_controller.lambda_velo[coupled_index] = - relaxation_parameter * interp_velo + (1 - relaxation_parameter) * lambda_veloᵖʳᵉᵛ - bc.coupled_subsim.integrator.acceleration = - global_sim.schwarz_controller.lambda_acce[coupled_index] = - relaxation_parameter * interp_acce + (1 - relaxation_parameter) * lambda_acceᵖʳᵉᵛ - else - bc.coupled_subsim.integrator.displacement = interp_disp - bc.coupled_subsim.integrator.velocity = interp_velo - bc.coupled_subsim.integrator.acceleration = interp_acce - end - # Copies from integrator to model - copy_solution_source_targets(bc.coupled_subsim.integrator, bc.coupled_subsim.solver, bc.coupled_subsim.model) - apply_bc_detail(model, bc) - bc.coupled_subsim.integrator.displacement = saved_disp - bc.coupled_subsim.integrator.velocity = saved_velo - bc.coupled_subsim.integrator.acceleration = saved_acce - bc.coupled_subsim.model.internal_force = saved_∂Ω_f - # Copy from integrator to model - return copy_solution_source_targets(bc.coupled_subsim.integrator, bc.coupled_subsim.solver, bc.coupled_subsim.model) -end function transfer_normal_component(source::Vector{Float64}, target::Vector{Float64}, normal::Vector{Float64}) normal_projection = normal * normal' @@ -1065,13 +803,6 @@ function apply_bcs(model::SolidMechanics) end end -function apply_bcs(model::RomModel) - model.reduced_boundary_forcing[:] .= 0.0 - for boundary_condition in model.boundary_conditions - apply_bc(model, boundary_condition) - end -end - function assign_velocity!( velocity::Matrix{Float64}, offset::Int64, node_index::Int32, velo_val::Float64, context::String ) @@ -1146,37 +877,6 @@ function apply_ics(params::Parameters, model::SolidMechanics) end end -function apply_ics(params::Parameters, model::RomModel) - apply_ics(params, model.fom_model) - - if haskey(params, "initial conditions") == false - return nothing - end - n_var, n_node, n_mode = model.basis.size - n_var_fom, n_node_fom = size(model.fom_model.current) - - # Make sure basis is the right size - if n_var != n_var_fom || n_node != n_node_fom - throw("Basis is wrong size") - end - - # project onto basis - for k in 1:n_mode - model.reduced_state[k] = 0.0 - model.reduced_velocity[k] = 0.0 - for j in 1:n_node - for n in 1:n_var - model.reduced_state[k] += - model.basis[n, j, k] * - (model.fom_model.current[n, j] - model.fom_model.reference[n, j]) - model.reduced_velocity[k] += - model.basis[n, j, k] * - (model.fom_model.velocity[n, j]) - end - end - end -end - function pair_schwarz_bcs(sim::MultiDomainSimulation) for subsim in sim.subsims model = subsim.model diff --git a/src/model.jl b/src/model.jl index c76bafef..2e5d014c 100644 --- a/src/model.jl +++ b/src/model.jl @@ -7,128 +7,8 @@ include("constitutive.jl") include("interpolation.jl") include("ics_bcs.jl") - +include("opinf/opinf_ics_bcs.jl") using Base.Threads: @threads, threadid, nthreads -using NPZ -using PyCall - -function LinearOpInfRom(params::Parameters) - params["mesh smoothing"] = false - fom_model = SolidMechanics(params) - reference = fom_model.reference - opinf_model_file = params["model"]["model-file"] - opinf_model = NPZ.npzread(opinf_model_file) - basis = opinf_model["basis"] - _, _, reduced_dim = size(basis) - num_dofs = reduced_dim - time = 0.0 - failed = false - null_vec = zeros(num_dofs) - - reduced_state = zeros(num_dofs) - reduced_velocity = zeros(num_dofs) - reduced_boundary_forcing = zeros(num_dofs) - free_dofs = trues(num_dofs) - boundary_conditions = Vector{BoundaryCondition}() - return LinearOpInfRom( - opinf_model, - basis, - reduced_state, - reduced_velocity, - reduced_boundary_forcing, - null_vec, - free_dofs, - boundary_conditions, - time, - failed, - fom_model, - reference, - false, - ) -end - -function QuadraticOpInfRom(params::Parameters) - params["mesh smoothing"] = false - fom_model = SolidMechanics(params) - reference = fom_model.reference - opinf_model_file = params["model"]["model-file"] - opinf_model = NPZ.npzread(opinf_model_file) - basis = opinf_model["basis"] - _, _, reduced_dim = size(basis) - num_dofs = reduced_dim - time = 0.0 - failed = false - null_vec = zeros(num_dofs) - - reduced_state = zeros(num_dofs) - reduced_velocity = zeros(num_dofs) - reduced_boundary_forcing = zeros(num_dofs) - free_dofs = trues(num_dofs) - boundary_conditions = Vector{BoundaryCondition}() - return QuadraticOpInfRom( - opinf_model, - basis, - reduced_state, - reduced_velocity, - reduced_boundary_forcing, - null_vec, - free_dofs, - boundary_conditions, - time, - failed, - fom_model, - reference, - false, - ) -end - -function NeuralNetworkOpInfRom(params::Dict{String,Any}) - params["mesh smoothing"] = false - fom_model = SolidMechanics(params) - reference = fom_model.reference - opinf_model_directory = params["model"]["model-directory"] - basis_file = opinf_model_directory * "/nn-opinf-basis.npz" - basis = NPZ.npzread(basis_file) - basis = basis["basis"] - py""" - import torch - def get_model(model_file): - return torch.load(model_file) - """ - ensemble_size = params["model"]["ensemble-size"] - model = [] - for i in 1:ensemble_size - tmp = py"get_model"(opinf_model_directory * "/stiffness-" * string(i-1) * ".pt") - push!(model,tmp) - end - num_dofs_per_node,num_nodes_basis,reduced_dim = size(basis) - num_dofs = reduced_dim - - time = 0.0 - failed = false - null_vec = zeros(num_dofs) - - reduced_state = zeros(num_dofs) - reduced_velocity = zeros(num_dofs) - reduced_boundary_forcing = zeros(num_dofs) - free_dofs = trues(num_dofs) - boundary_conditions = Vector{BoundaryCondition}() - NeuralNetworkOpInfRom( - model, - basis, - reduced_state, - reduced_velocity, - reduced_boundary_forcing, - null_vec, - free_dofs, - boundary_conditions, - time, - failed, - fom_model, - reference, - false - ) -end function SolidMechanics(params::Parameters) diff --git a/src/model_types.jl b/src/model_types.jl index 4d41594b..74de5258 100644 --- a/src/model_types.jl +++ b/src/model_types.jl @@ -5,8 +5,6 @@ # top-level Norma.jl directory. abstract type Model end -abstract type RomModel <: Model end -abstract type OpInfModel <: RomModel end using SparseArrays @enum Kinematics begin @@ -75,70 +73,4 @@ mutable struct HeatConduction <: Model failed::Bool end -mutable struct QuadraticOpInfRom <: OpInfModel - opinf_rom::Dict{Any,Any} - basis::Array{Float64} - reduced_state::Vector{Float64} - reduced_velocity::Vector{Float64} - reduced_boundary_forcing::Vector{Float64} - #internal_force not used, but include to ease interfacing in Schwarz - internal_force::Vector{Float64} - free_dofs::BitVector - boundary_conditions::Vector{BoundaryCondition} - time::Float64 - failed::Bool - fom_model::SolidMechanics - reference::Matrix{Float64} - inclined_support::Bool -end - -mutable struct GalerkinRom <: OpInfModel - opinf_rom::Dict{Any,Any} - basis::Array{Float64} - reduced_state::Vector{Float64} - reduced_velocity::Vector{Float64} - reduced_boundary_forcing::Vector{Float64} - #internal_force not used, but include to ease interfacing in Schwarz - internal_force::Vector{Float64} - free_dofs::BitVector - boundary_conditions::Vector{BoundaryCondition} - time::Float64 - failed::Bool - fom_model::SolidMechanics - reference::Matrix{Float64} - inclined_support::Bool -end -mutable struct LinearOpInfRom <: OpInfModel - opinf_rom::Dict{Any,Any} - basis::Array{Float64} - reduced_state::Vector{Float64} - reduced_velocity::Vector{Float64} - reduced_boundary_forcing::Vector{Float64} - #internal_force not used, but include to ease interfacing in Schwarz - internal_force::Vector{Float64} - free_dofs::BitVector - boundary_conditions::Vector{BoundaryCondition} - time::Float64 - failed::Bool - fom_model::SolidMechanics - reference::Matrix{Float64} - inclined_support::Bool -end - -mutable struct NeuralNetworkOpInfRom <: OpInfModel - nn_model::Any - basis::Array{Float64} - reduced_state::Vector{Float64} - reduced_velocity::Vector{Float64} - reduced_boundary_forcing::Vector{Float64} - #internal_force not used, but include to ease interfacing in Schwarz - internal_force::Vector{Float64} - free_dofs::BitVector - boundary_conditions::Vector{BoundaryCondition} - time::Float64 - failed::Bool - fom_model::SolidMechanics - reference::Matrix{Float64} - inclined_support::Bool -end diff --git a/src/opinf/opinf_ics_bcs.jl b/src/opinf/opinf_ics_bcs.jl new file mode 100644 index 00000000..3c5c033e --- /dev/null +++ b/src/opinf/opinf_ics_bcs.jl @@ -0,0 +1,370 @@ +# Norma: Copyright 2025 National Technology & Engineering Solutions of +# Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, +# the U.S. Government retains certain rights in this software. This software +# is released under the BSD license detailed in the file license.txt in the +# top-level Norma.jl directory. +using PyCall +import NPZ + +@variables t, x, y, z +D = Differential(t) + + +function SMOpInfDirichletBC(input_mesh::ExodusDatabase, bc_params::Dict{String,Any}) + fom_bc = SMDirichletBC(input_mesh,bc_params) + node_set_name = bc_params["node set"] + expression = bc_params["function"] + offset = component_offset_from_string(bc_params["component"]) + node_set_id = node_set_id_from_name(node_set_name, input_mesh) + node_set_node_indices = Exodus.read_node_set_nodes(input_mesh, node_set_id) + # expression is an arbitrary function of t, x, y, z in the input file + disp_num = eval(Meta.parse(expression)) + velo_num = expand_derivatives(D(disp_num)) + acce_num = expand_derivatives(D(velo_num)) + + opinf_model_file = bc_params["model-file"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + model = py"get_model"(opinf_model_file) + + opinf_model_file = bc_params["model-file"] + basis_file = bc_params["basis-file"] + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + + SMOpInfDirichletBC( + node_set_name, + offset, + node_set_id, + node_set_node_indices, + disp_num, + velo_num, + acce_num, + fom_bc, + model, + basis, + ) +end + + +function SMOpInfCouplingSchwarzBC( + subsim::SingleDomainSimulation, + coupled_subsim::SingleDomainSimulation, + input_mesh::ExodusDatabase, + bc_type::String, + bc_params::Parameters, +) + fom_bc = SMCouplingSchwarzBC(subsim,coupled_subsim,input_mesh,"Schwarz overlap",bc_params) + side_set_name = bc_params["side set"] + side_set_id = side_set_id_from_name(side_set_name, input_mesh) + _, _, side_set_node_indices = get_side_set_local_from_global_map(input_mesh, side_set_id) + coupled_block_name = bc_params["source block"] + if typeof(coupled_subsim.model) <: RomModel + coupled_mesh = coupled_subsim.model.fom_model.mesh + else + coupled_mesh = coupled_subsim.model.mesh + end + coupled_block_id = block_id_from_name(coupled_block_name, coupled_mesh) + element_type = Exodus.read_block_parameters(coupled_mesh, coupled_block_id)[1] + coupled_side_set_name = bc_params["source side set"] + coupled_side_set_id = side_set_id_from_name(coupled_side_set_name, coupled_mesh) + coupled_nodes_indices = Vector{Vector{Int64}}(undef, 0) + interpolation_function_values = Vector{Vector{Float64}}(undef, 0) + tol = 1.0e-06 + if haskey(bc_params, "search tolerance") == true + tol = bc_params["search tolerance"] + end + side_set_node_indices = unique(side_set_node_indices) + for node_index in side_set_node_indices + point = subsim.model.reference[:, node_index] + node_indices, ξ, found = find_point_in_mesh(point, coupled_subsim.model, coupled_block_id, tol) + if found == false + error("Could not find subdomain ", subsim.name, " point ", point, " in subdomain ", coupled_subsim.name) + end + N = interpolate(element_type, ξ)[1] + push!(coupled_nodes_indices, node_indices) + push!(interpolation_function_values, N) + end + is_dirichlet = true + swap_bcs = false + + opinf_model_directory = bc_params["model-directory"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + ensemble_size = bc_params["ensemble-size"] + model = [] + for i in 1:ensemble_size + tmp = py"get_model"(opinf_model_directory * "/BC-" * side_set_name * "-" * string(i-1) * ".pt") + push!(model,tmp) + end + + basis_file = bc_params["model-directory"] * "/nn-opinf-basis-" * side_set_name * ".npz" + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + + if bc_type == "OpInf Schwarz overlap" + SMOpInfOverlapSchwarzBC( + side_set_name, + side_set_node_indices, + coupled_nodes_indices, + interpolation_function_values, + coupled_subsim, + subsim, + is_dirichlet, + swap_bcs, + fom_bc, + model, + basis + ) + else + error("Unknown boundary condition type : ", bc_type) + end +end + + + +function apply_bc(model::OpInfModel, bc::SMDirichletBC) + model.fom_model.time = model.time + apply_bc(model.fom_model, bc) + bc_vector = zeros(0) + for node_index in bc.node_set_node_indices + disp_val = model.fom_model.current[bc.offset, node_index] - model.fom_model.reference[bc.offset, node_index] + push!(bc_vector, disp_val) + end + + offset = bc.offset + if offset == 1 + offset_name = "x" + end + if offset == 2 + offset_name = "y" + end + if offset == 3 + offset_name = "z" + end + + op_name = "B_" * bc.node_set_name * "-" * offset_name + bc_operator = model.opinf_rom[op_name] + # SM Dirichlet BC are only defined on a single x,y,z + return model.reduced_boundary_forcing[:] += bc_operator[1, :, :] * bc_vector +end + +function apply_bc(model::NeuralNetworkOpInfRom, bc::SMOpInfDirichletBC) + model.fom_model.time = model.time + apply_bc(model.fom_model,bc.fom_bc) + bc_vector = zeros(0) + for node_index ∈ bc.fom_bc.node_set_node_indices + dof_index = 3 * (node_index - 1) + bc.fom_bc.offset + disp_val = model.fom_model.current[bc.fom_bc.offset,node_index] - model.fom_model.reference[bc.fom_bc.offset, node_index] + push!(bc_vector,disp_val) + end + + py""" + import numpy as np + def setup_inputs(x): + xi = np.zeros((1,x.size)) + xi[0] = x + inputs = torch.tensor(xi) + return inputs + """ + + reduced_bc_vector = bc.basis[1,:,:]' * bc_vector + print(size(reduced_bc_vector),size(bc.basis),size(bc_vector)) + model_inputs = py"setup_inputs"(reduced_bc_vector) + ensemble_size = size(bc.nn_model)[1] + for i in 1:ensemble_size + reduced_forcing = bc.nn_model[i].forward(model_inputs) + reduced_forcing = reduced_forcing.detach().numpy()[1,:] + model.reduced_boundary_forcing[:] += reduced_forcing + end + model.reduced_boundary_forcing[:] = model.reduced_boundary_forcing[:] ./ ensemble_size +end + +function apply_bc_detail(model::OpInfModel, bc::CouplingSchwarzBoundaryCondition) + if (typeof(bc.coupled_subsim.model) == SolidMechanics) + ## Apply BC to the FOM vector + apply_bc_detail(model.fom_model, bc) + + # populate our own BC vector + bc_vector = zeros(3, length(bc.side_set_node_indices)) + for i in 1:length(bc.side_set_node_indices) + node_index = bc.side_set_node_indices[i] + bc_vector[:, i] = model.fom_model.current[:, node_index] - model.fom_model.reference[:, node_index] + end + op_name = "B_" * bc.side_set_name + bc_operator = model.opinf_rom[op_name] + for i in 1:3 + model.reduced_boundary_forcing[:] += bc_operator[i, :, :] * bc_vector[i, :] + end + else + throw("ROM-ROM coupling not supported yet") + end +end + + +function apply_bc_detail(model::NeuralNetworkOpInfRom, bc::CouplingSchwarzBoundaryCondition) + if (typeof(bc.coupled_subsim.model) == SolidMechanics) + ## Apply BC to the FOM vector + apply_bc_detail(model.fom_model, bc.fom_bc) + + # populate our own BC vector + bc_vector = zeros(3, length(bc.fom_bc.side_set_node_indices)) + for i in 1:length(bc.fom_bc.side_set_node_indices) + node_index = bc.fom_bc.side_set_node_indices[i] + bc_vector[:, i] = model.fom_model.current[:, node_index] - model.fom_model.reference[:, node_index] + end + + py""" + import numpy as np + def setup_inputs(x): + xi = np.zeros((1,x.size)) + xi[0] = x + inputs = torch.tensor(xi) + return inputs + """ + + reduced_bc_vector = zeros(bc.basis.size[3]) + for i in 1:3 + reduced_bc_vector[:] += bc.basis[i,:,:]' * bc_vector[i,:] + end + model_inputs = py"setup_inputs"(reduced_bc_vector) + ensemble_size = size(bc.nn_model)[1] + local_reduced_forcing = zeros(model.reduced_boundary_forcing.size[1]) + for i in 1:ensemble_size + reduced_forcing = bc.nn_model[i].forward(model_inputs) + reduced_forcing = reduced_forcing.detach().numpy()[1,:] + local_reduced_forcing[:] += reduced_forcing + end + local_reduced_forcing[:] = local_reduced_forcing[:] ./ ensemble_size + model.reduced_boundary_forcing[:] += local_reduced_forcing + + else + throw("ROM-ROM coupling not supported yet") + end +end + + +function apply_bc(model::RomModel, bc::SchwarzBoundaryCondition) + global_sim = bc.coupled_subsim.params["global_simulation"] + schwarz_controller = global_sim.schwarz_controller + if typeof(bc) == SMContactSchwarzBC && schwarz_controller.active_contact == false + return nothing + end + empty_history = length(global_sim.schwarz_controller.time_hist) == 0 + same_step = schwarz_controller.same_step == true + if empty_history == true + apply_bc_detail(model, bc) + return nothing + end + # Save solution of coupled simulation + saved_disp = bc.coupled_subsim.integrator.displacement + saved_velo = bc.coupled_subsim.integrator.velocity + saved_acce = bc.coupled_subsim.integrator.acceleration + saved_∂Ω_f = bc.coupled_subsim.model.internal_force + time = model.time + coupled_name = bc.coupled_subsim.name + coupled_index = global_sim.subsim_name_index_map[coupled_name] + time_hist = global_sim.schwarz_controller.time_hist[coupled_index] + disp_hist = global_sim.schwarz_controller.disp_hist[coupled_index] + velo_hist = global_sim.schwarz_controller.velo_hist[coupled_index] + acce_hist = global_sim.schwarz_controller.acce_hist[coupled_index] + ∂Ω_f_hist = global_sim.schwarz_controller.∂Ω_f_hist[coupled_index] + interp_disp = same_step == true ? disp_hist[end] : interpolate(time_hist, disp_hist, time) + interp_velo = same_step == true ? velo_hist[end] : interpolate(time_hist, velo_hist, time) + interp_acce = same_step == true ? acce_hist[end] : interpolate(time_hist, acce_hist, time) + interp_∂Ω_f = same_step == true ? ∂Ω_f_hist[end] : interpolate(time_hist, ∂Ω_f_hist, time) + if typeof(bc.coupled_subsim.model) == SolidMechanics + bc.coupled_subsim.model.internal_force = interp_∂Ω_f + elseif typeof(bc.coupled_subsim.model) <: RomModel + bc.coupled_subsim.model.fom_model.internal_force = interp_∂Ω_f + end + + if typeof(bc) == SMContactSchwarzBC || typeof(bc) == SMNonOverlapSchwarzBC + relaxation_parameter = global_sim.schwarz_controller.relaxation_parameter + schwarz_iteration = global_sim.schwarz_controller.iteration_number + if schwarz_iteration == 1 + lambda_dispᵖʳᵉᵛ = zeros(length(interp_disp)) + lambda_veloᵖʳᵉᵛ = zeros(length(interp_velo)) + lambda_acceᵖʳᵉᵛ = zeros(length(interp_acce)) + else + lambda_dispᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_disp[coupled_index] + lambda_veloᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_velo[coupled_index] + lambda_acceᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_acce[coupled_index] + end + bc.coupled_subsim.integrator.displacement = + global_sim.schwarz_controller.lambda_disp[coupled_index] = + relaxation_parameter * interp_disp + (1 - relaxation_parameter) * lambda_dispᵖʳᵉᵛ + bc.coupled_subsim.integrator.velocity = + global_sim.schwarz_controller.lambda_velo[coupled_index] = + relaxation_parameter * interp_velo + (1 - relaxation_parameter) * lambda_veloᵖʳᵉᵛ + bc.coupled_subsim.integrator.acceleration = + global_sim.schwarz_controller.lambda_acce[coupled_index] = + relaxation_parameter * interp_acce + (1 - relaxation_parameter) * lambda_acceᵖʳᵉᵛ + else + bc.coupled_subsim.integrator.displacement = interp_disp + bc.coupled_subsim.integrator.velocity = interp_velo + bc.coupled_subsim.integrator.acceleration = interp_acce + end + # Copies from integrator to model + copy_solution_source_targets(bc.coupled_subsim.integrator, bc.coupled_subsim.solver, bc.coupled_subsim.model) + apply_bc_detail(model, bc) + bc.coupled_subsim.integrator.displacement = saved_disp + bc.coupled_subsim.integrator.velocity = saved_velo + bc.coupled_subsim.integrator.acceleration = saved_acce + bc.coupled_subsim.model.internal_force = saved_∂Ω_f + # Copy from integrator to model + return copy_solution_source_targets(bc.coupled_subsim.integrator, bc.coupled_subsim.solver, bc.coupled_subsim.model) +end + + +function apply_bcs(model::RomModel) + model.reduced_boundary_forcing[:] .= 0.0 + for boundary_condition in model.boundary_conditions + apply_bc(model, boundary_condition) + end +end + + +function apply_ics(params::Parameters, model::RomModel) + apply_ics(params, model.fom_model) + + if haskey(params, "initial conditions") == false + return nothing + end + n_var, n_node, n_mode = model.basis.size + n_var_fom, n_node_fom = size(model.fom_model.current) + + # Make sure basis is the right size + if n_var != n_var_fom || n_node != n_node_fom + throw("Basis is wrong size") + end + + # project onto basis + for k in 1:n_mode + model.reduced_state[k] = 0.0 + model.reduced_velocity[k] = 0.0 + for j in 1:n_node + for n in 1:n_var + model.reduced_state[k] += + model.basis[n, j, k] * + (model.fom_model.current[n, j] - model.fom_model.reference[n, j]) + model.reduced_velocity[k] += + model.basis[n, j, k] * + (model.fom_model.velocity[n, j]) + end + end + end +end + + +function find_point_in_mesh(point::Vector{Float64}, model::RomModel, blk_id::Int, tol::Float64) + node_indices, ξ, found = find_point_in_mesh(point, model.fom_model, blk_id, tol) + return node_indices, ξ, found +end + diff --git a/src/opinf/opinf_ics_bcs_types.jl b/src/opinf/opinf_ics_bcs_types.jl new file mode 100644 index 00000000..abdf9738 --- /dev/null +++ b/src/opinf/opinf_ics_bcs_types.jl @@ -0,0 +1,47 @@ +# Norma: Copyright 2025 National Technology & Engineering Solutions of +# Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, +# the U.S. Government retains certain rights in this software. This software +# is released under the BSD license detailed in the file license.txt in the +# top-level Norma.jl directory. + +abstract type BoundaryCondition end +abstract type SchwarzBoundaryCondition <: BoundaryCondition end +abstract type RegularBoundaryCondition <: BoundaryCondition end +abstract type ContactSchwarzBoundaryCondition <: SchwarzBoundaryCondition end +abstract type CouplingSchwarzBoundaryCondition <: SchwarzBoundaryCondition end +abstract type OverlapSchwarzBoundaryCondition <: CouplingSchwarzBoundaryCondition end +abstract type NonOverlapSchwarzBoundaryCondition <: CouplingSchwarzBoundaryCondition end +abstract type InitialCondition end + +using Exodus +using Symbolics + +mutable struct SMOpInfDirichletBC <: RegularBoundaryCondition + node_set_name::String + offset::Int64 + node_set_id::Int64 + node_set_node_indices::Vector{Int64} + disp_num::Num + velo_num::Num + acce_num::Num + fom_bc::SMDirichletBC + nn_model::Any + basis::Any +end + + +mutable struct SMOpInfOverlapSchwarzBC <: OverlapSchwarzBoundaryCondition + side_set_name::String + side_set_node_indices::Vector{Int64} + coupled_nodes_indices::Vector{Vector{Int64}} + interpolation_function_values::Vector{Vector{Float64}} + coupled_subsim::Simulation + subsim::Simulation + is_dirichlet::Bool + swap_bcs::Bool + fom_bc::SMOverlapSchwarzBC + nn_model::Any + basis::Any +end + + diff --git a/src/opinf/opinf_model.jl b/src/opinf/opinf_model.jl new file mode 100644 index 00000000..8f17d18b --- /dev/null +++ b/src/opinf/opinf_model.jl @@ -0,0 +1,129 @@ +# Norma: Copyright 2025 National Technology & Engineering Solutions of +# Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, +# the U.S. Government retains certain rights in this software. This software +# is released under the BSD license detailed in the file license.txt in the +# top-level Norma.jl directory. + +using Base.Threads: @threads, threadid, nthreads +using NPZ +using PyCall + +function LinearOpInfRom(params::Parameters) + params["mesh smoothing"] = false + fom_model = SolidMechanics(params) + reference = fom_model.reference + opinf_model_file = params["model"]["model-file"] + opinf_model = NPZ.npzread(opinf_model_file) + basis = opinf_model["basis"] + _, _, reduced_dim = size(basis) + num_dofs = reduced_dim + time = 0.0 + failed = false + null_vec = zeros(num_dofs) + + reduced_state = zeros(num_dofs) + reduced_velocity = zeros(num_dofs) + reduced_boundary_forcing = zeros(num_dofs) + free_dofs = trues(num_dofs) + boundary_conditions = Vector{BoundaryCondition}() + return LinearOpInfRom( + opinf_model, + basis, + reduced_state, + reduced_velocity, + reduced_boundary_forcing, + null_vec, + free_dofs, + boundary_conditions, + time, + failed, + fom_model, + reference, + false, + ) +end + +function QuadraticOpInfRom(params::Parameters) + params["mesh smoothing"] = false + fom_model = SolidMechanics(params) + reference = fom_model.reference + opinf_model_file = params["model"]["model-file"] + opinf_model = NPZ.npzread(opinf_model_file) + basis = opinf_model["basis"] + _, _, reduced_dim = size(basis) + num_dofs = reduced_dim + time = 0.0 + failed = false + null_vec = zeros(num_dofs) + + reduced_state = zeros(num_dofs) + reduced_velocity = zeros(num_dofs) + reduced_boundary_forcing = zeros(num_dofs) + free_dofs = trues(num_dofs) + boundary_conditions = Vector{BoundaryCondition}() + return QuadraticOpInfRom( + opinf_model, + basis, + reduced_state, + reduced_velocity, + reduced_boundary_forcing, + null_vec, + free_dofs, + boundary_conditions, + time, + failed, + fom_model, + reference, + false, + ) +end + +function NeuralNetworkOpInfRom(params::Dict{String,Any}) + params["mesh smoothing"] = false + fom_model = SolidMechanics(params) + reference = fom_model.reference + opinf_model_directory = params["model"]["model-directory"] + basis_file = opinf_model_directory * "/nn-opinf-basis.npz" + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + ensemble_size = params["model"]["ensemble-size"] + model = [] + for i in 1:ensemble_size + tmp = py"get_model"(opinf_model_directory * "/stiffness-" * string(i-1) * ".pt") + push!(model,tmp) + end + num_dofs_per_node,num_nodes_basis,reduced_dim = size(basis) + num_dofs = reduced_dim + + time = 0.0 + failed = false + null_vec = zeros(num_dofs) + + reduced_state = zeros(num_dofs) + reduced_velocity = zeros(num_dofs) + reduced_boundary_forcing = zeros(num_dofs) + free_dofs = trues(num_dofs) + boundary_conditions = Vector{BoundaryCondition}() + NeuralNetworkOpInfRom( + model, + basis, + reduced_state, + reduced_velocity, + reduced_boundary_forcing, + null_vec, + free_dofs, + boundary_conditions, + time, + failed, + fom_model, + reference, + false + ) +end + + diff --git a/src/opinf/opinf_model_types.jl b/src/opinf/opinf_model_types.jl new file mode 100644 index 00000000..29963da8 --- /dev/null +++ b/src/opinf/opinf_model_types.jl @@ -0,0 +1,60 @@ +# Norma: Copyright 2025 National Technology & Engineering Solutions of +# Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, +# the U.S. Government retains certain rights in this software. This software +# is released under the BSD license detailed in the file license.txt in the +# top-level Norma.jl directory. + +abstract type RomModel <: Model end +abstract type OpInfModel <: RomModel end + +mutable struct QuadraticOpInfRom <: OpInfModel + opinf_rom::Dict{Any,Any} + basis::Array{Float64} + reduced_state::Vector{Float64} + reduced_velocity::Vector{Float64} + reduced_boundary_forcing::Vector{Float64} + #internal_force not used, but include to ease interfacing in Schwarz + internal_force::Vector{Float64} + free_dofs::BitVector + boundary_conditions::Vector{BoundaryCondition} + time::Float64 + failed::Bool + fom_model::SolidMechanics + reference::Matrix{Float64} + inclined_support::Bool +end + + +mutable struct LinearOpInfRom <: OpInfModel + opinf_rom::Dict{Any,Any} + basis::Array{Float64} + reduced_state::Vector{Float64} + reduced_velocity::Vector{Float64} + reduced_boundary_forcing::Vector{Float64} + #internal_force not used, but include to ease interfacing in Schwarz + internal_force::Vector{Float64} + free_dofs::BitVector + boundary_conditions::Vector{BoundaryCondition} + time::Float64 + failed::Bool + fom_model::SolidMechanics + reference::Matrix{Float64} + inclined_support::Bool +end + +mutable struct NeuralNetworkOpInfRom <: OpInfModel + nn_model::Any + basis::Array{Float64} + reduced_state::Vector{Float64} + reduced_velocity::Vector{Float64} + reduced_boundary_forcing::Vector{Float64} + #internal_force not used, but include to ease interfacing in Schwarz + internal_force::Vector{Float64} + free_dofs::BitVector + boundary_conditions::Vector{BoundaryCondition} + time::Float64 + failed::Bool + fom_model::SolidMechanics + reference::Matrix{Float64} + inclined_support::Bool +end diff --git a/src/simulation.jl b/src/simulation.jl index a73bcbd8..ce71e748 100644 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -13,6 +13,8 @@ include("time_integrator.jl") include("solver.jl") include("schwarz.jl") +include("opinf/opinf_model.jl") + function create_simulation(params::Parameters, name::String) params["name"] = name sim_type = params["type"] diff --git a/src/simulation_types.jl b/src/simulation_types.jl index b6c51fce..a71a118f 100644 --- a/src/simulation_types.jl +++ b/src/simulation_types.jl @@ -13,6 +13,8 @@ include("model_types.jl") include("time_integrator_types.jl") include("solver_types.jl") include("schwarz_types.jl") +include("opinf/opinf_model_types.jl") + mutable struct SingleDomainSimulation <: Simulation name::String From 8b89a16059ca269177dc85c7b9f1cd8819c8965b Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 17 Apr 2025 14:30:29 -0500 Subject: [PATCH 05/29] added example for making opinf model w/ automatic grid search --- .../dynamic-opinf-fom/make_op_inf_models.py | 136 +++--------------- 1 file changed, 21 insertions(+), 115 deletions(-) diff --git a/examples/ahead/overlap/torsion/dynamic-opinf-fom/make_op_inf_models.py b/examples/ahead/overlap/torsion/dynamic-opinf-fom/make_op_inf_models.py index 8e154264..40ca45e5 100644 --- a/examples/ahead/overlap/torsion/dynamic-opinf-fom/make_op_inf_models.py +++ b/examples/ahead/overlap/torsion/dynamic-opinf-fom/make_op_inf_models.py @@ -1,117 +1,23 @@ -import numpy as np -import opinf -import os -from matplotlib import pyplot as plt import normaopinf -import normaopinf.readers -import normaopinf.calculus -import romtools - -if __name__ == "__main__": - # Load in snapshots - cur_dir = os.getcwd() - solution_id = 2 - displacement_snapshots,times = normaopinf.readers.load_displacement_csv_files(solution_directory=cur_dir,solution_id=solution_id,skip_files=1) - - # Identify which DOFs are free - free_dofs = normaopinf.readers.get_free_dofs(solution_directory=cur_dir,solution_id=solution_id) - - # Set values = 0 if DOFs are fixed - displacement_snapshots[free_dofs[:,:]==False] = 0. - - #Get sideset snapshots - sidesets = ["-Z_ss"] - sideset_snapshots = normaopinf.readers.load_sideset_displacement_csv_files(solution_directory=cur_dir,sidesets=sidesets,solution_id=solution_id,skip_files=1) - - # Create an energy-based truncater - #tolerance = 1.e-5 - #my_energy_truncater = romtools.vector_space.utils.EnergyBasedTruncater(1. - tolerance) - my_energy_truncater = romtools.vector_space.utils.BasisSizeTruncater(4) - - # Now load in sidesets and create reduced spaces - # Note that I construct a separate basis for each x,y,z component. This isn't necessary - ss_tspace = {} - reduced_sideset_snapshots = {} - for sideset in sidesets: - if sideset_snapshots[sideset].shape[0] == 1: - ss_tspace[sideset] = romtools.VectorSpaceFromPOD(snapshots=sideset_snapshots[sideset], - truncater=my_energy_truncater, - shifter = None, - orthogonalizer=romtools.vector_space.utils.EuclideanL2Orthogonalizer(), - scaler = romtools.vector_space.utils.NoOpScaler()) - # Compute L2 orthogonal projection onto trial spaces - reduced_sideset_snapshots[sideset] = romtools.rom.optimal_l2_projection(sideset_snapshots[sideset],ss_tspace[sideset]) - else: - comp_trial_space = [] - for i in range(0,3): - tspace = romtools.VectorSpaceFromPOD(snapshots=sideset_snapshots[sideset][i:i+1], - truncater=my_energy_truncater, - shifter = None, - orthogonalizer=romtools.vector_space.utils.EuclideanL2Orthogonalizer(), - scaler = romtools.vector_space.utils.NoOpScaler()) - comp_trial_space.append(tspace) - ss_tspace[sideset] = romtools.CompositeVectorSpace(comp_trial_space) - reduced_sideset_snapshots[sideset] = romtools.rom.optimal_l2_projection(sideset_snapshots[sideset],ss_tspace[sideset]) - - - ## Stack sidesets into one matrix for OpInf - reduced_stacked_sideset_snapshots = None - for sideset in sidesets: - if reduced_stacked_sideset_snapshots is None: - reduced_stacked_sideset_snapshots = reduced_sideset_snapshots[sideset]*1. - else: - reduced_stacked_sideset_snapshots = np.append(reduced_stacked_sideset_snapshots,reduced_sideset_snapshots[sideset],axis=0) - - # Create trial space for displacement vector - # Note again that I construct a separate basis for each x,y,z component. This isn't necessary - trial_spaces = [] - - my_energy_truncater = romtools.vector_space.utils.BasisSizeTruncater(60) - - for i in range(0,3): - trial_space = romtools.VectorSpaceFromPOD(snapshots=displacement_snapshots[i:i+1], - truncater=my_energy_truncater, - shifter = None, - orthogonalizer=romtools.vector_space.utils.EuclideanL2Orthogonalizer(), - scaler = romtools.vector_space.utils.NoOpScaler()) - trial_spaces.append(trial_space) - - trial_space = romtools.CompositeVectorSpace(trial_spaces) - - # Compute L2 orthogonal projection onto trial spaces - uhat = romtools.rom.optimal_l2_projection(displacement_snapshots,trial_space) - u_ddots,times_dummy = normaopinf.readers.load_acceleration_csv_files(solution_directory=cur_dir,solution_id=solution_id,skip_files=1) - # Set values = 0 if DOFs are fixed - u_ddots[free_dofs[:,:]==False] = 0. - uhat_ddots = romtools.rom.optimal_l2_projection(u_ddots*1.,trial_space) - - # Construct an opinf "AB" model (linear in the state and linear in the exogenous inputs) - # Note: I don't construct a cAB ROM in this example since I know there is no forcing vector - l2solver = opinf.lstsq.L2Solver(regularizer=1e-11) - opinf_model = opinf.models.ContinuousModel("AB",solver=l2solver) - opinf_model.fit(states=uhat, ddts=uhat_ddots,inputs=reduced_stacked_sideset_snapshots) - - # Flip signs to match convention of K on the LHS - K = -opinf_model.A_.entries - B = opinf_model.B_.entries - - ## Now extract boundary operators and create dictionary to save - col_start = 0 - sideset_operators = {} - for sideset in sidesets: - num_dofs = reduced_sideset_snapshots[sideset].shape[0] - val = np.einsum('kr,vnr->vkn',B[:,col_start:col_start + num_dofs] , ss_tspace[sideset].get_basis() ) - shape2 = B[:,col_start:col_start + num_dofs] @ ss_tspace[sideset].get_basis()[0].transpose() - sideset_operators["B_" + sideset] = val# - col_start += num_dofs - - f = np.zeros(K.shape[0]) - vals_to_save = sideset_operators - vals_to_save["basis"] = trial_space.get_basis() - vals_to_save["K"] = K - vals_to_save["f"] = f - - np.savez('opinf-operator',**vals_to_save) - - +import normaopinf.opinf +import os +import numpy as np +if __name__ == '__main__': + settings = {} + settings['fom-yaml-file'] = "torsion.yaml" + settings['training-data-directories'] = [os.getcwd()] + settings['solution-id'] = 1 + settings['model-type'] = 'quadratic' + settings['forcing'] = False + settings['truncation-type'] = 'size' + settings['boundary-truncation-type'] = 'energy' + settings['regularization-parameter'] = 'automatic' + settings['trial-space-splitting-type'] = 'split' + settings['acceleration-computation-type'] = 'finite-difference' + sizes = np.array([2,4,6,8,10,12,14,16,18,20,40,60],dtype=int) + for i,size in enumerate(sizes): + settings['truncation-value'] = int(size) + settings['boundary-truncation-value'] = 1. - 1.e-5 + settings['operator-name'] = 'quadratic-opinf-operator-rom-dim-' + str(size) + normaopinf.opinf.make_opinf_model(settings) From e531aae0fc0e8d826b2fccf8a8289571886877c8 Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Tue, 8 Apr 2025 10:09:46 -0500 Subject: [PATCH 06/29] refactor of nn opinf from previous branch --- Project.toml | 1 + src/ics_bcs.jl | 73 ++++++++++++++++++++++++++++++++++++++++++++ src/ics_bcs_types.jl | 13 ++++++++ src/model.jl | 48 +++++++++++++++++++++++++++++ src/model_types.jl | 17 +++++++++++ src/solver.jl | 37 ++++++++++++++++------ 6 files changed, 180 insertions(+), 9 deletions(-) diff --git a/Project.toml b/Project.toml index 345650cd..02ed14d8 100644 --- a/Project.toml +++ b/Project.toml @@ -20,6 +20,7 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6" +PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" [compat] DelimitedFiles = "1" diff --git a/src/ics_bcs.jl b/src/ics_bcs.jl index 2d4ba835..4c4e51f9 100644 --- a/src/ics_bcs.jl +++ b/src/ics_bcs.jl @@ -3,6 +3,8 @@ # the U.S. Government retains certain rights in this software. This software # is released under the BSD license detailed in the file license.txt in the # top-level Norma.jl directory. +using PyCall +import NPZ @variables t, x, y, z D = Differential(t) @@ -20,6 +22,45 @@ function SMDirichletBC(input_mesh::ExodusDatabase, bc_params::Parameters) return SMDirichletBC(node_set_name, offset, node_set_id, node_set_node_indices, disp_num, velo_num, acce_num) end +function SMOpInfDirichletBC(input_mesh::ExodusDatabase, bc_params::Dict{String,Any}) + fom_bc = SMDirichletBC(input_mesh,bc_params) + node_set_name = bc_params["node set"] + expression = bc_params["function"] + offset = component_offset_from_string(bc_params["component"]) + node_set_id = node_set_id_from_name(node_set_name, input_mesh) + node_set_node_indices = Exodus.read_node_set_nodes(input_mesh, node_set_id) + # expression is an arbitrary function of t, x, y, z in the input file + disp_num = eval(Meta.parse(expression)) + velo_num = expand_derivatives(D(disp_num)) + acce_num = expand_derivatives(D(velo_num)) + + opinf_model_file = bc_params["model-file"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + model = py"get_model"(opinf_model_file) + + opinf_model_file = bc_params["model-file"] + basis_file = bc_params["basis-file"] + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + + SMOpInfDirichletBC( + node_set_name, + offset, + node_set_id, + node_set_node_indices, + disp_num, + velo_num, + acce_num, + fom_bc, + model, + basis, + ) +end + function SMDirichletInclined(input_mesh::ExodusDatabase, bc_params::Parameters) node_set_name = bc_params["node set"] expression = bc_params["function"] @@ -268,6 +309,35 @@ function apply_bc(model::OpInfModel, bc::SMDirichletBC) return model.reduced_boundary_forcing[:] += bc_operator[1, :, :] * bc_vector end +function apply_bc(model::NeuralNetworkOpInfRom, bc::SMOpInfDirichletBC) + model.fom_model.time = model.time + apply_bc(model.fom_model,bc.fom_bc) + bc_vector = zeros(0) + for node_index ∈ bc.fom_bc.node_set_node_indices + dof_index = 3 * (node_index - 1) + bc.fom_bc.offset + disp_val = model.fom_model.current[bc.fom_bc.offset,node_index] - model.fom_model.reference[bc.fom_bc.offset, node_index] + push!(bc_vector,disp_val) + end + + py""" + import numpy as np + def setup_inputs(x): + xi = np.zeros((1,x.size)) + xi[0] = x + inputs = torch.tensor(xi) + return inputs + """ + + reduced_bc_vector = bc.basis[1,:,:]' * bc_vector + print(size(reduced_bc_vector),size(bc.basis),size(bc_vector)) + model_inputs = py"setup_inputs"(reduced_bc_vector) + reduced_forcing = bc.nn_model.forward(model_inputs) + reduced_forcing = reduced_forcing.detach().numpy()[1,:] + # SM Dirichlet BC are only defined on a single x,y,z + model.reduced_boundary_forcing[:] += reduced_forcing + end + + function apply_bc(model::SolidMechanics, bc::SMDirichletBC) for node_index in bc.node_set_node_indices values = Dict( @@ -834,6 +904,9 @@ function create_bcs(params::Parameters) if bc_type == "Dirichlet" boundary_condition = SMDirichletBC(input_mesh, bc_setting_params) push!(boundary_conditions, boundary_condition) + elseif bc_type == "OpInf Dirichlet" + boundary_condition = SMOpInfDirichletBC(input_mesh, bc_setting_params) + push!(boundary_conditions, boundary_condition) elseif bc_type == "Neumann" boundary_condition = SMNeumannBC(input_mesh, bc_setting_params) push!(boundary_conditions, boundary_condition) diff --git a/src/ics_bcs_types.jl b/src/ics_bcs_types.jl index d450e14e..8bce0f32 100644 --- a/src/ics_bcs_types.jl +++ b/src/ics_bcs_types.jl @@ -26,6 +26,19 @@ mutable struct SMDirichletBC <: RegularBoundaryCondition acce_num::Num end +mutable struct SMOpInfDirichletBC <: RegularBoundaryCondition + node_set_name::String + offset::Int64 + node_set_id::Int64 + node_set_node_indices::Vector{Int64} + disp_num::Num + velo_num::Num + acce_num::Num + fom_bc::SMDirichletBC + nn_model::Any + basis::Any +end + mutable struct SMDirichletInclined <: RegularBoundaryCondition node_set_name::String node_set_id::Int64 diff --git a/src/model.jl b/src/model.jl index d0b23c08..f1dda778 100644 --- a/src/model.jl +++ b/src/model.jl @@ -10,6 +10,7 @@ include("ics_bcs.jl") using Base.Threads: @threads, threadid, nthreads using NPZ +using PyCall function LinearOpInfRom(params::Parameters) params["mesh smoothing"] = false @@ -81,6 +82,51 @@ function QuadraticOpInfRom(params::Parameters) ) end +function NeuralNetworkOpInfRom(params::Dict{String,Any}) + params["mesh smoothing"] = false + fom_model = SolidMechanics(params) + reference = fom_model.reference + opinf_model_file = params["model"]["model-file"] + + basis_file = params["model"]["basis-file"] + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + model = py"get_model"(opinf_model_file) + num_dofs_per_node,num_nodes_basis,reduced_dim = size(basis) + num_dofs = reduced_dim + + time = 0.0 + failed = false + null_vec = zeros(num_dofs) + + reduced_state = zeros(num_dofs) + reduced_velocity = zeros(num_dofs) + reduced_boundary_forcing = zeros(num_dofs) + free_dofs = trues(num_dofs) + boundary_conditions = Vector{BoundaryCondition}() + NeuralNetworkOpInfRom( + model, + basis, + reduced_state, + reduced_velocity, + reduced_boundary_forcing, + null_vec, + free_dofs, + boundary_conditions, + time, + failed, + fom_model, + reference, + false + ) +end + + function SolidMechanics(params::Parameters) input_mesh = params["input_mesh"] model_params = params["model"] @@ -208,6 +254,8 @@ function create_model(params::Parameters) return LinearOpInfRom(params) elseif model_name == "quadratic opinf rom" return QuadraticOpInfRom(params) + elseif model_name == "neural network opinf rom" + return NeuralNetworkOpInfRom(params) else error("Unknown type of model : ", model_name) diff --git a/src/model_types.jl b/src/model_types.jl index 391ed8fe..c2a7440c 100644 --- a/src/model_types.jl +++ b/src/model_types.jl @@ -128,3 +128,20 @@ mutable struct LinearOpInfRom <: OpInfModel reference::Matrix{Float64} inclined_support::Bool end + +mutable struct NeuralNetworkOpInfRom <: OpInfModel + nn_model::Any + basis::Array{Float64} + reduced_state::Vector{Float64} + reduced_velocity::Vector{Float64} + reduced_boundary_forcing::Vector{Float64} + #internal_force not used, but include to ease interfacing in Schwarz + internal_force::Vector{Float64} + free_dofs::BitVector + boundary_conditions::Vector{BoundaryCondition} + time::Float64 + failed::Bool + fom_model::SolidMechanics + reference::Matrix{Float64} + inclined_support::Bool +end diff --git a/src/solver.jl b/src/solver.jl index bde7568b..5ad5c0bc 100644 --- a/src/solver.jl +++ b/src/solver.jl @@ -408,15 +408,6 @@ function evaluate(integrator::Newmark, solver::HessianMinimizer, model::LinearOp beta = integrator.β gamma = integrator.γ dt = integrator.time_step - - #Ax* = b - # Put in residual format - #e = [x* - x] -> x* = x + e - #Ax + Ae = b - #Ax - b = -Ae - #Ae = r, r = b - Ax - ##M uddot + Ku = f - num_dof = length(model.free_dofs) I = Matrix{Float64}(LinearAlgebra.I, num_dof, num_dof) LHS = I / (dt * dt * beta) + Matrix{Float64}(model.opinf_rom["K"]) @@ -428,6 +419,34 @@ function evaluate(integrator::Newmark, solver::HessianMinimizer, model::LinearOp return nothing end +### Move to model? +function evaluate(integrator::Newmark, solver::HessianMinimizer, model::NeuralNetworkOpInfRom) + beta = integrator.β + gamma = integrator.γ + dt = integrator.time_step + num_dof, = size(model.free_dofs) + I = Matrix{Float64}(LinearAlgebra.I, num_dof,num_dof) + py""" + import numpy as np + def setup_inputs(x): + xi = np.zeros((1,x.size)) + xi[0] = x + inputs = torch.tensor(xi) + return inputs + """ + model_inputs = py"setup_inputs"(solver.solution) + Kx,K = model.nn_model.forward(model_inputs,return_stiffness=true) + + LHS = I / (dt*dt*beta) - K.detach().numpy()[1,:,:] + RHS = model.reduced_boundary_forcing + 1.0/(dt*dt*beta).*integrator.disp_pre + + residual = RHS - LHS * solver.solution + solver.hessian[:,:] = LHS + solver.gradient[:] = -residual +end + + + function evaluate(integrator::QuasiStatic, solver::HessianMinimizer, model::SolidMechanics) evaluate(model, integrator, solver) if model.failed == true From 7bbe8c0792ad7a47f7d26d87ecf97e43f3a5c0a7 Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Mon, 14 Apr 2025 11:49:58 -0500 Subject: [PATCH 07/29] added NN for Schwarz --- src/ics_bcs.jl | 85 ++++++++++++++++++++++++++++++++++++++++++++ src/ics_bcs_types.jl | 15 ++++++++ 2 files changed, 100 insertions(+) diff --git a/src/ics_bcs.jl b/src/ics_bcs.jl index 4c4e51f9..284109c1 100644 --- a/src/ics_bcs.jl +++ b/src/ics_bcs.jl @@ -61,6 +61,80 @@ function SMOpInfDirichletBC(input_mesh::ExodusDatabase, bc_params::Dict{String,A ) end + +function SMOpInfCouplingSchwarzBC( + subsim::SingleDomainSimulation, + coupled_subsim::SingleDomainSimulation, + input_mesh::ExodusDatabase, + bc_type::String, + bc_params::Parameters, +) + fom_bc = SMCouplingSchwarzBC(subsim,coupled_subsim,input_mesh,bc_type,bc_params) + side_set_name = bc_params["side set"] + side_set_id = side_set_id_from_name(side_set_name, input_mesh) + _, _, side_set_node_indices = get_side_set_local_from_global_map(input_mesh, side_set_id) + coupled_block_name = bc_params["source block"] + if typeof(coupled_subsim.model) <: RomModel + coupled_mesh = coupled_subsim.model.fom_model.mesh + else + coupled_mesh = coupled_subsim.model.mesh + end + coupled_block_id = block_id_from_name(coupled_block_name, coupled_mesh) + element_type = Exodus.read_block_parameters(coupled_mesh, coupled_block_id)[1] + coupled_side_set_name = bc_params["source side set"] + coupled_side_set_id = side_set_id_from_name(coupled_side_set_name, coupled_mesh) + coupled_nodes_indices = Vector{Vector{Int64}}(undef, 0) + interpolation_function_values = Vector{Vector{Float64}}(undef, 0) + tol = 1.0e-06 + if haskey(bc_params, "search tolerance") == true + tol = bc_params["search tolerance"] + end + side_set_node_indices = unique(side_set_node_indices) + for node_index in side_set_node_indices + point = subsim.model.reference[:, node_index] + node_indices, ξ, found = find_point_in_mesh(point, coupled_subsim.model, coupled_block_id, tol) + if found == false + error("Could not find subdomain ", subsim.name, " point ", point, " in subdomain ", coupled_subsim.name) + end + N = interpolate(element_type, ξ)[1] + push!(coupled_nodes_indices, node_indices) + push!(interpolation_function_values, N) + end + is_dirichlet = true + swap_bcs = false + + opinf_model_file = bc_params["model-file"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + model = py"get_model"(opinf_model_file) + + opinf_model_file = bc_params["model-file"] + basis_file = bc_params["basis-file"] + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + + if bc_type == "OpInf Schwarz overlap" + SMOpInfOverlapSchwarzBC( + side_set_name, + side_set_node_indices, + coupled_nodes_indices, + interpolation_function_values, + coupled_subsim, + subsim, + is_dirichlet, + swap_bcs, + model, + basis + ) + else + error("Unknown boundary condition type : ", bc_type) + end +end + + function SMDirichletInclined(input_mesh::ExodusDatabase, bc_params::Parameters) node_set_name = bc_params["node set"] expression = bc_params["function"] @@ -932,6 +1006,17 @@ function create_bcs(params::Parameters) coupled_subsim = sim.subsims[coupled_subdomain_index] boundary_condition = SMCouplingSchwarzBC(subsim, coupled_subsim, input_mesh, bc_type, bc_setting_params) push!(boundary_conditions, boundary_condition) + elseif bc_type == "OpInf Schwarz overlap" + sim = params["global_simulation"] + subsim_name = params["name"] + subdomain_index = sim.subsim_name_index_map[subsim_name] + subsim = sim.subsims[subdomain_index] + coupled_subsim_name = bc_setting_params["source"] + coupled_subdomain_index = sim.subsim_name_index_map[coupled_subsim_name] + coupled_subsim = sim.subsims[coupled_subdomain_index] + boundary_condition = SMOpInfCouplingSchwarzBC(subsim, coupled_subsim, input_mesh, bc_type, bc_setting_params) + push!(boundary_conditions, boundary_condition) + else error("Unknown boundary condition type : ", bc_type) end diff --git a/src/ics_bcs_types.jl b/src/ics_bcs_types.jl index 8bce0f32..586a1d6e 100644 --- a/src/ics_bcs_types.jl +++ b/src/ics_bcs_types.jl @@ -87,6 +87,21 @@ mutable struct SMOverlapSchwarzBC <: OverlapSchwarzBoundaryCondition swap_bcs::Bool end +mutable struct SMOpInfOverlapSchwarzBC <: OverlapSchwarzBoundaryCondition + side_set_name::String + side_set_node_indices::Vector{Int64} + coupled_nodes_indices::Vector{Vector{Int64}} + interpolation_function_values::Vector{Vector{Float64}} + coupled_subsim::Simulation + subsim::Simulation + is_dirichlet::Bool + swap_bcs::Bool + fom_bc::SMOverlapSchwarzBC + nn_model::Any + basis::Any +end + + mutable struct SMNonOverlapSchwarzBC <: NonOverlapSchwarzBoundaryCondition side_set_id::Int64 side_set_node_indices::Vector{Int64} From 2252e6f4623f3db53aeec3d454a24452b3745e90 Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Tue, 15 Apr 2025 16:00:25 -0500 Subject: [PATCH 08/29] switched to ensemble nn formulation --- src/model.jl | 12 ++++++++---- src/model_types.jl | 17 +++++++++++++++++ src/solver.jl | 16 +++++++++++++--- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/model.jl b/src/model.jl index f1dda778..db70efb9 100644 --- a/src/model.jl +++ b/src/model.jl @@ -86,9 +86,8 @@ function NeuralNetworkOpInfRom(params::Dict{String,Any}) params["mesh smoothing"] = false fom_model = SolidMechanics(params) reference = fom_model.reference - opinf_model_file = params["model"]["model-file"] - - basis_file = params["model"]["basis-file"] + opinf_model_directory = params["model"]["model-directory"] + basis_file = opinf_model_directory * "/nn-opinf-basis.npz" basis = NPZ.npzread(basis_file) basis = basis["basis"] py""" @@ -96,7 +95,12 @@ function NeuralNetworkOpInfRom(params::Dict{String,Any}) def get_model(model_file): return torch.load(model_file) """ - model = py"get_model"(opinf_model_file) + ensemble_size = params["model"]["ensemble-size"] + model = [] + for i in 1:ensemble_size + tmp = py"get_model"(opinf_model_directory * "/stiffness-" * string(i-1) * ".pt") + push!(model,tmp) + end num_dofs_per_node,num_nodes_basis,reduced_dim = size(basis) num_dofs = reduced_dim diff --git a/src/model_types.jl b/src/model_types.jl index c2a7440c..2654fef8 100644 --- a/src/model_types.jl +++ b/src/model_types.jl @@ -112,6 +112,23 @@ mutable struct QuadraticOpInfRom <: OpInfModel inclined_support::Bool end +mutable struct GalerkinRom <: OpInfModel + opinf_rom::Dict{Any,Any} + basis::Array{Float64} + reduced_state::Vector{Float64} + reduced_velocity::Vector{Float64} + reduced_boundary_forcing::Vector{Float64} + #internal_force not used, but include to ease interfacing in Schwarz + internal_force::Vector{Float64} + free_dofs::BitVector + boundary_conditions::Vector{BoundaryCondition} + time::Float64 + failed::Bool + fom_model::SolidMechanics + reference::Matrix{Float64} + inclined_support::Bool +end + mutable struct LinearOpInfRom <: OpInfModel opinf_rom::Dict{Any,Any} basis::Array{Float64} diff --git a/src/solver.jl b/src/solver.jl index 5ad5c0bc..fc691106 100644 --- a/src/solver.jl +++ b/src/solver.jl @@ -435,9 +435,19 @@ function evaluate(integrator::Newmark, solver::HessianMinimizer, model::NeuralNe return inputs """ model_inputs = py"setup_inputs"(solver.solution) - Kx,K = model.nn_model.forward(model_inputs,return_stiffness=true) - - LHS = I / (dt*dt*beta) - K.detach().numpy()[1,:,:] + ensemble_size = size(model.nn_model)[1] + stiffness = zeros( num_dof,num_dof ) + #Kx,K = model.nn_model[1].forward(model_inputs,return_stiffness=true) + #K = K.detach().numpy()[1,:,:] + for i in 1:ensemble_size + Kxt,Kt = model.nn_model[i].forward(model_inputs,return_stiffness=true) + #Kx += Kxt + Kt = Kt.detach().numpy()[1,:,:] + stiffness += Kt + + end + stiffness = stiffness./ensemble_size + LHS = I / (dt*dt*beta) - stiffness RHS = model.reduced_boundary_forcing + 1.0/(dt*dt*beta).*integrator.disp_pre residual = RHS - LHS * solver.solution From 69145c7c17f53f6ca6f932564a9bfc80b2f2068c Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Wed, 30 Apr 2025 16:21:02 -0500 Subject: [PATCH 09/29] fixed for rebase --- src/ics_bcs.jl | 299 ------------------------- src/model.jl | 122 +--------- src/model_types.jl | 68 ------ src/opinf/opinf_ics_bcs.jl | 370 +++++++++++++++++++++++++++++++ src/opinf/opinf_ics_bcs_types.jl | 47 ++++ src/opinf/opinf_model.jl | 129 +++++++++++ src/opinf/opinf_model_types.jl | 60 +++++ src/simulation.jl | 1 + src/simulation_types.jl | 1 + 9 files changed, 609 insertions(+), 488 deletions(-) create mode 100644 src/opinf/opinf_ics_bcs.jl create mode 100644 src/opinf/opinf_ics_bcs_types.jl create mode 100644 src/opinf/opinf_model.jl create mode 100644 src/opinf/opinf_model_types.jl diff --git a/src/ics_bcs.jl b/src/ics_bcs.jl index 284109c1..c55b6396 100644 --- a/src/ics_bcs.jl +++ b/src/ics_bcs.jl @@ -22,119 +22,6 @@ function SMDirichletBC(input_mesh::ExodusDatabase, bc_params::Parameters) return SMDirichletBC(node_set_name, offset, node_set_id, node_set_node_indices, disp_num, velo_num, acce_num) end -function SMOpInfDirichletBC(input_mesh::ExodusDatabase, bc_params::Dict{String,Any}) - fom_bc = SMDirichletBC(input_mesh,bc_params) - node_set_name = bc_params["node set"] - expression = bc_params["function"] - offset = component_offset_from_string(bc_params["component"]) - node_set_id = node_set_id_from_name(node_set_name, input_mesh) - node_set_node_indices = Exodus.read_node_set_nodes(input_mesh, node_set_id) - # expression is an arbitrary function of t, x, y, z in the input file - disp_num = eval(Meta.parse(expression)) - velo_num = expand_derivatives(D(disp_num)) - acce_num = expand_derivatives(D(velo_num)) - - opinf_model_file = bc_params["model-file"] - py""" - import torch - def get_model(model_file): - return torch.load(model_file) - """ - model = py"get_model"(opinf_model_file) - - opinf_model_file = bc_params["model-file"] - basis_file = bc_params["basis-file"] - basis = NPZ.npzread(basis_file) - basis = basis["basis"] - - SMOpInfDirichletBC( - node_set_name, - offset, - node_set_id, - node_set_node_indices, - disp_num, - velo_num, - acce_num, - fom_bc, - model, - basis, - ) -end - - -function SMOpInfCouplingSchwarzBC( - subsim::SingleDomainSimulation, - coupled_subsim::SingleDomainSimulation, - input_mesh::ExodusDatabase, - bc_type::String, - bc_params::Parameters, -) - fom_bc = SMCouplingSchwarzBC(subsim,coupled_subsim,input_mesh,bc_type,bc_params) - side_set_name = bc_params["side set"] - side_set_id = side_set_id_from_name(side_set_name, input_mesh) - _, _, side_set_node_indices = get_side_set_local_from_global_map(input_mesh, side_set_id) - coupled_block_name = bc_params["source block"] - if typeof(coupled_subsim.model) <: RomModel - coupled_mesh = coupled_subsim.model.fom_model.mesh - else - coupled_mesh = coupled_subsim.model.mesh - end - coupled_block_id = block_id_from_name(coupled_block_name, coupled_mesh) - element_type = Exodus.read_block_parameters(coupled_mesh, coupled_block_id)[1] - coupled_side_set_name = bc_params["source side set"] - coupled_side_set_id = side_set_id_from_name(coupled_side_set_name, coupled_mesh) - coupled_nodes_indices = Vector{Vector{Int64}}(undef, 0) - interpolation_function_values = Vector{Vector{Float64}}(undef, 0) - tol = 1.0e-06 - if haskey(bc_params, "search tolerance") == true - tol = bc_params["search tolerance"] - end - side_set_node_indices = unique(side_set_node_indices) - for node_index in side_set_node_indices - point = subsim.model.reference[:, node_index] - node_indices, ξ, found = find_point_in_mesh(point, coupled_subsim.model, coupled_block_id, tol) - if found == false - error("Could not find subdomain ", subsim.name, " point ", point, " in subdomain ", coupled_subsim.name) - end - N = interpolate(element_type, ξ)[1] - push!(coupled_nodes_indices, node_indices) - push!(interpolation_function_values, N) - end - is_dirichlet = true - swap_bcs = false - - opinf_model_file = bc_params["model-file"] - py""" - import torch - def get_model(model_file): - return torch.load(model_file) - """ - model = py"get_model"(opinf_model_file) - - opinf_model_file = bc_params["model-file"] - basis_file = bc_params["basis-file"] - basis = NPZ.npzread(basis_file) - basis = basis["basis"] - - if bc_type == "OpInf Schwarz overlap" - SMOpInfOverlapSchwarzBC( - side_set_name, - side_set_node_indices, - coupled_nodes_indices, - interpolation_function_values, - coupled_subsim, - subsim, - is_dirichlet, - swap_bcs, - model, - basis - ) - else - error("Unknown boundary condition type : ", bc_type) - end -end - - function SMDirichletInclined(input_mesh::ExodusDatabase, bc_params::Parameters) node_set_name = bc_params["node set"] expression = bc_params["function"] @@ -357,61 +244,6 @@ function SMCouplingSchwarzBC( end end -function apply_bc(model::OpInfModel, bc::SMDirichletBC) - model.fom_model.time = model.time - apply_bc(model.fom_model, bc) - bc_vector = zeros(0) - for node_index in bc.node_set_node_indices - disp_val = model.fom_model.current[bc.offset, node_index] - model.fom_model.reference[bc.offset, node_index] - push!(bc_vector, disp_val) - end - - offset = bc.offset - if offset == 1 - offset_name = "x" - end - if offset == 2 - offset_name = "y" - end - if offset == 3 - offset_name = "z" - end - - op_name = "B_" * bc.node_set_name * "-" * offset_name - bc_operator = model.opinf_rom[op_name] - # SM Dirichlet BC are only defined on a single x,y,z - return model.reduced_boundary_forcing[:] += bc_operator[1, :, :] * bc_vector -end - -function apply_bc(model::NeuralNetworkOpInfRom, bc::SMOpInfDirichletBC) - model.fom_model.time = model.time - apply_bc(model.fom_model,bc.fom_bc) - bc_vector = zeros(0) - for node_index ∈ bc.fom_bc.node_set_node_indices - dof_index = 3 * (node_index - 1) + bc.fom_bc.offset - disp_val = model.fom_model.current[bc.fom_bc.offset,node_index] - model.fom_model.reference[bc.fom_bc.offset, node_index] - push!(bc_vector,disp_val) - end - - py""" - import numpy as np - def setup_inputs(x): - xi = np.zeros((1,x.size)) - xi[0] = x - inputs = torch.tensor(xi) - return inputs - """ - - reduced_bc_vector = bc.basis[1,:,:]' * bc_vector - print(size(reduced_bc_vector),size(bc.basis),size(bc_vector)) - model_inputs = py"setup_inputs"(reduced_bc_vector) - reduced_forcing = bc.nn_model.forward(model_inputs) - reduced_forcing = reduced_forcing.detach().numpy()[1,:] - # SM Dirichlet BC are only defined on a single x,y,z - model.reduced_boundary_forcing[:] += reduced_forcing - end - - function apply_bc(model::SolidMechanics, bc::SMDirichletBC) for node_index in bc.node_set_node_indices values = Dict( @@ -548,10 +380,6 @@ function find_point_in_mesh(point::Vector{Float64}, model::SolidMechanics, blk_i return node_indices, ξ, found end -function find_point_in_mesh(point::Vector{Float64}, model::RomModel, blk_id::Int, tol::Float64) - node_indices, ξ, found = find_point_in_mesh(point, model.fom_model, blk_id, tol) - return node_indices, ξ, found -end function apply_bc_detail(model::SolidMechanics, bc::SMContactSchwarzBC) if bc.is_dirichlet == true @@ -569,26 +397,6 @@ function apply_bc_detail(model::SolidMechanics, bc::CouplingSchwarzBoundaryCondi end end -function apply_bc_detail(model::OpInfModel, bc::CouplingSchwarzBoundaryCondition) - if bc.coupled_subsim.model isa SolidMechanics - ## Apply BC to the FOM vector - apply_bc_detail(model.fom_model, bc) - - # populate our own BC vector - bc_vector = zeros(3, length(bc.side_set_node_indices)) - for i in 1:length(bc.side_set_node_indices) - node_index = bc.side_set_node_indices[i] - bc_vector[:, i] = model.fom_model.current[:, node_index] - model.fom_model.reference[:, node_index] - end - op_name = "B_" * bc.side_set_name - bc_operator = model.opinf_rom[op_name] - for i in 1:3 - model.reduced_boundary_forcing[:] += bc_operator[i, :, :] * bc_vector[i, :] - end - else - throw("ROM-ROM coupling not supported yet") - end -end function apply_sm_schwarz_coupling_dirichlet(model::SolidMechanics, bc::CouplingSchwarzBoundaryCondition) if bc.coupled_subsim.model isa SolidMechanics @@ -704,78 +512,6 @@ function apply_bc(model::SolidMechanics, bc::SchwarzBoundaryCondition) return copy_solution_source_targets(bc.coupled_subsim.integrator, bc.coupled_subsim.solver, bc.coupled_subsim.model) end -function apply_bc(model::RomModel, bc::SchwarzBoundaryCondition) - global_sim = bc.coupled_subsim.params["global_simulation"] - controller = global_sim.controller - if bc isa SMContactSchwarzBC && controller.active_contact == false - return nothing - end - empty_history = length(controller.time_hist) == 0 - same_step = controller.same_step - if empty_history == true - apply_bc_detail(model, bc) - return nothing - end - # Save solution of coupled simulation - saved_disp = bc.coupled_subsim.integrator.displacement - saved_velo = bc.coupled_subsim.integrator.velocity - saved_acce = bc.coupled_subsim.integrator.acceleration - saved_∂Ω_f = bc.coupled_subsim.model.internal_force - time = model.time - coupled_name = bc.coupled_subsim.name - coupled_index = global_sim.subsim_name_index_map[coupled_name] - time_hist = controller.time_hist[coupled_index] - disp_hist = controller.disp_hist[coupled_index] - velo_hist = controller.velo_hist[coupled_index] - acce_hist = controller.acce_hist[coupled_index] - ∂Ω_f_hist = controller.∂Ω_f_hist[coupled_index] - interp_disp = same_step == true ? disp_hist[end] : interpolate(time_hist, disp_hist, time) - interp_velo = same_step == true ? velo_hist[end] : interpolate(time_hist, velo_hist, time) - interp_acce = same_step == true ? acce_hist[end] : interpolate(time_hist, acce_hist, time) - interp_∂Ω_f = same_step == true ? ∂Ω_f_hist[end] : interpolate(time_hist, ∂Ω_f_hist, time) - if bc.coupled_subsim.model isa SolidMechanics - bc.coupled_subsim.model.internal_force = interp_∂Ω_f - elseif bc.coupled_subsim.model isa RomModel - bc.coupled_subsim.model.fom_model.internal_force = interp_∂Ω_f - end - - if bc isa SMContactSchwarzBC || bc isa SMNonOverlapSchwarzBC - relaxation_parameter = controller.relaxation_parameter - schwarz_iteration = controller.iteration_number - if schwarz_iteration == 1 - lambda_dispᵖʳᵉᵛ = zeros(length(interp_disp)) - lambda_veloᵖʳᵉᵛ = zeros(length(interp_velo)) - lambda_acceᵖʳᵉᵛ = zeros(length(interp_acce)) - else - lambda_dispᵖʳᵉᵛ = controller.lambda_disp[coupled_index] - lambda_veloᵖʳᵉᵛ = controller.lambda_velo[coupled_index] - lambda_acceᵖʳᵉᵛ = controller.lambda_acce[coupled_index] - end - bc.coupled_subsim.integrator.displacement = - controller.lambda_disp[coupled_index] = - relaxation_parameter * interp_disp + (1 - relaxation_parameter) * lambda_dispᵖʳᵉᵛ - bc.coupled_subsim.integrator.velocity = - controller.lambda_velo[coupled_index] = - relaxation_parameter * interp_velo + (1 - relaxation_parameter) * lambda_veloᵖʳᵉᵛ - bc.coupled_subsim.integrator.acceleration = - controller.lambda_acce[coupled_index] = - relaxation_parameter * interp_acce + (1 - relaxation_parameter) * lambda_acceᵖʳᵉᵛ - else - bc.coupled_subsim.integrator.displacement = interp_disp - bc.coupled_subsim.integrator.velocity = interp_velo - bc.coupled_subsim.integrator.acceleration = interp_acce - end - # Copies from integrator to model - copy_solution_source_targets(bc.coupled_subsim.integrator, bc.coupled_subsim.solver, bc.coupled_subsim.model) - apply_bc_detail(model, bc) - bc.coupled_subsim.integrator.displacement = saved_disp - bc.coupled_subsim.integrator.velocity = saved_velo - bc.coupled_subsim.integrator.acceleration = saved_acce - bc.coupled_subsim.model.internal_force = saved_∂Ω_f - # Copy from integrator to model - return copy_solution_source_targets(bc.coupled_subsim.integrator, bc.coupled_subsim.solver, bc.coupled_subsim.model) -end - function transfer_normal_component(source::Vector{Float64}, target::Vector{Float64}, normal::Vector{Float64}) normal_projection = normal * normal' tangent_projection = I(length(normal)) - normal_projection @@ -1038,13 +774,6 @@ function apply_bcs(model::SolidMechanics) end end -function apply_bcs(model::RomModel) - model.reduced_boundary_forcing[:] .= 0.0 - for boundary_condition in model.boundary_conditions - apply_bc(model, boundary_condition) - end -end - function assign_velocity!( velocity::Matrix{Float64}, offset::Int64, node_index::Int32, velo_val::Float64, context::String ) @@ -1108,34 +837,6 @@ function apply_ics(params::Parameters, model::SolidMechanics) end end -function apply_ics(params::Parameters, model::RomModel) - apply_ics(params, model.fom_model) - - if haskey(params, "initial conditions") == false - return nothing - end - n_var, n_node, n_mode = model.basis.size - n_var_fom, n_node_fom = size(model.fom_model.current) - - # Make sure basis is the right size - if n_var != n_var_fom || n_node != n_node_fom - throw("Basis is wrong size") - end - - # project onto basis - for k in 1:n_mode - model.reduced_state[k] = 0.0 - model.reduced_velocity[k] = 0.0 - for j in 1:n_node - for n in 1:n_var - model.reduced_state[k] += - model.basis[n, j, k] * (model.fom_model.current[n, j] - model.fom_model.reference[n, j]) - model.reduced_velocity[k] += model.basis[n, j, k] * (model.fom_model.velocity[n, j]) - end - end - end -end - function pair_schwarz_bcs(sim::MultiDomainSimulation) for subsim in sim.subsims model = subsim.model diff --git a/src/model.jl b/src/model.jl index db70efb9..a9c15781 100644 --- a/src/model.jl +++ b/src/model.jl @@ -7,128 +7,8 @@ include("constitutive.jl") include("interpolation.jl") include("ics_bcs.jl") - +include("opinf/opinf_ics_bcs.jl") using Base.Threads: @threads, threadid, nthreads -using NPZ -using PyCall - -function LinearOpInfRom(params::Parameters) - params["mesh smoothing"] = false - fom_model = SolidMechanics(params) - reference = fom_model.reference - opinf_model_file = params["model"]["model-file"] - opinf_model = NPZ.npzread(opinf_model_file) - basis = opinf_model["basis"] - _, _, reduced_dim = size(basis) - num_dofs = reduced_dim - time = 0.0 - failed = false - null_vec = zeros(num_dofs) - - reduced_state = zeros(num_dofs) - reduced_velocity = zeros(num_dofs) - reduced_boundary_forcing = zeros(num_dofs) - free_dofs = trues(num_dofs) - boundary_conditions = Vector{BoundaryCondition}() - return LinearOpInfRom( - opinf_model, - basis, - reduced_state, - reduced_velocity, - reduced_boundary_forcing, - null_vec, - free_dofs, - boundary_conditions, - time, - failed, - fom_model, - reference, - false, - ) -end - -function QuadraticOpInfRom(params::Parameters) - params["mesh smoothing"] = false - fom_model = SolidMechanics(params) - reference = fom_model.reference - opinf_model_file = params["model"]["model-file"] - opinf_model = NPZ.npzread(opinf_model_file) - basis = opinf_model["basis"] - _, _, reduced_dim = size(basis) - num_dofs = reduced_dim - time = 0.0 - failed = false - null_vec = zeros(num_dofs) - - reduced_state = zeros(num_dofs) - reduced_velocity = zeros(num_dofs) - reduced_boundary_forcing = zeros(num_dofs) - free_dofs = trues(num_dofs) - boundary_conditions = Vector{BoundaryCondition}() - return QuadraticOpInfRom( - opinf_model, - basis, - reduced_state, - reduced_velocity, - reduced_boundary_forcing, - null_vec, - free_dofs, - boundary_conditions, - time, - failed, - fom_model, - reference, - false, - ) -end - -function NeuralNetworkOpInfRom(params::Dict{String,Any}) - params["mesh smoothing"] = false - fom_model = SolidMechanics(params) - reference = fom_model.reference - opinf_model_directory = params["model"]["model-directory"] - basis_file = opinf_model_directory * "/nn-opinf-basis.npz" - basis = NPZ.npzread(basis_file) - basis = basis["basis"] - py""" - import torch - def get_model(model_file): - return torch.load(model_file) - """ - ensemble_size = params["model"]["ensemble-size"] - model = [] - for i in 1:ensemble_size - tmp = py"get_model"(opinf_model_directory * "/stiffness-" * string(i-1) * ".pt") - push!(model,tmp) - end - num_dofs_per_node,num_nodes_basis,reduced_dim = size(basis) - num_dofs = reduced_dim - - time = 0.0 - failed = false - null_vec = zeros(num_dofs) - - reduced_state = zeros(num_dofs) - reduced_velocity = zeros(num_dofs) - reduced_boundary_forcing = zeros(num_dofs) - free_dofs = trues(num_dofs) - boundary_conditions = Vector{BoundaryCondition}() - NeuralNetworkOpInfRom( - model, - basis, - reduced_state, - reduced_velocity, - reduced_boundary_forcing, - null_vec, - free_dofs, - boundary_conditions, - time, - failed, - fom_model, - reference, - false - ) -end function SolidMechanics(params::Parameters) diff --git a/src/model_types.jl b/src/model_types.jl index 2654fef8..c08ac671 100644 --- a/src/model_types.jl +++ b/src/model_types.jl @@ -5,8 +5,6 @@ # top-level Norma.jl directory. abstract type Model end -abstract type RomModel <: Model end -abstract type OpInfModel <: RomModel end using SparseArrays @enum Kinematics begin @@ -95,70 +93,4 @@ mutable struct SolidMechanics <: Model kinematics::Kinematics end -mutable struct QuadraticOpInfRom <: OpInfModel - opinf_rom::Dict{Any,Any} - basis::Array{Float64} - reduced_state::Vector{Float64} - reduced_velocity::Vector{Float64} - reduced_boundary_forcing::Vector{Float64} - #internal_force not used, but include to ease interfacing in Schwarz - internal_force::Vector{Float64} - free_dofs::BitVector - boundary_conditions::Vector{BoundaryCondition} - time::Float64 - failed::Bool - fom_model::SolidMechanics - reference::Matrix{Float64} - inclined_support::Bool -end - -mutable struct GalerkinRom <: OpInfModel - opinf_rom::Dict{Any,Any} - basis::Array{Float64} - reduced_state::Vector{Float64} - reduced_velocity::Vector{Float64} - reduced_boundary_forcing::Vector{Float64} - #internal_force not used, but include to ease interfacing in Schwarz - internal_force::Vector{Float64} - free_dofs::BitVector - boundary_conditions::Vector{BoundaryCondition} - time::Float64 - failed::Bool - fom_model::SolidMechanics - reference::Matrix{Float64} - inclined_support::Bool -end -mutable struct LinearOpInfRom <: OpInfModel - opinf_rom::Dict{Any,Any} - basis::Array{Float64} - reduced_state::Vector{Float64} - reduced_velocity::Vector{Float64} - reduced_boundary_forcing::Vector{Float64} - #internal_force not used, but include to ease interfacing in Schwarz - internal_force::Vector{Float64} - free_dofs::BitVector - boundary_conditions::Vector{BoundaryCondition} - time::Float64 - failed::Bool - fom_model::SolidMechanics - reference::Matrix{Float64} - inclined_support::Bool -end - -mutable struct NeuralNetworkOpInfRom <: OpInfModel - nn_model::Any - basis::Array{Float64} - reduced_state::Vector{Float64} - reduced_velocity::Vector{Float64} - reduced_boundary_forcing::Vector{Float64} - #internal_force not used, but include to ease interfacing in Schwarz - internal_force::Vector{Float64} - free_dofs::BitVector - boundary_conditions::Vector{BoundaryCondition} - time::Float64 - failed::Bool - fom_model::SolidMechanics - reference::Matrix{Float64} - inclined_support::Bool -end diff --git a/src/opinf/opinf_ics_bcs.jl b/src/opinf/opinf_ics_bcs.jl new file mode 100644 index 00000000..3c5c033e --- /dev/null +++ b/src/opinf/opinf_ics_bcs.jl @@ -0,0 +1,370 @@ +# Norma: Copyright 2025 National Technology & Engineering Solutions of +# Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, +# the U.S. Government retains certain rights in this software. This software +# is released under the BSD license detailed in the file license.txt in the +# top-level Norma.jl directory. +using PyCall +import NPZ + +@variables t, x, y, z +D = Differential(t) + + +function SMOpInfDirichletBC(input_mesh::ExodusDatabase, bc_params::Dict{String,Any}) + fom_bc = SMDirichletBC(input_mesh,bc_params) + node_set_name = bc_params["node set"] + expression = bc_params["function"] + offset = component_offset_from_string(bc_params["component"]) + node_set_id = node_set_id_from_name(node_set_name, input_mesh) + node_set_node_indices = Exodus.read_node_set_nodes(input_mesh, node_set_id) + # expression is an arbitrary function of t, x, y, z in the input file + disp_num = eval(Meta.parse(expression)) + velo_num = expand_derivatives(D(disp_num)) + acce_num = expand_derivatives(D(velo_num)) + + opinf_model_file = bc_params["model-file"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + model = py"get_model"(opinf_model_file) + + opinf_model_file = bc_params["model-file"] + basis_file = bc_params["basis-file"] + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + + SMOpInfDirichletBC( + node_set_name, + offset, + node_set_id, + node_set_node_indices, + disp_num, + velo_num, + acce_num, + fom_bc, + model, + basis, + ) +end + + +function SMOpInfCouplingSchwarzBC( + subsim::SingleDomainSimulation, + coupled_subsim::SingleDomainSimulation, + input_mesh::ExodusDatabase, + bc_type::String, + bc_params::Parameters, +) + fom_bc = SMCouplingSchwarzBC(subsim,coupled_subsim,input_mesh,"Schwarz overlap",bc_params) + side_set_name = bc_params["side set"] + side_set_id = side_set_id_from_name(side_set_name, input_mesh) + _, _, side_set_node_indices = get_side_set_local_from_global_map(input_mesh, side_set_id) + coupled_block_name = bc_params["source block"] + if typeof(coupled_subsim.model) <: RomModel + coupled_mesh = coupled_subsim.model.fom_model.mesh + else + coupled_mesh = coupled_subsim.model.mesh + end + coupled_block_id = block_id_from_name(coupled_block_name, coupled_mesh) + element_type = Exodus.read_block_parameters(coupled_mesh, coupled_block_id)[1] + coupled_side_set_name = bc_params["source side set"] + coupled_side_set_id = side_set_id_from_name(coupled_side_set_name, coupled_mesh) + coupled_nodes_indices = Vector{Vector{Int64}}(undef, 0) + interpolation_function_values = Vector{Vector{Float64}}(undef, 0) + tol = 1.0e-06 + if haskey(bc_params, "search tolerance") == true + tol = bc_params["search tolerance"] + end + side_set_node_indices = unique(side_set_node_indices) + for node_index in side_set_node_indices + point = subsim.model.reference[:, node_index] + node_indices, ξ, found = find_point_in_mesh(point, coupled_subsim.model, coupled_block_id, tol) + if found == false + error("Could not find subdomain ", subsim.name, " point ", point, " in subdomain ", coupled_subsim.name) + end + N = interpolate(element_type, ξ)[1] + push!(coupled_nodes_indices, node_indices) + push!(interpolation_function_values, N) + end + is_dirichlet = true + swap_bcs = false + + opinf_model_directory = bc_params["model-directory"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + ensemble_size = bc_params["ensemble-size"] + model = [] + for i in 1:ensemble_size + tmp = py"get_model"(opinf_model_directory * "/BC-" * side_set_name * "-" * string(i-1) * ".pt") + push!(model,tmp) + end + + basis_file = bc_params["model-directory"] * "/nn-opinf-basis-" * side_set_name * ".npz" + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + + if bc_type == "OpInf Schwarz overlap" + SMOpInfOverlapSchwarzBC( + side_set_name, + side_set_node_indices, + coupled_nodes_indices, + interpolation_function_values, + coupled_subsim, + subsim, + is_dirichlet, + swap_bcs, + fom_bc, + model, + basis + ) + else + error("Unknown boundary condition type : ", bc_type) + end +end + + + +function apply_bc(model::OpInfModel, bc::SMDirichletBC) + model.fom_model.time = model.time + apply_bc(model.fom_model, bc) + bc_vector = zeros(0) + for node_index in bc.node_set_node_indices + disp_val = model.fom_model.current[bc.offset, node_index] - model.fom_model.reference[bc.offset, node_index] + push!(bc_vector, disp_val) + end + + offset = bc.offset + if offset == 1 + offset_name = "x" + end + if offset == 2 + offset_name = "y" + end + if offset == 3 + offset_name = "z" + end + + op_name = "B_" * bc.node_set_name * "-" * offset_name + bc_operator = model.opinf_rom[op_name] + # SM Dirichlet BC are only defined on a single x,y,z + return model.reduced_boundary_forcing[:] += bc_operator[1, :, :] * bc_vector +end + +function apply_bc(model::NeuralNetworkOpInfRom, bc::SMOpInfDirichletBC) + model.fom_model.time = model.time + apply_bc(model.fom_model,bc.fom_bc) + bc_vector = zeros(0) + for node_index ∈ bc.fom_bc.node_set_node_indices + dof_index = 3 * (node_index - 1) + bc.fom_bc.offset + disp_val = model.fom_model.current[bc.fom_bc.offset,node_index] - model.fom_model.reference[bc.fom_bc.offset, node_index] + push!(bc_vector,disp_val) + end + + py""" + import numpy as np + def setup_inputs(x): + xi = np.zeros((1,x.size)) + xi[0] = x + inputs = torch.tensor(xi) + return inputs + """ + + reduced_bc_vector = bc.basis[1,:,:]' * bc_vector + print(size(reduced_bc_vector),size(bc.basis),size(bc_vector)) + model_inputs = py"setup_inputs"(reduced_bc_vector) + ensemble_size = size(bc.nn_model)[1] + for i in 1:ensemble_size + reduced_forcing = bc.nn_model[i].forward(model_inputs) + reduced_forcing = reduced_forcing.detach().numpy()[1,:] + model.reduced_boundary_forcing[:] += reduced_forcing + end + model.reduced_boundary_forcing[:] = model.reduced_boundary_forcing[:] ./ ensemble_size +end + +function apply_bc_detail(model::OpInfModel, bc::CouplingSchwarzBoundaryCondition) + if (typeof(bc.coupled_subsim.model) == SolidMechanics) + ## Apply BC to the FOM vector + apply_bc_detail(model.fom_model, bc) + + # populate our own BC vector + bc_vector = zeros(3, length(bc.side_set_node_indices)) + for i in 1:length(bc.side_set_node_indices) + node_index = bc.side_set_node_indices[i] + bc_vector[:, i] = model.fom_model.current[:, node_index] - model.fom_model.reference[:, node_index] + end + op_name = "B_" * bc.side_set_name + bc_operator = model.opinf_rom[op_name] + for i in 1:3 + model.reduced_boundary_forcing[:] += bc_operator[i, :, :] * bc_vector[i, :] + end + else + throw("ROM-ROM coupling not supported yet") + end +end + + +function apply_bc_detail(model::NeuralNetworkOpInfRom, bc::CouplingSchwarzBoundaryCondition) + if (typeof(bc.coupled_subsim.model) == SolidMechanics) + ## Apply BC to the FOM vector + apply_bc_detail(model.fom_model, bc.fom_bc) + + # populate our own BC vector + bc_vector = zeros(3, length(bc.fom_bc.side_set_node_indices)) + for i in 1:length(bc.fom_bc.side_set_node_indices) + node_index = bc.fom_bc.side_set_node_indices[i] + bc_vector[:, i] = model.fom_model.current[:, node_index] - model.fom_model.reference[:, node_index] + end + + py""" + import numpy as np + def setup_inputs(x): + xi = np.zeros((1,x.size)) + xi[0] = x + inputs = torch.tensor(xi) + return inputs + """ + + reduced_bc_vector = zeros(bc.basis.size[3]) + for i in 1:3 + reduced_bc_vector[:] += bc.basis[i,:,:]' * bc_vector[i,:] + end + model_inputs = py"setup_inputs"(reduced_bc_vector) + ensemble_size = size(bc.nn_model)[1] + local_reduced_forcing = zeros(model.reduced_boundary_forcing.size[1]) + for i in 1:ensemble_size + reduced_forcing = bc.nn_model[i].forward(model_inputs) + reduced_forcing = reduced_forcing.detach().numpy()[1,:] + local_reduced_forcing[:] += reduced_forcing + end + local_reduced_forcing[:] = local_reduced_forcing[:] ./ ensemble_size + model.reduced_boundary_forcing[:] += local_reduced_forcing + + else + throw("ROM-ROM coupling not supported yet") + end +end + + +function apply_bc(model::RomModel, bc::SchwarzBoundaryCondition) + global_sim = bc.coupled_subsim.params["global_simulation"] + schwarz_controller = global_sim.schwarz_controller + if typeof(bc) == SMContactSchwarzBC && schwarz_controller.active_contact == false + return nothing + end + empty_history = length(global_sim.schwarz_controller.time_hist) == 0 + same_step = schwarz_controller.same_step == true + if empty_history == true + apply_bc_detail(model, bc) + return nothing + end + # Save solution of coupled simulation + saved_disp = bc.coupled_subsim.integrator.displacement + saved_velo = bc.coupled_subsim.integrator.velocity + saved_acce = bc.coupled_subsim.integrator.acceleration + saved_∂Ω_f = bc.coupled_subsim.model.internal_force + time = model.time + coupled_name = bc.coupled_subsim.name + coupled_index = global_sim.subsim_name_index_map[coupled_name] + time_hist = global_sim.schwarz_controller.time_hist[coupled_index] + disp_hist = global_sim.schwarz_controller.disp_hist[coupled_index] + velo_hist = global_sim.schwarz_controller.velo_hist[coupled_index] + acce_hist = global_sim.schwarz_controller.acce_hist[coupled_index] + ∂Ω_f_hist = global_sim.schwarz_controller.∂Ω_f_hist[coupled_index] + interp_disp = same_step == true ? disp_hist[end] : interpolate(time_hist, disp_hist, time) + interp_velo = same_step == true ? velo_hist[end] : interpolate(time_hist, velo_hist, time) + interp_acce = same_step == true ? acce_hist[end] : interpolate(time_hist, acce_hist, time) + interp_∂Ω_f = same_step == true ? ∂Ω_f_hist[end] : interpolate(time_hist, ∂Ω_f_hist, time) + if typeof(bc.coupled_subsim.model) == SolidMechanics + bc.coupled_subsim.model.internal_force = interp_∂Ω_f + elseif typeof(bc.coupled_subsim.model) <: RomModel + bc.coupled_subsim.model.fom_model.internal_force = interp_∂Ω_f + end + + if typeof(bc) == SMContactSchwarzBC || typeof(bc) == SMNonOverlapSchwarzBC + relaxation_parameter = global_sim.schwarz_controller.relaxation_parameter + schwarz_iteration = global_sim.schwarz_controller.iteration_number + if schwarz_iteration == 1 + lambda_dispᵖʳᵉᵛ = zeros(length(interp_disp)) + lambda_veloᵖʳᵉᵛ = zeros(length(interp_velo)) + lambda_acceᵖʳᵉᵛ = zeros(length(interp_acce)) + else + lambda_dispᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_disp[coupled_index] + lambda_veloᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_velo[coupled_index] + lambda_acceᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_acce[coupled_index] + end + bc.coupled_subsim.integrator.displacement = + global_sim.schwarz_controller.lambda_disp[coupled_index] = + relaxation_parameter * interp_disp + (1 - relaxation_parameter) * lambda_dispᵖʳᵉᵛ + bc.coupled_subsim.integrator.velocity = + global_sim.schwarz_controller.lambda_velo[coupled_index] = + relaxation_parameter * interp_velo + (1 - relaxation_parameter) * lambda_veloᵖʳᵉᵛ + bc.coupled_subsim.integrator.acceleration = + global_sim.schwarz_controller.lambda_acce[coupled_index] = + relaxation_parameter * interp_acce + (1 - relaxation_parameter) * lambda_acceᵖʳᵉᵛ + else + bc.coupled_subsim.integrator.displacement = interp_disp + bc.coupled_subsim.integrator.velocity = interp_velo + bc.coupled_subsim.integrator.acceleration = interp_acce + end + # Copies from integrator to model + copy_solution_source_targets(bc.coupled_subsim.integrator, bc.coupled_subsim.solver, bc.coupled_subsim.model) + apply_bc_detail(model, bc) + bc.coupled_subsim.integrator.displacement = saved_disp + bc.coupled_subsim.integrator.velocity = saved_velo + bc.coupled_subsim.integrator.acceleration = saved_acce + bc.coupled_subsim.model.internal_force = saved_∂Ω_f + # Copy from integrator to model + return copy_solution_source_targets(bc.coupled_subsim.integrator, bc.coupled_subsim.solver, bc.coupled_subsim.model) +end + + +function apply_bcs(model::RomModel) + model.reduced_boundary_forcing[:] .= 0.0 + for boundary_condition in model.boundary_conditions + apply_bc(model, boundary_condition) + end +end + + +function apply_ics(params::Parameters, model::RomModel) + apply_ics(params, model.fom_model) + + if haskey(params, "initial conditions") == false + return nothing + end + n_var, n_node, n_mode = model.basis.size + n_var_fom, n_node_fom = size(model.fom_model.current) + + # Make sure basis is the right size + if n_var != n_var_fom || n_node != n_node_fom + throw("Basis is wrong size") + end + + # project onto basis + for k in 1:n_mode + model.reduced_state[k] = 0.0 + model.reduced_velocity[k] = 0.0 + for j in 1:n_node + for n in 1:n_var + model.reduced_state[k] += + model.basis[n, j, k] * + (model.fom_model.current[n, j] - model.fom_model.reference[n, j]) + model.reduced_velocity[k] += + model.basis[n, j, k] * + (model.fom_model.velocity[n, j]) + end + end + end +end + + +function find_point_in_mesh(point::Vector{Float64}, model::RomModel, blk_id::Int, tol::Float64) + node_indices, ξ, found = find_point_in_mesh(point, model.fom_model, blk_id, tol) + return node_indices, ξ, found +end + diff --git a/src/opinf/opinf_ics_bcs_types.jl b/src/opinf/opinf_ics_bcs_types.jl new file mode 100644 index 00000000..abdf9738 --- /dev/null +++ b/src/opinf/opinf_ics_bcs_types.jl @@ -0,0 +1,47 @@ +# Norma: Copyright 2025 National Technology & Engineering Solutions of +# Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, +# the U.S. Government retains certain rights in this software. This software +# is released under the BSD license detailed in the file license.txt in the +# top-level Norma.jl directory. + +abstract type BoundaryCondition end +abstract type SchwarzBoundaryCondition <: BoundaryCondition end +abstract type RegularBoundaryCondition <: BoundaryCondition end +abstract type ContactSchwarzBoundaryCondition <: SchwarzBoundaryCondition end +abstract type CouplingSchwarzBoundaryCondition <: SchwarzBoundaryCondition end +abstract type OverlapSchwarzBoundaryCondition <: CouplingSchwarzBoundaryCondition end +abstract type NonOverlapSchwarzBoundaryCondition <: CouplingSchwarzBoundaryCondition end +abstract type InitialCondition end + +using Exodus +using Symbolics + +mutable struct SMOpInfDirichletBC <: RegularBoundaryCondition + node_set_name::String + offset::Int64 + node_set_id::Int64 + node_set_node_indices::Vector{Int64} + disp_num::Num + velo_num::Num + acce_num::Num + fom_bc::SMDirichletBC + nn_model::Any + basis::Any +end + + +mutable struct SMOpInfOverlapSchwarzBC <: OverlapSchwarzBoundaryCondition + side_set_name::String + side_set_node_indices::Vector{Int64} + coupled_nodes_indices::Vector{Vector{Int64}} + interpolation_function_values::Vector{Vector{Float64}} + coupled_subsim::Simulation + subsim::Simulation + is_dirichlet::Bool + swap_bcs::Bool + fom_bc::SMOverlapSchwarzBC + nn_model::Any + basis::Any +end + + diff --git a/src/opinf/opinf_model.jl b/src/opinf/opinf_model.jl new file mode 100644 index 00000000..8f17d18b --- /dev/null +++ b/src/opinf/opinf_model.jl @@ -0,0 +1,129 @@ +# Norma: Copyright 2025 National Technology & Engineering Solutions of +# Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, +# the U.S. Government retains certain rights in this software. This software +# is released under the BSD license detailed in the file license.txt in the +# top-level Norma.jl directory. + +using Base.Threads: @threads, threadid, nthreads +using NPZ +using PyCall + +function LinearOpInfRom(params::Parameters) + params["mesh smoothing"] = false + fom_model = SolidMechanics(params) + reference = fom_model.reference + opinf_model_file = params["model"]["model-file"] + opinf_model = NPZ.npzread(opinf_model_file) + basis = opinf_model["basis"] + _, _, reduced_dim = size(basis) + num_dofs = reduced_dim + time = 0.0 + failed = false + null_vec = zeros(num_dofs) + + reduced_state = zeros(num_dofs) + reduced_velocity = zeros(num_dofs) + reduced_boundary_forcing = zeros(num_dofs) + free_dofs = trues(num_dofs) + boundary_conditions = Vector{BoundaryCondition}() + return LinearOpInfRom( + opinf_model, + basis, + reduced_state, + reduced_velocity, + reduced_boundary_forcing, + null_vec, + free_dofs, + boundary_conditions, + time, + failed, + fom_model, + reference, + false, + ) +end + +function QuadraticOpInfRom(params::Parameters) + params["mesh smoothing"] = false + fom_model = SolidMechanics(params) + reference = fom_model.reference + opinf_model_file = params["model"]["model-file"] + opinf_model = NPZ.npzread(opinf_model_file) + basis = opinf_model["basis"] + _, _, reduced_dim = size(basis) + num_dofs = reduced_dim + time = 0.0 + failed = false + null_vec = zeros(num_dofs) + + reduced_state = zeros(num_dofs) + reduced_velocity = zeros(num_dofs) + reduced_boundary_forcing = zeros(num_dofs) + free_dofs = trues(num_dofs) + boundary_conditions = Vector{BoundaryCondition}() + return QuadraticOpInfRom( + opinf_model, + basis, + reduced_state, + reduced_velocity, + reduced_boundary_forcing, + null_vec, + free_dofs, + boundary_conditions, + time, + failed, + fom_model, + reference, + false, + ) +end + +function NeuralNetworkOpInfRom(params::Dict{String,Any}) + params["mesh smoothing"] = false + fom_model = SolidMechanics(params) + reference = fom_model.reference + opinf_model_directory = params["model"]["model-directory"] + basis_file = opinf_model_directory * "/nn-opinf-basis.npz" + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + ensemble_size = params["model"]["ensemble-size"] + model = [] + for i in 1:ensemble_size + tmp = py"get_model"(opinf_model_directory * "/stiffness-" * string(i-1) * ".pt") + push!(model,tmp) + end + num_dofs_per_node,num_nodes_basis,reduced_dim = size(basis) + num_dofs = reduced_dim + + time = 0.0 + failed = false + null_vec = zeros(num_dofs) + + reduced_state = zeros(num_dofs) + reduced_velocity = zeros(num_dofs) + reduced_boundary_forcing = zeros(num_dofs) + free_dofs = trues(num_dofs) + boundary_conditions = Vector{BoundaryCondition}() + NeuralNetworkOpInfRom( + model, + basis, + reduced_state, + reduced_velocity, + reduced_boundary_forcing, + null_vec, + free_dofs, + boundary_conditions, + time, + failed, + fom_model, + reference, + false + ) +end + + diff --git a/src/opinf/opinf_model_types.jl b/src/opinf/opinf_model_types.jl new file mode 100644 index 00000000..29963da8 --- /dev/null +++ b/src/opinf/opinf_model_types.jl @@ -0,0 +1,60 @@ +# Norma: Copyright 2025 National Technology & Engineering Solutions of +# Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, +# the U.S. Government retains certain rights in this software. This software +# is released under the BSD license detailed in the file license.txt in the +# top-level Norma.jl directory. + +abstract type RomModel <: Model end +abstract type OpInfModel <: RomModel end + +mutable struct QuadraticOpInfRom <: OpInfModel + opinf_rom::Dict{Any,Any} + basis::Array{Float64} + reduced_state::Vector{Float64} + reduced_velocity::Vector{Float64} + reduced_boundary_forcing::Vector{Float64} + #internal_force not used, but include to ease interfacing in Schwarz + internal_force::Vector{Float64} + free_dofs::BitVector + boundary_conditions::Vector{BoundaryCondition} + time::Float64 + failed::Bool + fom_model::SolidMechanics + reference::Matrix{Float64} + inclined_support::Bool +end + + +mutable struct LinearOpInfRom <: OpInfModel + opinf_rom::Dict{Any,Any} + basis::Array{Float64} + reduced_state::Vector{Float64} + reduced_velocity::Vector{Float64} + reduced_boundary_forcing::Vector{Float64} + #internal_force not used, but include to ease interfacing in Schwarz + internal_force::Vector{Float64} + free_dofs::BitVector + boundary_conditions::Vector{BoundaryCondition} + time::Float64 + failed::Bool + fom_model::SolidMechanics + reference::Matrix{Float64} + inclined_support::Bool +end + +mutable struct NeuralNetworkOpInfRom <: OpInfModel + nn_model::Any + basis::Array{Float64} + reduced_state::Vector{Float64} + reduced_velocity::Vector{Float64} + reduced_boundary_forcing::Vector{Float64} + #internal_force not used, but include to ease interfacing in Schwarz + internal_force::Vector{Float64} + free_dofs::BitVector + boundary_conditions::Vector{BoundaryCondition} + time::Float64 + failed::Bool + fom_model::SolidMechanics + reference::Matrix{Float64} + inclined_support::Bool +end diff --git a/src/simulation.jl b/src/simulation.jl index ae8d91c6..c79a682f 100644 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -14,6 +14,7 @@ include("model.jl") include("time_integrator.jl") include("solver.jl") include("schwarz.jl") +include("opinf/opinf_model.jl") function create_simulation(input_file::String) norma_log(0, :setup, "Reading from " * input_file) diff --git a/src/simulation_types.jl b/src/simulation_types.jl index 9669db85..d3bb020a 100644 --- a/src/simulation_types.jl +++ b/src/simulation_types.jl @@ -12,6 +12,7 @@ include("ics_bcs_types.jl") include("model_types.jl") include("time_integrator_types.jl") include("solver_types.jl") +include("opinf/opinf_model_types.jl") abstract type SingleController end abstract type MultiDomainController end From 9cf9c080cf753b912bb3cd3975a1a59b7043780d Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 17 Apr 2025 14:30:29 -0500 Subject: [PATCH 10/29] added example for making opinf model w/ automatic grid search --- .../dynamic-opinf-fom/make_op_inf_models.py | 136 +++--------------- 1 file changed, 21 insertions(+), 115 deletions(-) diff --git a/examples/ahead/overlap/torsion/dynamic-opinf-fom/make_op_inf_models.py b/examples/ahead/overlap/torsion/dynamic-opinf-fom/make_op_inf_models.py index 8e154264..40ca45e5 100644 --- a/examples/ahead/overlap/torsion/dynamic-opinf-fom/make_op_inf_models.py +++ b/examples/ahead/overlap/torsion/dynamic-opinf-fom/make_op_inf_models.py @@ -1,117 +1,23 @@ -import numpy as np -import opinf -import os -from matplotlib import pyplot as plt import normaopinf -import normaopinf.readers -import normaopinf.calculus -import romtools - -if __name__ == "__main__": - # Load in snapshots - cur_dir = os.getcwd() - solution_id = 2 - displacement_snapshots,times = normaopinf.readers.load_displacement_csv_files(solution_directory=cur_dir,solution_id=solution_id,skip_files=1) - - # Identify which DOFs are free - free_dofs = normaopinf.readers.get_free_dofs(solution_directory=cur_dir,solution_id=solution_id) - - # Set values = 0 if DOFs are fixed - displacement_snapshots[free_dofs[:,:]==False] = 0. - - #Get sideset snapshots - sidesets = ["-Z_ss"] - sideset_snapshots = normaopinf.readers.load_sideset_displacement_csv_files(solution_directory=cur_dir,sidesets=sidesets,solution_id=solution_id,skip_files=1) - - # Create an energy-based truncater - #tolerance = 1.e-5 - #my_energy_truncater = romtools.vector_space.utils.EnergyBasedTruncater(1. - tolerance) - my_energy_truncater = romtools.vector_space.utils.BasisSizeTruncater(4) - - # Now load in sidesets and create reduced spaces - # Note that I construct a separate basis for each x,y,z component. This isn't necessary - ss_tspace = {} - reduced_sideset_snapshots = {} - for sideset in sidesets: - if sideset_snapshots[sideset].shape[0] == 1: - ss_tspace[sideset] = romtools.VectorSpaceFromPOD(snapshots=sideset_snapshots[sideset], - truncater=my_energy_truncater, - shifter = None, - orthogonalizer=romtools.vector_space.utils.EuclideanL2Orthogonalizer(), - scaler = romtools.vector_space.utils.NoOpScaler()) - # Compute L2 orthogonal projection onto trial spaces - reduced_sideset_snapshots[sideset] = romtools.rom.optimal_l2_projection(sideset_snapshots[sideset],ss_tspace[sideset]) - else: - comp_trial_space = [] - for i in range(0,3): - tspace = romtools.VectorSpaceFromPOD(snapshots=sideset_snapshots[sideset][i:i+1], - truncater=my_energy_truncater, - shifter = None, - orthogonalizer=romtools.vector_space.utils.EuclideanL2Orthogonalizer(), - scaler = romtools.vector_space.utils.NoOpScaler()) - comp_trial_space.append(tspace) - ss_tspace[sideset] = romtools.CompositeVectorSpace(comp_trial_space) - reduced_sideset_snapshots[sideset] = romtools.rom.optimal_l2_projection(sideset_snapshots[sideset],ss_tspace[sideset]) - - - ## Stack sidesets into one matrix for OpInf - reduced_stacked_sideset_snapshots = None - for sideset in sidesets: - if reduced_stacked_sideset_snapshots is None: - reduced_stacked_sideset_snapshots = reduced_sideset_snapshots[sideset]*1. - else: - reduced_stacked_sideset_snapshots = np.append(reduced_stacked_sideset_snapshots,reduced_sideset_snapshots[sideset],axis=0) - - # Create trial space for displacement vector - # Note again that I construct a separate basis for each x,y,z component. This isn't necessary - trial_spaces = [] - - my_energy_truncater = romtools.vector_space.utils.BasisSizeTruncater(60) - - for i in range(0,3): - trial_space = romtools.VectorSpaceFromPOD(snapshots=displacement_snapshots[i:i+1], - truncater=my_energy_truncater, - shifter = None, - orthogonalizer=romtools.vector_space.utils.EuclideanL2Orthogonalizer(), - scaler = romtools.vector_space.utils.NoOpScaler()) - trial_spaces.append(trial_space) - - trial_space = romtools.CompositeVectorSpace(trial_spaces) - - # Compute L2 orthogonal projection onto trial spaces - uhat = romtools.rom.optimal_l2_projection(displacement_snapshots,trial_space) - u_ddots,times_dummy = normaopinf.readers.load_acceleration_csv_files(solution_directory=cur_dir,solution_id=solution_id,skip_files=1) - # Set values = 0 if DOFs are fixed - u_ddots[free_dofs[:,:]==False] = 0. - uhat_ddots = romtools.rom.optimal_l2_projection(u_ddots*1.,trial_space) - - # Construct an opinf "AB" model (linear in the state and linear in the exogenous inputs) - # Note: I don't construct a cAB ROM in this example since I know there is no forcing vector - l2solver = opinf.lstsq.L2Solver(regularizer=1e-11) - opinf_model = opinf.models.ContinuousModel("AB",solver=l2solver) - opinf_model.fit(states=uhat, ddts=uhat_ddots,inputs=reduced_stacked_sideset_snapshots) - - # Flip signs to match convention of K on the LHS - K = -opinf_model.A_.entries - B = opinf_model.B_.entries - - ## Now extract boundary operators and create dictionary to save - col_start = 0 - sideset_operators = {} - for sideset in sidesets: - num_dofs = reduced_sideset_snapshots[sideset].shape[0] - val = np.einsum('kr,vnr->vkn',B[:,col_start:col_start + num_dofs] , ss_tspace[sideset].get_basis() ) - shape2 = B[:,col_start:col_start + num_dofs] @ ss_tspace[sideset].get_basis()[0].transpose() - sideset_operators["B_" + sideset] = val# - col_start += num_dofs - - f = np.zeros(K.shape[0]) - vals_to_save = sideset_operators - vals_to_save["basis"] = trial_space.get_basis() - vals_to_save["K"] = K - vals_to_save["f"] = f - - np.savez('opinf-operator',**vals_to_save) - - +import normaopinf.opinf +import os +import numpy as np +if __name__ == '__main__': + settings = {} + settings['fom-yaml-file'] = "torsion.yaml" + settings['training-data-directories'] = [os.getcwd()] + settings['solution-id'] = 1 + settings['model-type'] = 'quadratic' + settings['forcing'] = False + settings['truncation-type'] = 'size' + settings['boundary-truncation-type'] = 'energy' + settings['regularization-parameter'] = 'automatic' + settings['trial-space-splitting-type'] = 'split' + settings['acceleration-computation-type'] = 'finite-difference' + sizes = np.array([2,4,6,8,10,12,14,16,18,20,40,60],dtype=int) + for i,size in enumerate(sizes): + settings['truncation-value'] = int(size) + settings['boundary-truncation-value'] = 1. - 1.e-5 + settings['operator-name'] = 'quadratic-opinf-operator-rom-dim-' + str(size) + normaopinf.opinf.make_opinf_model(settings) From e784864bf3fe8e38878a282b62cb9836cb9ca903 Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Wed, 30 Apr 2025 17:05:14 -0500 Subject: [PATCH 11/29] fix opinf bcs error from rebase and merge --- src/opinf/opinf_ics_bcs.jl | 42 ++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/opinf/opinf_ics_bcs.jl b/src/opinf/opinf_ics_bcs.jl index 3c5c033e..7b3af368 100644 --- a/src/opinf/opinf_ics_bcs.jl +++ b/src/opinf/opinf_ics_bcs.jl @@ -249,15 +249,14 @@ function apply_bc_detail(model::NeuralNetworkOpInfRom, bc::CouplingSchwarzBounda end end - function apply_bc(model::RomModel, bc::SchwarzBoundaryCondition) global_sim = bc.coupled_subsim.params["global_simulation"] - schwarz_controller = global_sim.schwarz_controller - if typeof(bc) == SMContactSchwarzBC && schwarz_controller.active_contact == false + controller = global_sim.controller + if bc isa SMContactSchwarzBC && controller.active_contact == false return nothing end - empty_history = length(global_sim.schwarz_controller.time_hist) == 0 - same_step = schwarz_controller.same_step == true + empty_history = length(controller.time_hist) == 0 + same_step = controller.same_step if empty_history == true apply_bc_detail(model, bc) return nothing @@ -270,41 +269,41 @@ function apply_bc(model::RomModel, bc::SchwarzBoundaryCondition) time = model.time coupled_name = bc.coupled_subsim.name coupled_index = global_sim.subsim_name_index_map[coupled_name] - time_hist = global_sim.schwarz_controller.time_hist[coupled_index] - disp_hist = global_sim.schwarz_controller.disp_hist[coupled_index] - velo_hist = global_sim.schwarz_controller.velo_hist[coupled_index] - acce_hist = global_sim.schwarz_controller.acce_hist[coupled_index] - ∂Ω_f_hist = global_sim.schwarz_controller.∂Ω_f_hist[coupled_index] + time_hist = controller.time_hist[coupled_index] + disp_hist = controller.disp_hist[coupled_index] + velo_hist = controller.velo_hist[coupled_index] + acce_hist = controller.acce_hist[coupled_index] + ∂Ω_f_hist = controller.∂Ω_f_hist[coupled_index] interp_disp = same_step == true ? disp_hist[end] : interpolate(time_hist, disp_hist, time) interp_velo = same_step == true ? velo_hist[end] : interpolate(time_hist, velo_hist, time) interp_acce = same_step == true ? acce_hist[end] : interpolate(time_hist, acce_hist, time) interp_∂Ω_f = same_step == true ? ∂Ω_f_hist[end] : interpolate(time_hist, ∂Ω_f_hist, time) - if typeof(bc.coupled_subsim.model) == SolidMechanics + if bc.coupled_subsim.model isa SolidMechanics bc.coupled_subsim.model.internal_force = interp_∂Ω_f - elseif typeof(bc.coupled_subsim.model) <: RomModel + elseif bc.coupled_subsim.model isa RomModel bc.coupled_subsim.model.fom_model.internal_force = interp_∂Ω_f end - if typeof(bc) == SMContactSchwarzBC || typeof(bc) == SMNonOverlapSchwarzBC - relaxation_parameter = global_sim.schwarz_controller.relaxation_parameter - schwarz_iteration = global_sim.schwarz_controller.iteration_number + if bc isa SMContactSchwarzBC || bc isa SMNonOverlapSchwarzBC + relaxation_parameter = controller.relaxation_parameter + schwarz_iteration = controller.iteration_number if schwarz_iteration == 1 lambda_dispᵖʳᵉᵛ = zeros(length(interp_disp)) lambda_veloᵖʳᵉᵛ = zeros(length(interp_velo)) lambda_acceᵖʳᵉᵛ = zeros(length(interp_acce)) else - lambda_dispᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_disp[coupled_index] - lambda_veloᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_velo[coupled_index] - lambda_acceᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_acce[coupled_index] + lambda_dispᵖʳᵉᵛ = controller.lambda_disp[coupled_index] + lambda_veloᵖʳᵉᵛ = controller.lambda_velo[coupled_index] + lambda_acceᵖʳᵉᵛ = controller.lambda_acce[coupled_index] end bc.coupled_subsim.integrator.displacement = - global_sim.schwarz_controller.lambda_disp[coupled_index] = + controller.lambda_disp[coupled_index] = relaxation_parameter * interp_disp + (1 - relaxation_parameter) * lambda_dispᵖʳᵉᵛ bc.coupled_subsim.integrator.velocity = - global_sim.schwarz_controller.lambda_velo[coupled_index] = + controller.lambda_velo[coupled_index] = relaxation_parameter * interp_velo + (1 - relaxation_parameter) * lambda_veloᵖʳᵉᵛ bc.coupled_subsim.integrator.acceleration = - global_sim.schwarz_controller.lambda_acce[coupled_index] = + controller.lambda_acce[coupled_index] = relaxation_parameter * interp_acce + (1 - relaxation_parameter) * lambda_acceᵖʳᵉᵛ else bc.coupled_subsim.integrator.displacement = interp_disp @@ -322,7 +321,6 @@ function apply_bc(model::RomModel, bc::SchwarzBoundaryCondition) return copy_solution_source_targets(bc.coupled_subsim.integrator, bc.coupled_subsim.solver, bc.coupled_subsim.model) end - function apply_bcs(model::RomModel) model.reduced_boundary_forcing[:] .= 0.0 for boundary_condition in model.boundary_conditions From cf98ef69372120f86071c88a7bd6958906c3f5bd Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Wed, 30 Apr 2025 19:39:20 -0500 Subject: [PATCH 12/29] rebased to simplify merge --- src/ics_bcs.jl | 39 +++++++++++++++++++++++++++++++ src/model.jl | 47 +++++++++++++++++++++++++++++++++++++- src/model_types.jl | 1 - src/opinf/opinf_ics_bcs.jl | 42 ++++++++++++++++------------------ 4 files changed, 105 insertions(+), 24 deletions(-) diff --git a/src/ics_bcs.jl b/src/ics_bcs.jl index c55b6396..7d617fb2 100644 --- a/src/ics_bcs.jl +++ b/src/ics_bcs.jl @@ -22,6 +22,45 @@ function SMDirichletBC(input_mesh::ExodusDatabase, bc_params::Parameters) return SMDirichletBC(node_set_name, offset, node_set_id, node_set_node_indices, disp_num, velo_num, acce_num) end +function SMOpInfDirichletBC(input_mesh::ExodusDatabase, bc_params::Dict{String,Any}) + fom_bc = SMDirichletBC(input_mesh,bc_params) + node_set_name = bc_params["node set"] + expression = bc_params["function"] + offset = component_offset_from_string(bc_params["component"]) + node_set_id = node_set_id_from_name(node_set_name, input_mesh) + node_set_node_indices = Exodus.read_node_set_nodes(input_mesh, node_set_id) + # expression is an arbitrary function of t, x, y, z in the input file + disp_num = eval(Meta.parse(expression)) + velo_num = expand_derivatives(D(disp_num)) + acce_num = expand_derivatives(D(velo_num)) + + opinf_model_file = bc_params["model-file"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + model = py"get_model"(opinf_model_file) + + opinf_model_file = bc_params["model-file"] + basis_file = bc_params["basis-file"] + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + + SMOpInfDirichletBC( + node_set_name, + offset, + node_set_id, + node_set_node_indices, + disp_num, + velo_num, + acce_num, + fom_bc, + model, + basis, + ) +end + function SMDirichletInclined(input_mesh::ExodusDatabase, bc_params::Parameters) node_set_name = bc_params["node set"] expression = bc_params["function"] diff --git a/src/model.jl b/src/model.jl index a9c15781..96632ada 100644 --- a/src/model.jl +++ b/src/model.jl @@ -11,6 +11,51 @@ include("opinf/opinf_ics_bcs.jl") using Base.Threads: @threads, threadid, nthreads +function NeuralNetworkOpInfRom(params::Dict{String,Any}) + params["mesh smoothing"] = false + fom_model = SolidMechanics(params) + reference = fom_model.reference + opinf_model_file = params["model"]["model-file"] + + basis_file = params["model"]["basis-file"] + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + model = py"get_model"(opinf_model_file) + num_dofs_per_node,num_nodes_basis,reduced_dim = size(basis) + num_dofs = reduced_dim + + time = 0.0 + failed = false + null_vec = zeros(num_dofs) + + reduced_state = zeros(num_dofs) + reduced_velocity = zeros(num_dofs) + reduced_boundary_forcing = zeros(num_dofs) + free_dofs = trues(num_dofs) + boundary_conditions = Vector{BoundaryCondition}() + NeuralNetworkOpInfRom( + model, + basis, + reduced_state, + reduced_velocity, + reduced_boundary_forcing, + null_vec, + free_dofs, + boundary_conditions, + time, + failed, + fom_model, + reference, + false + ) +end + + function SolidMechanics(params::Parameters) input_mesh = params["input_mesh"] model_params = params["model"] @@ -711,4 +756,4 @@ function get_block_connectivity(mesh::ExodusDatabase, blk_id::Integer) _, num_elems, num_nodes, _, _, _ = Exodus.read_block_parameters(mesh, Int32(blk_id)) conn = Exodus.read_block_connectivity(mesh, Int32(blk_id), num_elems * num_nodes) return reshape(conn, (num_elems, num_nodes)) -end \ No newline at end of file +end diff --git a/src/model_types.jl b/src/model_types.jl index c08ac671..6cbf6919 100644 --- a/src/model_types.jl +++ b/src/model_types.jl @@ -93,4 +93,3 @@ mutable struct SolidMechanics <: Model kinematics::Kinematics end - diff --git a/src/opinf/opinf_ics_bcs.jl b/src/opinf/opinf_ics_bcs.jl index 3c5c033e..7b3af368 100644 --- a/src/opinf/opinf_ics_bcs.jl +++ b/src/opinf/opinf_ics_bcs.jl @@ -249,15 +249,14 @@ function apply_bc_detail(model::NeuralNetworkOpInfRom, bc::CouplingSchwarzBounda end end - function apply_bc(model::RomModel, bc::SchwarzBoundaryCondition) global_sim = bc.coupled_subsim.params["global_simulation"] - schwarz_controller = global_sim.schwarz_controller - if typeof(bc) == SMContactSchwarzBC && schwarz_controller.active_contact == false + controller = global_sim.controller + if bc isa SMContactSchwarzBC && controller.active_contact == false return nothing end - empty_history = length(global_sim.schwarz_controller.time_hist) == 0 - same_step = schwarz_controller.same_step == true + empty_history = length(controller.time_hist) == 0 + same_step = controller.same_step if empty_history == true apply_bc_detail(model, bc) return nothing @@ -270,41 +269,41 @@ function apply_bc(model::RomModel, bc::SchwarzBoundaryCondition) time = model.time coupled_name = bc.coupled_subsim.name coupled_index = global_sim.subsim_name_index_map[coupled_name] - time_hist = global_sim.schwarz_controller.time_hist[coupled_index] - disp_hist = global_sim.schwarz_controller.disp_hist[coupled_index] - velo_hist = global_sim.schwarz_controller.velo_hist[coupled_index] - acce_hist = global_sim.schwarz_controller.acce_hist[coupled_index] - ∂Ω_f_hist = global_sim.schwarz_controller.∂Ω_f_hist[coupled_index] + time_hist = controller.time_hist[coupled_index] + disp_hist = controller.disp_hist[coupled_index] + velo_hist = controller.velo_hist[coupled_index] + acce_hist = controller.acce_hist[coupled_index] + ∂Ω_f_hist = controller.∂Ω_f_hist[coupled_index] interp_disp = same_step == true ? disp_hist[end] : interpolate(time_hist, disp_hist, time) interp_velo = same_step == true ? velo_hist[end] : interpolate(time_hist, velo_hist, time) interp_acce = same_step == true ? acce_hist[end] : interpolate(time_hist, acce_hist, time) interp_∂Ω_f = same_step == true ? ∂Ω_f_hist[end] : interpolate(time_hist, ∂Ω_f_hist, time) - if typeof(bc.coupled_subsim.model) == SolidMechanics + if bc.coupled_subsim.model isa SolidMechanics bc.coupled_subsim.model.internal_force = interp_∂Ω_f - elseif typeof(bc.coupled_subsim.model) <: RomModel + elseif bc.coupled_subsim.model isa RomModel bc.coupled_subsim.model.fom_model.internal_force = interp_∂Ω_f end - if typeof(bc) == SMContactSchwarzBC || typeof(bc) == SMNonOverlapSchwarzBC - relaxation_parameter = global_sim.schwarz_controller.relaxation_parameter - schwarz_iteration = global_sim.schwarz_controller.iteration_number + if bc isa SMContactSchwarzBC || bc isa SMNonOverlapSchwarzBC + relaxation_parameter = controller.relaxation_parameter + schwarz_iteration = controller.iteration_number if schwarz_iteration == 1 lambda_dispᵖʳᵉᵛ = zeros(length(interp_disp)) lambda_veloᵖʳᵉᵛ = zeros(length(interp_velo)) lambda_acceᵖʳᵉᵛ = zeros(length(interp_acce)) else - lambda_dispᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_disp[coupled_index] - lambda_veloᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_velo[coupled_index] - lambda_acceᵖʳᵉᵛ = global_sim.schwarz_controller.lambda_acce[coupled_index] + lambda_dispᵖʳᵉᵛ = controller.lambda_disp[coupled_index] + lambda_veloᵖʳᵉᵛ = controller.lambda_velo[coupled_index] + lambda_acceᵖʳᵉᵛ = controller.lambda_acce[coupled_index] end bc.coupled_subsim.integrator.displacement = - global_sim.schwarz_controller.lambda_disp[coupled_index] = + controller.lambda_disp[coupled_index] = relaxation_parameter * interp_disp + (1 - relaxation_parameter) * lambda_dispᵖʳᵉᵛ bc.coupled_subsim.integrator.velocity = - global_sim.schwarz_controller.lambda_velo[coupled_index] = + controller.lambda_velo[coupled_index] = relaxation_parameter * interp_velo + (1 - relaxation_parameter) * lambda_veloᵖʳᵉᵛ bc.coupled_subsim.integrator.acceleration = - global_sim.schwarz_controller.lambda_acce[coupled_index] = + controller.lambda_acce[coupled_index] = relaxation_parameter * interp_acce + (1 - relaxation_parameter) * lambda_acceᵖʳᵉᵛ else bc.coupled_subsim.integrator.displacement = interp_disp @@ -322,7 +321,6 @@ function apply_bc(model::RomModel, bc::SchwarzBoundaryCondition) return copy_solution_source_targets(bc.coupled_subsim.integrator, bc.coupled_subsim.solver, bc.coupled_subsim.model) end - function apply_bcs(model::RomModel) model.reduced_boundary_forcing[:] .= 0.0 for boundary_condition in model.boundary_conditions From 6b7c3b115fcf2f1c168ee374d088d4777be48487 Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Mon, 14 Apr 2025 11:49:58 -0500 Subject: [PATCH 13/29] added NN for Schwarz --- src/ics_bcs.jl | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/ics_bcs.jl b/src/ics_bcs.jl index 7d617fb2..57f5745f 100644 --- a/src/ics_bcs.jl +++ b/src/ics_bcs.jl @@ -61,6 +61,80 @@ function SMOpInfDirichletBC(input_mesh::ExodusDatabase, bc_params::Dict{String,A ) end + +function SMOpInfCouplingSchwarzBC( + subsim::SingleDomainSimulation, + coupled_subsim::SingleDomainSimulation, + input_mesh::ExodusDatabase, + bc_type::String, + bc_params::Parameters, +) + fom_bc = SMCouplingSchwarzBC(subsim,coupled_subsim,input_mesh,bc_type,bc_params) + side_set_name = bc_params["side set"] + side_set_id = side_set_id_from_name(side_set_name, input_mesh) + _, _, side_set_node_indices = get_side_set_local_from_global_map(input_mesh, side_set_id) + coupled_block_name = bc_params["source block"] + if typeof(coupled_subsim.model) <: RomModel + coupled_mesh = coupled_subsim.model.fom_model.mesh + else + coupled_mesh = coupled_subsim.model.mesh + end + coupled_block_id = block_id_from_name(coupled_block_name, coupled_mesh) + element_type = Exodus.read_block_parameters(coupled_mesh, coupled_block_id)[1] + coupled_side_set_name = bc_params["source side set"] + coupled_side_set_id = side_set_id_from_name(coupled_side_set_name, coupled_mesh) + coupled_nodes_indices = Vector{Vector{Int64}}(undef, 0) + interpolation_function_values = Vector{Vector{Float64}}(undef, 0) + tol = 1.0e-06 + if haskey(bc_params, "search tolerance") == true + tol = bc_params["search tolerance"] + end + side_set_node_indices = unique(side_set_node_indices) + for node_index in side_set_node_indices + point = subsim.model.reference[:, node_index] + node_indices, ξ, found = find_point_in_mesh(point, coupled_subsim.model, coupled_block_id, tol) + if found == false + error("Could not find subdomain ", subsim.name, " point ", point, " in subdomain ", coupled_subsim.name) + end + N = interpolate(element_type, ξ)[1] + push!(coupled_nodes_indices, node_indices) + push!(interpolation_function_values, N) + end + is_dirichlet = true + swap_bcs = false + + opinf_model_file = bc_params["model-file"] + py""" + import torch + def get_model(model_file): + return torch.load(model_file) + """ + model = py"get_model"(opinf_model_file) + + opinf_model_file = bc_params["model-file"] + basis_file = bc_params["basis-file"] + basis = NPZ.npzread(basis_file) + basis = basis["basis"] + + if bc_type == "OpInf Schwarz overlap" + SMOpInfOverlapSchwarzBC( + side_set_name, + side_set_node_indices, + coupled_nodes_indices, + interpolation_function_values, + coupled_subsim, + subsim, + is_dirichlet, + swap_bcs, + model, + basis + ) + else + error("Unknown boundary condition type : ", bc_type) + end +end + + function SMDirichletInclined(input_mesh::ExodusDatabase, bc_params::Parameters) node_set_name = bc_params["node set"] expression = bc_params["function"] From 8864db1005f1931648f5fbb0c662add6a340db2f Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Tue, 15 Apr 2025 16:00:25 -0500 Subject: [PATCH 14/29] switched to ensemble nn formulation --- src/model.jl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/model.jl b/src/model.jl index 96632ada..40107edd 100644 --- a/src/model.jl +++ b/src/model.jl @@ -15,9 +15,8 @@ function NeuralNetworkOpInfRom(params::Dict{String,Any}) params["mesh smoothing"] = false fom_model = SolidMechanics(params) reference = fom_model.reference - opinf_model_file = params["model"]["model-file"] - - basis_file = params["model"]["basis-file"] + opinf_model_directory = params["model"]["model-directory"] + basis_file = opinf_model_directory * "/nn-opinf-basis.npz" basis = NPZ.npzread(basis_file) basis = basis["basis"] py""" @@ -25,7 +24,12 @@ function NeuralNetworkOpInfRom(params::Dict{String,Any}) def get_model(model_file): return torch.load(model_file) """ - model = py"get_model"(opinf_model_file) + ensemble_size = params["model"]["ensemble-size"] + model = [] + for i in 1:ensemble_size + tmp = py"get_model"(opinf_model_directory * "/stiffness-" * string(i-1) * ".pt") + push!(model,tmp) + end num_dofs_per_node,num_nodes_basis,reduced_dim = size(basis) num_dofs = reduced_dim From d620d599ff955c7dee1b95004e1c625addf903b8 Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 17 Apr 2025 14:25:27 -0500 Subject: [PATCH 15/29] refactored opinf code to live in its own directory --- src/ics_bcs.jl | 114 ------------------------------------- src/model.jl | 50 ---------------- src/model_types.jl | 1 + src/opinf/opinf_ics_bcs.jl | 1 + src/simulation.jl | 1 + src/simulation_types.jl | 1 + 6 files changed, 4 insertions(+), 164 deletions(-) diff --git a/src/ics_bcs.jl b/src/ics_bcs.jl index 57f5745f..1c422ea1 100644 --- a/src/ics_bcs.jl +++ b/src/ics_bcs.jl @@ -22,119 +22,6 @@ function SMDirichletBC(input_mesh::ExodusDatabase, bc_params::Parameters) return SMDirichletBC(node_set_name, offset, node_set_id, node_set_node_indices, disp_num, velo_num, acce_num) end -function SMOpInfDirichletBC(input_mesh::ExodusDatabase, bc_params::Dict{String,Any}) - fom_bc = SMDirichletBC(input_mesh,bc_params) - node_set_name = bc_params["node set"] - expression = bc_params["function"] - offset = component_offset_from_string(bc_params["component"]) - node_set_id = node_set_id_from_name(node_set_name, input_mesh) - node_set_node_indices = Exodus.read_node_set_nodes(input_mesh, node_set_id) - # expression is an arbitrary function of t, x, y, z in the input file - disp_num = eval(Meta.parse(expression)) - velo_num = expand_derivatives(D(disp_num)) - acce_num = expand_derivatives(D(velo_num)) - - opinf_model_file = bc_params["model-file"] - py""" - import torch - def get_model(model_file): - return torch.load(model_file) - """ - model = py"get_model"(opinf_model_file) - - opinf_model_file = bc_params["model-file"] - basis_file = bc_params["basis-file"] - basis = NPZ.npzread(basis_file) - basis = basis["basis"] - - SMOpInfDirichletBC( - node_set_name, - offset, - node_set_id, - node_set_node_indices, - disp_num, - velo_num, - acce_num, - fom_bc, - model, - basis, - ) -end - - -function SMOpInfCouplingSchwarzBC( - subsim::SingleDomainSimulation, - coupled_subsim::SingleDomainSimulation, - input_mesh::ExodusDatabase, - bc_type::String, - bc_params::Parameters, -) - fom_bc = SMCouplingSchwarzBC(subsim,coupled_subsim,input_mesh,bc_type,bc_params) - side_set_name = bc_params["side set"] - side_set_id = side_set_id_from_name(side_set_name, input_mesh) - _, _, side_set_node_indices = get_side_set_local_from_global_map(input_mesh, side_set_id) - coupled_block_name = bc_params["source block"] - if typeof(coupled_subsim.model) <: RomModel - coupled_mesh = coupled_subsim.model.fom_model.mesh - else - coupled_mesh = coupled_subsim.model.mesh - end - coupled_block_id = block_id_from_name(coupled_block_name, coupled_mesh) - element_type = Exodus.read_block_parameters(coupled_mesh, coupled_block_id)[1] - coupled_side_set_name = bc_params["source side set"] - coupled_side_set_id = side_set_id_from_name(coupled_side_set_name, coupled_mesh) - coupled_nodes_indices = Vector{Vector{Int64}}(undef, 0) - interpolation_function_values = Vector{Vector{Float64}}(undef, 0) - tol = 1.0e-06 - if haskey(bc_params, "search tolerance") == true - tol = bc_params["search tolerance"] - end - side_set_node_indices = unique(side_set_node_indices) - for node_index in side_set_node_indices - point = subsim.model.reference[:, node_index] - node_indices, ξ, found = find_point_in_mesh(point, coupled_subsim.model, coupled_block_id, tol) - if found == false - error("Could not find subdomain ", subsim.name, " point ", point, " in subdomain ", coupled_subsim.name) - end - N = interpolate(element_type, ξ)[1] - push!(coupled_nodes_indices, node_indices) - push!(interpolation_function_values, N) - end - is_dirichlet = true - swap_bcs = false - - opinf_model_file = bc_params["model-file"] - py""" - import torch - def get_model(model_file): - return torch.load(model_file) - """ - model = py"get_model"(opinf_model_file) - - opinf_model_file = bc_params["model-file"] - basis_file = bc_params["basis-file"] - basis = NPZ.npzread(basis_file) - basis = basis["basis"] - - if bc_type == "OpInf Schwarz overlap" - SMOpInfOverlapSchwarzBC( - side_set_name, - side_set_node_indices, - coupled_nodes_indices, - interpolation_function_values, - coupled_subsim, - subsim, - is_dirichlet, - swap_bcs, - model, - basis - ) - else - error("Unknown boundary condition type : ", bc_type) - end -end - - function SMDirichletInclined(input_mesh::ExodusDatabase, bc_params::Parameters) node_set_name = bc_params["node set"] expression = bc_params["function"] @@ -510,7 +397,6 @@ function apply_bc_detail(model::SolidMechanics, bc::CouplingSchwarzBoundaryCondi end end - function apply_sm_schwarz_coupling_dirichlet(model::SolidMechanics, bc::CouplingSchwarzBoundaryCondition) if bc.coupled_subsim.model isa SolidMechanics for i in 1:length(bc.side_set_node_indices) diff --git a/src/model.jl b/src/model.jl index 40107edd..56cbcb50 100644 --- a/src/model.jl +++ b/src/model.jl @@ -10,56 +10,6 @@ include("ics_bcs.jl") include("opinf/opinf_ics_bcs.jl") using Base.Threads: @threads, threadid, nthreads - -function NeuralNetworkOpInfRom(params::Dict{String,Any}) - params["mesh smoothing"] = false - fom_model = SolidMechanics(params) - reference = fom_model.reference - opinf_model_directory = params["model"]["model-directory"] - basis_file = opinf_model_directory * "/nn-opinf-basis.npz" - basis = NPZ.npzread(basis_file) - basis = basis["basis"] - py""" - import torch - def get_model(model_file): - return torch.load(model_file) - """ - ensemble_size = params["model"]["ensemble-size"] - model = [] - for i in 1:ensemble_size - tmp = py"get_model"(opinf_model_directory * "/stiffness-" * string(i-1) * ".pt") - push!(model,tmp) - end - num_dofs_per_node,num_nodes_basis,reduced_dim = size(basis) - num_dofs = reduced_dim - - time = 0.0 - failed = false - null_vec = zeros(num_dofs) - - reduced_state = zeros(num_dofs) - reduced_velocity = zeros(num_dofs) - reduced_boundary_forcing = zeros(num_dofs) - free_dofs = trues(num_dofs) - boundary_conditions = Vector{BoundaryCondition}() - NeuralNetworkOpInfRom( - model, - basis, - reduced_state, - reduced_velocity, - reduced_boundary_forcing, - null_vec, - free_dofs, - boundary_conditions, - time, - failed, - fom_model, - reference, - false - ) -end - - function SolidMechanics(params::Parameters) input_mesh = params["input_mesh"] model_params = params["model"] diff --git a/src/model_types.jl b/src/model_types.jl index 6cbf6919..c08ac671 100644 --- a/src/model_types.jl +++ b/src/model_types.jl @@ -93,3 +93,4 @@ mutable struct SolidMechanics <: Model kinematics::Kinematics end + diff --git a/src/opinf/opinf_ics_bcs.jl b/src/opinf/opinf_ics_bcs.jl index 7b3af368..e35f3a4c 100644 --- a/src/opinf/opinf_ics_bcs.jl +++ b/src/opinf/opinf_ics_bcs.jl @@ -321,6 +321,7 @@ function apply_bc(model::RomModel, bc::SchwarzBoundaryCondition) return copy_solution_source_targets(bc.coupled_subsim.integrator, bc.coupled_subsim.solver, bc.coupled_subsim.model) end + function apply_bcs(model::RomModel) model.reduced_boundary_forcing[:] .= 0.0 for boundary_condition in model.boundary_conditions diff --git a/src/simulation.jl b/src/simulation.jl index c79a682f..49944ec2 100644 --- a/src/simulation.jl +++ b/src/simulation.jl @@ -16,6 +16,7 @@ include("solver.jl") include("schwarz.jl") include("opinf/opinf_model.jl") + function create_simulation(input_file::String) norma_log(0, :setup, "Reading from " * input_file) params = YAML.load_file(input_file; dicttype=Parameters) diff --git a/src/simulation_types.jl b/src/simulation_types.jl index d3bb020a..bdcf8f04 100644 --- a/src/simulation_types.jl +++ b/src/simulation_types.jl @@ -90,3 +90,4 @@ mutable struct MultiDomainSimulation <: Simulation subsim_name_index_map::Dict{String,Int64} failed::Bool end + From 2487b8d606f5d2bff8b91bbadeb4dba0663b9a08 Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Wed, 30 Apr 2025 17:05:14 -0500 Subject: [PATCH 16/29] fix opinf bcs error from rebase and merge --- src/opinf/opinf_ics_bcs.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/opinf/opinf_ics_bcs.jl b/src/opinf/opinf_ics_bcs.jl index e35f3a4c..7b3af368 100644 --- a/src/opinf/opinf_ics_bcs.jl +++ b/src/opinf/opinf_ics_bcs.jl @@ -321,7 +321,6 @@ function apply_bc(model::RomModel, bc::SchwarzBoundaryCondition) return copy_solution_source_targets(bc.coupled_subsim.integrator, bc.coupled_subsim.solver, bc.coupled_subsim.model) end - function apply_bcs(model::RomModel) model.reduced_boundary_forcing[:] .= 0.0 for boundary_condition in model.boundary_conditions From 3d5d25d04c3f0a9abdaed0d538b996051e25d446 Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 1 May 2025 05:06:21 -0500 Subject: [PATCH 17/29] fixed OpInf dirichlet BC to use the same ensemble formulation as Schwarz --- src/opinf/opinf_ics_bcs.jl | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/opinf/opinf_ics_bcs.jl b/src/opinf/opinf_ics_bcs.jl index 7b3af368..a2a5cc38 100644 --- a/src/opinf/opinf_ics_bcs.jl +++ b/src/opinf/opinf_ics_bcs.jl @@ -22,19 +22,39 @@ function SMOpInfDirichletBC(input_mesh::ExodusDatabase, bc_params::Dict{String,A velo_num = expand_derivatives(D(disp_num)) acce_num = expand_derivatives(D(velo_num)) - opinf_model_file = bc_params["model-file"] + opinf_model_directory = bc_params["model-directory"] py""" import torch def get_model(model_file): + import os + print(model_file) + assert os.path.isfile(model_file) , print(model_file + " cannot be found" ) return torch.load(model_file) """ - model = py"get_model"(opinf_model_file) + ensemble_size = bc_params["ensemble-size"] - opinf_model_file = bc_params["model-file"] - basis_file = bc_params["basis-file"] + if offset == 1 + offset_name = "x" + end + if offset == 2 + offset_name = "y" + end + if offset == 3 + offset_name = "z" + end + + + model = [] + for i in 1:ensemble_size + tmp = py"get_model"(opinf_model_directory * "/BC-" * node_set_name * "-" * offset_name * "-" * string(i-1) * ".pt") + push!(model,tmp) + end + + basis_file = bc_params["model-directory"] * "/nn-opinf-basis-" * node_set_name * "-" * offset_name * ".npz" basis = NPZ.npzread(basis_file) basis = basis["basis"] + SMOpInfDirichletBC( node_set_name, offset, @@ -68,7 +88,8 @@ function SMOpInfCouplingSchwarzBC( coupled_mesh = coupled_subsim.model.mesh end coupled_block_id = block_id_from_name(coupled_block_name, coupled_mesh) - element_type = Exodus.read_block_parameters(coupled_mesh, coupled_block_id)[1] + element_type_string = Exodus.read_block_parameters(coupled_mesh, coupled_block_id)[1] + element_type = element_type_from_string(element_type_string) coupled_side_set_name = bc_params["source side set"] coupled_side_set_id = side_set_id_from_name(coupled_side_set_name, coupled_mesh) coupled_nodes_indices = Vector{Vector{Int64}}(undef, 0) From 2ea39ac34301741ebd17e4465aaefb00f4cf8a9b Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 1 May 2025 06:08:48 -0500 Subject: [PATCH 18/29] removed prints --- src/opinf/opinf_ics_bcs.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/opinf/opinf_ics_bcs.jl b/src/opinf/opinf_ics_bcs.jl index a2a5cc38..7baa2110 100644 --- a/src/opinf/opinf_ics_bcs.jl +++ b/src/opinf/opinf_ics_bcs.jl @@ -27,7 +27,6 @@ function SMOpInfDirichletBC(input_mesh::ExodusDatabase, bc_params::Dict{String,A import torch def get_model(model_file): import os - print(model_file) assert os.path.isfile(model_file) , print(model_file + " cannot be found" ) return torch.load(model_file) """ @@ -196,7 +195,6 @@ function apply_bc(model::NeuralNetworkOpInfRom, bc::SMOpInfDirichletBC) """ reduced_bc_vector = bc.basis[1,:,:]' * bc_vector - print(size(reduced_bc_vector),size(bc.basis),size(bc_vector)) model_inputs = py"setup_inputs"(reduced_bc_vector) ensemble_size = size(bc.nn_model)[1] for i in 1:ensemble_size From ef9bb82d262cbad7d97ad0f5f7591a10b2898e4b Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 1 May 2025 08:28:42 -0500 Subject: [PATCH 19/29] added example for NN-based Schwarz and dummy test --- .../cuboid-1-for-test.yaml | 40 +++++++++++++++++ .../cuboid/dynamic-opinf-nn-rom/cuboid-1.yaml | 40 +++++++++++++++++ .../cuboid-2-for-test.yaml | 41 ++++++++++++++++++ .../cuboid/dynamic-opinf-nn-rom/cuboid-2.yaml | 41 ++++++++++++++++++ .../cuboids-for-test.yaml | 11 +++++ .../cuboid/dynamic-opinf-nn-rom/cuboids.yaml | 11 +++++ .../neural-network-model/BC-nsz+-z-0.pt | Bin 0 -> 7097 bytes .../neural-network-model/BC-nsz+-z-1.pt | Bin 0 -> 7097 bytes .../neural-network-model/BC-nsz+-z-2.pt | Bin 0 -> 7097 bytes .../neural-network-model/BC-nsz+-z-3.pt | Bin 0 -> 7097 bytes .../neural-network-model/BC-nsz+-z-4.pt | Bin 0 -> 7097 bytes .../neural-network-model/BC-ssz--0.pt | Bin 0 -> 7197 bytes .../neural-network-model/BC-ssz--1.pt | Bin 0 -> 7197 bytes .../neural-network-model/BC-ssz--2.pt | Bin 0 -> 7197 bytes .../neural-network-model/BC-ssz--3.pt | Bin 0 -> 7197 bytes .../neural-network-model/BC-ssz--4.pt | Bin 0 -> 7197 bytes .../nn-opinf-basis-nsz+-z.npz | Bin 0 -> 336 bytes .../nn-opinf-basis-ssz-.npz | Bin 0 -> 1128 bytes .../neural-network-model/nn-opinf-basis.npz | Bin 0 -> 3504 bytes .../neural-network-model/pytorch-model.pt | Bin 0 -> 23523 bytes .../pytorch-model_not_scaled.pt | Bin 0 -> 24091 bytes .../pytorch-model_training_stats.npz | Bin 0 -> 160800 bytes .../neural-network-model/stiffness-0.pt | Bin 0 -> 9851 bytes .../neural-network-model/stiffness-1.pt | Bin 0 -> 9851 bytes .../neural-network-model/stiffness-2.pt | Bin 0 -> 9851 bytes .../neural-network-model/stiffness-3.pt | Bin 0 -> 9851 bytes .../neural-network-model/stiffness-4.pt | Bin 0 -> 9851 bytes test/nnopinf-schwarz-overlap-cuboid-hex8.jl | 28 ++++++++++++ 28 files changed, 212 insertions(+) create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-1-for-test.yaml create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-1.yaml create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-2-for-test.yaml create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-2.yaml create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboids-for-test.yaml create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboids.yaml create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-nsz+-z-0.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-nsz+-z-1.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-nsz+-z-2.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-nsz+-z-3.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-nsz+-z-4.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-ssz--0.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-ssz--1.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-ssz--2.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-ssz--3.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-ssz--4.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/nn-opinf-basis-nsz+-z.npz create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/nn-opinf-basis-ssz-.npz create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/nn-opinf-basis.npz create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/pytorch-model.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/pytorch-model_not_scaled.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/pytorch-model_training_stats.npz create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/stiffness-0.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/stiffness-1.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/stiffness-2.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/stiffness-3.pt create mode 100644 examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/stiffness-4.pt create mode 100644 test/nnopinf-schwarz-overlap-cuboid-hex8.jl diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-1-for-test.yaml b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-1-for-test.yaml new file mode 100644 index 00000000..ccc396e4 --- /dev/null +++ b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-1-for-test.yaml @@ -0,0 +1,40 @@ +type: single +input mesh file: cuboid-1.g +output mesh file: cuboid-1.e +model: + type: solid mechanics + material: + blocks: + fine: hyperelastic + hyperelastic: + model: neohookean + elastic modulus: 1.0e+09 + Poisson's ratio: 0.25 + density: 1000.0 +time integrator: + type: Newmark + β: 0.25 + γ: 0.5 +boundary conditions: + Dirichlet: + - node set: nsx- + component: x + function: "0.0" + - node set: nsy- + component: y + function: "0.0" + - node set: nsz- + component: z + function: "0.0" + Schwarz overlap: + - side set: ssz+ + source: cuboid-2-for-test.yaml + source block: coarse + source side set: ssz- +solver: + type: Hessian minimizer + step: full Newton + minimum iterations: 1 + maximum iterations: 16 + relative tolerance: 1.0e-10 + absolute tolerance: 1.0e-06 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-1.yaml b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-1.yaml new file mode 100644 index 00000000..8e56c8f2 --- /dev/null +++ b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-1.yaml @@ -0,0 +1,40 @@ +type: single +input mesh file: cuboid-1.g +output mesh file: cuboid-1.e +model: + type: solid mechanics + material: + blocks: + fine: hyperelastic + hyperelastic: + model: neohookean + elastic modulus: 1.0e+09 + Poisson's ratio: 0.25 + density: 1000.0 +time integrator: + type: Newmark + β: 0.25 + γ: 0.5 +boundary conditions: + Dirichlet: + - node set: nsx- + component: x + function: "0.0" + - node set: nsy- + component: y + function: "0.0" + - node set: nsz- + component: z + function: "0.0" + Schwarz overlap: + - side set: ssz+ + source: cuboid-2.yaml + source block: coarse + source side set: ssz- +solver: + type: Hessian minimizer + step: full Newton + minimum iterations: 1 + maximum iterations: 16 + relative tolerance: 1.0e-10 + absolute tolerance: 1.0e-06 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-2-for-test.yaml b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-2-for-test.yaml new file mode 100644 index 00000000..84ea9cb9 --- /dev/null +++ b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-2-for-test.yaml @@ -0,0 +1,41 @@ +type: single +input mesh file: cuboid-2.g +output mesh file: cuboid-2.e +model: + type: neural network opinf rom + model-directory: neural-network-model/ + ensemble-size: 0 + material: + blocks: + coarse: hyperelastic + hyperelastic: + model: neohookean + elastic modulus: 1.0e+09 + Poisson's ratio: 0.25 + density: 1000.0 +time integrator: + type: Newmark + β: 0.25 + γ: 0.5 +boundary conditions: + OpInf Dirichlet: + - node set: nsz+ + component: z + function: "1.0 * t" + model-directory: neural-network-model/ + ensemble-size: 0 + + OpInf Schwarz overlap: + - side set: ssz- + source: cuboid-1-for-test.yaml + source block: fine + source side set: ssz+ + model-directory: neural-network-model/ + ensemble-size: 0 +solver: + type: Hessian minimizer + step: full Newton + minimum iterations: 1 + maximum iterations: 1 + relative tolerance: 1.0e-10 + absolute tolerance: 1.0e-06 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-2.yaml b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-2.yaml new file mode 100644 index 00000000..a3d84e7a --- /dev/null +++ b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboid-2.yaml @@ -0,0 +1,41 @@ +type: single +input mesh file: cuboid-2.g +output mesh file: cuboid-2.e +model: + type: neural network opinf rom + model-directory: neural-network-model/ + ensemble-size: 5 + material: + blocks: + coarse: hyperelastic + hyperelastic: + model: neohookean + elastic modulus: 1.0e+09 + Poisson's ratio: 0.25 + density: 1000.0 +time integrator: + type: Newmark + β: 0.25 + γ: 0.5 +boundary conditions: + OpInf Dirichlet: + - node set: nsz+ + component: z + function: "1.0 * t" + model-directory: neural-network-model/ + ensemble-size: 5 + + OpInf Schwarz overlap: + - side set: ssz- + source: cuboid-1.yaml + source block: fine + source side set: ssz+ + model-directory: neural-network-model/ + ensemble-size: 5 +solver: + type: Hessian minimizer + step: full Newton + minimum iterations: 1 + maximum iterations: 16 + relative tolerance: 1.0e-10 + absolute tolerance: 1.0e-06 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboids-for-test.yaml b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboids-for-test.yaml new file mode 100644 index 00000000..c67bbe5b --- /dev/null +++ b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboids-for-test.yaml @@ -0,0 +1,11 @@ +type: multi +domains: ["cuboid-1-for-test.yaml", "cuboid-2-for-test.yaml"] +Exodus output interval: 1 +CSV output interval: 1 +initial time: 0.0 +final time: 0.01 +time step: 0.01 +minimum iterations: 1 +maximum iterations: 1 +relative tolerance: 1.0e-12 +absolute tolerance: 1.0e-08 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboids.yaml b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboids.yaml new file mode 100644 index 00000000..f5fd5338 --- /dev/null +++ b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/cuboids.yaml @@ -0,0 +1,11 @@ +type: multi +domains: ["cuboid-1.yaml", "cuboid-2.yaml"] +Exodus output interval: 1 +CSV output interval: 1 +initial time: 0.0 +final time: 1.0 +time step: 0.01 +minimum iterations: 1 +maximum iterations: 16 +relative tolerance: 1.0e-12 +absolute tolerance: 1.0e-08 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-nsz+-z-0.pt b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-nsz+-z-0.pt new file mode 100644 index 0000000000000000000000000000000000000000..27d3778f3ff0885b8635f0e3b6eb66e237fca016 GIT binary patch literal 7097 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 B22%h4 literal 0 HcmV?d00001 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-nsz+-z-1.pt b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-nsz+-z-1.pt new file mode 100644 index 0000000000000000000000000000000000000000..852b8c350c6bcda3e9b4df2249f456bbe2db3c05 GIT binary patch literal 7097 zcmbVR33O9M7fvaK60p!kcFLk)5oqb&g7C`L0x6|DL;?aaP13$dnsg>FES3I%+7u8K zfhwz_>^C?cy0D#Cwf=9Q*ND(7_0$;^E-bMM^wzWZi*N!R$+ zSE>B{RURos6`<-rpoi6-+pR}#kH`q4Ue<@_Otd8TSF6T8N;B#itX5l&*_s({%Q0cS zY{Pc-NLg<+>alUSQlmzyt}~HO9;z3nr6b8$Z?>APS;%*mwlO^Ga`afAZIVscj_Ml> zHjBk%kj*x$T|FEdP1s}{Xg0`dR?2>*)E%` za#bIl-6W$%1|2+`Ewb6F)2X3Gjnv~&<9yUaL{Dggsdk3mFmW_N-e6PK8G|o4snlj;voN&SSRC=&+dV=qXnDw6+O&Q3aO;cto8I z5K#sx+-eP1`i2{9RvF^Qgw?~Cb}IB!qb?9(&*Y)7Fv)k8RG*paD)I1mR%#>x+uia} zcM)KB7O zBhhe)nM-Hp^3e#5Pd*a4sgW{zUPPm~sTT^+Xc1{03T*PhFl3tavI9djN(QSVJ7+3H8a9PzJ7Cq& zW2~R5wj1;oAQ72)yV@vE%`vIb%i4M{d2<4qID1wBvWO@>F?$>>!c z0e(*O8V`imIc@nea*C*c(*|>%EuuM+JMq!z4Nm-Ah4?oq@o#YpZ_8+&h~D8A<`z|534?fZ9@^pp@u!{;$Dq$R#GgNc zxQIhs%mLaeqY@Eq;{a_hKs!YAg$Km3=t~aqP6gtxD8z(Y*d?Q{MO4Zylog=eBHB|0 z@m>ybc|Q6^MEewozf~ZvsD^mI0`UO_;)4prhZKkpD-a)1AU>)TeIB~#0`VnJh~v-?9O54zL427*{1XT0 zij001(JvgJs|Dy+5nc0uI3E4RA-=9ae1k%KlUukYquV07!!7(?fc_BCpH&dw-VCvFI(i2WgV;zmqX(-v=GjvLb!&}qW@Jps8B2QaxQBIWPaErN)l>r- zmD&MZg&sMK=4?C=9&6cm;q(KCKD&$^a1FqN9(7dHTL#^jaWb_9Or$^wyCLMN3_O@V zz%+(HDdM4!7hnyOQy~w7GfjhhJPbrBdp0Qb1SNQ2scI{IT#3`DI*c(KN)e|+F5?l9 zXRz7o;Tkgu_HmmPi%?m8EX;IR*$qc;QDc+Ep~fSjncWu5R=`t7Pa1GR(&6W!{?T!Y z9y20);!(72pvFhWFMvd28e(}gh_s}0=P^A7BpHu|T*NOz&aN?>uWI|_prNMyNIag_ zv3F(yNXk2-1CiQ)2}*8X50Z>CAQ!O#az{pZx~8Fj?3~4Y0^r_5f9bW}E`wB29=FSg zp2_{gUGA2TE}8!!OuCUW^v;%fq_pRPnKxFQ5Uzh$`1>1UE|GtVEvVV~%fjfNyZ(JE z{<=Ut8{yxvC=4K^zyI>ArKe|+$^ApTdNy-y%H+SgA0w5$0*8mptsrB{vx16>e<04i z8{6z#by?{AlCFIInq7pB?|irL`NxkRtZLz9e1Im{n*%IbRsKoT{;iN_&eH2O{nb9d zU{$;q`!K<1Z}$HKde*diZn<#iJ#EtCO6uS4R_9)P{QQGdUht2q4ga&Nix%%6vsT#A zal^A4@01Hc4K?4Su3aPS)xC6JclR?wW>#RDcF0lTQnBC77vDNfj&J{T+|@&$6Td=f zZrg`P$(fHv;>mwr7xLtd^OLWakWLz_@4zjUgivd*iJ^bH1=wxt0daQU!&Lmbw(I5efIL5D=Gx*uB*!$3@RpQ@U}%+g@=gk zbf~F(U767M7qYbb*T;qEoE=?eTK5Z=OY#Q%a_6RS{-+)TOdpr9_%trsviuw1j{a)r zFG!W($zODG|CqYaH@pvm@Or^(;hcRT;texGot*YMnXTUDg~%C;RE-``78TX$Rk z*%POjf6q6Xu&Ivx#ni^%Q)Nx!r_TPAEbMfATJVyCB=w7mw*%j)B=c?#IXie;1!-f7 zSMBSxhh!aS|7P609m3(_tb@%u9TCEVW}G_HVLwp`k*lts+e+3qy>|7y|6#Jxyzig( zudEPKK26%RIDVJV?!A~!rJt3O-$J|pDW|R_+CLUn%$T=Hka9=N`my)~8MD8i=Gy#H z()ROP>n1EeNt&$t@ztx1a)hsTOq^CWvy{y1xc~B(w+;&3UcR#I!IERb)gfK)?`gb& zykjXVDIT^@==)SV^@6~Q44*8mzJDG0i>)hvd+O`P2VUDkdKFA7=`!IM+1)Q?;OzmW zBr&Pws5Qfn3kSBGc*XZtIrA@~_~h4h3X7S=}*Fg;M0kln;a#os3ha{4$~+g+E-MmV82(ef-_n zr<5gYbeCBx^=uJEtw$X|S2`CDFG@p|*I$AnnR z_P6F&@#*bqCX{8Zjr;oT*> zn)~hDOCp1pC$Ij72ydso@Jr~m3Q`Z<{MQn5mh2qs``fLoZ-uOLrPibcXULR3mo65Y zi-bAf?f+|07EhIZ+2Lp_6Re*g1p0q7)I zac*&&$+=gABHs(|3|(_VxIL;eYi#Z*GUbB`)URWuP!WGu$Ty#4_yjh*7*t3866?xe z!o6t^mozRH&K*TjLF12-J?SepELc`aLIUq=<7OTqC6WdeTKBN{O6isQ?Bn@!-~T7k zFWNS1E&G48nnuQv_p?UP!y)~pYkX+&=$pRlc`SrB)JWdG@Vi@ZJvY(M(bZ`JoblW( zMD%pmcRlyi)W7FVKhj;_^;|K4V?07z{3OAow?==rZn^7 zL6rLpTzAfz`itPJyvGcp-Su5}mYVtr@aUl*TmO=pWv?V`sUTtKL!8ac#NOuuJ8JO)eN91Eo$m3 zZ)>`yL4)o}oQC@LcoeY^?<`f7`SkV+=_r_?3H0dqdevz9hZZEbRzaN~{bKmj>yzY`taO2=A;C*YTDhjsD;5|vF4gAS>e`P zBhgD%VpFF}dP}CBWTq-LYOD%djD7NPi%=~aNg{fa#bn9GzJ=P>@T|+#6Mc?RG7=m1 zGZ?I9v(X@#tQMO(m1G);F*DI*kkr^eOuzx$fG)$HmBo6u6j@~}Z8v7cVzuaCM4QPb z87)$MADzu8;Xs29o=s-SWYOu=P@~4`nYeWUZX@9Pv>ljshTbq|E;y~rB}ScSwa&5O zw%q#t+WQ-|%Wds=TYGIM)@HKlvg~GagALXP2c>A+veqms;f7_!4qP!<+ts5?Zn13V4txvl=eS3`U4SDWc3tq$q_#*J#QXB|J(op|eXSvrVldMi#yh zob_*t8lxt#-l?+wJpJ@4M5aq8}S%fMV)B1XPAxYa;+LCD1JYp^@Yxc zTsuzG_)GZFi5iv0U*qGzNg5wkJXXYU9V_8+0v=z;A2d#!EZ_-RSVdGBCcO=(h)9HS z#WA96HVUVT+*}$rSAZvKdkvGoZR^kX~YMhg?vIG4W+EOuDJmF9kDqplyG5bmC^yQY@nWFt z0SP}NM}WTrKPv~qb24oO5_SmKDbogXE)wt((Vh6g_<5Q5r3&%O81XO27M4qRg@9Me z7G8AXRRVsgp7_-=@oNh3%K~1j5Wh|#etiS+|5J$Hpb)=NA%2rW{3{CauPVg9rVzhb zA%2S}dPDDZhTc{Izu~}d3ivJ2E5zGar?&-G%rA8R8FRfOboGkAU~e0Da`d`vm;42gEV>6B*+D3dEl>h^cJhGYKCM@Il$a zAtx>oaA`fnhh>P56yT!*KBho?T!FZ(0piaUh)*aGf1yBJu0Z^y0`W-&;;$5lPbm;r zc!#)>p?6xqRSsM&;2Q4`pJAQO3izA@pBM1g3dDo)Hyq+`^YH~2h%b6VJQRN?LtJ|o z;_qdMe~sn z_j-u`kRkrF0N)buUkb#3D-i$F0P$@F;yVh&|H=>(6|4s`%EX6>eD5JGn8>#u;>$$7 z0}($a@=b{NGtpaIlK|F29Hb?b0%--gg9LI}&04&LIcd#WK&K7wcOT>q(w58jL#|LV zgtUVP#tLZ~UI8UTANE~R=1tf_)0J%WA zLe3E<-9U1Z?!2}Kf zfX+zXZxrMXg19^ya(jl|o?+7l!fs>H!Df_emuzGVG-&+!858zSooLF;G+K0K{d_o& zlLXf2r&TizXjEz+kyY4{Bh!>a65+9leHTtYaOks1#17X0^61@;YIe(D8#75_wt$Jp zpoHBJa#aQy%N}4FAgyl!qj1m zR44_K2DwBgLY~2AtA}e$9_-^*3lX5Q;aHeux9}T|-mE4@vt3Qnp_$(nOcuaX$4(k> zLDG@Oq5kf1iXAheBFQ9Hm#FcP$YhXMOhYVB0g;t#?s7~|1xX^)AQ#AV$oVx!=BvT} z3}|Ro%ChhK>unRHIp5L6ZH54FZzyI>Ai>GH%NeRJTJ?j?UbGK?wDatz=S$Z?>EIr}ca*2;$T-IaQy~ppM>g;8FfTn{t2Y9rqT8(D&s|)UT zmR+yeuj%=9P{n$&4-*{h&HjHt&znk?9zo?VYe(O!Wd4;raBlj&=O3iV_}fl( z@7~Av7&Xlwy*@j*FWtRuxzE5o7tyQz){p9Z^&~YfaJ*1aQh`FS&+Whc&eGng3xCs% zI*gEG|Ev;JMNcNu*w1zyrAMbsKUHKqh<0U-%bFQ`f_@xSx~^p0CbXxx>x!4kD$t>I zs}?7H|0OLezH_MC`bv7S&kunsT2kwD zt}UlaHSe`Od3Yr%^s9_I{ZlpFyrFQz_Ki2u?`!Ih07 z^H}A}byc+MqE)v7ub!c^m&dgmHS9FiclonROuw(tro5OQ_UvOcFTQx}*}c2aOjF{r zh?wJOaq0Ej>o4y_f3>Xrv~_MZS{t@tiKhESw5T{gNY(2MT`?&)aAk50rFAMp`y~|Z zOSATReMB|g5|P{YK=3ij;``>6@z!SY7t>t+PV_cpb=!Lm)n#6H#1_}0mhYd;nZBxw zezandPxRcc>Dtvd-fs0_1?R8r*WX1olfR)&@t57RPml7?D$(9gPVJkN@C6Dx-6r^Q z94Q!k+ST79s+w3_N--machTSe)lck_qd`3ObYmyRnd`WVGm6%~Fv;ulo+ zpkK!?mzAMq=Ym2*s@|micON*ia_o7u*tz_sEqWuuzNv@i{Cb1Fa=rZ0QT=JUd{|=K zMd=JRzB_v5<@XQL+(Vnz{&ln(rG`9pPV?3l)MnbaPPd;uh7Qi3eWhO!rCXm$Tb_9E z1P$DoHPhhShcFj8hNwCx9Om)mTUGc>Gx>{aioXrF{MOfgwvDzJdExSh z^G+cjrw}y!i5+Nyd9`-)cW3C(^sya3KXQVmEPPGZcT)+Sv3l`8y|PM?u46dr(Bddf z_ifky_|`4-lf4IiF75gynwdIvSNb?R#j~Wg!pG{OAcZ-FWAv z**D&%%SGSPUnqEgE0N`Lq47pnWs>i*GJ}FNl5% zuO$N>f0WNNA3lz*1$=HknqG||u9S+$qP2$!=2j&4Y^;Ed;PAtKUU z-}T(nSpQFN`cdxsuIC!|slFl2gD>+Q<41Wo;Ck?AY@ph!0S^yccjm@3nAgsW2hr{` zaNRi@>#v2c@*Xo7?5^*+vozL^gGUejA@2IFD}Q5s_TPtxevG@m>&nBqV2yS{mi^{e6k8;|kh-Su7Huf_p1uXAI4TUay+M{?LL1*D9#i;kq#FDj^2$dYLbdcxGS-`|W@{GmUZ%YVu5~$jtj{*d zCTvGOMx)JQF&SmE&1zRqz!@fN${1rd%4+00SVVrzfX?8^%%rnBO0?5odq1^ewOMs= zN4wcBo2+uv9G%@HqfSO0T$?Sj*{aj2p+}9>v(Y^TsI!Q=XoIM>L2sOw3r_2Dut_J` zZ1e0WfLZUV?b>3Tk9A{X_iFE>V`jT9(_yhR+n{q$V7fMdj%M00Gt3o(m}0QDx7&~| z*DOmq>O__ub!XG=(+0XuYj&*%8}6wM@i^SVQZF{OxAtB-)h_F0lP&}BRMvPu8||a* z-fFb@Ru8ZlA>3h$O`qXvqLDd;^Fa# z)JX!ihZLZpA_|i@tKkx7FhUAUmpCVp5(hm>;^;<89J&~ZgA^-qDB>i()_94pB|+lu zCx&Xh!2d`z%-v}VIIPF&alAo~g15u-(FhlB9}Cs`fp)IRoF&O3{0n_i*KpwMPiXvcM!Z7GEI8ffgu_tqt%g}lLwK8 zP2t%Ncx&h}*5|41M!f|{L}os&&XDtROltI$wgW8QoQUQXEh|J85oL#Jycwh;kTpF$ zJ?(EOtkS4tWHT6bMq7r-Xjhx8R8XTF_At=cp#>op2o^oOtfCNhBD;uWCvu2rK6^fF z4!POYT*)kDOWc4j*H@th5@_X#XrU85EuuvZ19K({qO7FYE0B>>M1`C-SaXqxmP@UPk3lbR;#VlducXAU;ucoR=tU8| z#4W5TL~BK~u8H_!PW<`;^sc8b$OUZsGSr^oNM9 zH$i-ZLwvIU-4fA%6^Q?*K>TMj#D6Ie-&P?0n?sCM@IK%uV=pSQy$5%oBHMnjHx=0q zgng*UHX-awMNe^!{pbjG;*QXY*dJ;q?!;s@9q|Jsh0F%2ytxyt+ zyTJuz1>XxoJ`RMsnHwC$E?d835^*rqqVE~p9i%3b@O^N>c)~qEVnE}bAj!BF)FSQ; zHA5WV4^koS!+IZp+Ks$nID~f7?FaV-iBpLCfe0+({?Ky!4}v7)hoBbm0I0eBfgly) zhgt6+s1<5)D9DUmJQ$<`{0N&dg!Z!J0uQAk%P(*k6`N8F98RxTmVqOv$Px`4Nkx`x z;3z7xbOT3Ik+U8{Mb3IG6*=p1ROGD3Q_&;q33LP)OoUd%!=QHJN0~gFj(B8!1RVjL z$Jo4)P&=`}#TK{sZcLTv#PsnEi1 z2(`+9AEy_vj3=NKaT?TxSi|ITP=`WJ<5z&kgD7Pefl?$W!38T#Tj||eoKDr@9uuGy z@kFR)JPB$8Tdf|BG4o*`w^^|Woy}=sro+lkIC_g3n=B4Bo(#k6v|zRZo;sRozyV2z zr$GOmbc&`KQIU8m?Hi-%0M(?0gh%mGPxW^^D@`*We?_Vpmi*Z{SNjZixb;S)6-{o|Hzd@KOYJ@hxR&C_L| zD%$OI84;P19PV2jTFA;^h?=FAGcR=ky+3aa@RaIl-X}B;;cJ?bkQe z3Eob@*Z5F9**j8~er32_c4q+e$AwdC zg2Kw)t0Ijx4s)tu16grp`OirQUnHeR7nJXs@_}%+pVp9gt&$vEvN0qwuUeR1aO35W z^9RWj9U8_}7at*)2YtDAORv+!cl8wiZkrC1lzGoO*6b@Kxr^)4*Uk8d#pfd7R=4B8 z9sRY)t5@AttOgpGPf?h?;J@enf8~la~0XY$GIVKLzUpO z>(J3ob0@R-Eq&y2V>|haZHvDx?~VDStnnCm>C~P{2_w!5{=0p2tLN4WsToysf9m_P z&@s0%DNT1&(2o!!vtkYkYaEN7uiL*{=zaBkN?J(;DZPHEaAo{CG9;)WeW~|ZVcnzK z!qRhVi6(pYMfKWMLX73akuKX$koY0mgZ7A%!ZY@FeC(f{BE`qjrX7yhE2MhG^nc?0 zTHzB@m!sRB|BwXj!;Ag;Y!R$!b4?>3{gAA>w0Pya5qrtAXM9e+J-tdWr(Sw|cx0Wh z=79CJh}G4?%v}l2aic03e|z;SCbW~kxc2h5`RW&^<4cGTSvqt5zB4ZhZ`OA0wQpdh zu+|a0XZ)4VNb#gFFU#x47=P<4E!W%0Uwm8qeZTVSfaxpV6Y_r87Ps+>bHdD%9slzG z>HxWA-}d_D<0V4SuqpkF*DFc)nSE|=Osyk0>1??+se;%Vue~*T)qb+*lD^`t+n3DM}tNWq1jBXG(Oy*k0?%>SwGWH_FxtZ`kIP ze?Gp7q(6PY|C<37_69}s+QuHQR&`#GVpOUbH% zA0HCDW~`lha9|Co@47v=U|a)P_{*Z$Enhepf9riyYTLx!Sb})1H#%zRu)WrrAl~ssAWK8-%rWk_dIV{ z_xMgSbl}RqhsurQ`uWpdgZga~rsfo1{PLMoWKebF5301IZABlbHrJwc) zr*iBOMzu-EGa!ey3c;78gJf4KIk2Lb;;mz5;UoB)>idShR>zTR!cki8`fU_ z9$fmyFR4pwg|uTce*bfMHF;yu&bQC>+)KV&QQX&GeS`$(IjZwU@%?}6h+)6ooxj%m z|9tvI+h%QJ|Bq4Ao%QT%SyO3pNPiPGUbMOMP2V*i3#J1til;C9tu46bP4shgbD0A< zUTX^xk*)Pz^PZOaH$CY`wbpmdYv`x?=CT03%)8w`%FO}S#G|EwI*$h2JaFBaTdrV1 zHxC{}w_btk&e>9b1ALWtTR}`~eb=3(rG6q@y6MNZ)^~mLx74TqeYokzx#_#S8C&Y# z^rRo(THp1})l$DF{O{m)|Af~1u1|JL{k@*_6I<)MK6x$m>)`(zxBCxkt?&ALwG5yI zJzDB3PwPaDpWjd=PD8xI?nEraJ1b3X0X_XfsRT2$fFAu`ubNK((1HTTD(Lg2e+3JsJ9yUR>ajk@ zESs?dwJ{p)R;$@4TkJN6Is==`*lZeMG0JM>|BQ$Nm;s%^Ic*y4-BzNNf!ZF_ip_4* z!H5ovLpIyw#y&cSSw=xd9XwmCvc;y;si8)V)GwoU`KY~!9?^zTZG+x8eI_`q%f)7$ zWVcUupkQV_L>tnqows#hZI5a@(>9AkH_d6aHrb$kP-wa~n6^%{V`i8uhB3vC+U_1r zx|tSP(orX}9jFuQ)>#|s+O5g8F08q$Hr#7-GfUlA*Y4U!X;+7=m(4m8;Hk{92W#!A z?c~u!bYS)`wjWl)Z#kxJtsqpA#cQg+81nc)C=P}1_a$3y})SFd4rEL#h zG{R*C9?{cAh$y2JWwS*oeWQ$an+)+|#_BYtJr??@Q6Gq~zIiAjLh@T6wPEI-k$8AK zD+Niw_H+5DpNJwQ&T5pz8SF2Grc0cYXo-U!BXM+NB@SJj#6gOeI1~dUKGy_^&m~df z?k7cPeZc={G|$sj>an~)gTULtd1#1>x5*LO0MMRkwq#2(8mgGkIc1C0q1ItD z4POY(#y3TcgciC$@H^~SHzQV!hDAhZ+k#OCfT+(lqv2ddJ;LrZSj{i;S~W^h{C-~R z2c3<%PBcQ}FQbv;G%Ah1#-{+KYJ6xhY9YgQw2WR5(U=A7K~spviYQGBtBA^A(K}GO zBuFr>G+g3lV^D^~%w;lj`DmQRCm)I2)Qd8DNkrqhsR@N>qKLFkg|@$N+OqP|BoR$k zXnR?qZAueuI)%2W3T=9YHiJT&QK8MG&}LR>o2Jl~&DJ1T7^a!^vJ*ozN=BPACwDeP z8a7AeIAPV$W2~R8b{O?mAQ4%3yV@kr&NZviU$wq4c}o(SzHmVyvWh4tLgU9E9fEA> z>FHzcLt>dmB_q4Rs59D4W}`!GwoyTia@os3V}~Y$SRh#Suvo<)T!0)Rk_(ViL^Ig? zVRy<+uFjM!QjWw8_;Y;~nk9kOY!S^VK(C1CRi}YD6NB<38h3xoL$A5S-P{Ois2gRm zXdY13O-A#11o#)A*Lfhk!D-8vQGtjGIc+fKg(6xcxf36U-sHqDR)~L#62F97cw0tG zMO4HsyitqR1k=u-yqwmkHi3&fv$ zLL85_bBO=>0OB1S;+-6zFJ$yD5$)mteOZWhi)fDr!~;+XhqzRM_$vxA;TFD@(Owbl z;}*&a(S8vfXoUD6hxkxFDi_gV1>z$L#7CPTKBhoip+J0Gfw)qE_=Ey+l>%|K0&$H3 zajkcVPg3+wiKwms)r;t~cZko>PG?1Qt^j=_qVo#Gap>O+;&1a%gA2qLJRwd%7dgb2 z9zcAVL;M{F=zAGm5z&7*KvxUVH4**b0dXSwkwg5G0`Y$-#6NQj*JbpJh;DETHw)1% z5&hZ-@oyaB+xh5s5&fY+d`E%!e@zhIRUp2nKzyG=j8(86aFnqR71`c{eW}Q{AM8g( zwgcfdRAieF_NSt^xW)mr1sCA9P>MJZ@&X*hWHoK^7UsAeZ2_J3tluM$7vNwfhd{1S z5|2B;17!t23PK(Zg}jLy9L65q7nvm7k!sO(26qCfktEz19vDx!3rGxT+!Z7lcY|ES z-63a);~pRt;-0MbF~~j08;HYcHQj!2FOWEe_;C<{Mf?Pm-2Rgw$+$PTZvSbJ z3UMD++ZS?$S{wl~V;4UIQa*l`^>~ifvcm=LM@4qNz>!pJJYwJ|`ovBdxIY!yK?6rq zk)1Vg3>DdN1IJR4vmQr9&U!o*IqL(c$XQRIqF2@vX$vrz1f_@vLSBFeF?ld;@yhxT z+5$Sstlv<`3$VcCVURlwPN%`44T9aqri0BW*C{*jaA?r@voj{_ojS>4GMQ~Ut9~|| z$8id6Y@=0E4QNzqpXVy{$YHYN;1TfH!oCZqA2{?mWbA}%03P|Eqnh3_=*EmwsV!h) z6qK+VLas94(eweP@dA`09s_wH)-ZW2o(N=CrXJj^3)qW~)<;Uxa3MTd>#wPaQpJ zzy(Q%UxNAv$0>Tuh>6DIY265okBldPL}MCac_N6kq;uyn{TGmAoCUdvCqd4xF`Ta^ z`;(!ex&0XYGOc6p%oLE6cSZ*ywLcX~ZeI_Qj17>B*a*4P5S6KE8#rLmd&$9Y@1eiU z7H^lKs#uTPW&h~Zlqi?G<%3J+p9rPvmo66${w(bE{Uuqo>i{`CKYP)+ixnig{n?Ov zJEZJaRe8Zbwk7;G zE`QaUXss0bE>G=zKJYL(*qQuzzO0H&FHQY&!o*tP+lo#8foXMQnWkO+A#$0N`2 zt!lS0Damhg*I!POJ^RXoQpfBi#@^pcyGQ8Ayi3WUm(gXi`ShqO;UOo5SNbi#w`ED0 zFv_;!s+ew0{dV}AggJe+H`%?$LT`P>gziG(c=l2TV zFR4uVe(DZk==v2KJI%OCiq6|B{ueWy#4jJ$zv#eba_ANGVcGkYWcQDIuCICNJd4k9 zbAGQa2kz*vW&T1{NuK=0rKZHUhQ6%S+F$4IJiU<=BIooQekX*9KW};L>@tcp;jXYH=++I6s zXokH)(DPahM$nsZ}8#l%|j-u#Z~Q8AZ<;~$&W79T4m zzV-zTJ9?Ip#Z$|Q3I^;Yo7CFwV|(u+<%zF!>!nASe?Kf2;#$dHLQDKb?pwBR{%`ArNewSF6b9}W z(muKWw?Wb6C+)x4UHP90XUJHe)8iWYl#}pJmw)d6-3ijGm*1^+8E44w zbM|kxgp~>_!#5@!omfLgZ<#x8*vA#ZiWM1Ek=kuS$y+x^+^H-T!j~;DtXp+dC_44d z1=~A&g?m}6O4fHO6_PdIjtV$jO`?mpd{I(Sud5PJ6Abs@62 zT)3tg^jhiWTEWtF*1b0qj|&6igG+oi)sgrN=WKKENg{_DQy0H`g)Ch0S@QOgM}(7) z+^hUzKn+Rhu_S9>?<$g3)nP|;W-&3{shE}W+i~G;uW`ec<{u+BB7eGY+Hg=1E)MUv zZAz&yX7?}aY{At+pRbCBmfrfe5O;WO-RN~^gtF?hnjvXx$eWduF9p^dW$|6-*LF@T z`5V|;{sM8&!o6u_Lh<{r9nN}v2l?ZV8NZKq*oD=_X!x3WJB7gw_Iq+urnJ-1S}0HS|+`QQP;L+Sby;lPs9=Ptz z&1W#HgBK5C-Dlvsb2iss1z+VoW)SDD@4B-z*H3~+5B+#|eb<%0xjy~x!$W_7yT0qn z*j)d%H~j>6eb<$%xqesp-@#-2M0b7HH@mt1E^qot?)t88UUU6=`2WUZ{DJQJuJ2d# z0GidMxxVtYW@-Wg`YCZ5?icwWVjVoj?6! O0RD~(R?+^v@BaZQ77|B1f`;bC5?KY89-aBk@>cG#kxX$a9{m9o(yPG+2{u;0@S{ zymUH?$z;&+MvK`hABXh@Y|xK1>UcTw9>^je!GT(9%gm(OtvTA*M)fT9Vz!voFrw9H ze*zslczvIHAvNws#JJ$hUn_m0`wY-R5ppW@gi*#^3g;V zy)MxLZOuL$fDJ$br!urXO$bwRFI<_VKPtz>4lJx2qq1@tP}{_k(EWf9obkk zU6>yh8{Zh}49>`9bD{%p(O!mfIk1|^qFHwI28-UbX@y`ED37Cg_f{U7?T~kG2XimD zP!@sa0A+zZdPmFvZ##My5&<$`u1H%xkL)Ze5NU&Fp3kBMoGbB>=sl76LW%hIDe)hO z9v1Rw5sMa!9zHBUOIY+#1Mwe=#4pW9%UHBrB7TKL{K`h+Kaq(4R3iQ}iTG6#@v9}` z*GR;FE)l<0B7PmmHHF@KirxkmePKr%S@b2>B*b4)rA;i_Y)4yIv{iyQ0&No@-kyiP zc7XUBcZj3Vw<5$loFN^lMK^%>~7a`s+LHq-Sn1~*V zc~rupQqjYK0#wGL9~&S(C_;QFACAU-ZZd_sb_ zT7vkb1aVE%5TByx)w1Zc9i3s(*`^^rN0rXA=z<;n#G;E5#F3~@fcWP;bjbnY%kB`z zperK8SD!$9O@#Ot5ujgrbe%>276JOL0Nr5GO*e>R(Jc|;-zA7|Q;6?~9{%9be^~Ti z(Zk&WbdN>%8zBBugt$H*Jz&v83F5ybh#xgV{8)nce-gxhix6WOtR*7K*n^6~+JjqB zQCNPkCl!Si2zyacScI@Q6`RUy>_dC79k+&>#ciOpGFf014*P1_W}`E#J!;w{r3Th$9 z@xP!H{r3Z@0QVPK2S6!Ni-SQH*u?`u%EyBQjlr~4*j(TtR223XIE0D~TMQgZ?}S|j z4x^&5(ZJzU6!sceK}BJ^fg`9WvK~oAk@YAlimXRdQDi-aicPW}OM8I9IH*}X6iPcD zCdk8SPm`?2(;l#SUeJ31N;_r*IRQ$W)@IXMRc&FlF{@!Q%CYfQ{33KHy@fp{tetAk zsMj0JYLjLr?8k8;?etQ~sReXOts_Jey5-Otv++o{ZD!qt-4AT~tUR{CF#wNxq9~`Q z47xDmB!qaNt7&9H#af=zV(Ac;w%(R(>6OP6t#|D#4j?fTx=7G~j@w#;-v8lkF7UW+=jO25lRu^y2ZWAkm!m%EzyPNOPJ#yO`7CLE`ZQ zC|NuaO5qqI^3~}7b?9*RufUUNn=of4gCxxvHHg&z6sSf28jyIbg_6ZOC~ex%RAuWn zH6K?z@P%^^{iQa0y7ZStxtuP;!jlrioz9jH$INEM?!Bwh-ikR(CRGGSl;rLvmC1EJ z*IqI*SHEBL;PAWyOuP3tE{-j{MvB{ooc&MgH)L~ZhCZRLn#oK!zjKuFnd0qUpC1kM zt6);J66+1mq(E7tXuEx27rc0~{VbsavySZ*kdv*UM>y;;THA3W+*Oc8kweYAWHP)C3N zgh(G(L=;JhJ)eR|+dYGVF1Fi40)8ms9)?_CR?S)0#jk%kSvP)Sq1XO0=2pz!8!@R` zhQ^V;XxZanh=Kl^kE5e3&@B$2rc2X-sM&;1eINh9G*{Dca}Ve5?FD1f-#?Ap+ihF` zphwf=HY7w@;UT7KnJVFFHI1Vz@WRBWKfk{$$Tg0Lq{Obxgi$!JDrKYk92pc=qAi-x_zIg3;X9Jx*h%7BL26NMP$UT+7lhrHyG9N}%ogkU-TM{En?l9?!=^bkJ zJYX^gDC~zSPcik()*-FrH6(XsT57k0Cz;at06t^Q8D>q2Z$kHeXG#33DDMFtXPG3w zxAq0Dze#@RH{|pmcdN;^MVG?7&zvHo&Rq_=mvx^|d}4>a`>-51qQB;O@|O*B#V0Z; zF{T-O8hC21Ufh-+wfayc$(?e2_5A1`nTXomUm(vKW>Z*}@nOt4vc6R%U%TakkiXfZ zEyXS3FRFR|HhdGXWY4TxQokl>+Wz3BjN0_tLLd1V;<;|iZmzwZB(1zwJ2&(kb2e0= zJn40WxtBhmU{m;EQuTA;ve|VzN%E{$a*}S8le>PUIb(fl$n>qlQ+iFwWhQv4I&^2Q zGhg-Hx$*vn%Vcub$o=1$YM2+!=?fMvzstma?sF&cmvW|C)YjwN>S`wOz!%d!%Bsnk zE{eX<>bc~NDIa7#eD4sWc|G-*x1o%DdYLJY>wS!*-FYzgt3}61+uP&+S`=78GE(=i zeW$!kz~|DWCAV6{Uv$g)OMW%|{*ldP0K0Urh7-&D}P8?Zhp`O#G$X+N$oKlNCJ|hrV|82PW>wg6qE@Dr1WL z>SnF)c#`1XEw^8Tb!syn$J5(}@e;)X_c3lN?=*@S$25mpbY(18| zO1ps&a!)rzS95g% z+4a}0ySI{x$uXa=JCFaVf+Qvn(}jOo!lYe&yeC8JB)hj)JIWONw%qhk92PtYv;lp$$@~7F%xPzNR z3pdx28U1h0O`BRn{QnNRwDje2rr-Hv6W2yxB|CPmIQ!9`2g#j-Tv^EG3S!Or-@uzo z&yeh?r%j5n6=eBm58D4a{5sRS+mvOwOOBGnAyu0`@u*=&Uaf6^r(X$aeLd{WI9n-m z-1pYE1xt^T3*Ojv@RQTz{GWSPF5F!s%)dwfopicI{0(h6e;@GyrTo+#r1SMtKGr^8 zGUg5|{7VMUAvag~-&ZTXBC$Ommn;nVT8OXxfU+Y`=g)clpHBZzx0st<|0CseL(<9h5Id%e4Pglp`MtEIDbH=w~~@JLr{ z$F;=SdbhiEl&iJlo8H;_418;G89Un5+VMT;Y@OTL%{s=_+VL&rY`q-5akz{f>uT*d zhn=nC;7gB-b)2iU;|z1QRz2fpJ=E3OaTYnJOTCk|G$&J)K0ZUFjO^(d@+3FuHz#^E n)JA&3hKubc)?i1!3CQ-*k7jVe(G4c8H~shkHoh{dFY5ju!~IVM literal 0 HcmV?d00001 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-ssz--1.pt b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-ssz--1.pt new file mode 100644 index 0000000000000000000000000000000000000000..64acf53d96a3dcfbc938d6ec9d7093a905760b51 GIT binary patch literal 7197 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 r=0vZ?T1QXVfJUv)P!2dv*mf8SKSu6pm)>)-EH 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 literal 0 HcmV?d00001 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-ssz--4.pt b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/BC-ssz--4.pt new file mode 100644 index 0000000000000000000000000000000000000000..390bf7b82fc6938fcce7c4c0b4ac0918e8d9e118 GIT binary patch literal 7197 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 Fr2%>cPqhF5 literal 0 HcmV?d00001 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/nn-opinf-basis-ssz-.npz b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/nn-opinf-basis-ssz-.npz new file mode 100644 index 0000000000000000000000000000000000000000..d679ad19fc563eda7fc8b36c213ef7063f217d92 GIT binary patch literal 1128 zcmWIWW@Zs#fB;2?V+~aYA22g8fG{V62t!h0ab~ezUO^=zg8*0%q!1(t0+anheFGvH z8Oj){)l*W7lZ(`?6x40fEYx)r)YI~dN{SNm;`57AQbFQwi8;loK=I;?#DY{HU&B~O z!BR)TL{mqhR)IXg#o)7%Exhf*eu!%@N#27MzWC(-NcE|Ov^~J7KXmIcDSYxd!A4uc z@Yuhw+vt8U9`i9>Od}Bj;S@~3pM=7Rn?N{I>Z^PL;XI#8;k@RX^t#Z{tNRUgA3nCr zon?>i?z#m*3%r5y^K@!{#U8kbk!~D=TiG3f@^?3GZ`!W<1Vg?Ws{gbWcKzFo3_aq3 z`i&=Rv^=Z5gkgWho+~L~K=~-ayH2s+XJW|1^dBLh|B)(o{ofBxUY`au|7cfamh9ID m_5t3EOd`y<3I{~!G%$kL@S-BXo0SbDzzBrqKw6pw!~+1%X0Sm3 literal 0 HcmV?d00001 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/nn-opinf-basis.npz b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/nn-opinf-basis.npz new file mode 100644 index 0000000000000000000000000000000000000000..dd005f4bd2efd1520852b6923016cba698aa7ead GIT binary patch literal 3504 zcmWIWW@Zs#fB;2?nfI4G(%@xa0AWrB5r(A1;>=>byn;$b1_7`jNFhiP1Sb21`UXTY zGL$h?tEZ$ECl{$(DX80|S*YtMsHf!@l@ulB#pf5Lq=LlV5_5`Ef#Ss(i3O=ZzJ{@m zf|0q7f~lsCLahQ>fD7UZ7&&1NjD?+EU;`0%u*5D87j5|W9|ZEC>fz$(45+&4IPLus z7P4@1-0}T)e%ki;ow{m|ZUBt`a);H*9-z9Cd5f={%AJT)UBK6PwFy9VsXfQ$`RDDz zsjf^jTVg6u-JzI{jybXSaH@ma-+)HLoP|ygd3CP*BBh}4doh#Im{0Zq&Tv_4VV4Ao zH~Ro1m90S!aH>NKH(2> zr_k~t`3L^;0cvgogoc%4FxxO_sJaO_%P(JOITGA}zZ@xtmLoR;NGV4y?80ynssPk| z4|bvoWALErCRkv|pb9|MF~De4Ss-r~G`}R6(0p~0 z&iM)&0I=|Y(NJLw3MLPuF%&pM%gv-!_{+^b&~o#1Abrct6VP>^{~&KjdjI;eV_@dq=HSarnO`>Uk&WXJ%xk63?DTixwUYq>Bg z`uCq$po~8i&7-z|i8YVda2f3W4GUMe>Oq6}2=HcP5@E*G$%2M5kZJ%oFyLLT0B=?{ PkR&4zngi)%aJLHpPgGAt literal 0 HcmV?d00001 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/pytorch-model.pt b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/pytorch-model.pt new file mode 100644 index 0000000000000000000000000000000000000000..68cf51d72b4a99d62a74163872c2851bd55981ba GIT binary patch literal 23523 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 C^TwM1 literal 0 HcmV?d00001 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/pytorch-model_not_scaled.pt b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/pytorch-model_not_scaled.pt new file mode 100644 index 0000000000000000000000000000000000000000..e3ff6de410fc78a4704aecb51b826470a1564522 GIT binary patch literal 24091 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 literal 0 HcmV?d00001 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/pytorch-model_training_stats.npz b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/pytorch-model_training_stats.npz new file mode 100644 index 0000000000000000000000000000000000000000..dace07676d6a4e5f01854303634c60e0a3ad18c4 GIT binary patch literal 160800 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…Fq=SzIpsf^ zxY)wqK2=!e3E{r&d3uNnaU*BeneV}WX|M2x9kBDxl{9zMX_kx`(?Or9`=ZUnp%(}? zQ1~I=T60c~`558vx!<)~!Y@~jsD1^g?7^w)Gl}yi9r|_|{0)PoToURH9_7*D!~H8A zZdZs#eE;gbhVRJNuYNb_3i4B1&AX}+?&q!ZococtS=?i?4S7OEm(|ZBk3Fx-8&1sM zZ}?5{z!X0BGnO7bgI?d%L>35r;27)vH$eUucUDV~*ZIn|hc)0`$DD0Pu-9Ue(-!y8 zhfUv-nGTTJ=|ylE`lu!TcE4>JeVebme2Tgn?%CwAxIfkjNtsH>zx6BFObC1957C(k zg}UsOQOogNW%y*KDx8RrJrsX7nI?Tg)7SAx>T*mhv zQ`l2Kod4Ghf0_g(&LZTWjHcCigL8Cv7xjA_ag)mBT-^je!EW>Yuxk(p^3H<0E)Wgm zW1hn`3892Nhep>-GJz+3j7-0Ve*E=<2CBd};xal|vA6Jn<-m8q;iieyWb~QXB5!>V zeHdTs8eNB<(!J1$Z^)D4S!SmMKfNQ#L+n7a<3tP9$mb#PI;3(2pU*ECTG+rX$`79D z0m|M}IOc9~zW%dB56UivDHR z-`X)^kE!Phj=CWay=n5~aqLHU(8TsQ`V+8y#UT#=pW>@hkHP!%?|CjFj*RAK_8{`} z`Q}X#-Xp85lz|^$Pp`P#pN~H3dw(yS1Xmr;daI1QRlBW0&oPI;18L}6^s%s{VLXg^ z``LF@e!$-8eshQ#`5wC6JGgHa?@=YDt+UYm+}SylpsUL~r~UN}@1K`cTWQeQO8YoJ zqRxmohn&eY7Zao9&2o}T&*oED@Wt3bQ~(Wu!G_`58#UO9~XN<%?c zOW>ck8*Bds`uWS+qE+Bz1&Q{AxH7Tc$~@?im75UWAHQO|v?gQF!2U?uNUKdCk9-6|Qn5o{~h`s60FDU-g;6S{U+IrT-_~jd&qzUsZnWMYPgT@g-p&yQc)Kpf?WZ21w!@rf2;A zy9xi|+Z-kX$fF=R8h9Rcygin8nBk|(-e#ABb5BU1Va)>X->Z1K2KI(u)$fMp@m`sw zAK1noSIG`~_=EFK%wBoAfcGE!IbvP-5p`3!wnJ-LkcpMPsqIp&&*AOGM**iUtU>?zoVui^Iufg2|ry6b~{N>sEjUtsTx zUVL-Y=(BW8Ry7rUSO@mMxQ_n97F=>m;9rnYX=jT0dAk_wKfr!fDMhyu`4Y^d9vP#L zTJt>ZKGdt%Db4anKP*r6UR1+R_RVwF)9_oGI!-i$_`@sjTa-{Yv?owX2zrI>+sS3b zTh^AeaREb>#~GD?Wv5xh_;9`sb}pwI5hq$`LqrOF#ZLQl8}bg4va_B*yspfCHZs_U z%-UUq;2(53=$PvwaES8uF>r<9fgc6nBTOoLYf;xOu_0(G zbYKYchM)O;G#>e{6uNI(V2=tV_Wa$LhntRc#})e-s^c*1Mjwn1fBtz3f1Sj6HH{@a zN76*sY_Z2H_SfFb;{4jegOzGfe>K^MRT}<7?O$i_BYra3^JN$GfgSpD6R5K!?9cTV z{y{Ug+@k2u=H6wM@5oDWNGmcB^Ra}zSno!EIi%+D445;CX8GeH;*0M#oZ3ZQv2Xt< z(vY9_#U5!g=)&C^o^H_TJ(CQ)kag-{F0J7COyhae3?BR_hV%za|Ou z8t$uCe`GS=Uz^tc7B#3-JCVvXgL!3F4>e3;&gKPYnID_jgQITnY1HTa+v|~yx)qV} zm(50G=l0j6qp!(Lk{8brZ+kmcY|F&iTyO8 z?}$JezcAQ$tb_vi3HQLr>x3k9$HtWCDfC@(H)g{Tb#%S<76@a1(|ZImoY9}CNK8;L z^z&`OXF8ED&iAZk6LguD?7dI1H;;814OQ&1LchU64|Qelurr#Vj(N&r)+_k=6~6h< zh&_ew-7?fb-_5ml`Gk3Ra|_jKkT3IIvqlW!MnCtnJc57p#FUQ=`WmNtbLT$Jn|I-7 z&L41xT+Y5je{g@cF7_vao7UBg%oF<2*Wvqu`b&PZvmVfQ&YEa@q0W>d-Qah``yXb^ z|BU?Y`n2+a(62r@d+Z4?-MH%sA#Y)_am5A9A^nYcfAX-Y|C#eVcq*Ciw&2Qc|5h`WNRTw#Q@!=UvZg`|>FINxa{YbR6>=?5(Jng}POs9Bxw=<_gV+xf8{C*T((FP>fxeN>HuQi8Cb z%8>IZggvG*RNaK`m`bKH4IVdJzIF@!*U?z3l!5mzrxytT8(yXd>7)K(Unva^a0QcB zQRl(+KarRH1dcd1+>n5tX=nGE80YG+oLBCJ`_d*{Z%X=;!BGn!FP9Z~T6N#2>mJTPr^q{8?*hRUaYVtN5XLH_nNw+i!jjb$?1#JdXq4 z;hf%11u95IHT-~Ib^e4+mKIi^(D+Boyu2p%J{=<8c=1&yC-{$N6eQKDS zUZu}PeG8wXLyf$z(AVKT>jpEhN8I^2D@L>|r@osOgNxm07eAj>+&T9Ky5IHCpRbX>=UA|!9Qu{- zJ8PVPd5^|^w%&{S4fnGj|A)NMgAIpjFn@E%Yd$yd^)#;Qp@_Rk^2Ur9{j6Kw5z@hV z3LFhF`2s((!!tVKMlL>78x(i@M6kQ?Ag!em$;UY8`a~K14UG zK@Sz5$QwXjsgXM+N$}4+abU~_`G(thww|JIZwvodccI7eNcs_;D_7p^)oIwhxRfKK z!7si}oZ`Y95nHL!{pd3x|5L>V@|E03riesbXZ_8X+u-$l7LQaAw=UE1`zZ1j_(g&4l~DAWZWJ zoGmfRgpW{H=**@ZbnA~@?fE$WPru1dKPO(68{(z$e$_Wx4*F z+)q7lZUO6+_o`5zDofLB5&5{!pL{O>e~W|rve)33=y`-v1^v`a4HR1YD?r_6P1pV#M}!zcK`UHyQp2;h{4=58ZF-$k$8{*AOQ}`bAj+_EYg2+M<|ut>nK|Y2eGswf%qbo~z7hl%;?_16w`SE1Z|Z7d;wQ z@TD{|Q$EsVxspcNhxak>(E8iU_Q=yOH29Yp`{WAF8>dA-B^)Vd`SvX1?|pS{KlwNWbw8Z{(xQv}yw>57vzYJvU4yM8@DktQLrvhv zJG9+|zQj2}ir}xp_IV#xq ziuxWjfcg8cxR?Wkd!{7}gg=}{S=URI6zB}?r z)GyqR!5q<#_Kkl5Kicr^h89rxE9DX~_Vq2iV^jfr^)21IZ{WY*2~ZwJe|yPW6_`*@ z(w{3Q6r6uloar3=_?_Mg`T}1?S-c^{-QV|oVZYZn;2Vp6ir;E2GLtRKmHy>%azE^?QCm8M^XPOrK_UWP$2R$N z8~2dOcKD|O`gL@Vid9EFtGbLs#Nf_*&(-fEUzTH=au7Aa9vUpWiA53L*f{Wv@H`JX zF^Y)+Ra(mHDB;KV!kl9PbBg4@ZYJC-3SQ+564W{1%)#~({N|UjnO^XFBq~C85N8&7 zZkOn5xi`}_jW5FQ%5bcMGjuCwRUIWDyP7GN9Qp{%4%fSadF^OT zY6qP4qiNM=Z38c)nSKcQnxZj^?rm!_SRm zZo(US5R>jE8T{5sRzj+Q6hw~t#@K_q9+#&P_@t&temMFnICAmQf8aXN+>)f=25%$| z6Ye)1z3PioK=&G5l0fwH^-0lWHtc0V`Vp$A7rOLjAs_X2msK9v0lh_EyO-lUFZ_CT zKMdUYK$5vC;x|V&qgY&!tj@*tNkqx zzlGno=oz2~>>SeI0cvRblU72<_oLzpyzjf?V$bVf-!}G7jVmyZonm1Z58|&Se5S91 zuGW5)P6qFh8-_m*1t4B$h~erZ#HaW3NN!=C#`LeFHz=0n9$d0cJCFFT&Ec=cJ~2gKg@3j2mO(4Hy?<5aNz|JeHnnhv-_h=%sPD)lV?cLG4)#yiTblw<@0?`e zO>^X>%KIothrHE4@*ex)TrR1WDQAL5RH_bKM!ZkkVNr4LkfZ0zqTo*#e)GvN@}+%p zU~fX*Y5U2+{m5J4a#d+B{1x8Ou#Up-)b$%0yo5fh9(l;3g zwQ~@cwtv<=2b{aRWd9T7^Dw0)I{`oEMKjuM*abXq1SbJG51;DhLEk%xf9@!OSJzJd zz61Z(?K>CL;2&v2(yu|t&m{M#3jNiFb2bFP&!o-d!YuL!Z2EJA0ZZmCj$fig9`V&f zg!ihPMq)=R^3ctvT5}-&QRQ`KR`eTJmGI6Ee$FqSJ>Nl`aBcDDDCl2;+&avm(@e^a znInHv5rwcQ_Pt=U=Wi3@HGNH=7Q>&K>a$S|^uKmR!&%UuXCzb5qh8bf6Y(*Ke=F6x zwFrLP{`|e0uwQ-C>O_d&%#pv`hkL)HDzQ+E_?I2=dn8eZQMT-(7xLPcIc07_|FV6u zAr$vpKKqDF8ua&tgUz21uS=J9{5|@#(*3*dDe6!djL2-jzF6oPzJ)w+V3W z7uGO)fIZ{2-%~|}=WitLge~g)XILWi1bHuA_0ZP81pE6XjnaUHtNqmMml(DTn--0eeNq1EU23H!(>${W!` zo}T@??_!|;WV-v{#D936G(9YRU}vDaIa&nVS!Tb^g!%!_Pm;Lce_;RelS1$>OPmMV zk@rgCNCq|f7m`%p{DF9GkGEM3$T$Ape@hneq$v&S8_=^FC?$j8*J#CeL;?E#1D8Xe zAkQTO(#zH8*Oy;;;tlx0+{s8L^!+PkHmw4>XpQdmFEn^=zpcp=;>f>gyUzg+Rev

#)0w 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 literal 0 HcmV?d00001 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/stiffness-1.pt b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/stiffness-1.pt new file mode 100644 index 0000000000000000000000000000000000000000..edc68d0469490bdf48a85957e371d03b52ce92e8 GIT binary patch literal 9851 zcmd5?d3;RQ7atNR>L-8wUOsc~_b%t$bI^ z6pC846qT+9indodZ?p=d)g3c{`YN(&DQZc^timo1LG@8N<#$Y#7xSr?) z6EQ14F@=Sj%mzU}TrN^#MaMa~S`@CX(y*=sqB9r`#t>{5uc-rhZMcr;!t{ckm~oB3 zKvQU_K2R{2jArF<5~L^kpaF(JL5b}Z5nvMi%!--zYn@m&9 zxUOj3N#j(mUaG4n*45WEWOW9!HrNsxT4sZ_!Os4gx~w+XL`1`qqKl~5K-08Rm3Epz z;I+(&5Hofan>ExpTQ@6nt&v#WSkuyGbva8-#HLL(^;uK1pcC}kAc&_t#%5w|bB$|- z+On=%h%H)54u_g_LDojfnza&}eJD{;shP=K(qU_{T^otVFjJ5vRBy&@#lm)Cq$L+i zrxX4t5N1^%?`AZ*$<5sYO-2Exke(=qi3WxOw#+aG00{qDlSD5UVQTEFu`xP$xg=N2p?3Y<3 zb1zh$uc0tkbwoK-O0WgIjfle2th|j>X&hi!)AWWAUcl336IzR42sJCUM9&s4Sei=K zNhO}~jwZ15nN36+;i1G|s8pJoVAKrvk1j-yXG$u{0VYd8sD6}Gs>D&U-?KD!&^R#M zf@iCn2snDATA{XA+sEQLkW`Dsa~FuW)I0&t=WtBCn2C+UaU71 ze)*ZaWY!%owe$~^E_Bp)1}haeM`F*QxIYpH21~X`Qj_Jx zU6RydP~0d4gWN=Q9cvn!U= zhm<4EAY+M(h#RmPTMQ9bRs%W>#deKA#*)S&ZURy!%#$>Q4C9P60}w@;gDhi?v=Flu z?~fj&CDUTJ2x$eVghKKmWJDH8Yd|6mqzxc}v<1nLb|6JQ$VY(UNPDrg1IS9$dJ`2Z zW-BM@2uLEBbOHz*laC>l>~{tvkS-uO;s#Q(-xW|C=_Z!CgDhd)1F*!pC!iSOCAR3! zN+s5NFetI^&0s0(J}f7(-jhLz^_V01*#lH8xrIV>O^NNNhJ4WGwkq#6v*Jta}kPWJK170*E5RKw7cx zFJ>#U?n8z%t=Vd|Kt=#!j5xvWNJc_NVx0pdu`&vfKt2P>klB|BV)x@<^5dtUMB_e}w`n%Uvb;k`a|uRxB?W z+575CMo5-2H((^oAmzw%kOD~n84ydpghW#tEE%dFdXi*ydfw^ zZ`6kBro-z3vXWKS&?uP(RLZ4aNh-{-AVV1W8nWesw2GBTK}rP3k#9hubM zp%x`s57pv32!j#Apk*gmcP9NHR$AkzERNnf+~0QQp2g+0M#D`#S*1_ICng_IE)l+20LFAbUV^Yl6)X#B10-+!|ass3!fYO7CJoRwK_q$i6-1@+;U2rJE(ARerm+VQz-oNi&5T3e@3Zx z3){YUcAsu*etKKv_dlayV>>)P+58UuZ{0nY>fgGJd~}BSfxkSaw_2I!{q_7F+Hm*5 z3XA7W>Nl+G)#&6xlssg%<*&&CrOh1IoO$&#x)_qU@z-zD>EE0Cu9>jqJaV4&daq?o zG&(%z%AAxVm(hxzlb>y!k&Wt%o#gVQSw3Z9TzqU*wR13++3%mi=urRSM%d z&oS$q+GV3mSO3oScRWBI)&3r{I^{a`xEu@dGxfW;Z*E{B|10yIsvj3y0!O5gpH=u^SF_obKcBM>h;sB-oAAVAKm~J<{oZ|lI4c@gzCXPDd>h!jJ^rjc z4}Wkhg9f-hb<>|or4M&Z$}=^&Op8(FR9~%`4Kf(DC89K6LI8x_VQmgWD3* zQ1Qk_$%$VTQeT&|MdwEs(~qYd@p`gwGu`7jI=T6pe43vSnRdyXOPBR)Tx-+%0+duR z^U<@|$B*_-lQR~cbunn_kA;AbwW11>53jZaj;Q?wWsL+3-T@%ITx;4$h)cW4J$dPP!`c7x95MsEYjc zco%=y=e+oYpQA$CeqaBE*MV%hJ!3)5Yp2uc`0j0*E(}jZS#L96ZT>z7ecu0Gi~9Zl zpzdoN+()6h zeIbFyLYkBozOZMO}jMg=};t{PlfR;Xb-MP-998~ncIP}6- zsdV!%<8{k}_tM1EEoRrepGj5wxVp72rJ%#9XV1(J$V8s5Pr^UybpDT7{K7k+tcOa=;W9x+$xeiJ?4wxQmI3;A@Xi^ZWJ?VRY} z7XReyRpig-UHr|u*}qxA?)@}R|I0k>u5)Ngx1>YoUtU9wNsC_nH!huqKK-rV8DcRStK-M8sUdS_;nnj;=xp)J!oMw{LqqISc3 zjCQ}V4;>%aYFzPG=c!#|zoO{x(`jn6tL+>f7SUc~a{3Q(IEac@D9q&79Vo1rpSb94 z9y*4OpZIZLA??#j*?#VuOq7(FyDzS0I<24GyZY*HchlcpW4gDCN<~Bcf?Ex&o{4^I zIm$2MY%*$fG52KPj2tv|F}j^$*^LVQT0WdS<0yT5cvbX%RVp2R)6xFou~gLM$==mF z-jn8t%Y%ijln_jq?cZAYLeZ4Xk-A<(upDLFu&n=?sy6WpMaZ91a zW;^e=uIXsf>wxF}tuLTACvQJZ&|gJ|B0gJZxSL0v*3I)w+k2MQS?m0yZB{mV;y?KE zCg(h4P|ge(<5h(4?0#RSxfCIE_tw^FIS1*a?HP|fPvxMH?XBu@u7xyat6$D5mrT_D z^-Rt1=jUi}!OCe#l#P#)ds;_CWKnm(f{h!XH^zf^Wb&@b{3loeo__`O$@m&juf-zogXZa`5ye z)Vt##?bc4Y)JK^5;rg>DP<@}AaDLZ*I@bA%y51LZkjg1!#DGB?QS$N^9=-Si8fN)x zSKoTu(UP2p6AT%<(7lCE9aeNu^qYQp^1RUX&uEt4G{eMCE~8_kW9@!Oyduum8N&w7 zXaAdFzkk{2?OKj<|-d%~?NY{Nu)_Z&?fIwWqkXTW@r~ZQ`WSne#D4D{ zpNpbqx$#A?4$6>j7&f+HpppaDXRq@5jo~|% zN+YP$zxCOqync!;eXolCTb~`u>leVMKb89LUQyqAZ!NDM+1!SHkBa)%dtZ6|#1=O6 zy({WluSMncec%h8O8)y))VIFZ%j@6&(1w1`iu%@fc=-u*u622Rd3_k6c5vt{-zhEa ux|H5C^<-3%)|6k!I=7K3K#zTLplH%w%EK!!SW@iSw+)JV@WRgK7&U_p%PEmH#uLkVz~Lfr$IR^^phOJ@6bLTZ~5iz1dyhfHUU9U%%yZZ@xP-@6DU{owm!4P+8k3 z6!!LtYL~O3p28@EhJ@&KMq}r0U4pfO)-5tD+_#@n5m0{3s1>Bw8zMvXA#R399nlH~ zVpR4wL_`{lp@Qx+IZKHZ9cE&yI9$tB&6*O3HdG&~pMirOxGsm^Q#WMVf!d(3DOwV&i6lA=Z!m-z zaXry`eRcgx^Dyp>%n#7Ijn zmJcV~HV8&l5bvhfyUEqvf(&{AhLDaZhl>V=0p&{E4hC8KIPB`mThHcgM2j7GX@qp- z>+nGM#|gL-hdc8Uv2MIXVi(@YkC$la%1d!~=cNF5voz7BI0C*Q~lxA1TaZkv@Wu^+hoSsWf==C;4=HYd3~ zG68?i;Zd^NU&wBcu5eo|yFEsBJ3w}OtnBtU+3oSN+Y@BBHL}|i)pa4nWpgSVn8#Y4 zQ6CtGgUtL2mi^L6euX69NgU>7zd~idCRg|sCi@jG`xPPkrI-CO$bLo2eqq@!BKu`@ zmAMzHkJoS*t6HKQCMDPe-bTgYDQ4bAyQ*tLx2EVqC-DM~kxgh!LTI>AsUbSHc)`+C zwoWQ>>|3h9(q}XfO;k4}p6cqVwgaO^m_OP{Iy_BMQ4TPe0>gD9rCcSBll}fuZ4H%! zB29R@%0<94{8b8-t;#kL&xFJ(5q~vDoKo=up2gvW+2T!N5>DdqY&C2mioj5<5zpac zd1#lP#!F`1@mzko%2vQ%i{=t=vdSs}ecn^G3BL0|6{BcJ7 z3CY6m0zS#%EXl&DBz&5~*=5A%NW`B>z-Kv}D-)k56Q5r}{2wy$=VamwWa0~D;)`VB z&&$MLkcq!26MuS@f=O_-Z1)#^LL{#Sq_Mm2PtQ&qRES!^LulyW!hnh)d#d zsac5cR1dKyzAJ_JUU`V`OCf$B1?ZuGA947x6rd+b_$h~YuW+8qd zS$HYne>nU~vhX?yzu|}i_9i*X#0sz&Xi^JM0M@3RunX(ZNb3^I{45wQzMnJ^F16mA%2q#1xX@&U*S=16n# zw(9)wA}yE}n|rc1o9zBj(h}C}wkfCSPRBuCspO7^<|N+MmwTz8OVtak$}vF-sVfq05F zy0ct~buR`b)_X8m&bl|dlUVP`pv3wo3`(r`Vo+keH-i%EeHfHj|CB+Ab;O{=x(|aE zS?|kAfWdx{a-=`VL^43c16heh)_qwC=nN9;4F;J=hKP75NSSp{qJkTd^m8yOMNdU;Np94zKqSKOqOz|p9*-3(3Ra15_W7peKcKl-3yHb+K z+yGybAmzwxkOG+lGH^2M?&Pxm&1E-IPre4okz|l^PrdHejVw>J{}UkD{w9FT{$@xe`&$4BWGhIHWPlW3 zfK+xrLMv#Qs?!}7#-_5vzzXCK^1?d%66wQ~TFKqyF#90X|!~&oEd%6D#>Tk>q!h25Xyg2`(=9apLALYgVGVt@rB3-MdruC!*Jskr ze?~j|+=}ZFJTBoOirB0jeodK;zMb#*Iw0*iy}UJd{>&~pv`gfvwGVs$L+!S>1oj&8 zCr!`T(Oq%-DxI@(@Pln9t$cb?h5s}AEb7(t@7Vs^Y<)U*dsVWo_y%{_6KfCOX+Z$GXFYvSu(&C zd}F`2``1v>*n)q+b3;oG@^y`S2YspVc~{EXU#N=quW9=bAdBt)mV69lFI4-yD`oBf z=5k?dP22DGmi7nP`1XCRyo?~E|LOXFt^!OTVF&= z>h;cF(daC#bsH^7{ooc-t=XNuW_T&JMXi@yb-Iq$$0a+XK6mL%v zs}i)CU-B%v)kErdr18;{3mzc5)QN@TZatw6kG^Y?JSUI78hK*D_UyZK?Y865Q+|Dp zX2!X^h_Sv)&&|th63g90M~1d+D&>_`&e2S^VPu+~}sn$I;=mj@^$e zU5na{_Sss{^*oAK+TDJcc@up;|KoXXDVIh6R!^{XafLC+es7;2PKqYg=ZB|nKWB^6 zqwnAT4fb3+LVX&|zE}7~E{d!jx&8j^lk}-=-xEJIzD^JC*+1*>fO*J!^qWUZ^Nvx+ zn^B7=JS#zCFW-83WK|vtLXW3JI;5jWmoo{qNC~=lFCjW(%sFJ#Hx2GHp@{yiS+H@V zdoJ>sH+f>0okwWkpbvf8ta(Ho&i9yCe?~4n5-`i%4ml`ef;zxqKfRgjnK6QQSi%*`T_70hYdN+7TqjO#! zyc2ea77v_YcYjtE9jkwN=XTmA6k{LIyW_nRwAI$Y*~K0wsY6_c4u+qz(7?%>kNnQ( zBD*Qidd8*|&|?!%-f$|(rfqIx1@xCL zBe&d~{uJ%lc_1>q`2qT7>+oxa!KYAWd|+DS@XM(7_^-~F?9HNwvT&_udADfx+&^~b zy}BgwH={#*?;7&gqo(|oj@}i1?Cx=t;+syD>`tORhKXOLj>@9Z9#dA{ zaLYoq^egZ0F3mx4w{@O_?F-TUcCM%HygWmf@m7Pa|4BhR57v2fATWz+I)&~2DfKAI zc;(XmwDkt`W=X79=RO7W`q>^C$)#uMi{zAm_y&KV%{_j1uDAUJ%^%bt-6pAs9w>Cb zP}pxdx`rJMP3C7&?QgrhUJSfQH*FaA`bEqoIzD|-z=S6&kS6v1m3G<3kX`(z=N=r& zqT9cBzFIIY0j=CWNg1~K5{mz`iI@L~bF}q>RrS#6Y;-I+#-?kF40?M_)Znvr>rv_L z`;($F#v$@~gZA;IdFWB6THh@DF^^t#jE{To@oD6LZLts%cZ?o8I7Accb_qQga3Xfr zb4opY$JzRRpMi>pMEK92nU73So6i;o#dYB&mDK(;J+ns3F zo3^Xp_!ZMRXC@`S^1Xs4`%my|`C>6ztZcb&#_T-$^!A0K;(|HO!- z1yk42oG*SEd%ENtnqjr4*Zk6RDDAOYt*^>CjVWz zh3L^-(^(swL+>6MCA@f@i%RIAruS!DL(8_UIW}x=G4(sI(oTJH6)FC>GtaHo4!Sul zDgOM^0u*Gwp<#^aGJ4UsX!gE^2T`v>zZUmaU8hB1omC@+bh>ci>NyJs7SU1b?c(Yt z7SkSXE*qB_@=&j44-YRXxQIry-qPQ(Acwk`(o5FfJ4_p0@EQF4=@}GzF~vLG|AIK5 zLX@B8vj5Gn-@ok_uGRO8p1%D)vbbMVJx_h6qbFNF*+&(bKW7b57NT~uY#ts6JxSLs zZ>+VRKbI~mPMEfB$9c48WUTYO<`?Lk{P(=u&RL2~&FghocH|g(`NMRlZyTLO3)W|U zy=#32%B&M{G_z9?ZDHMZ==9PH^mA9dT<3L^wl|o_UUd=ja41RLv~-);zX9R%N5K*FH+?Ptw8=%HjQ^O^DMX#gg)GLd+jdomoMp%hDGj zRV|o5m$9!IDy|fG;H_$*OV_IU=Fd%)^$WnEYWnU~_06Aq*q0s^S2TR|P+h;81LhAd zl?^<%XrP)0<|q5g2BP8fk?J0FtJ($gqitmaiyB)p;8E3p`Pr^Y44YUmP|X4JvsY#P zCh#3gwH{P!-~4P+S$~%$eb1`?o1Yyj>leVMKh@grURB?GZ>_8!{ecC2ud4dydtYV! zMa?bf_o%9Gz7|#1_l7Tcs`>9-Ro{HBSJp3XWkJ7ZRekdrUU>i=Z(UhmULQuNYS->0 y&y*I{oy+G;0~wX2cJd2Zr#4an=&?@@6fW(gdwAsqONuS~wn5PV{#bpf?*9PHXC>AE literal 0 HcmV?d00001 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/stiffness-3.pt b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/stiffness-3.pt new file mode 100644 index 0000000000000000000000000000000000000000..770e090d2c2a5fbc21838c53157acb3fc6fa74f3 GIT binary patch literal 9851 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

{R$4)KDd-M z@Y1e<3Le;=?8_R6fzL-Odf;8Y3${nwvIbT(wr9YnyaC&@UAY)Gv16cu1GZWg*%2PXUu!~g&Q literal 0 HcmV?d00001 diff --git a/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/stiffness-4.pt b/examples/ahead/overlap/cuboid/dynamic-opinf-nn-rom/neural-network-model/stiffness-4.pt new file mode 100644 index 0000000000000000000000000000000000000000..251e54a2e147569d2478c842d138b00ff3ab5076 GIT binary patch literal 9851 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 qRb^C^y2vkN-5W~ Date: Thu, 1 May 2025 08:36:46 -0500 Subject: [PATCH 20/29] updated runtests.jl --- test/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/runtests.jl b/test/runtests.jl index 7daccf63..bd76084b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -33,6 +33,7 @@ const all_test_files = [ "schwarz-contact-dynamic-cubes.jl", "solid-inclined-displacement.jl", "opinf-schwarz-overlap-cuboid-hex8.jl", + "nnopinf-schwarz-overlap-cuboid-hex8.jl", "quadratic-opinf-schwarz-overlap-cuboid-hex8.jl", "adaptive-time-stepping.jl", # Must go last for now due to FPE trapping From 9fed32f370ddcc472867301c6f338a9a93e9c45e Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 1 May 2025 09:08:34 -0500 Subject: [PATCH 21/29] trying to add torch to the install --- .github/workflows/ci.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 941657a0..6d4f4aa2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,6 +33,16 @@ jobs: with: version: ${{ matrix.version }} + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.10' # Specify your Python version + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install torch + - name: Cache Julia artifacts uses: julia-actions/cache@v2 From c83a5cc18797a0e8733dc8753d1ad96e0efe9fc8 Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 1 May 2025 09:17:25 -0500 Subject: [PATCH 22/29] updated ci to call correct python installed in CI --- .github/workflows/ci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6d4f4aa2..890bcf3e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -48,6 +48,8 @@ jobs: - name: Build project uses: julia-actions/julia-buildpkg@v1 + env: + PYTHON : python - name: Run tests uses: julia-actions/julia-runtest@v1 From c636972f9e17d9c31a51f052bd75c7d6243c97bf Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 1 May 2025 09:28:06 -0500 Subject: [PATCH 23/29] format to rerun --- test/nnopinf-schwarz-overlap-cuboid-hex8.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/nnopinf-schwarz-overlap-cuboid-hex8.jl b/test/nnopinf-schwarz-overlap-cuboid-hex8.jl index 55cec2db..bd167006 100644 --- a/test/nnopinf-schwarz-overlap-cuboid-hex8.jl +++ b/test/nnopinf-schwarz-overlap-cuboid-hex8.jl @@ -24,5 +24,6 @@ for file in readdir("neural-network-model") rm(joinpath("neural-network-model", file); force=true) # Remove each file/directory end + rm("neural-network-model"; force=true) end From c7c4b15c6e2333a259d97426e3fc20439fbca0ac Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 1 May 2025 09:30:34 -0500 Subject: [PATCH 24/29] fixed white space --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 890bcf3e..7d6a1729 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -49,7 +49,7 @@ jobs: - name: Build project uses: julia-actions/julia-buildpkg@v1 env: - PYTHON : python + PYTHON: python - name: Run tests uses: julia-actions/julia-runtest@v1 From 5a9659fde15416ceef62444a31631019466ade53 Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 1 May 2025 09:32:44 -0500 Subject: [PATCH 25/29] fixed formatting for env --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7d6a1729..ab8cf8fb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -48,8 +48,8 @@ jobs: - name: Build project uses: julia-actions/julia-buildpkg@v1 - env: - PYTHON: python + env: + PYTHON: python - name: Run tests uses: julia-actions/julia-runtest@v1 From 3db27b8493b821263092ddc3f6cda80e9ac603c6 Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 1 May 2025 09:37:21 -0500 Subject: [PATCH 26/29] added numpy --- .github/workflows/ci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ab8cf8fb..9bd80113 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,6 +42,7 @@ jobs: run: | python -m pip install --upgrade pip pip install torch + pip install numpy - name: Cache Julia artifacts uses: julia-actions/cache@v2 From 3f23a4c0b46d160f712a68615bb2178aa4f1b1ef Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 1 May 2025 09:50:34 -0500 Subject: [PATCH 27/29] defining type of basis and changing size call to fix CI --- src/opinf/opinf_ics_bcs.jl | 2 +- src/opinf/opinf_ics_bcs_types.jl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/opinf/opinf_ics_bcs.jl b/src/opinf/opinf_ics_bcs.jl index 7baa2110..ba06edbd 100644 --- a/src/opinf/opinf_ics_bcs.jl +++ b/src/opinf/opinf_ics_bcs.jl @@ -248,7 +248,7 @@ function apply_bc_detail(model::NeuralNetworkOpInfRom, bc::CouplingSchwarzBounda return inputs """ - reduced_bc_vector = zeros(bc.basis.size[3]) + reduced_bc_vector = zeros(size(bc.basis)[3]) for i in 1:3 reduced_bc_vector[:] += bc.basis[i,:,:]' * bc_vector[i,:] end diff --git a/src/opinf/opinf_ics_bcs_types.jl b/src/opinf/opinf_ics_bcs_types.jl index abdf9738..f9deea1e 100644 --- a/src/opinf/opinf_ics_bcs_types.jl +++ b/src/opinf/opinf_ics_bcs_types.jl @@ -26,7 +26,7 @@ mutable struct SMOpInfDirichletBC <: RegularBoundaryCondition acce_num::Num fom_bc::SMDirichletBC nn_model::Any - basis::Any + basis::Array{Float64} end @@ -41,7 +41,7 @@ mutable struct SMOpInfOverlapSchwarzBC <: OverlapSchwarzBoundaryCondition swap_bcs::Bool fom_bc::SMOverlapSchwarzBC nn_model::Any - basis::Any + basis::Array{Float64} end From 642b2d85c1993eae1db43cedc30041577528e381 Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 1 May 2025 09:57:25 -0500 Subject: [PATCH 28/29] fixing additional size commands to pass Julia 1.10 tests --- src/opinf/opinf_ics_bcs.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/opinf/opinf_ics_bcs.jl b/src/opinf/opinf_ics_bcs.jl index ba06edbd..8fe45c0d 100644 --- a/src/opinf/opinf_ics_bcs.jl +++ b/src/opinf/opinf_ics_bcs.jl @@ -254,7 +254,7 @@ function apply_bc_detail(model::NeuralNetworkOpInfRom, bc::CouplingSchwarzBounda end model_inputs = py"setup_inputs"(reduced_bc_vector) ensemble_size = size(bc.nn_model)[1] - local_reduced_forcing = zeros(model.reduced_boundary_forcing.size[1]) + local_reduced_forcing = zeros(size(model.reduced_boundary_forcing)[1]) for i in 1:ensemble_size reduced_forcing = bc.nn_model[i].forward(model_inputs) reduced_forcing = reduced_forcing.detach().numpy()[1,:] @@ -354,7 +354,7 @@ function apply_ics(params::Parameters, model::RomModel) if haskey(params, "initial conditions") == false return nothing end - n_var, n_node, n_mode = model.basis.size + n_var, n_node, n_mode = size(model.basis) n_var_fom, n_node_fom = size(model.fom_model.current) # Make sure basis is the right size From e0c89cca9c7492876dd867ad134d06109d527d9e Mon Sep 17 00:00:00 2001 From: Eric Joshua Parish Date: Thu, 1 May 2025 11:31:07 -0500 Subject: [PATCH 29/29] updated runtests.jl to only run nn opinf test if passed command line arg --- .github/workflows/ci.yaml | 2 ++ test/runtests.jl | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9bd80113..75588875 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -54,6 +54,8 @@ jobs: - name: Run tests uses: julia-actions/julia-runtest@v1 + with: + test_args: "run-nn-opinf-tests" - name: Process coverage uses: julia-actions/julia-processcoverage@v1 diff --git a/test/runtests.jl b/test/runtests.jl index bd76084b..103de30a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,7 +12,7 @@ using Test include("../src/Norma.jl") include("helpers.jl") -const all_test_files = [ +all_test_files = [ "minitensor.jl", "interpolation.jl", "constitutive.jl", @@ -33,13 +33,22 @@ const all_test_files = [ "schwarz-contact-dynamic-cubes.jl", "solid-inclined-displacement.jl", "opinf-schwarz-overlap-cuboid-hex8.jl", - "nnopinf-schwarz-overlap-cuboid-hex8.jl", "quadratic-opinf-schwarz-overlap-cuboid-hex8.jl", "adaptive-time-stepping.jl", - # Must go last for now due to FPE trapping - "utils.jl", ] +nn_opinf_test_files = [ + "nnopinf-schwarz-overlap-cuboid-hex8.jl", +] + +if "run-nn-opinf-tests" in ARGS + append!(all_test_files,nn_opinf_test_files) +end + +# Must go last for now due to FPE trapping +"utils.jl", +push!(all_test_files,"utils.jl") + const indexed_test_files = collect(enumerate(all_test_files)) # Display test options @@ -49,8 +58,9 @@ for (i, file) in indexed_test_files end # Parse command-line arguments as indices (if any) -selected_test_indices = parse.(Int, ARGS) +selected_test_indices = parse.(Int, filter(arg-> arg!= "run-nn-opinf-tests",ARGS)) + # Determine which tests to run test_files_to_run = isempty(selected_test_indices) ? indexed_test_files : filter(t -> t[1] in selected_test_indices, indexed_test_files)