优化点5,6

This commit is contained in:
xinxiao
2026-04-03 15:51:34 +08:00
parent 2622ee0c79
commit 88382efb44
5 changed files with 892 additions and 38 deletions
+392
View File
@@ -0,0 +1,392 @@
# Fracture Well UI Redesign Plan
## Problem Summary
Current `Wells` page structure is not suitable for the following scenario:
- reservoir may contain zero fracture wells
- reservoir may contain one fracture well
- reservoir may contain multiple fracture wells
- `num_fracture_wells` must equal the fracture-well count
- `welloc{i,1}` stores perforation coordinates for fracture well `i`
- perforation coordinates are often fracture coordinates, but they may also be independent coordinates
- fracture engineering input can stay unified in `fracture.input_content`
- perforation data cannot be merged into one shared array, otherwise multiple fracture wells cannot be distinguished
The current controller only reads and writes the first entry of `c.wells.welloc`, so the present UI effectively assumes only one fracture well.
## Current Constraint In Existing Code
Existing runtime logic already supports multiple fracture wells in principle:
- `run_case.m`
- `build_fracture_wells(...)` loops over `num_fracture_wells`
- original case files
- example case 7 uses `num_fracture_wells = 6`
- `welloc{1}` to `welloc{6}` are defined separately
- `well2` also has 6 rows and matches them one by one
The main mismatch is in the GUI/controller layer:
- `FractureWellLocationTable` is a single table
- controller uses `unwrapFirstCell(c.wells.welloc)`
- controller writes back with `replaceFirstCell(...)`
So the GUI schema is the real bottleneck.
## Recommended UI Principle
Do not organize the fracture-well section as:
- one table for all perforation coordinates only
Instead organize it as:
- one summary table for fracture well definitions
- one detail editor for the currently selected fracture well's perforation coordinates
This is the safest structure because:
- it supports `0 / 1 / N` fracture wells
- each fracture well keeps its own `welloc{i,1}`
- `well2(i,:)` and `welloc{i,1}` can stay naturally linked by row index
- fracture geometry remains independent in `fracture.input_content`
## Recommended Data Mapping
### Keep These Backend Fields
Keep the runtime/backend idea below:
```matlab
c.wells.num_fracture_wells
c.wells.well2
c.wells.welloc
```
Recommended interpretation:
- `c.wells.num_fracture_wells = size(c.wells.well2, 1)`
- `c.wells.welloc{i,1}` is the perforation coordinate matrix of fracture well `i`
- `size(c.wells.welloc, 1)` should equal `size(c.wells.well2, 1)`
Recommended invariant:
```matlab
num_fracture_wells == size(well2,1) == numel(welloc)
```
### Suggested Meaning Of `well2`
Treat `well2` as the fracture-well summary table only.
Recommended editable columns:
1. `well_name`
2. `rw`
3. `skin`
4. `welltype`
Recommended non-editable or derived columns:
1. `nperf`
2. `perf_index`
Reason:
- `nperf` is derived from `welloc{i,1}`
- `perf_index` is generated by `findWelloc(...)`
- these two should not be manually edited in the GUI
If keeping the current 6-column backend shape for compatibility, then GUI can still display 6 columns, but:
- column 2 `nperf` should be read-only
- column 3 `perf_index` should be read-only or hidden
## Recommended Wells Page Layout
### Section A: Conventional Wells
Keep existing `Well1Table`.
This remains for ordinary wells already handled by `handle_well1(...)`.
### Section B: Fracture Wells Summary
Repurpose current `Well2Table` into a fracture-well summary table.
Recommended label:
- `Fracture Wells`
Recommended columns:
1. `well_name`
2. `rw`
3. `skin`
4. `welltype`
5. `nperf` optional read-only display
Buttons:
- `Add Fracture Well`
- `Delete Fracture Well`
Behavior:
- add one row to `well2`
- also create one empty `welloc{i,1} = []`
- delete row `i` from both `well2` and `welloc`
- after any add/delete:
- `num_fracture_wells = size(well2,1)`
### Section C: Perforation Detail Editor
Do not keep one generic `FractureWellLocationTable` without context.
Replace it conceptually with:
- one selector showing which fracture well is being edited
- one table that edits only that well's perforation coordinates
Recommended controls:
1. `SelectedFracWellDropDown` or `SelectedFracWellListBox`
2. `FractureWellLocationTable`
3. `Add Perf Row`
4. `Delete Perf Row`
5. optional helper text label
Recommended table columns:
1. `x`
2. `y`
3. `z`
Behavior:
- when user selects fracture well `i`
- table shows `welloc{i,1}`
- when table changes
- write back to `welloc{i,1}`
- when no fracture well exists
- disable this table and show hint text like:
- `No fracture well is defined. Please add a fracture well first.`
This is the key redesign.
## Relationship Between Fracture Input And Perforation Input
These two parts should stay separate in the UI.
### Fracture Tab
Keep:
- `fracture.input_content`
This is for fracture geometry/engineering input.
### Wells Tab
Keep separate:
- fracture-well summary
- per-well perforation coordinates
Reason:
- fractures describe the reservoir fracture system
- perforations describe where a specific fracture well connects to the model
- one fracture well may connect to one or more fracture coordinates
- perforation coordinates may match fracture coordinates, but not always
So they should not be merged into the same editable widget.
## Whether `num_fracture_wells` Should Be A Separate UI Field
Recommendation:
- do not expose `num_fracture_wells` as an independent editable numeric field
Instead:
- derive it automatically from fracture-well summary row count
Reason:
- avoids inconsistency
- user only manages actual fracture-well rows
- controller maintains `num_fracture_wells`
## Recommended App Designer Changes
These are the parts you should change manually in `.mlapp`.
### Minimum Change Version
If you want the smallest UI change:
1. keep `Well1Table`
2. keep `Well2Table`
3. keep `FractureWellLocationTable`
4. add one selector control above `FractureWellLocationTable`
- name suggestion: `SelectedFracWellDropDown`
5. change labels so that:
- `Well2Label` -> `Fracture Wells`
- `WellLocationLabel` -> `Perforation Coordinates`
6. change `FractureWellLocationTable` columns from one generic column to three columns:
- `x`, `y`, `z`
7. add a small note label:
- `Each fracture well has its own perforation coordinate list.`
This is the recommended minimum viable fix.
### Better Version
If you can slightly improve layout:
Use a 3-block layout in `WellsTab`:
1. top-left: `Well1Table`
2. top-right: `Well2Table`
3. bottom-wide: selected fracture well perforation editor
This will be clearer than the current four unrelated tables.
## Recommended Table Definitions
### `Well1Table`
No major structural change required.
### `Well2Table`
Recommended columns:
1. `well_name`
2. `rw`
3. `skin`
4. `welltype`
Optional extra read-only columns:
5. `nperf`
If you want to keep backend compatibility without changing much controller code later, you may still visually keep 6 columns:
1. `well_name`
2. `nperf` read-only
3. `perf_index` hidden or read-only expression
4. `rw`
5. `skin`
6. `welltype`
But from user experience perspective, 4 editable columns are better.
### `FractureWellLocationTable`
Recommended columns:
1. `x`
2. `y`
3. `z`
Each row is one perforation point for the currently selected fracture well.
## Controller Change Direction
When you later modify `EDFMAppController.m`, the well section should change conceptually as follows.
### Current Wrong Logic
```matlab
obj.setTableValue('FractureWellLocationTable', obj.unwrapFirstCell(c.wells.welloc));
...
fractureWellLocation = obj.getNumericTableValue('FractureWellLocationTable', obj.unwrapFirstCell(c.wells.welloc));
c.wells.welloc = obj.replaceFirstCell(c.wells.welloc, fractureWellLocation);
c.wells.num_fracture_wells = numel(c.wells.welloc);
```
This only handles the first fracture well.
### Target Logic
Need a selected index such as:
```matlab
selectedFracWellIndex
```
Read UI:
```matlab
c.wells.well2 = ...
c.wells.num_fracture_wells = size(c.wells.well2,1);
ensure c.wells.welloc has same length
c.wells.welloc{selectedFracWellIndex,1} = currentPerfTable
```
Write UI:
```matlab
set Well2Table from c.wells.well2
refresh fracture-well selector items
load FractureWellLocationTable from c.wells.welloc{selectedFracWellIndex,1}
```
## Validation Rules To Add
When this section is later coded, these checks are recommended:
1. `size(well2,1) == numel(welloc)`
2. `num_fracture_wells == size(well2,1)`
3. each `welloc{i,1}` must be empty or an `N x 3` numeric matrix
4. if `well2(i,1)` is empty, generate default name like `wf1`, `wf2`
5. when no fracture well exists:
- `well2 = {}`
- `welloc = {}`
- `num_fracture_wells = 0`
6. if user deletes a fracture well, also delete its perforation list
## Practical Recommendation
For your current stage, the best balance is:
1. fracture geometry remains in `FractureTab`
2. ordinary wells remain in `Well1Table`
3. fracture wells use `Well2Table`
4. perforations are edited through one selected-well detail table
5. `num_fracture_wells` is auto-derived, not manually entered
This will match both:
- the old case-script logic
- the future GUI editing logic
And it also naturally supports the case where there is no fracture well.
## Manual UI Rename Suggestions
Suggested visible labels:
- `Well1Label` -> `Conventional Wells`
- `Well2Label` -> `Fracture Wells`
- `WellLocationLabel` -> `Perforation Coordinates`
- `ScheduleLabel` keep as `Schedule`
Suggested new control names if you add them:
- `SelectedFracWellDropDown`
- `SelectedFracWellDropDownLabel`
- `FracWellHintLabel`
## Conclusion
The main point is:
- fracture information can stay unified in `fracture.input_content`
- perforation information must be stored per fracture well
- therefore the GUI must be changed from "one perforation table" to "fracture-well summary + selected-well perforation detail"
That is the most stable and least confusing design for the current codebase.
+415 -5
View File
@@ -5,6 +5,7 @@ classdef EDFMAppController < handle
Results struct = struct() Results struct = struct()
ProjectRoot char ProjectRoot char
CurrentPlotAction string = "" CurrentPlotAction string = ""
CurrentFracWellIndex double = 1
end end
methods methods
@@ -25,6 +26,7 @@ classdef EDFMAppController < handle
obj.setDropDownItems('OWoil_modelDropDown', {'1', '2'}, '1'); obj.setDropDownItems('OWoil_modelDropDown', {'1', '2'}, '1');
obj.setDropDownItems('MCgas_modelDropDown', {'1'}, '1'); obj.setDropDownItems('MCgas_modelDropDown', {'1'}, '1');
obj.configureWellTables(); obj.configureWellTables();
obj.configureFracWellControls();
obj.applyFixedUIState(); obj.applyFixedUIState();
obj.Results = struct(); obj.Results = struct();
@@ -180,6 +182,9 @@ classdef EDFMAppController < handle
obj.App.(tableName).UserData = []; obj.App.(tableName).UserData = [];
else else
obj.App.(tableName).UserData = indices(1, 1); obj.App.(tableName).UserData = indices(1, 1);
if strcmp(tableName, 'Well2Table')
obj.setSelectedFracWellIndex(indices(1, 1), true);
end
end end
end end
@@ -188,6 +193,37 @@ classdef EDFMAppController < handle
return; return;
end end
switch tableName
case 'Well2Table'
obj.pushUIToConfig();
c = normalize_config(obj.Config);
well2 = obj.getTableValue('Well2Table', c.wells.well2);
newRow = obj.getDefaultRowForTable(tableName);
nextIndex = size(well2, 1) + 1;
if isempty(newRow{1})
newRow{1} = sprintf('wf%d', nextIndex);
end
if isempty(well2)
well2 = newRow;
else
well2(end + 1, :) = newRow;
end
c.wells.well2 = well2;
c.wells.welloc = obj.resizeWellocCell(c.wells.welloc, size(well2, 1));
c.wells.num_fracture_wells = size(well2, 1);
obj.Config = c;
obj.CurrentFracWellIndex = c.wells.num_fracture_wells;
obj.refreshUIFromConfig();
return;
case 'FractureWellLocationTable'
if obj.getFractureWellCount() == 0
uialert(obj.App.UIFigure, ...
'Please add a fracture well before editing perforation coordinates.', ...
'No Fracture Well');
return;
end
end
data = obj.App.(tableName).Data; data = obj.App.(tableName).Data;
newRow = obj.getDefaultRowForTable(tableName); newRow = obj.getDefaultRowForTable(tableName);
@@ -209,6 +245,38 @@ classdef EDFMAppController < handle
return; return;
end end
if strcmp(tableName, 'Well2Table')
obj.pushUIToConfig();
c = normalize_config(obj.Config);
selectedRow = [];
if isprop(obj.App.(tableName), 'UserData')
selectedRow = obj.App.(tableName).UserData;
end
if isempty(selectedRow) || ~isscalar(selectedRow) || selectedRow < 1
uialert(obj.App.UIFigure, ...
sprintf('Please select a row in %s first.', tableName), ...
'Delete Row');
return;
end
if selectedRow > size(c.wells.well2, 1)
return;
end
c.wells.well2(selectedRow, :) = [];
if iscell(c.wells.welloc) && numel(c.wells.welloc) >= selectedRow
c.wells.welloc(selectedRow, :) = [];
end
c.wells.welloc = obj.resizeWellocCell(c.wells.welloc, size(c.wells.well2, 1));
c.wells.num_fracture_wells = size(c.wells.well2, 1);
if c.wells.num_fracture_wells == 0
obj.CurrentFracWellIndex = 1;
else
obj.CurrentFracWellIndex = min(selectedRow, c.wells.num_fracture_wells);
end
obj.Config = c;
obj.refreshUIFromConfig();
return;
end
data = obj.App.(tableName).Data; data = obj.App.(tableName).Data;
if isempty(data) if isempty(data)
return; return;
@@ -455,8 +523,10 @@ classdef EDFMAppController < handle
obj.setNumericFieldValue('MCstress_factor_ref_pressureEditField', c.flow.multi_component.gas_prop.stress_factor_ref_pressure); obj.setNumericFieldValue('MCstress_factor_ref_pressureEditField', c.flow.multi_component.gas_prop.stress_factor_ref_pressure);
obj.setTableValue('Well1Table', c.wells.well1); obj.setTableValue('Well1Table', c.wells.well1);
obj.setTableValue('FractureWellLocationTable', obj.unwrapFirstCell(c.wells.welloc));
obj.setTableValue('Well2Table', c.wells.well2); obj.setTableValue('Well2Table', c.wells.well2);
c = obj.normalizeScheduleConfig(c);
obj.Config = c;
obj.refreshFracWellControlsFromConfig();
obj.setTableValue('ScheduleTable', obj.unwrapFirstCell(c.schedule.well_schedules)); obj.setTableValue('ScheduleTable', obj.unwrapFirstCell(c.schedule.well_schedules));
obj.setTextAreaExpr('TimeTextArea', c.schedule.time); obj.setTextAreaExpr('TimeTextArea', c.schedule.time);
obj.setTextAreaExpr('DtMinTextArea', c.schedule.dtmin); obj.setTextAreaExpr('DtMinTextArea', c.schedule.dtmin);
@@ -648,11 +718,11 @@ classdef EDFMAppController < handle
c.flow.multi_component.cvw = commonCvw; c.flow.multi_component.cvw = commonCvw;
c.wells.well1 = obj.getTableValue('Well1Table', c.wells.well1); c.wells.well1 = obj.getTableValue('Well1Table', c.wells.well1);
fractureWellLocation = obj.getNumericTableValue( ...
'FractureWellLocationTable', obj.unwrapFirstCell(c.wells.welloc));
c.wells.welloc = obj.replaceFirstCell(c.wells.welloc, fractureWellLocation);
c.wells.num_fracture_wells = numel(c.wells.welloc);
c.wells.well2 = obj.getTableValue('Well2Table', c.wells.well2); c.wells.well2 = obj.getTableValue('Well2Table', c.wells.well2);
c.wells.num_fracture_wells = size(c.wells.well2, 1);
c.wells.welloc = obj.resizeWellocCell(c.wells.welloc, c.wells.num_fracture_wells);
c = obj.persistCurrentFractureWellLocation(c);
c.wells.welloc = obj.resizeWellocCell(c.wells.welloc, c.wells.num_fracture_wells);
firstPhaseSchedule = obj.getTableValue( ... firstPhaseSchedule = obj.getTableValue( ...
'ScheduleTable', obj.unwrapFirstCell(c.schedule.well_schedules)); 'ScheduleTable', obj.unwrapFirstCell(c.schedule.well_schedules));
@@ -661,6 +731,7 @@ classdef EDFMAppController < handle
c.schedule.time = obj.getTextAreaExpr('TimeTextArea', c.schedule.time); c.schedule.time = obj.getTextAreaExpr('TimeTextArea', c.schedule.time);
c.schedule.dtmin = obj.getTextAreaExpr('DtMinTextArea', c.schedule.dtmin); c.schedule.dtmin = obj.getTextAreaExpr('DtMinTextArea', c.schedule.dtmin);
c.schedule.dtmax = obj.getTextAreaExpr('DtMaxTextArea', c.schedule.dtmax); c.schedule.dtmax = obj.getTextAreaExpr('DtMaxTextArea', c.schedule.dtmax);
c = obj.normalizeScheduleConfig(c);
c.solver.yitap = obj.getNumericFieldValue('YitaPEditField', c.solver.yitap); c.solver.yitap = obj.getNumericFieldValue('YitaPEditField', c.solver.yitap);
c.solver.yitas = obj.getNumericFieldValue('YitaSEditField', c.solver.yitas); c.solver.yitas = obj.getNumericFieldValue('YitaSEditField', c.solver.yitas);
@@ -673,6 +744,16 @@ classdef EDFMAppController < handle
obj.syncModelTabs(); obj.syncModelTabs();
end end
function onSelectedFracWellChanged(obj)
if isempty(fieldnames(obj.Config))
obj.Config = normalize_config(create_empty_config());
else
obj.Config = obj.persistCurrentFractureWellLocation(normalize_config(obj.Config));
end
obj.CurrentFracWellIndex = obj.getSelectedFracWellIndexFromUI();
obj.refreshFracWellControlsFromConfig();
end
function applyFixedUIState(obj) function applyFixedUIState(obj)
obj.setControlVisible('ModelFlagDropDownLabel', false); obj.setControlVisible('ModelFlagDropDownLabel', false);
obj.setControlVisible('ModelFlagDropDown', false); obj.setControlVisible('ModelFlagDropDown', false);
@@ -1505,6 +1586,14 @@ classdef EDFMAppController < handle
end end
end end
function configureFracWellControls(obj)
if isprop(obj.App, 'SelectedFracWellDropDown')
obj.App.SelectedFracWellDropDown.Items = {''};
obj.App.SelectedFracWellDropDown.Value = '';
obj.App.SelectedFracWellDropDown.ValueChangedFcn = @(src, event) obj.onSelectedFracWellChanged();
end
end
function configureSingleTable(obj, tableName) function configureSingleTable(obj, tableName)
if ~isprop(obj.App, tableName) if ~isprop(obj.App, tableName)
return; return;
@@ -1518,8 +1607,13 @@ classdef EDFMAppController < handle
tableHandle = obj.App.(tableName); tableHandle = obj.App.(tableName);
tableHandle.ColumnName = columnNames; tableHandle.ColumnName = columnNames;
tableHandle.RowName = {}; tableHandle.RowName = {};
switch tableName
case 'Well2Table'
tableHandle.ColumnEditable = [true, false, false, true, true, true];
otherwise
tableHandle.ColumnEditable = true(1, numel(columnNames)); tableHandle.ColumnEditable = true(1, numel(columnNames));
end end
end
function columnNames = getColumnNamesForTable(~, tableName) function columnNames = getColumnNamesForTable(~, tableName)
switch tableName switch tableName
@@ -1589,5 +1683,321 @@ classdef EDFMAppController < handle
end end
textValue = strtrim(evalc('disp(value)')); textValue = strtrim(evalc('disp(value)'));
end end
function c = persistCurrentFractureWellLocation(obj, c)
c = normalize_config(c);
fractureWellCount = size(c.wells.well2, 1);
c.wells.welloc = obj.resizeWellocCell(c.wells.welloc, fractureWellCount);
if fractureWellCount == 0
c.wells.welloc = cell(0, 1);
c.wells.num_fracture_wells = 0;
return;
end
selectedIndex = min(max(obj.getSelectedFracWellIndex(), 1), fractureWellCount);
fractureWellLocation = obj.getNumericTableValue('FractureWellLocationTable', []);
c.wells.welloc{selectedIndex, 1} = fractureWellLocation;
c.wells.num_fracture_wells = fractureWellCount;
end
function refreshFracWellControlsFromConfig(obj)
c = normalize_config(obj.Config);
fractureWellCount = size(c.wells.well2, 1);
c.wells.welloc = obj.resizeWellocCell(c.wells.welloc, fractureWellCount);
c.wells.num_fracture_wells = fractureWellCount;
obj.Config = c;
obj.refreshFracWellDropDown(fractureWellCount, c.wells.well2);
if fractureWellCount == 0
obj.CurrentFracWellIndex = 1;
obj.setTableValue('FractureWellLocationTable', []);
obj.setControlEnabled('SelectedFracWellDropDown', false);
obj.setControlEnabled('FractureWellLocationTable', false);
obj.setControlEnabled('AddFracLocRowButton', false);
obj.setControlEnabled('DeleteFracLocRowButton', false);
return;
end
obj.CurrentFracWellIndex = min(max(obj.CurrentFracWellIndex, 1), fractureWellCount);
obj.setSelectedFracWellIndex(obj.CurrentFracWellIndex, false);
obj.setControlEnabled('SelectedFracWellDropDown', true);
obj.setControlEnabled('FractureWellLocationTable', true);
obj.setControlEnabled('AddFracLocRowButton', true);
obj.setControlEnabled('DeleteFracLocRowButton', true);
obj.setTableValue('FractureWellLocationTable', c.wells.welloc{obj.CurrentFracWellIndex, 1});
end
function refreshFracWellDropDown(obj, fractureWellCount, well2)
if ~isprop(obj.App, 'SelectedFracWellDropDown')
return;
end
if fractureWellCount == 0
obj.App.SelectedFracWellDropDown.Items = {''};
obj.App.SelectedFracWellDropDown.Value = '';
return;
end
items = cell(fractureWellCount, 1);
for i = 1:fractureWellCount
wellName = sprintf('wf%d', i);
if size(well2, 1) >= i && size(well2, 2) >= 1 && ~isempty(well2{i, 1})
wellName = char(string(well2{i, 1}));
end
items{i} = sprintf('%d: %s', i, wellName);
end
obj.App.SelectedFracWellDropDown.Items = items;
selectedValue = items{min(max(obj.CurrentFracWellIndex, 1), fractureWellCount)};
obj.App.SelectedFracWellDropDown.Value = selectedValue;
end
function count = getFractureWellCount(obj)
if isempty(fieldnames(obj.Config))
count = 0;
return;
end
c = normalize_config(obj.Config);
count = size(c.wells.well2, 1);
end
function index = getSelectedFracWellIndex(obj)
fractureWellCount = obj.getFractureWellCount();
if fractureWellCount == 0
index = 1;
return;
end
index = min(max(obj.CurrentFracWellIndex, 1), fractureWellCount);
end
function index = getSelectedFracWellIndexFromUI(obj)
if ~isprop(obj.App, 'SelectedFracWellDropDown')
index = obj.CurrentFracWellIndex;
return;
end
value = string(obj.App.SelectedFracWellDropDown.Value);
token = regexp(char(value), '^\s*(\d+)', 'tokens', 'once');
if isempty(token)
index = obj.CurrentFracWellIndex;
else
index = str2double(token{1});
end
if isnan(index) || index < 1
index = obj.CurrentFracWellIndex;
end
end
function setSelectedFracWellIndex(obj, index, refreshTable)
if nargin < 3
refreshTable = true;
end
fractureWellCount = obj.getFractureWellCount();
if fractureWellCount == 0
obj.CurrentFracWellIndex = 1;
return;
end
obj.CurrentFracWellIndex = min(max(index, 1), fractureWellCount);
if isprop(obj.App, 'SelectedFracWellDropDown') && ~isempty(obj.App.SelectedFracWellDropDown.Items)
items = obj.App.SelectedFracWellDropDown.Items;
obj.App.SelectedFracWellDropDown.Value = items{obj.CurrentFracWellIndex};
end
if refreshTable
obj.onSelectedFracWellChanged();
end
end
function welloc = resizeWellocCell(~, welloc, targetCount)
if nargin < 3 || isempty(targetCount) || targetCount <= 0
welloc = cell(0, 1);
return;
end
if isempty(welloc) || ~iscell(welloc)
welloc = cell(targetCount, 1);
else
welloc = welloc(:);
currentCount = numel(welloc);
if currentCount < targetCount
welloc(currentCount + 1:targetCount, 1) = {[]};
elseif currentCount > targetCount
welloc = welloc(1:targetCount, 1);
end
end
for i = 1:targetCount
if isempty(welloc{i, 1})
welloc{i, 1} = [];
end
end
end
function setControlEnabled(obj, propName, isEnabled)
if ~isprop(obj.App, propName)
return;
end
component = obj.App.(propName);
if isprop(component, 'Enable')
if isEnabled
component.Enable = 'on';
else
component.Enable = 'off';
end
end
end
function c = normalizeScheduleConfig(obj, c)
c = normalize_config(c);
wellNames = obj.getAllConfiguredWellNames(c);
totalWells = numel(wellNames);
phaseCount = max([ ...
numel(c.schedule.well_schedules), ...
numel(c.schedule.time), ...
numel(c.schedule.dtmin), ...
numel(c.schedule.dtmax), ...
c.schedule.number_phases, ...
1]);
if ~iscell(c.schedule.well_schedules)
c.schedule.well_schedules = cell(phaseCount, 1);
elseif numel(c.schedule.well_schedules) < phaseCount
c.schedule.well_schedules(end + 1:phaseCount, 1) = {[]};
elseif numel(c.schedule.well_schedules) > phaseCount
c.schedule.well_schedules = c.schedule.well_schedules(1:phaseCount, 1);
else
c.schedule.well_schedules = c.schedule.well_schedules(:);
end
for k = 1:phaseCount
c.schedule.well_schedules{k, 1} = obj.normalizeSinglePhaseSchedule( ...
c.schedule.well_schedules{k, 1}, wellNames, totalWells);
end
c.schedule.number_phases = phaseCount;
end
function phaseSchedule = normalizeSinglePhaseSchedule(obj, phaseSchedule, wellNames, totalWells)
if totalWells == 0
phaseSchedule = cell(0, 10);
return;
end
phaseSchedule = obj.ensureScheduleCellMatrix(phaseSchedule);
normalized = cell(totalWells, 10);
usedRows = false(size(phaseSchedule, 1), 1);
for i = 1:totalWells
matchedRow = [];
for rowIndex = 1:size(phaseSchedule, 1)
if usedRows(rowIndex)
continue;
end
if size(phaseSchedule, 2) >= 1 && strcmp(string(phaseSchedule{rowIndex, 1}), string(wellNames{i}))
matchedRow = rowIndex;
break;
end
end
if isempty(matchedRow) && i <= size(phaseSchedule, 1) && ~usedRows(i)
matchedRow = i;
end
if isempty(matchedRow)
normalized(i, :) = obj.getDefaultClosedScheduleRow(wellNames{i});
else
rowData = obj.padScheduleRow(phaseSchedule(matchedRow, :));
rowData{1} = wellNames{i};
normalized(i, :) = rowData;
usedRows(matchedRow) = true;
end
end
phaseSchedule = normalized;
end
function phaseSchedule = ensureScheduleCellMatrix(~, phaseSchedule)
if isempty(phaseSchedule)
phaseSchedule = cell(0, 10);
return;
end
if ~iscell(phaseSchedule)
phaseSchedule = num2cell(phaseSchedule);
end
if isvector(phaseSchedule) && size(phaseSchedule, 1) == 1
if size(phaseSchedule, 2) == 10
return;
end
end
if isvector(phaseSchedule) && size(phaseSchedule, 2) == 1
phaseSchedule = phaseSchedule';
end
if size(phaseSchedule, 2) > 10
phaseSchedule = phaseSchedule(:, 1:10);
elseif size(phaseSchedule, 2) < 10
phaseSchedule(:, end + 1:10) = {[]};
end
end
function rowData = padScheduleRow(~, rowData)
if ~iscell(rowData)
rowData = num2cell(rowData);
end
if size(rowData, 1) ~= 1
rowData = rowData(1, :);
end
if size(rowData, 2) > 10
rowData = rowData(:, 1:10);
elseif size(rowData, 2) < 10
rowData(:, end + 1:10) = {[]};
end
end
function row = getDefaultClosedScheduleRow(~, wellName)
row = {char(string(wellName)), 'close', 'pro', 'const_pwf', 0, 0, '', '', '', ''};
end
function wellNames = getAllConfiguredWellNames(~, c)
well1Names = {};
well2Names = {};
if isfield(c.wells, 'well1') && iscell(c.wells.well1) && ~isempty(c.wells.well1)
well1Names = cell(size(c.wells.well1, 1), 1);
for i = 1:size(c.wells.well1, 1)
name = '';
if size(c.wells.well1, 2) >= 1 && ~isempty(c.wells.well1{i, 1})
name = char(string(c.wells.well1{i, 1}));
end
if isempty(name)
name = sprintf('w%d', i);
end
well1Names{i} = name;
end
end
if isfield(c.wells, 'well2') && iscell(c.wells.well2) && ~isempty(c.wells.well2)
well2Names = cell(size(c.wells.well2, 1), 1);
for i = 1:size(c.wells.well2, 1)
name = '';
if size(c.wells.well2, 2) >= 1 && ~isempty(c.wells.well2{i, 1})
name = char(string(c.wells.well2{i, 1}));
end
if isempty(name)
name = sprintf('wf%d', i);
c.wells.well2{i, 1} = name;
end
well2Names{i} = name;
end
end
wellNames = [well1Names; well2Names];
end
end end
end end
Binary file not shown.
+33 -25
View File
@@ -183,10 +183,11 @@ classdef EDFM_Simulator_App < matlab.apps.AppBase
MCcs_NcTextArea matlab.ui.control.TextArea MCcs_NcTextArea matlab.ui.control.TextArea
cs_NcTextAreaLabel matlab.ui.control.Label cs_NcTextAreaLabel matlab.ui.control.Label
WellsTab matlab.ui.container.Tab WellsTab matlab.ui.container.Tab
SelectedFracWellDropDown matlab.ui.control.DropDown
SelectedFractureWellLabel matlab.ui.control.Label
ScheduleLabel matlab.ui.control.Label ScheduleLabel matlab.ui.control.Label
WellLocationLabel matlab.ui.control.Label FractureWellsLabel matlab.ui.control.Label
Well2Label matlab.ui.control.Label ConventionalWellsLabel matlab.ui.control.Label
Well1Label matlab.ui.control.Label
ScheduleTimeConfigLabel matlab.ui.control.Label ScheduleTimeConfigLabel matlab.ui.control.Label
AddFracLocRowButton matlab.ui.control.Button AddFracLocRowButton matlab.ui.control.Button
DeleteFracLocRowButton matlab.ui.control.Button DeleteFracLocRowButton matlab.ui.control.Button
@@ -1390,7 +1391,7 @@ classdef EDFM_Simulator_App < matlab.apps.AppBase
app.Well1Table.RowName = {}; app.Well1Table.RowName = {};
app.Well1Table.ColumnEditable = true; app.Well1Table.ColumnEditable = true;
app.Well1Table.CellSelectionCallback = createCallbackFcn(app, @Well1TableCellSelection, true); app.Well1Table.CellSelectionCallback = createCallbackFcn(app, @Well1TableCellSelection, true);
app.Well1Table.Position = [35 475 372 182]; app.Well1Table.Position = [43 194 446 167];
% Create FractureWellLocationTable % Create FractureWellLocationTable
app.FractureWellLocationTable = uitable(app.WellsTab); app.FractureWellLocationTable = uitable(app.WellsTab);
@@ -1398,7 +1399,7 @@ classdef EDFM_Simulator_App < matlab.apps.AppBase
app.FractureWellLocationTable.RowName = {}; app.FractureWellLocationTable.RowName = {};
app.FractureWellLocationTable.ColumnEditable = true; app.FractureWellLocationTable.ColumnEditable = true;
app.FractureWellLocationTable.CellSelectionCallback = createCallbackFcn(app, @FractureWellLocationTableCellSelection, true); app.FractureWellLocationTable.CellSelectionCallback = createCallbackFcn(app, @FractureWellLocationTableCellSelection, true);
app.FractureWellLocationTable.Position = [44 179 366 185]; app.FractureWellLocationTable.Position = [737 520 445 100];
% Create Well2Table % Create Well2Table
app.Well2Table = uitable(app.WellsTab); app.Well2Table = uitable(app.WellsTab);
@@ -1406,7 +1407,7 @@ classdef EDFM_Simulator_App < matlab.apps.AppBase
app.Well2Table.RowName = {}; app.Well2Table.RowName = {};
app.Well2Table.ColumnEditable = true; app.Well2Table.ColumnEditable = true;
app.Well2Table.CellSelectionCallback = createCallbackFcn(app, @Well2TableCellSelection, true); app.Well2Table.CellSelectionCallback = createCallbackFcn(app, @Well2TableCellSelection, true);
app.Well2Table.Position = [552 472 594 185]; app.Well2Table.Position = [35 476 594 185];
% Create ScheduleTable % Create ScheduleTable
app.ScheduleTable = uitable(app.WellsTab); app.ScheduleTable = uitable(app.WellsTab);
@@ -1449,25 +1450,25 @@ classdef EDFM_Simulator_App < matlab.apps.AppBase
% Create AddWell1RowButton % Create AddWell1RowButton
app.AddWell1RowButton = uibutton(app.WellsTab, 'push'); app.AddWell1RowButton = uibutton(app.WellsTab, 'push');
app.AddWell1RowButton.ButtonPushedFcn = createCallbackFcn(app, @AddWell1RowButtonPushed, true); app.AddWell1RowButton.ButtonPushedFcn = createCallbackFcn(app, @AddWell1RowButtonPushed, true);
app.AddWell1RowButton.Position = [136 420 100 23]; app.AddWell1RowButton.Position = [144 128 100 41];
app.AddWell1RowButton.Text = 'AddWell1Row'; app.AddWell1RowButton.Text = 'AddWell1Row';
% Create DeleteWell1RowButton % Create DeleteWell1RowButton
app.DeleteWell1RowButton = uibutton(app.WellsTab, 'push'); app.DeleteWell1RowButton = uibutton(app.WellsTab, 'push');
app.DeleteWell1RowButton.ButtonPushedFcn = createCallbackFcn(app, @DeleteWell1RowButtonPushed, true); app.DeleteWell1RowButton.ButtonPushedFcn = createCallbackFcn(app, @DeleteWell1RowButtonPushed, true);
app.DeleteWell1RowButton.Position = [301 418 103 23]; app.DeleteWell1RowButton.Position = [309 126 103 41];
app.DeleteWell1RowButton.Text = 'DeleteWell1Row'; app.DeleteWell1RowButton.Text = 'DeleteWell1Row';
% Create DeleteWell2RowButton % Create DeleteWell2RowButton
app.DeleteWell2RowButton = uibutton(app.WellsTab, 'push'); app.DeleteWell2RowButton = uibutton(app.WellsTab, 'push');
app.DeleteWell2RowButton.ButtonPushedFcn = createCallbackFcn(app, @DeleteWell2RowButtonPushed, true); app.DeleteWell2RowButton.ButtonPushedFcn = createCallbackFcn(app, @DeleteWell2RowButtonPushed, true);
app.DeleteWell2RowButton.Position = [931 421 103 23]; app.DeleteWell2RowButton.Position = [414 425 103 23];
app.DeleteWell2RowButton.Text = 'DeleteWell2Row'; app.DeleteWell2RowButton.Text = 'DeleteWell2Row';
% Create AddWell2RowButton % Create AddWell2RowButton
app.AddWell2RowButton = uibutton(app.WellsTab, 'push'); app.AddWell2RowButton = uibutton(app.WellsTab, 'push');
app.AddWell2RowButton.ButtonPushedFcn = createCallbackFcn(app, @AddWell2RowButtonPushed, true); app.AddWell2RowButton.ButtonPushedFcn = createCallbackFcn(app, @AddWell2RowButtonPushed, true);
app.AddWell2RowButton.Position = [705 423 100 23]; app.AddWell2RowButton.Position = [188 427 100 23];
app.AddWell2RowButton.Text = 'AddWell2Row'; app.AddWell2RowButton.Text = 'AddWell2Row';
% Create AddScheduleRowButton % Create AddScheduleRowButton
@@ -1485,13 +1486,13 @@ classdef EDFM_Simulator_App < matlab.apps.AppBase
% Create DeleteFracLocRowButton % Create DeleteFracLocRowButton
app.DeleteFracLocRowButton = uibutton(app.WellsTab, 'push'); app.DeleteFracLocRowButton = uibutton(app.WellsTab, 'push');
app.DeleteFracLocRowButton.ButtonPushedFcn = createCallbackFcn(app, @DeleteFracLocRowButtonPushed, true); app.DeleteFracLocRowButton.ButtonPushedFcn = createCallbackFcn(app, @DeleteFracLocRowButtonPushed, true);
app.DeleteFracLocRowButton.Position = [288 123 117 23]; app.DeleteFracLocRowButton.Position = [1096 466 117 23];
app.DeleteFracLocRowButton.Text = 'DeleteFracLocRow'; app.DeleteFracLocRowButton.Text = 'DeleteFracLocRow';
% Create AddFracLocRowButton % Create AddFracLocRowButton
app.AddFracLocRowButton = uibutton(app.WellsTab, 'push'); app.AddFracLocRowButton = uibutton(app.WellsTab, 'push');
app.AddFracLocRowButton.ButtonPushedFcn = createCallbackFcn(app, @AddFracLocRowButtonPushed, true); app.AddFracLocRowButton.ButtonPushedFcn = createCallbackFcn(app, @AddFracLocRowButtonPushed, true);
app.AddFracLocRowButton.Position = [149 123 104 23]; app.AddFracLocRowButton.Position = [954 464 104 23];
app.AddFracLocRowButton.Text = 'AddFracLocRow'; app.AddFracLocRowButton.Text = 'AddFracLocRow';
% Create ScheduleTimeConfigLabel % Create ScheduleTimeConfigLabel
@@ -1499,26 +1500,33 @@ classdef EDFM_Simulator_App < matlab.apps.AppBase
app.ScheduleTimeConfigLabel.Position = [1003 331 131 30]; app.ScheduleTimeConfigLabel.Position = [1003 331 131 30];
app.ScheduleTimeConfigLabel.Text = 'Schedule Time Config'; app.ScheduleTimeConfigLabel.Text = 'Schedule Time Config';
% Create Well1Label % Create ConventionalWellsLabel
app.Well1Label = uilabel(app.WellsTab); app.ConventionalWellsLabel = uilabel(app.WellsTab);
app.Well1Label.Position = [41 422 35 22]; app.ConventionalWellsLabel.Position = [27 130 108 40];
app.Well1Label.Text = 'Well1'; app.ConventionalWellsLabel.Text = 'Conventional Wells';
% Create Well2Label % Create FractureWellsLabel
app.Well2Label = uilabel(app.WellsTab); app.FractureWellsLabel = uilabel(app.WellsTab);
app.Well2Label.Position = [560 424 35 22]; app.FractureWellsLabel.Position = [43 428 82 22];
app.Well2Label.Text = 'Well2'; app.FractureWellsLabel.Text = 'Fracture Wells';
% Create WellLocationLabel
app.WellLocationLabel = uilabel(app.WellsTab);
app.WellLocationLabel.Position = [48 125 73 22];
app.WellLocationLabel.Text = 'WellLocation';
% Create ScheduleLabel % Create ScheduleLabel
app.ScheduleLabel = uilabel(app.WellsTab); app.ScheduleLabel = uilabel(app.WellsTab);
app.ScheduleLabel.Position = [555 127 55 22]; app.ScheduleLabel.Position = [555 127 55 22];
app.ScheduleLabel.Text = 'Schedule'; app.ScheduleLabel.Text = 'Schedule';
% Create SelectedFractureWellLabel
app.SelectedFractureWellLabel = uilabel(app.WellsTab);
app.SelectedFractureWellLabel.HorizontalAlignment = 'right';
app.SelectedFractureWellLabel.Position = [723 464 126 22];
app.SelectedFractureWellLabel.Text = 'Selected Fracture Well';
% Create SelectedFracWellDropDown
app.SelectedFracWellDropDown = uidropdown(app.WellsTab);
app.SelectedFracWellDropDown.Items = {''};
app.SelectedFracWellDropDown.Position = [864 464 70 22];
app.SelectedFracWellDropDown.Value = '';
% Create SolverRunTab % Create SolverRunTab
app.SolverRunTab = uitab(app.TabGroup); app.SolverRunTab = uitab(app.TabGroup);
app.SolverRunTab.Title = 'Solver&Run'; app.SolverRunTab.Title = 'Solver&Run';
+51 -7
View File
@@ -48,29 +48,32 @@ switch metric
case "GPR" case "GPR"
field_name = 'qg'; field_name = 'qg';
y_label = 'Gas production rate, m^3/d'; y_label = 'Gas production rate, m^3/d';
for i = 1:n, data(i) = wellpara{i}{1, well_index}.(field_name); end for i = 1:n, data(i) = get_well_metric_value(wellpara, i, well_index, field_name); end
plot(ax, times, data, 'k^-', 'LineWidth', 1.2); plot(ax, times, data, 'k^-', 'LineWidth', 1.2);
case "OPR" case "OPR"
field_name = 'qo'; field_name = 'qo';
y_label = 'Oil production rate, m^3/d'; y_label = 'Oil production rate, m^3/d';
for i = 1:n, data(i) = wellpara{i}{1, well_index}.(field_name); end for i = 1:n, data(i) = get_well_metric_value(wellpara, i, well_index, field_name); end
plot(ax, times, data, 'k^-', 'LineWidth', 1.2); plot(ax, times, data, 'k^-', 'LineWidth', 1.2);
case "WPR" case "WPR"
field_name = 'qw'; field_name = 'qw';
y_label = 'Water production rate, m^3/d'; y_label = 'Water production rate, m^3/d';
for i = 1:n, data(i) = wellpara{i}{1, well_index}.(field_name); end for i = 1:n, data(i) = get_well_metric_value(wellpara, i, well_index, field_name); end
plot(ax, times, data, 'k^-', 'LineWidth', 1.2); plot(ax, times, data, 'k^-', 'LineWidth', 1.2);
case "DPWF" case "DPWF"
y_label = 'Pressure derivative, MPa'; y_label = 'Pressure derivative, MPa';
for i = 1:n for i = 1:n
if i == 1 if i == 1
data(i) = (wellpara{i + 1}{1, well_index}.pwf - wellpara{i}{1, well_index}.pwf) / ... data(i) = (get_well_metric_value(wellpara, i + 1, well_index, 'pwf') - ...
get_well_metric_value(wellpara, i, well_index, 'pwf')) / ...
(log(times(i + 1)) - log(times(i))); (log(times(i + 1)) - log(times(i)));
elseif i == n elseif i == n
data(i) = (wellpara{i}{1, well_index}.pwf - wellpara{i - 1}{1, well_index}.pwf) / ... data(i) = (get_well_metric_value(wellpara, i, well_index, 'pwf') - ...
get_well_metric_value(wellpara, i - 1, well_index, 'pwf')) / ...
(log(times(i)) - log(times(i - 1))); (log(times(i)) - log(times(i - 1)));
else else
data(i) = (wellpara{i + 1}{1, well_index}.pwf - wellpara{i - 1}{1, well_index}.pwf) / ... data(i) = (get_well_metric_value(wellpara, i + 1, well_index, 'pwf') - ...
get_well_metric_value(wellpara, i - 1, well_index, 'pwf')) / ...
(log(times(i + 1)) - log(times(i - 1))); (log(times(i + 1)) - log(times(i - 1)));
end end
end end
@@ -79,7 +82,7 @@ switch metric
otherwise otherwise
field_name = 'pwf'; field_name = 'pwf';
y_label = 'BHP, MPa'; y_label = 'BHP, MPa';
for i = 1:n, data(i) = wellpara{i}{1, well_index}.(field_name); end for i = 1:n, data(i) = get_well_metric_value(wellpara, i, well_index, field_name); end
plot(ax, times, data, 'k^-', 'LineWidth', 1.2); plot(ax, times, data, 'k^-', 'LineWidth', 1.2);
end end
@@ -89,6 +92,47 @@ ylabel(ax, y_label);
title(ax, sprintf('Well Response: well %d, %s', well_index, metric)); title(ax, sprintf('Well Response: well %d, %s', well_index, metric));
end end
function value = get_well_metric_value(wellpara, time_index, well_index, field_name)
well_entry = get_well_entry(wellpara, time_index, well_index);
if isempty(well_entry)
value = NaN;
return;
end
if ~isstruct(well_entry)
value = NaN;
return;
end
if ~isfield(well_entry, field_name)
value = NaN;
return;
end
value = well_entry.(field_name);
end
function well_entry = get_well_entry(wellpara, time_index, well_index)
time_entry = wellpara{time_index};
if iscell(time_entry)
if isempty(time_entry)
well_entry = [];
return;
end
if well_index > numel(time_entry)
well_entry = [];
return;
end
well_entry = time_entry{well_index};
elseif isstruct(time_entry)
if numel(time_entry) < well_index
well_entry = [];
return;
end
well_entry = time_entry(well_index);
else
well_entry = [];
end
end
function render_2d_layer(ax, config, results, options) function render_2d_layer(ax, config, results, options)
r = results.r; r = results.r;
output = results.OutputRs{get_option(options, 'time_step', numel(results.OutputRs))}; output = results.OutputRs{get_option(options, 'time_step', numel(results.OutputRs))};