diff --git a/lib/+artoa/+controller/+edit/+offsets/buttonCalculateOffsets.m b/lib/+artoa/+controller/+edit/+offsets/buttonCalculateOffsets.m
new file mode 100644
index 0000000000000000000000000000000000000000..554e3ae6faee1e5b640b9256259ce820334909a4
--- /dev/null
+++ b/lib/+artoa/+controller/+edit/+offsets/buttonCalculateOffsets.m
@@ -0,0 +1,57 @@
+function [] = buttonCalculateOffsets(~, ~)
+%UNTITLED Summary of this function goes here
+%   Detailed explanation goes here
+
+global artoaWorkspace artoaDataInput;
+
+%% Initialize required variables
+soundsources = artoa.controller.getSoundsourcesWithAppliedToa();
+satData = artoaWorkspace.satData;
+
+%% Calculate offsets
+
+[
+    artoaWorkspace.editOffsets.calculatedMatrixA, ...
+    artoaWorkspace.editOffsets.calculatedMatrixB, ...
+    artoaWorkspace.editOffsets.calculatedMatrixX ...
+] = artoa.offsets.solve( ...
+    artoaDataInput.rfb, ...
+    soundsources, ...
+    satData, ...
+    artoaWorkspace.temperature(artoaWorkspace.statusTemperature == 1), ...
+    artoaWorkspace.pressure(artoaWorkspace.statusPressure == 1), ...
+    artoaWorkspace.trackParameter.soundspeedMethodString, ...
+    artoa.data.getMember(artoaDataInput, {'ini', 'leapseconds'}) ...
+);
+
+%% Store offsets in table
+[offsets, drifts] = artoa.offsets.extractOffsetsDriftsFromSolved(artoaWorkspace.editOffsets.calculatedMatrixX);
+
+artoaWorkspace.editOffsets.soundsourceOffsets.OptimumTotalOffset = offsets;
+artoaWorkspace.editOffsets.soundsourceOffsets.OptimumTotalDrift = drifts;
+
+
+%% Update TOA
+
+% combine initial toa and current applied soundsources
+toaData = artoaWorkspace.toaData;
+toaData.toa = artoaDataInput.toaData.toa;
+
+%% Recalculate drift
+artoa.controller.edit.offsets.updateWorkspaceOffsetsTable();
+artoa.controller.edit.offsets.updateGui();
+
+%% Select offset and drift from the table
+artoa.controller.edit.offsets.selectOffsetsAndDrift();
+
+artoaWorkspace.toaData = artoa.toa.recalculate( ...
+    artoaWorkspace.float, ...
+    toaData, ...
+    artoa.controller.getSoundsourcesWithAppliedToa(), ...
+    artoaWorkspace.editOffsets.selectedOffsets ...
+);
+
+artoa.controller.edit.timeOfArrival.plot();
+
+end
+
diff --git a/lib/+artoa/+controller/+edit/+offsets/checkboxForceOptimumOffsets.m b/lib/+artoa/+controller/+edit/+offsets/checkboxForceOptimumOffsets.m
new file mode 100644
index 0000000000000000000000000000000000000000..49efe4569be623a8242aa371795bc7ba56211966
--- /dev/null
+++ b/lib/+artoa/+controller/+edit/+offsets/checkboxForceOptimumOffsets.m
@@ -0,0 +1,35 @@
+function [newValue] = checkboxForceOptimumOffsets(~, ~)
+%CHECKBOXDOPPLERCORRECTION Summary of this function goes here
+%   Detailed explanation goes here
+
+global artoaGui artoaWorkspace artoaDataInput;
+
+newValue = logical(artoaGui.editOffsets.checkboxForceOptimumOffsets.Value);
+
+artoaWorkspace.editOffsets.forceOptimumOffsets = newValue;
+
+%% Update TOA
+
+% combine initial toa and current applied soundsources
+toaData = artoaWorkspace.toaData;
+toaData.toa = artoaDataInput.toaData.toa;
+
+%% Recalculate drift
+artoa.controller.edit.offsets.updateWorkspaceOffsetsTable();
+artoa.controller.edit.offsets.updateGui();
+
+%% Select offset and drift from the table
+artoa.controller.edit.offsets.selectOffsetsAndDrift();
+
+artoaWorkspace.toaData = artoa.toa.recalculate( ...
+    artoaWorkspace.float, ...
+    toaData, ...
+    artoa.controller.getSoundsourcesWithAppliedToa(), ...
+    artoaWorkspace.editOffsets.selectedOffsets ...
+);
+
+artoa.controller.edit.timeOfArrival.plot();
+
+
+end
+
diff --git a/lib/+artoa/+controller/+edit/+offsets/open.m b/lib/+artoa/+controller/+edit/+offsets/open.m
index bad746d33015e189d93861a6c561903e5da1b223..7d846394495ba90a591e8d531c5a73df45bd1f26 100644
--- a/lib/+artoa/+controller/+edit/+offsets/open.m
+++ b/lib/+artoa/+controller/+edit/+offsets/open.m
@@ -15,6 +15,8 @@ end
 
 callbacks = struct();
 callbacks.tableSoundsourceOffsetsEdit = @artoa.controller.edit.offsets.tableSoundsourceOffsetsEdit;
+callbacks.buttonCalculateOffsets = @artoa.controller.edit.offsets.buttonCalculateOffsets;
+callbacks.checkboxForceOptimumOffsets = @artoa.controller.edit.offsets.checkboxForceOptimumOffsets;
 
 %% Open the gui
 artoa.gui.edit.offsets(callbacks);
@@ -26,6 +28,7 @@ end
 
 %% Create workspace variables
 artoaWorkspace.editOffsets = struct();
+artoaWorkspace.editOffsets.forceOptimumOffsets = false;
 artoa.controller.edit.offsets.updateWorkspaceOffsetsTable();
 
 %% Update gui
diff --git a/lib/+artoa/+controller/+edit/+offsets/selectOffsetsAndDrift.m b/lib/+artoa/+controller/+edit/+offsets/selectOffsetsAndDrift.m
index d37ddd11595b966c8f2bb0f8d0a6332effa6fd23..bb5bc2a4686bef83f37438df66e6830fe6e1c078 100644
--- a/lib/+artoa/+controller/+edit/+offsets/selectOffsetsAndDrift.m
+++ b/lib/+artoa/+controller/+edit/+offsets/selectOffsetsAndDrift.m
@@ -10,8 +10,16 @@ selectedOffsets = table();
 %% Get required variables
 offsetsTable = artoaWorkspace.editOffsets.soundsourceOffsets;
 
-%% Select float offset and drift;
-selectedOffsets('Float', :) = selectFromRow(offsetsTable('Float', :));
+%% Select float offset and drift
+if artoaWorkspace.editOffsets.forceOptimumOffsets
+    tmp = table();
+    tmp.offset = offsetsTable{'Float', 'OptimumTotalOffset'};
+    tmp.drift = offsetsTable{'Float', 'OptimumTotalDrift'};
+    selectedOffsets('Float', :) = tmp;
+    clear tmp;
+else
+    selectedOffsets('Float', :) = selectFromRow(offsetsTable('Float', :));
+end
 
 %% Run through all soundsources
 variableNames = offsetsTable.Properties.RowNames;
@@ -19,7 +27,15 @@ for i = 1:length(variableNames)
     if strcmp('Float', variableNames{i})
         continue;
     end
-    selectedOffsets(variableNames{i}, :) = selectFromRow(offsetsTable(variableNames{i}, :));
+    if artoaWorkspace.editOffsets.forceOptimumOffsets
+        tmp = table();
+        tmp.offset = offsetsTable{variableNames{i}, 'OptimumTotalOffset'};
+        tmp.drift = offsetsTable{variableNames{i}, 'OptimumTotalDrift'};
+        selectedOffsets(variableNames{i}, :) = tmp;
+        clear tmp;
+    else
+        selectedOffsets(variableNames{i}, :) = selectFromRow(offsetsTable(variableNames{i}, :));
+    end
 end
 
 %% Store selected values
diff --git a/lib/+artoa/+controller/+edit/+offsets/updateGui.m b/lib/+artoa/+controller/+edit/+offsets/updateGui.m
index 91d1319f46a096df0f28f7bd6543b53c0bcd398f..32df12f30f30f9252dd41704eb27637664066bff 100644
--- a/lib/+artoa/+controller/+edit/+offsets/updateGui.m
+++ b/lib/+artoa/+controller/+edit/+offsets/updateGui.m
@@ -16,6 +16,8 @@ fieldNames = fieldnames(fields);
 for i = 1:length(fieldNames)
     currentValue = fields.(fieldNames{i});
     switch fieldNames{i}
+        case 'forceOptimumOffsets'
+            artoaGui.editOffsets.checkboxForceOptimumOffsets.Value = currentValue;
         case 'soundsourceOffsets'
             artoaGui.editOffsets.tableSoundsourceOffsets.Data = table2cell( ...
                 currentValue ...
diff --git a/lib/+artoa/+data/calculateSoundVelocity.m b/lib/+artoa/+data/calculateSoundVelocity.m
index 4f5079526a7dcb3543d3cc555c9fd673d1d814a9..96c6a0ddbac9822699fdb1e6cdbf353f6cc4b11b 100644
--- a/lib/+artoa/+data/calculateSoundVelocity.m
+++ b/lib/+artoa/+data/calculateSoundVelocity.m
@@ -6,7 +6,6 @@ function [calculatedSoundVelocity] = calculateSoundVelocity(pTemperature, pPress
 %       pMethod         The chosen method. Available methods are:
 %                       del grosso
 %                       linear
-%                       soundsource
 %
 %   Returns:
 %       The calculated sound velocity in m/s.
diff --git a/lib/+artoa/+gui/+edit/offsets.m b/lib/+artoa/+gui/+edit/offsets.m
index bebec33eb046b2bea1637e8c3bddd8b65b872162..8198af2b72dee8a3e04e134b63fe61340718e506 100644
--- a/lib/+artoa/+gui/+edit/offsets.m
+++ b/lib/+artoa/+gui/+edit/offsets.m
@@ -12,7 +12,9 @@ windowTitle = [ 'ARTOA4 - Float ' num2str(artoaWorkspace.float.floatname) ' - Of
 availableCallbacks = { ...
     'CloseRequestFcn', ...
     'tableSoundsourceOffsetsSelect', ...
-    'tableSoundsourceOffsetsEdit' ...
+    'tableSoundsourceOffsetsEdit', ...
+    'buttonCalculateOffsets', ...
+    'checkboxForceOptimumOffsets' ...
 };
 
 for i = 1:length(availableCallbacks) % check if a callback is undefined
@@ -56,6 +58,35 @@ artoaGui.editOffsets.tableSoundsourceOffsets = uitable( ...
     'CellEditCallback', pCallbacks.tableSoundsourceOffsetsEdit ...
 );
 
+%% Optimum offsets frame
+artoaGui.editOffsets.frameOptimumOffsets = uipanel( ...
+    'Title', 'Offset controls', ...
+    'Units', 'normalized', ...
+    'BackgroundColor', 'white', ...
+    'Position', [left .05 fullwidth/2 .35] ...
+);
+
+%% Setup calculate offsets button
+artoaGui.editOffsets.buttonCalculateOffsets = uicontrol( ...
+    'Parent', artoaGui.editOffsets.frameOptimumOffsets, ...
+    'String', 'Calculate offsets', ...
+    'Style', 'PushButton', ...
+    'FontSize', 8, ...
+    'Units', 'normalized', ...
+    'Position', [.65 .1 .25 .2], ...
+    'CallBack', pCallbacks.buttonCalculateOffsets ...
+);
+
+artoaGui.editOffsets.checkboxForceOptimumOffsets = uicontrol( ...
+    'Parent', artoaGui.editOffsets.frameOptimumOffsets, ...
+    'String', 'Force using optimum offsets', ...
+    'Style', 'checkbox', ...
+    'FontSize', 8, ...
+    'Units', 'normalized', ...
+    'Position', [left .75 fullwidth/2 .2], ...
+    'CallBack', pCallbacks.checkboxForceOptimumOffsets ...
+);
+
 
 end
 
diff --git a/lib/+artoa/+offsets/extractOffsetsDriftsFromSolved.m b/lib/+artoa/+offsets/extractOffsetsDriftsFromSolved.m
new file mode 100644
index 0000000000000000000000000000000000000000..0bd4aea47184ae137e20e8ffc638a7fdd669ea3d
--- /dev/null
+++ b/lib/+artoa/+offsets/extractOffsetsDriftsFromSolved.m
@@ -0,0 +1,20 @@
+function [offsets, drifts] = extractOffsetsDriftsFromSolved(pX)
+%EXTRACTOFFSETSDRIFTSFROMSOLVED Summary of this function goes here
+%   Detailed explanation goes here
+
+soundsourceCount = (length(pX) - 4) / 2;
+
+offsets = NaN(soundsourceCount + 1, 1);
+drifts = NaN(soundsourceCount + 1, 1);
+
+offsets(1) = pX(end - 1);
+drifts(1) = pX(end);
+
+for i = 2:soundsourceCount + 1
+    startIndex = (i - 1) * 2 + 3;
+    offsets(i) = pX(startIndex);
+    drifts(i) = pX(startIndex + 1);
+end
+
+end
+
diff --git a/lib/+artoa/+offsets/solve.m b/lib/+artoa/+offsets/solve.m
new file mode 100644
index 0000000000000000000000000000000000000000..98f6f45f5eb30558cf0d83368898e85df7891aa1
--- /dev/null
+++ b/lib/+artoa/+offsets/solve.m
@@ -0,0 +1,106 @@
+function [a, b, x] = solve(pRfb, pSoundsources, pSatData, pAppliedTemperature, pAppliedPressure, pSoundspeedMethod, pLeapsecondsMatrix)
+%UNTITLED Summary of this function goes here
+%   Detailed explanation goes here
+
+%% Initialize required variables
+%pSoundsources = artoa.controller.getSoundsourcesWithAppliedToa();
+satPositions = [pSatData.lat_sat, pSatData.lon_sat];
+satDates = artoa.convert.dmy2rd(pSatData.day_sat, pSatData.month_sat, pSatData.year_sat);
+toaDates = [];
+satToas = [];
+satDistances = [];
+floatDetails = pRfb.FLOAT;
+
+%% Create table
+results = struct();
+
+%% Calculate SAT TOAs for every soundsource
+fnames = fieldnames(pSoundsources);
+for i = 1:length(fnames)
+    results.(fnames{i}) = table();
+    [date, toa] = artoa.toa.predictFromGps( ...
+        pRfb, ...
+        pSoundsources.(fnames{i}), ...
+        struct( ...
+            'temperature', pAppliedTemperature, ...
+            'pressure', pAppliedPressure, ...
+            'method', pSoundspeedMethod, ...
+            'soundSource', NaN ...
+        ), ...
+        pLeapsecondsMatrix ...
+    );
+    results.(fnames{i}).satDate = date;
+    results.(fnames{i}).satToa = toa;
+    results.(fnames{i}).daysSinceStart = ...
+        date ...
+        - artoa.convert.dmy2rd( ...
+            pSoundsources.(fnames{i}).begemis(3), ...
+            pSoundsources.(fnames{i}).begemis(2), ...
+            pSoundsources.(fnames{i}).begemis(1) ...
+        );
+    tmpDistances = [];
+    % calculate distances
+    for oDistance = 1:length(toa)
+        if isnan(toa(oDistance)) | any(isnan(satPositions(oDistance, :)))
+            tmpDistances = [tmpDistances; NaN];
+            continue;
+        end
+        tmpDistances = [ ...
+            tmpDistances; ...
+            artoa.data.calculateGeodist( ...
+                satPositions(oDistance, :), ...
+                pSoundsources.(fnames{i}).position ...
+            ) ...
+        ];
+    end
+    results.(fnames{i}).satDistances = tmpDistances;
+    clear tmpDistances;
+end
+
+%% Construct matrices A and B
+
+rowCount = length(satDates) * length(fnames);
+
+aCore = zeros(rowCount, 2 * length(fnames));
+b = zeros(rowCount, 1);
+distances = zeros(rowCount, 1);
+daysSinceFloatStart = NaN(rowCount, 1);
+soundVelocity = NaN(rowCount, 1);
+soundVelocity(:) = artoa.data.calculateSoundVelocity( ...
+    pAppliedTemperature, ...
+    pAppliedPressure, ...
+    pSoundspeedMethod ...
+);
+
+for i = 1:length(fnames)
+    rowIndices = ((i - 1) * length(satDates) + 1):i * length(satDates);
+    startColIndex = (2 * (i - 1)) + 1;
+    distances(rowIndices, 1) = results.(fnames{i}).satDistances;
+    aCore(rowIndices, startColIndex) = 1;
+    aCore(rowIndices, startColIndex + 1) = results.(fnames{i}).daysSinceStart;
+    b(rowIndices, 1) = results.(fnames{i}).satToa;
+    daysSinceFloatStart(rowIndices, 1) = ...
+        results.(fnames{i}).satDate ...
+        - artoa.convert.dmy2rd( ...
+            floatDetails.launchtime(3), ...
+            floatDetails.launchtime(2), ...
+            floatDetails.launchtime(1) ...
+        );
+end
+
+a = [ ...
+    soundVelocity, distances, aCore, ones(size(daysSinceFloatStart)), daysSinceFloatStart ...
+];
+
+% remove all NaN from matrix
+indicesToUse = all(~isnan(a), 2) & all(aCore >= 0, 2);
+
+%x = a(indicesToUse, :) * flipud(b(indicesToUse, :));
+
+x = pinv(a(indicesToUse, :)) * b(indicesToUse, :);
+
+
+
+
+end
+