classdef EDFMSimulatorApp < matlab.apps.AppBase properties (Access = public) UIFigure matlab.ui.Figure end properties (Access = private) UI struct Config struct Results struct end methods (Access = private) function startup(app) app.Config = load_case_template('case01'); app.Results = struct(); app.refreshUIFromConfig(); app.appendLog('Loaded default template: case01'); app.updateStatus('Ready'); end function createComponents(app) app.UIFigure = uifigure('Visible', 'off', 'Name', 'EDFM Simulator App'); app.UIFigure.Position = [100 100 1500 900]; root = uigridlayout(app.UIFigure, [2 1]); root.RowHeight = {44, '1x'}; top = uigridlayout(root, [1 9]); top.ColumnWidth = {140, 110, 110, 110, 90, 110, 110, '1x', 180}; app.UI.TemplateDropDown = uidropdown(top, 'Items', {'case01'}, 'Value', 'case01'); app.UI.TemplateDropDown.Layout.Column = 1; app.UI.LoadTemplateButton = uibutton(top, 'push', 'Text', 'Load Template', ... 'ButtonPushedFcn', @(~,~) app.onLoadTemplate()); app.UI.LoadTemplateButton.Layout.Column = 2; app.UI.ImportConfigButton = uibutton(top, 'push', 'Text', 'Import Config', ... 'ButtonPushedFcn', @(~,~) app.onImportConfig()); app.UI.ImportConfigButton.Layout.Column = 3; app.UI.ExportConfigButton = uibutton(top, 'push', 'Text', 'Export Config', ... 'ButtonPushedFcn', @(~,~) app.onExportConfig()); app.UI.ExportConfigButton.Layout.Column = 4; app.UI.RunButton = uibutton(top, 'push', 'Text', 'Run', ... 'ButtonPushedFcn', @(~,~) app.onRun()); app.UI.RunButton.Layout.Column = 5; app.UI.ImportResultButton = uibutton(top, 'push', 'Text', 'Import Result', ... 'ButtonPushedFcn', @(~,~) app.onImportResult()); app.UI.ImportResultButton.Layout.Column = 6; app.UI.ExportResultButton = uibutton(top, 'push', 'Text', 'Export Result', ... 'ButtonPushedFcn', @(~,~) app.onExportResult()); app.UI.ExportResultButton.Layout.Column = 7; app.UI.StatusLabel = uilabel(top, 'Text', 'Status: Ready', 'HorizontalAlignment', 'right'); app.UI.StatusLabel.Layout.Column = 9; tabs = uitabgroup(root); tabs.Layout.Row = 2; app.UI.Tabs = tabs; app.UI.Part1Tab = uitab(tabs, 'Title', 'Part1 Model'); app.UI.Part2Tab = uitab(tabs, 'Title', 'Part2 Grid'); app.UI.Part3Tab = uitab(tabs, 'Title', 'Part3 Fracture'); app.UI.Part4Tab = uitab(tabs, 'Title', 'Part4 Discretization'); app.UI.Part5Tab = uitab(tabs, 'Title', 'Part5 Flow'); app.UI.Part6Tab = uitab(tabs, 'Title', 'Part6 Wells'); app.UI.Part7Tab = uitab(tabs, 'Title', 'Part7 Solver'); app.UI.RunTab = uitab(tabs, 'Title', 'Run'); app.UI.ResultTab = uitab(tabs, 'Title', 'Results'); app.buildPart1(); app.buildPart2(); app.buildPart3(); app.buildPart4(); app.buildPart5(); app.buildPart6(); app.buildPart7(); app.buildRunTab(); app.buildResultTab(); app.UIFigure.Visible = 'on'; end function buildPart1(app) g = uigridlayout(app.UI.Part1Tab, [4 2]); g.RowHeight = {30,30,30,'1x'}; g.ColumnWidth = {160,'1x'}; uilabel(g, 'Text', 'Model Flag'); app.UI.ModelFlag = uidropdown(g, 'Items', {'1'}); app.UI.ModelFlag.Layout.Column = 2; lbl = uilabel(g, 'Text', 'Grid Model'); lbl.Layout.Row = 2; lbl.Layout.Column = 1; app.UI.GridModel = uidropdown(g, 'Items', {'1','2'}); app.UI.GridModel.Layout.Row = 2; app.UI.GridModel.Layout.Column = 2; lbl = uilabel(g, 'Text', 'Flow Model'); lbl.Layout.Row = 3; lbl.Layout.Column = 1; app.UI.FlowModel = uidropdown(g, 'Items', {'1','2','3'}); app.UI.FlowModel.Layout.Row = 3; app.UI.FlowModel.Layout.Column = 2; end function buildPart2(app) g = uigridlayout(app.UI.Part2Tab, [4 2]); g.ColumnWidth = {130,'1x'}; g.RowHeight = {100,100,100,'1x'}; app.createAreaField(g, 1, 'dx', 'Dx'); app.createAreaField(g, 2, 'dy', 'Dy'); app.createAreaField(g, 3, 'dz', 'Dz'); app.createAreaField(g, 4, 'ntg', 'NTG'); end function buildPart3(app) g = uigridlayout(app.UI.Part3Tab, [5 2]); g.ColumnWidth = {160,'1x'}; g.RowHeight = {30,120,120,120,80}; uilabel(g, 'Text', 'Input Style'); app.UI.InputStyle = uidropdown(g, 'Items', {'1','2','3','4'}); app.UI.InputStyle.Layout.Column = 2; app.createAreaField(g, 2, 'fractureInput', 'Input Content'); app.createAreaField(g, 3, 'fractureLines', 'Fracture Lines'); app.createAreaField(g, 4, 'fractureHeights', 'Fracture Heights'); app.createAreaField(g, 5, 'flowBarrier', 'Flow Barrier Flags'); end function buildPart4(app) g = uigridlayout(app.UI.Part4Tab, [7 2]); g.ColumnWidth = {180,'1x'}; g.RowHeight = {90,60,70,70,70,30,'1x'}; app.createAreaField(g, 1, 'boundary', 'Boundary Polygon'); app.createAreaField(g, 2, 'invalidLayer', 'Invalid Layer'); app.createAreaField(g, 3, 'matrixKx', 'Matrix kx'); app.createAreaField(g, 4, 'matrixKy', 'Matrix ky'); app.createAreaField(g, 5, 'matrixKz', 'Matrix kz'); app.createAreaField(g, 6, 'matrixPori', 'Matrix pori'); panel = uipanel(g); panel.Layout.Row = 7; panel.Layout.Column = 2; sub = uigridlayout(panel, [1 6]); uilabel(sub, 'Text', 'prpor'); app.UI.MatrixPrpor = uieditfield(sub, 'numeric'); uilabel(sub, 'Text', 'cpor'); app.UI.MatrixCpor = uieditfield(sub, 'numeric'); uilabel(sub, 'Text', 'rho'); app.UI.RockDensity = uieditfield(sub, 'numeric'); end function buildPart5(app) g = uigridlayout(app.UI.Part5Tab, [3 1]); g.RowHeight = {100,'1x',120}; init = uipanel(g, 'Title', 'Initial State'); initg = uigridlayout(init, [2 4]); uilabel(initg, 'Text', 'Pressure'); uilabel(initg, 'Text', 'Sw'); uilabel(initg, 'Text', 'Cs'); uilabel(initg, 'Text', 'Cb'); app.UI.InitialPressure = uieditfield(initg, 'numeric'); app.UI.InitialPressure.Layout.Row = 2; app.UI.InitialSw = uieditfield(initg, 'numeric'); app.UI.InitialSw.Layout.Row = 2; app.UI.InitialCs = uieditfield(initg, 'numeric'); app.UI.InitialCs.Layout.Row = 2; app.UI.InitialCb = uieditfield(initg, 'numeric'); app.UI.InitialCb.Layout.Row = 2; subtabs = uitabgroup(g); app.UI.GasTab = uitab(subtabs, 'Title', 'Gas-Water'); app.UI.OilTab = uitab(subtabs, 'Title', 'Oil-Water'); app.UI.MultiTab = uitab(subtabs, 'Title', 'Multi-Component'); app.UI.GasArea = uitextarea(uigridlayout(app.UI.GasTab, [1 1])); app.UI.OilArea = uitextarea(uigridlayout(app.UI.OilTab, [1 1])); mg = uigridlayout(app.UI.MultiTab, [2 3]); app.UI.MultiAdsorption = uitextarea(mg); app.UI.MultiNc = uitextarea(mg); app.UI.MultiPc = uitextarea(mg); app.UI.MultiKrNoSurf = uitextarea(mg); app.UI.MultiKrSurf = uitextarea(mg); app.UI.MultiNote = uitextarea(mg, 'Editable', 'off', 'Value', {'Active flow model only.'}); hint = uitextarea(g, 'Editable', 'off', 'Value', {'Flow models are mutually exclusive.', 'Only the selected model will be used at run time.'}); hint.Layout.Row = 3; end function buildPart6(app) g = uigridlayout(app.UI.Part6Tab, [4 2]); g.ColumnWidth = {160,'1x'}; g.RowHeight = {110,110,140,'1x'}; app.createAreaField(g, 1, 'well1', 'Well1'); app.createAreaField(g, 2, 'welloc', 'Well Locations'); app.createAreaField(g, 3, 'well2', 'Well2'); p = uipanel(g); p.Layout.Row = 4; p.Layout.Column = 2; sub = uigridlayout(p, [1 4]); app.UI.ScheduleArea = uitextarea(sub); app.UI.TimeArea = uitextarea(sub); app.UI.DtMinArea = uitextarea(sub); app.UI.DtMaxArea = uitextarea(sub); lbl = uilabel(g, 'Text', 'Schedule / Time / dt'); lbl.Layout.Row = 4; lbl.Layout.Column = 1; end function buildPart7(app) g = uigridlayout(app.UI.Part7Tab, [2 6]); names = {'YitaP','YitaS','Omega','Nmax','EpsAve','EpsMax'}; for i = 1:numel(names) lbl = uilabel(g, 'Text', names{i}); lbl.Layout.Row = 1; lbl.Layout.Column = i; app.UI.(names{i}) = uieditfield(g, 'numeric'); app.UI.(names{i}).Layout.Row = 2; app.UI.(names{i}).Layout.Column = i; end end function buildRunTab(app) g = uigridlayout(app.UI.RunTab, [2 1]); g.RowHeight = {40,'1x'}; uibutton(g, 'push', 'Text', 'Run Current Config', 'ButtonPushedFcn', @(~,~) app.onRun()); app.UI.RunLog = uitextarea(g, 'Editable', 'off'); app.UI.RunLog.Layout.Row = 2; end function buildResultTab(app) g = uigridlayout(app.UI.ResultTab, [1 2]); g.ColumnWidth = {360,'1x'}; app.UI.ResultSummary = uitextarea(g, 'Editable', 'off'); app.UI.ResultAxes = uiaxes(g); title(app.UI.ResultAxes, 'Results'); end function createAreaField(app, parent, row, field_name, label_text) lbl = uilabel(parent, 'Text', label_text); lbl.Layout.Row = row; lbl.Layout.Column = 1; app.UI.(field_name) = uitextarea(parent); app.UI.(field_name).Layout.Row = row; app.UI.(field_name).Layout.Column = 2; end function onLoadTemplate(app) app.Config = load_case_template(app.UI.TemplateDropDown.Value); app.refreshUIFromConfig(); app.appendLog(['Loaded template: ', app.UI.TemplateDropDown.Value]); app.updateStatus('Template loaded'); end function onImportConfig(app) [f, p] = uigetfile('*.mat', 'Import Config'); if isequal(f, 0), return; end app.Config = load_config_mat(fullfile(p, f)); app.refreshUIFromConfig(); app.appendLog(['Imported config: ', fullfile(p, f)]); app.updateStatus('Config imported'); end function onExportConfig(app) app.pushUIToConfig(); [f, p] = uiputfile('*.mat', 'Export Config', 'config.mat'); if isequal(f, 0), return; end save_config_mat(app.Config, fullfile(p, f)); app.appendLog(['Exported config: ', fullfile(p, f)]); app.updateStatus('Config exported'); end function onImportResult(app) [f, p] = uigetfile('*.mat', 'Import Result'); if isequal(f, 0), return; end app.Results = load_results_mat(fullfile(p, f)); app.refreshResults(); app.updateStatus('Result imported'); end function onExportResult(app) if isempty(fieldnames(app.Results)), uialert(app.UIFigure, 'No results available.', 'Export Result'); return; end [f, p] = uiputfile('*.mat', 'Export Result', 'results.mat'); if isequal(f, 0), return; end save_results_mat(app.Results, fullfile(p, f)); app.appendLog(['Exported result: ', fullfile(p, f)]); app.updateStatus('Result exported'); end function onRun(app) app.pushUIToConfig(); app.updateStatus('Running'); app.appendLog('Run started'); drawnow; try log_text = evalc('[r, Times, OutputRs, Wellpara, trun] = run_case(app.Config);'); app.Results = struct('r', r, 'Times', Times, 'OutputRs', {OutputRs}, 'Wellpara', {Wellpara}, 'trun', trun, 'captured_log', log_text); app.appendLog(log_text); app.refreshResults(); app.UI.Tabs.SelectedTab = app.UI.ResultTab; app.updateStatus('Run completed'); catch ME app.appendLog(getReport(ME, 'extended', 'hyperlinks', 'off')); app.updateStatus('Run failed'); uialert(app.UIFigure, ME.message, 'Run Failed'); end end function refreshUIFromConfig(app) c = app.Config; app.UI.ModelFlag.Value = string(c.model.modelflag); app.UI.GridModel.Value = string(c.model.grid_model); app.UI.FlowModel.Value = string(c.model.flow_model); app.UI.Dx.Value = splitlines(app.toExpr(c.grid.dx)); app.UI.Dy.Value = splitlines(app.toExpr(c.grid.dy)); app.UI.Dz.Value = splitlines(app.toExpr(c.grid.dz)); app.UI.Ntg.Value = splitlines(app.toExpr(c.grid.NTG)); app.UI.InputStyle.Value = string(c.fracture.input_style); app.UI.fractureInput.Value = splitlines(app.toExpr(c.fracture.input_content)); app.UI.fractureLines.Value = splitlines(app.toExpr(c.fracture.fractureLines)); app.UI.fractureHeights.Value = splitlines(app.toExpr(c.fracture.fractureHeights)); app.UI.flowBarrier.Value = splitlines(app.toExpr(c.fracture.flowBarrierFlags)); app.UI.boundary.Value = splitlines(app.toExpr(c.discretization.sp.boundary_polygon)); app.UI.invalidLayer.Value = splitlines(app.toExpr(c.discretization.sp.invalid_layer)); app.UI.matrixKx.Value = splitlines(app.toExpr(c.discretization.sp.matrix.kx)); app.UI.matrixKy.Value = splitlines(app.toExpr(c.discretization.sp.matrix.ky)); app.UI.matrixKz.Value = splitlines(app.toExpr(c.discretization.sp.matrix.kz)); app.UI.matrixPori.Value = splitlines(app.toExpr(c.discretization.sp.matrix.pori)); app.UI.MatrixPrpor.Value = c.discretization.sp.matrix.prpor; app.UI.MatrixCpor.Value = c.discretization.sp.matrix.cpor; app.UI.RockDensity.Value = c.discretization.sp.matrix.rock_density; app.UI.GasArea.Value = splitlines(app.toExpr(c.flow.gas_water.matrix_relperm_table)); app.UI.OilArea.Value = splitlines(app.toExpr(c.flow.oil_water.matrix_relperm_table)); app.UI.MultiAdsorption.Value = splitlines(app.toExpr(c.flow.multi_component.c_ca_table)); app.UI.MultiNc.Value = splitlines(app.toExpr(c.flow.multi_component.cs_Nc)); app.UI.MultiPc.Value = splitlines(app.toExpr(c.flow.multi_component.PC)); app.UI.MultiKrNoSurf.Value = splitlines(app.toExpr(c.flow.multi_component.kr_nosurf)); app.UI.MultiKrSurf.Value = splitlines(app.toExpr(c.flow.multi_component.kr_surf)); app.UI.InitialPressure.Value = app.scalarValue(c.initial.pressure); app.UI.InitialSw.Value = app.scalarValue(c.initial.sw); app.UI.InitialCs.Value = app.scalarValue(c.initial.cs); app.UI.InitialCb.Value = app.scalarValue(c.initial.cb); app.UI.well1.Value = splitlines(app.toExpr(c.wells.well1)); app.UI.welloc.Value = splitlines(app.toExpr(c.wells.welloc)); app.UI.well2.Value = splitlines(app.toExpr(c.wells.well2)); app.UI.ScheduleArea.Value = splitlines(app.toExpr(c.schedule.well_schedules)); app.UI.TimeArea.Value = splitlines(app.toExpr(c.schedule.time)); app.UI.DtMinArea.Value = splitlines(app.toExpr(c.schedule.dtmin)); app.UI.DtMaxArea.Value = splitlines(app.toExpr(c.schedule.dtmax)); app.UI.YitaP.Value = c.solver.yitap; app.UI.YitaS.Value = c.solver.yitas; app.UI.Omega.Value = c.solver.omega; app.UI.Nmax.Value = c.solver.Nmax; app.UI.EpsAve.Value = c.solver.epsave; app.UI.EpsMax.Value = c.solver.epsmax; end function pushUIToConfig(app) c = app.Config; c.model.modelflag = str2double(app.UI.ModelFlag.Value); c.model.grid_model = str2double(app.UI.GridModel.Value); c.model.flow_model = str2double(app.UI.FlowModel.Value); c.grid.dx = app.parseExpr(app.UI.Dx.Value); c.grid.dy = app.parseExpr(app.UI.Dy.Value); c.grid.dz = app.parseExpr(app.UI.Dz.Value); c.grid.NTG = app.parseExpr(app.UI.Ntg.Value); c.grid.nx = numel(c.grid.dx); c.grid.ny = numel(c.grid.dy); c.grid.nz = numel(c.grid.dz); c.fracture.input_style = str2double(app.UI.InputStyle.Value); c.fracture.input_content = app.parseExpr(app.UI.fractureInput.Value); c.fracture.fractureLines = app.parseExpr(app.UI.fractureLines.Value); c.fracture.fractureHeights = app.parseExpr(app.UI.fractureHeights.Value); c.fracture.flowBarrierFlags = app.parseExpr(app.UI.flowBarrier.Value); c.discretization.sp.boundary_polygon = app.parseExpr(app.UI.boundary.Value); c.discretization.sp.invalid_layer = app.parseExpr(app.UI.invalidLayer.Value); c.discretization.sp.matrix.kx = app.parseExpr(app.UI.matrixKx.Value); c.discretization.sp.matrix.ky = app.parseExpr(app.UI.matrixKy.Value); c.discretization.sp.matrix.kz = app.parseExpr(app.UI.matrixKz.Value); c.discretization.sp.matrix.pori = app.parseExpr(app.UI.matrixPori.Value); c.discretization.sp.matrix.prpor = app.UI.MatrixPrpor.Value; c.discretization.sp.matrix.cpor = app.UI.MatrixCpor.Value; c.discretization.sp.matrix.rock_density = app.UI.RockDensity.Value; c.flow.gas_water.matrix_relperm_table = app.parseExpr(app.UI.GasArea.Value); c.flow.oil_water.matrix_relperm_table = app.parseExpr(app.UI.OilArea.Value); c.flow.multi_component.c_ca_table = app.parseExpr(app.UI.MultiAdsorption.Value); c.flow.multi_component.cs_Nc = app.parseExpr(app.UI.MultiNc.Value); c.flow.multi_component.PC = app.parseExpr(app.UI.MultiPc.Value); c.flow.multi_component.kr_nosurf = app.parseExpr(app.UI.MultiKrNoSurf.Value); c.flow.multi_component.kr_surf = app.parseExpr(app.UI.MultiKrSurf.Value); c.initial.pressure = app.UI.InitialPressure.Value; c.initial.sw = app.UI.InitialSw.Value; c.initial.cs = app.UI.InitialCs.Value; c.initial.cb = app.UI.InitialCb.Value; c.wells.well1 = app.parseExpr(app.UI.well1.Value); c.wells.welloc = app.parseExpr(app.UI.welloc.Value); c.wells.num_fracture_wells = numel(c.wells.welloc); c.wells.well2 = app.parseExpr(app.UI.well2.Value); c.schedule.well_schedules = app.parseExpr(app.UI.ScheduleArea.Value); c.schedule.time = app.parseExpr(app.UI.TimeArea.Value); c.schedule.dtmin = app.parseExpr(app.UI.DtMinArea.Value); c.schedule.dtmax = app.parseExpr(app.UI.DtMaxArea.Value); c.schedule.number_phases = size(c.schedule.well_schedules, 1); c.solver.yitap = app.UI.YitaP.Value; c.solver.yitas = app.UI.YitaS.Value; c.solver.omega = app.UI.Omega.Value; c.solver.Nmax = app.UI.Nmax.Value; c.solver.epsave = app.UI.EpsAve.Value; c.solver.epsmax = app.UI.EpsMax.Value; app.Config = c; end function refreshResults(app) if isempty(fieldnames(app.Results)), app.UI.ResultSummary.Value = {'No results loaded.'}; cla(app.UI.ResultAxes); return; end summary = {}; if isfield(app.Results, 'Times'), summary{end+1} = ['Time steps: ', num2str(numel(app.Results.Times))]; end %#ok if isfield(app.Results, 'trun') && isstruct(app.Results.trun) if isfield(app.Results.trun, 'run_time'), summary{end+1} = ['Run time: ', num2str(app.Results.trun.run_time)]; end %#ok if isfield(app.Results.trun, 'Newton_step'), summary{end+1} = ['Newton steps: ', num2str(app.Results.trun.Newton_step)]; end %#ok end app.UI.ResultSummary.Value = summary; cla(app.UI.ResultAxes); if isfield(app.Results, 'trun') && isfield(app.Results.trun, 'Newtons_vs_time') && ~isempty(app.Results.trun.Newtons_vs_time) plot(app.UI.ResultAxes, app.Results.trun.Newtons_vs_time(:,1), app.Results.trun.Newtons_vs_time(:,2), '-o'); title(app.UI.ResultAxes, 'Newtons vs Time'); xlabel(app.UI.ResultAxes, 'Time'); ylabel(app.UI.ResultAxes, 'Newton Count'); elseif isfield(app.Results, 'Times') && ~isempty(app.Results.Times) plot(app.UI.ResultAxes, app.Results.Times, 1:numel(app.Results.Times), '-o'); title(app.UI.ResultAxes, 'Time Step Index'); xlabel(app.UI.ResultAxes, 'Time'); ylabel(app.UI.ResultAxes, 'Step'); end end function appendLog(app, txt) lines = string(txt); lines = splitlines(lines); current = string(app.UI.RunLog.Value); if isequal(current, ""), app.UI.RunLog.Value = cellstr(lines); else, app.UI.RunLog.Value = cellstr([current; lines]); end end function updateStatus(app, txt) app.UI.StatusLabel.Text = ['Status: ', txt]; end function out = parseExpr(app, lines) %#ok txt = strtrim(strjoin(string(lines), newline)); if txt == "", out = []; else, out = eval(txt); end %#ok end function txt = toExpr(app, value) %#ok if isempty(value), txt = '[]'; return; end if isnumeric(value) || islogical(value), txt = mat2str(value); return; end if ischar(value), txt = ['''', strrep(value, '''', ''''''), '''']; return; end if isstring(value) && isscalar(value), txt = ['''', strrep(char(value), '''', ''''''), '''']; return; end if iscell(value) rows = cell(size(value,1),1); for i = 1:size(value,1) cols = cell(1,size(value,2)); for j = 1:size(value,2), cols{j} = app.toExpr(value{i,j}); end rows{i} = strjoin(cols, ', '); end txt = ['{', strjoin(rows, [';', newline]), '}']; return; end txt = evalc('disp(value)'); end function value = scalarValue(app, raw) %#ok if isempty(raw), value = 0; elseif isscalar(raw), value = raw; else, value = raw(1); end end end methods (Access = public) function app = EDFMSimulatorApp() createComponents(app); registerApp(app, app.UIFigure); runStartupFcn(app, @(~,~) app.startup()); end function delete(app) delete(app.UIFigure); end end end