Repository: NREL/OpenStudio (language: python)

Issue:
Wrap the E+ Table:Lookup, Table:IndependentVariableList, and Table:IndependentVariable objects.
Enhancement Request

Coming from @asparke2 from an email request. 

Wrap the E+ Table:Lookup, Table:IndependentVariableList, and Table:IndependentVariable objects. OS has the previous E+ incarnation of the LookupTable objects wrapped, but I believe those were replaced with these new ones. This new format is what Eric Bonnema used to represent the Carrier HP performance.



Wrap the E+ Table:Lookup, Table:IndependentVariableList, and Table:IndependentVariable objects.
Enhancement Request

Coming from @asparke2 from an email request. 

Wrap the E+ Table:Lookup, Table:IndependentVariableList, and Table:IndependentVariable objects. OS has the previous E+ incarnation of the LookupTable objects wrapped, but I believe those were replaced with these new ones. This new format is what Eric Bonnema used to represent the Carrier HP performance.





Discussion / hints:
Looks like these three objects would replace `Table:MultiVariableLookup` (and `Table:MultiVariableLookupPoint`). Should we:
* add the new objects and deprecate the old objects, or
* add the new objects and _not_ deprecate the old objects, or
* add the new objects and remove the old objects

?

We'd need VT if we remove the old objects.
It appears that the old objects no longer exist in EnergyPlus and have been replaced by the newer ones. If this is true, my vote is to deprecate the old objects, add the new ones, and have a version translator from old to new.
Looks like these three objects would replace `Table:MultiVariableLookup` (and `Table:MultiVariableLookupPoint`). Should we:
* add the new objects and deprecate the old objects, or
* add the new objects and _not_ deprecate the old objects, or
* add the new objects and remove the old objects

?

We'd need VT if we remove the old objects.
It appears that the old objects no longer exist in EnergyPlus and have been replaced by the newer ones. If this is true, my vote is to deprecate the old objects, add the new ones, and have a version translator from old to new.

Fix patch:
diff --git a/resources/model/OpenStudio.idd b/resources/model/OpenStudio.idd
index 20c25148ad7..ce242a1c7bd 100644
--- a/resources/model/OpenStudio.idd
+++ b/resources/model/OpenStudio.idd
@@ -21052,6 +21052,119 @@ OS:Table:MultiVariableLookup,
        \type real
        \begin-extensible
 
+OS:Table:IndependentVariable,
+       \memo An independent variable representing a single dimension of a Table:Lookup
+       \memo object.
+       \extensible:1
+  A1,  \field Handle
+       \type handle
+       \required-field
+  A2,  \field Name
+       \required-field
+       \reference IndependentVariableName
+       \type alpha
+  A3,  \field Interpolation Method
+       \type choice
+       \key Linear
+       \key Cubic
+       \required-field
+  A4,  \field Extrapolation Method
+       \type choice
+       \key Constant
+       \key Linear
+       \required-field
+  N1,  \field Minimum Value
+       \type real
+       \unitsBasedOnField A5
+  N2,  \field Maximum Value
+       \type real
+       \unitsBasedOnField A5
+  N3,  \field Normalization Reference Value
+       \type real
+       \unitsBasedOnField A5
+  A5,  \field Unit Type
+       \type choice
+       \key Dimensionless
+       \key Temperature
+       \key VolumetricFlow
+       \key MassFlow
+       \key Power
+       \key Distance
+       \key Angle
+       \required-field
+  A6,  \field External File Name
+       \type object-list
+       \object-list ExternalFileNames
+  N4,  \field External File Column Number
+       \type integer
+       \minimum 1
+  N5,  \field External File Starting Row Number
+       \type integer
+       \minimum 1
+  N6;  \field Value
+       \type real
+       \begin-extensible
+
+OS:Table:Lookup,
+       \memo Lookup tables are used in place of curves and can represent any number
+       \memo of independent variables (defined as Table:IndependentVariable objects
+       \memo in a Table:IndependentVariableList). Output values are interpolated
+       \memo within the bounds defined by each independent variable and extrapolated
+       \memo beyond the bounds according to the interpolation/extrapolation methods
+       \memo defined by each independent variable.
+       \extensible:1
+  A1,  \field Handle
+       \type handle
+       \required-field
+  A2,  \field Name
+       \required-field
+       \reference UnivariateFunctions
+       \reference BivariateFunctions
+       \reference TrivariateFunctions
+       \reference QuadvariateFunctions
+       \reference QuintvariateFunctions
+       \reference MultivariateFunctions
+       \reference AllCurves
+       \type alpha
+  A3,  \field Independent Variable List Name
+       \required-field
+       \type object-list
+       \object-list ModelObjectLists
+  A4,  \field Normalization Method
+       \type choice
+       \key None
+       \key DivisorOnly
+       \key AutomaticWithDivisor
+       \required-field
+  N1,  \field Normalization Divisor
+       \type real
+       \required-field
+  N2,  \field Minimum Output
+       \type real
+       \unitsBasedOnField A5
+  N3,  \field Maximum Output
+       \type real
+       \unitsBasedOnField A5
+  A5,  \field Output Unit Type
+       \type choice
+       \key Dimensionless
+       \key Capacity
+       \key Power
+       \required-field
+  A6,  \field External File Name
+       \type object-list
+       \object-list ExternalFileNames
+  N4,  \field External File Column Number
+       \type integer
+       \minimum 1
+  N5,  \field External File Starting Row Number
+       \type integer
+       \minimum 1
+  N6;  \field Output Value
+       \type real
+       \unitsBasedOnField A5
+       \begin-extensible
+
 OS:DesignSpecification:OutdoorAir,
        \memo This object is used to describe general outdoor air requirements which
        \memo are referenced by other objects.
diff --git a/src/energyplus/CMakeLists.txt b/src/energyplus/CMakeLists.txt
index 0e55c1ec154..54fa411346c 100644
--- a/src/energyplus/CMakeLists.txt
+++ b/src/energyplus/CMakeLists.txt
@@ -390,6 +390,8 @@ set(${target_name}_src
   ForwardTranslator/ForwardTranslateSurfacePropertySurroundingSurfaces.cpp
   ForwardTranslator/ForwardTranslateSwimmingPoolIndoor.cpp
   ForwardTranslator/ForwardTranslateTableMultiVariableLookup.cpp
+  ForwardTranslator/ForwardTranslateTableLookup.cpp
+  ForwardTranslator/ForwardTranslateTableIndependentVariable.cpp
   ForwardTranslator/ForwardTranslateTemperingValve.cpp
   ForwardTranslator/ForwardTranslateThermalStorageIceDetailed.cpp
   ForwardTranslator/ForwardTranslateThermalStorageChilledWaterStratified.cpp
@@ -587,6 +589,8 @@ set(${target_name}_src
   ReverseTranslator/ReverseTranslateSurfacePropertyLocalEnvironment.cpp
   ReverseTranslator/ReverseTranslateSurfacePropertySurroundingSurfaces.cpp
   ReverseTranslator/ReverseTranslateSwimmingPoolIndoor.cpp
+  ReverseTranslator/ReverseTranslateTableLookup.cpp
+  ReverseTranslator/ReverseTranslateTableIndependentVariable.cpp
   ReverseTranslator/ReverseTranslateThermostatSetpointDualSetpoint.cpp
   ReverseTranslator/ReverseTranslateTimestep.cpp
   ReverseTranslator/ReverseTranslateVersion.cpp
@@ -759,6 +763,7 @@ set(${target_name}_test_src
   Test/SurfacePropertySurroundingSurfaces_GTest.cpp
   Test/SwimmingPoolIndoor_GTest.cpp
   Test/TableMultiVariableLookup_GTest.cpp
+  Test/TableLookup_GTest.cpp
   Test/ThermalStorageChilledWaterStratified_GTest.cpp
   Test/ThermalZone_GTest.cpp
   Test/ThermostatSetpointDualSetpoint_GTest.cpp
diff --git a/src/energyplus/ForwardTranslator.cpp b/src/energyplus/ForwardTranslator.cpp
index d7d38b74fc8..d82916c1405 100644
--- a/src/energyplus/ForwardTranslator.cpp
+++ b/src/energyplus/ForwardTranslator.cpp
@@ -100,6 +100,8 @@
 
 #include "../utilities/idd/IddEnums.hpp"
 
+#include "../utilities/core/Deprecated.hpp"
+
 #include <algorithm>
 #include <sstream>
 #include <thread>
@@ -2880,11 +2882,35 @@ namespace energyplus {
         retVal = translateSwimmingPoolIndoor(obj);
         break;
       }
+
+#if defined(_MSC_VER)
+#  pragma warning(push)
+#  pragma warning(disable : 4996)
+#elif (defined(__GNUC__))
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
       case openstudio::IddObjectType::OS_Table_MultiVariableLookup: {
         model::TableMultiVariableLookup table = modelObject.cast<TableMultiVariableLookup>();
         retVal = translateTableMultiVariableLookup(table);
         break;
       }
+#if defined(_MSC_VER)
+#  pragma warning(pop)
+#elif (defined(__GNUC__))
+#  pragma GCC diagnostic pop
+#endif
+
+      case openstudio::IddObjectType::OS_Table_Lookup: {
+        auto table = modelObject.cast<TableLookup>();
+        retVal = translateTableLookup(table);
+        break;
+      }
+      case openstudio::IddObjectType::OS_Table_IndependentVariable: {
+        auto tableIndependentVariable = modelObject.cast<TableIndependentVariable>();
+        retVal = translateTableIndependentVariable(tableIndependentVariable);
+        break;
+      }
       case openstudio::IddObjectType::OS_TemperingValve: {
         auto mo = modelObject.cast<TemperingValve>();
         retVal = translateTemperingValve(mo);
@@ -3414,6 +3440,7 @@ namespace energyplus {
     result.push_back(IddObjectType::OS_Curve_Sigmoid);
     result.push_back(IddObjectType::OS_Curve_Triquadratic);
     result.push_back(IddObjectType::OS_Table_MultiVariableLookup);
+    result.push_back(IddObjectType::OS_Table_Lookup);
     result.push_back(IddObjectType::OS_DistrictCooling);
     result.push_back(IddObjectType::OS_DistrictHeating);
     result.push_back(IddObjectType::OS_EvaporativeCooler_Direct_ResearchSpecial);
diff --git a/src/energyplus/ForwardTranslator.hpp b/src/energyplus/ForwardTranslator.hpp
index 1a441361fa9..c3b73cbb7d6 100644
--- a/src/energyplus/ForwardTranslator.hpp
+++ b/src/energyplus/ForwardTranslator.hpp
@@ -39,6 +39,8 @@
 #include "../utilities/core/StringStreamLogSink.hpp"
 #include "../utilities/time/Time.hpp"
 
+#include "../utilities/core/Deprecated.hpp"
+
 #include <iostream>
 
 namespace openstudio {
@@ -448,6 +450,8 @@ namespace model {
   class SurfacePropertySurroundingSurfaces;
   class SwimmingPoolIndoor;
   class TableMultiVariableLookup;
+  class TableLookup;
+  class TableIndependentVariable;
   class TemperingValve;
   class ThermalZone;
   class ThermalStorageIceDetailed;
@@ -1427,7 +1431,11 @@ namespace energyplus {
 
     boost::optional<IdfObject> translateSwimmingPoolIndoor(model::SwimmingPoolIndoor& modelObject);
 
-    boost::optional<IdfObject> translateTableMultiVariableLookup(model::TableMultiVariableLookup& modelObject);
+    OS_DEPRECATED boost::optional<IdfObject> translateTableMultiVariableLookup(model::TableMultiVariableLookup& modelObject);
+
+    boost::optional<IdfObject> translateTableLookup(model::TableLookup& modelObject);
+
+    boost::optional<IdfObject> translateTableIndependentVariable(model::TableIndependentVariable& modelObject);
 
     boost::optional<IdfObject> translateTemperingValve(model::TemperingValve& modelObject);
 
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateTableIndependentVariable.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateTableIndependentVariable.cpp
new file mode 100644
index 00000000000..d5bad248677
--- /dev/null
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateTableIndependentVariable.cpp
@@ -0,0 +1,93 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include "../ForwardTranslator.hpp"
+#include "../../model/Model.hpp"
+
+#include "../../model/TableIndependentVariable.hpp"
+
+#include "../../utilities/idf/IdfExtensibleGroup.hpp"
+
+#include <utilities/idd/Table_IndependentVariable_FieldEnums.hxx>
+// #include "../../utilities/idd/IddEnums.hpp"
+#include <utilities/idd/IddEnums.hxx>
+
+using namespace openstudio::model;
+
+namespace openstudio {
+
+namespace energyplus {
+
+  boost::optional<IdfObject> ForwardTranslator::translateTableIndependentVariable(model::TableIndependentVariable& modelObject) {
+    boost::optional<IdfObject> result;
+
+    // Instantiate an IdfObject of the class to store the values
+    IdfObject idfObject = createRegisterAndNameIdfObject(openstudio::IddObjectType::Table_IndependentVariable, modelObject);
+
+    // Interpolation Method: String
+    idfObject.setString(Table_IndependentVariableFields::InterpolationMethod, modelObject.interpolationMethod());
+
+    // Extrapolation Method: String
+    idfObject.setString(Table_IndependentVariableFields::ExtrapolationMethod, modelObject.extrapolationMethod());
+
+    // Minimum Value: boost::optional<double>
+    if (boost::optional<double> minimumValue_ = modelObject.minimumValue()) {
+      idfObject.setDouble(Table_IndependentVariableFields::MinimumValue, minimumValue_.get());
+    }
+
+    // Maximum Value: boost::optional<double>
+    if (boost::optional<double> maximumValue_ = modelObject.maximumValue()) {
+      idfObject.setDouble(Table_IndependentVariableFields::MaximumValue, maximumValue_.get());
+    }
+
+    // Normalization Reference Value: boost::optional<double>
+    if (boost::optional<double> normalizationReferenceValue_ = modelObject.normalizationReferenceValue()) {
+      idfObject.setDouble(Table_IndependentVariableFields::NormalizationReferenceValue, normalizationReferenceValue_.get());
+    }
+
+    // Unit Type: String
+    idfObject.setString(Table_IndependentVariableFields::UnitType, modelObject.unitType());
+
+    // Value
+    for (const double& value : modelObject.values()) {
+      IdfExtensibleGroup eg = idfObject.pushExtensibleGroup();
+      eg.setDouble(Table_IndependentVariableExtensibleFields::Value, value);
+    }
+
+    // External File Name: boost::optional<std::string>
+
+    // External File Column Number: boost::optional<int>
+
+    // External File Starting Row Number: boost::optional<int>
+
+    return idfObject;
+  }  // End of translate function
+
+}  // end namespace energyplus
+}  // end namespace openstudio
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateTableLookup.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateTableLookup.cpp
new file mode 100644
index 00000000000..7676f23091e
--- /dev/null
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateTableLookup.cpp
@@ -0,0 +1,129 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include "../ForwardTranslator.hpp"
+#include "../../model/Model.hpp"
+#include "../../model/TableLookup.hpp"
+#include "../../model/TableLookup_Impl.hpp"
+#include "../../model/TableIndependentVariable.hpp"
+
+#include "../../utilities/idf/IdfExtensibleGroup.hpp"
+#include "../../utilities/idf/Workspace.hpp"
+#include "../../utilities/idf/WorkspaceObjectOrder.hpp"
+#include "../../utilities/core/Logger.hpp"
+
+#include <utilities/idd/Table_Lookup_FieldEnums.hxx>
+#include <utilities/idd/Table_IndependentVariableList_FieldEnums.hxx>
+#include <utilities/idd/Table_IndependentVariable_FieldEnums.hxx>
+#include <utilities/idd/IddEnums.hxx>
+
+#include <algorithm>  // For sort, unique
+#include <iterator>   // For distance
+
+using namespace openstudio::model;
+
+namespace openstudio {
+
+namespace energyplus {
+
+  boost::optional<IdfObject> ForwardTranslator::translateTableLookup(TableLookup& modelObject) {
+
+    std::vector<TableIndependentVariable> independentVariables = modelObject.independentVariables();
+
+    // If the TableLookup doesn't have at least one TableIndependentVariable, then it shouldn't be translated
+    if (independentVariables.empty()) {
+      LOG(Warn, "TableLookup " << modelObject.name().get() << " has no TableIndependentVariables, it will not be translated");
+      return boost::none;
+    }
+
+    unsigned independentVariableGridSize = 1;
+    std::vector<double> outputValues = modelObject.outputValues();
+    unsigned outValueSize = outputValues.size();
+    for (const auto& independentVariable : independentVariables) {
+      auto v = independentVariable.values();
+      std::sort(v.begin(), v.end());
+      unsigned uniqueCount = std::distance(v.begin(), std::unique(v.begin(), v.end()));
+      independentVariableGridSize *= uniqueCount;
+    }
+
+    if (independentVariableGridSize != outValueSize) {
+      LOG(Warn, "Not translating " << modelObject.briefDescription() << ", it has a mismatch between number of outputValues (=" << outValueSize
+                                   << ") and the grid size of the TableIndependentVariable(s) (=" << independentVariableGridSize << ").");
+      return boost::none;
+    }
+
+    // Table:Lookup
+    IdfObject idfObject = createRegisterAndNameIdfObject(openstudio::IddObjectType::Table_Lookup, modelObject);
+
+    // Normalization Method: String
+    idfObject.setString(Table_LookupFields::NormalizationMethod, modelObject.normalizationMethod());
+
+    // Normalization Divisor: Double
+    idfObject.setDouble(Table_LookupFields::NormalizationDivisor, modelObject.normalizationDivisor());
+
+    // Minimum Output: boost::optional<double>
+    if (boost::optional<double> minimumOutput_ = modelObject.minimumOutput()) {
+      idfObject.setDouble(Table_LookupFields::MinimumOutput, minimumOutput_.get());
+    }
+
+    // Maximum Output: boost::optional<double>
+    if (boost::optional<double> maximumOutput_ = modelObject.maximumOutput()) {
+      idfObject.setDouble(Table_LookupFields::MaximumOutput, maximumOutput_.get());
+    }
+
+    // Output Unit Type: String
+    idfObject.setString(Table_LookupFields::OutputUnitType, modelObject.outputUnitType());
+
+    // OutputValue
+    for (const double& outputValue : outputValues) {
+      IdfExtensibleGroup eg = idfObject.pushExtensibleGroup();
+      eg.setDouble(Table_LookupExtensibleFields::OutputValue, outputValue);
+    }
+
+    // Table:IndependentVariableList
+    IdfObject tableIndependentVariableList(IddObjectType::Table_IndependentVariableList);
+    tableIndependentVariableList.setName(modelObject.nameString() + " Independent Variable List");
+    m_idfObjects.push_back(tableIndependentVariableList);
+
+    // IndependentVariableListName
+    idfObject.setString(Table_LookupFields::IndependentVariableListName, tableIndependentVariableList.nameString());
+
+    // Table:IndependentVariable
+    for (auto& independentVariable : independentVariables) {
+      if (auto tableIndependentVariable_ = translateAndMapModelObject(independentVariable)) {
+        IdfExtensibleGroup independentVariableGroup = tableIndependentVariableList.pushExtensibleGroup({tableIndependentVariable_->nameString()});
+      }
+    }
+
+    return idfObject;
+  }
+
+}  // namespace energyplus
+
+}  // namespace openstudio
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateTableMultiVariableLookup.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateTableMultiVariableLookup.cpp
index e5acad2ec58..108de5b007c 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateTableMultiVariableLookup.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateTableMultiVariableLookup.cpp
@@ -40,12 +40,22 @@
 #include <utilities/idd/Table_IndependentVariable_FieldEnums.hxx>
 #include <utilities/idd/IddEnums.hxx>
 
+#include "../../utilities/core/Deprecated.hpp"
+
 using namespace openstudio::model;
 
 namespace openstudio {
 
 namespace energyplus {
 
+#if defined(_MSC_VER)
+#  pragma warning(push)
+#  pragma warning(disable : 4996)
+#elif (defined(__GNUC__))
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
   boost::optional<IdfObject> ForwardTranslator::translateTableMultiVariableLookup(TableMultiVariableLookup& modelObject) {
     OptionalString s;
     OptionalDouble d;
@@ -155,6 +165,9 @@ namespace energyplus {
     if ((d = modelObject.normalizationReference())) {
       tableLookup.setString(Table_LookupFields::NormalizationMethod, "DivisorOnly");
       tableLookup.setDouble(Table_LookupFields::NormalizationDivisor, d.get());
+    } else {
+      tableLookup.setString(Table_LookupFields::NormalizationMethod, "None");
+      tableLookup.setDouble(Table_LookupFields::NormalizationDivisor, 1.0);
     }
 
     // MinimumTableOutput
@@ -184,6 +197,12 @@ namespace energyplus {
     return tableLookup;
   }
 
+#if defined(_MSC_VER)
+#  pragma warning(pop)
+#elif (defined(__GNUC__))
+#  pragma GCC diagnostic pop
+#endif
+
 }  // namespace energyplus
 
 }  // namespace openstudio
diff --git a/src/energyplus/ReverseTranslator.cpp b/src/energyplus/ReverseTranslator.cpp
index b93398c8ffd..4c52b3c7415 100644
--- a/src/energyplus/ReverseTranslator.cpp
+++ b/src/energyplus/ReverseTranslator.cpp
@@ -909,6 +909,18 @@ namespace energyplus {
         //modelObject = translateSwimmingPoolIndoor(workspaceObject);
         //break;
         //}
+
+      case openstudio::IddObjectType::Table_Lookup: {
+        modelObject = translateTableLookup(workspaceObject);
+        break;
+      }
+      case openstudio::IddObjectType::Table_IndependentVariableList: {
+        break;  // Done via translateTableLookup
+      }
+      case openstudio::IddObjectType::Table_IndependentVariable: {
+        break;  // Done via translateTableLookup
+      }
+
       case openstudio::IddObjectType::ThermostatSetpoint_DualSetpoint: {
         modelObject = translateThermostatSetpointDualSetpoint(workspaceObject);
         break;
diff --git a/src/energyplus/ReverseTranslator.hpp b/src/energyplus/ReverseTranslator.hpp
index 58d389a1641..afde8e55708 100644
--- a/src/energyplus/ReverseTranslator.hpp
+++ b/src/energyplus/ReverseTranslator.hpp
@@ -365,6 +365,10 @@ namespace energyplus {
 
     boost::optional<model::ModelObject> translateSwimmingPoolIndoor(const WorkspaceObject& workspaceObject);
 
+    boost::optional<model::ModelObject> translateTableLookup(const WorkspaceObject& workspaceObject);
+
+    boost::optional<model::ModelObject> translateTableIndependentVariable(const WorkspaceObject& workspaceObject);
+
     boost::optional<model::ModelObject> translateThermostatSetpointDualSetpoint(const WorkspaceObject& workspaceObject);
 
     boost::optional<model::ModelObject> translateTimestep(const WorkspaceObject& workspaceObject);
diff --git a/src/energyplus/ReverseTranslator/ReverseTranslateTableIndependentVariable.cpp b/src/energyplus/ReverseTranslator/ReverseTranslateTableIndependentVariable.cpp
new file mode 100644
index 00000000000..8e3cc0e4e0a
--- /dev/null
+++ b/src/energyplus/ReverseTranslator/ReverseTranslateTableIndependentVariable.cpp
@@ -0,0 +1,115 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include "../ReverseTranslator.hpp"
+
+#include "../../model/TableIndependentVariable.hpp"
+
+#include "../../utilities/idf/IdfExtensibleGroup.hpp"
+
+#include <utilities/idd/Table_IndependentVariable_FieldEnums.hxx>
+#include <utilities/idd/IddEnums.hxx>
+
+using namespace openstudio::model;
+
+namespace openstudio {
+
+namespace energyplus {
+
+  boost::optional<ModelObject> ReverseTranslator::translateTableIndependentVariable(const WorkspaceObject& workspaceObject) {
+
+    // External File Name: Optional String
+    if (boost::optional<std::string> _externalFileName = workspaceObject.getString(Table_IndependentVariableFields::ExternalFileName, false, true)) {
+      LOG(Error, workspaceObject.briefDescription() << " is referencing an External File, which is not supported");
+      return boost::none;
+    }
+
+    // Instantiate an object of the class to store the values,
+    openstudio::model::TableIndependentVariable modelObject(m_model);
+
+    // Name
+    if (boost::optional<std::string> _name = workspaceObject.name()) {
+      modelObject.setName(_name.get());
+    }
+
+    // Interpolation Method: Optional String
+    if (boost::optional<std::string> _interpolationMethod =
+          workspaceObject.getString(Table_IndependentVariableFields::InterpolationMethod, false, true)) {
+      modelObject.setInterpolationMethod(_interpolationMethod.get());
+    }
+
+    // Extrapolation Method: Optional String
+    if (boost::optional<std::string> _extrapolationMethod =
+          workspaceObject.getString(Table_IndependentVariableFields::ExtrapolationMethod, false, true)) {
+      modelObject.setExtrapolationMethod(_extrapolationMethod.get());
+    }
+
+    // Minimum Value: Optional Double
+    if (boost::optional<double> _minimumValue = workspaceObject.getDouble(Table_IndependentVariableFields::MinimumValue)) {
+      modelObject.setMinimumValue(_minimumValue.get());
+    }
+
+    // Maximum Value: Optional Double
+    if (boost::optional<double> _maximumValue = workspaceObject.getDouble(Table_IndependentVariableFields::MaximumValue)) {
+      modelObject.setMaximumValue(_maximumValue.get());
+    }
+
+    // Normalization Reference Value: Optional Double
+    if (boost::optional<double> _normalizationReferenceValue =
+          workspaceObject.getDouble(Table_IndependentVariableFields::NormalizationReferenceValue)) {
+      modelObject.setNormalizationReferenceValue(_normalizationReferenceValue.get());
+    }
+
+    // Unit Type: Optional String
+    if (boost::optional<std::string> _unitType = workspaceObject.getString(Table_IndependentVariableFields::UnitType, false, true)) {
+      modelObject.setUnitType(_unitType.get());
+    }
+
+    // // External File Column Number: Optional Integer
+    // if (boost::optional<int> _externalFileColumnNumber = workspaceObject.getInt(Table_IndependentVariableFields::ExternalFileColumnNumber)) {
+    //   modelObject.setExternalFileColumnNumber(_externalFileColumnNumber.get());
+    // }
+
+    // // External File Starting Row Number: Optional Integer
+    // if (boost::optional<int> _externalFileStartingRowNumber = workspaceObject.getInt(Table_IndependentVariableFields::ExternalFileStartingRowNumber)) {
+    //   modelObject.setExternalFileStartingRowNumber(_externalFileStartingRowNumber.get());
+    // }
+
+    // Extensible Values
+    for (const IdfExtensibleGroup& eg : workspaceObject.extensibleGroups()) {
+      if (boost::optional<double> value = eg.getDouble(Table_IndependentVariableExtensibleFields::Value)) {
+        modelObject.addValue(value.get());
+      }
+    }
+
+    return modelObject;
+  }  // End of translate function
+
+}  // end namespace energyplus
+}  // end namespace openstudio
diff --git a/src/energyplus/ReverseTranslator/ReverseTranslateTableLookup.cpp b/src/energyplus/ReverseTranslator/ReverseTranslateTableLookup.cpp
new file mode 100644
index 00000000000..5c46d7506f6
--- /dev/null
+++ b/src/energyplus/ReverseTranslator/ReverseTranslateTableLookup.cpp
@@ -0,0 +1,143 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include "../ReverseTranslator.hpp"
+
+#include "../../model/TableLookup.hpp"
+
+#include "../../model/TableIndependentVariable.hpp"
+#include "../../model/TableIndependentVariable_Impl.hpp"
+
+#include "../../utilities/idf/IdfExtensibleGroup.hpp"
+#include "../../utilities/idf/WorkspaceExtensibleGroup.hpp"
+
+#include <utilities/idd/Table_Lookup_FieldEnums.hxx>
+#include <utilities/idd/Table_IndependentVariableList_FieldEnums.hxx>
+
+#include <utilities/idd/IddEnums.hxx>
+
+using namespace openstudio::model;
+
+namespace openstudio {
+
+namespace energyplus {
+
+  boost::optional<ModelObject> ReverseTranslator::translateTableLookup(const WorkspaceObject& workspaceObject) {
+
+    // External File Name: Optional String
+    if (boost::optional<std::string> _externalFileName = workspaceObject.getString(Table_LookupFields::ExternalFileName, false, true)) {
+      LOG(Error, workspaceObject.briefDescription()
+                   << " is referencing an External File '" << _externalFileName.get() << "', which is not supported.");
+      return boost::none;
+    }
+
+    // Instantiate an object of the class to store the values,
+    openstudio::model::TableLookup modelObject(m_model);
+
+    // Name
+    if (boost::optional<std::string> _name = workspaceObject.name()) {
+      modelObject.setName(_name.get());
+    }
+
+    // Independent Variable List Name: Required Object
+    if (boost::optional<WorkspaceObject> varList_ = workspaceObject.getTarget(Table_LookupFields::IndependentVariableListName)) {
+
+      for (unsigned i = 0; const IdfExtensibleGroup& idfGroup : varList_->extensibleGroups()) {
+        auto weg = idfGroup.cast<WorkspaceExtensibleGroup>();
+        if (boost::optional<WorkspaceObject> var_ = weg.getTarget(Table_IndependentVariableListExtensibleFields::IndependentVariableName)) {
+
+          if (auto mo_ = translateTableIndependentVariable(var_.get())) {
+            if (auto v_ = mo_->optionalCast<openstudio::model::TableIndependentVariable>()) {
+              modelObject.addIndependentVariable(v_.get());
+            } else {
+              LOG(Warn, "For " << workspaceObject.briefDescription() << ", in the Table:IndependentVariableList named '" << varList_->nameString()
+                               << "', the extensible group " << i << " has a wrong type for 'Independent Variable', skipping.");
+            }
+          }
+
+        } else {
+          LOG(Error, "For " << workspaceObject.briefDescription() << ", in the Table:IndependentVariableList named '" << varList_->nameString()
+                            << "', the extensible group " << i << " does not refer to a Table:IndependentVariable, skipping.");
+        }
+
+        ++i;
+      }
+
+    } else {
+      LOG(Error, "For " << workspaceObject.briefDescription() << ", cannot find required object 'Independent Variable List Name'");
+      return boost::none;
+    }
+
+    // Normalization Method: Optional String
+    if (boost::optional<std::string> _normalizationMethod = workspaceObject.getString(Table_LookupFields::NormalizationMethod, false, true)) {
+      modelObject.setNormalizationMethod(_normalizationMethod.get());
+    }
+
+    // Normalization Divisor: Optional Double
+    if (boost::optional<double> _normalizationDivisor = workspaceObject.getDouble(Table_LookupFields::NormalizationDivisor)) {
+      modelObject.setNormalizationDivisor(_normalizationDivisor.get());
+    }
+
+    // Minimum Output: Optional Double
+    if (boost::optional<double> _minimumOutput = workspaceObject.getDouble(Table_LookupFields::MinimumOutput)) {
+      modelObject.setMinimumOutput(_minimumOutput.get());
+    }
+
+    // Maximum Output: Optional Double
+    if (boost::optional<double> _maximumOutput = workspaceObject.getDouble(Table_LookupFields::MaximumOutput)) {
+      modelObject.setMaximumOutput(_maximumOutput.get());
+    }
+
+    // Output Unit Type: Optional String
+    if (boost::optional<std::string> _outputUnitType = workspaceObject.getString(Table_LookupFields::OutputUnitType, false, true)) {
+      modelObject.setOutputUnitType(_outputUnitType.get());
+    }
+
+    // // External File Column Number: Optional Integer
+    // if (boost::optional<int> _externalFileColumnNumber = workspaceObject.getInt(Table_LookupFields::ExternalFileColumnNumber)) {
+    //   modelObject.setExternalFileColumnNumber(_externalFileColumnNumber.get());
+    // }
+
+    // // External File Starting Row Number: Optional Integer
+    // if (boost::optional<int> _externalFileStartingRowNumber = workspaceObject.getInt(Table_LookupFields::ExternalFileStartingRowNumber)) {
+    //   modelObject.setExternalFileStartingRowNumber(_externalFileStartingRowNumber.get());
+    // }
+
+    // Extensible Output Values
+    for (const IdfExtensibleGroup& eg : workspaceObject.extensibleGroups()) {
+      if (boost::optional<double> value = eg.getDouble(Table_LookupExtensibleFields::OutputValue)) {
+        modelObject.addOutputValue(value.get());
+      }
+    }
+
+    return modelObject;
+  }  // End of translate function
+
+}  // end namespace energyplus
+}  // end namespace openstudio
diff --git a/src/energyplus/Test/TableLookup_GTest.cpp b/src/energyplus/Test/TableLookup_GTest.cpp
new file mode 100644
index 00000000000..ff4aa864d21
--- /dev/null
+++ b/src/energyplus/Test/TableLookup_GTest.cpp
@@ -0,0 +1,528 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include <gtest/gtest.h>
+#include "EnergyPlusFixture.hpp"
+
+#include "../ForwardTranslator.hpp"
+#include "../ReverseTranslator.hpp"
+
+#include "../../model/Model.hpp"
+#include "../../model/TableLookup.hpp"
+#include "../../model/TableLookup_Impl.hpp"
+#include "../../model/TableIndependentVariable.hpp"
+#include "../../model/TableIndependentVariable_Impl.hpp"
+
+#include "../../utilities/idf/IdfFile.hpp"
+#include "../../utilities/idf/Workspace.hpp"
+#include "../../utilities/idf/IdfObject.hpp"
+#include "../../utilities/idf/WorkspaceObject.hpp"
+#include "../../utilities/idf/IdfExtensibleGroup.hpp"
+#include "../../utilities/idf/WorkspaceExtensibleGroup.hpp"
+
+#include <utilities/idd/Table_Lookup_FieldEnums.hxx>
+#include <utilities/idd/Table_IndependentVariableList_FieldEnums.hxx>
+#include <utilities/idd/Table_IndependentVariable_FieldEnums.hxx>
+#include <utilities/idd/IddEnums.hxx>
+#include <utilities/idd/IddFactory.hxx>
+
+#include <resources.hxx>
+#include <sstream>
+
+using namespace openstudio::energyplus;
+using namespace openstudio::model;
+using namespace openstudio;
+
+TEST_F(EnergyPlusFixture, ForwardTranslator_TableLookup_Basic) {
+  Model m;
+  TableLookup tableLookup(m);
+  tableLookup.addOutputValue(0.1);
+  tableLookup.addOutputValue(0.3);
+  tableLookup.addOutputValue(0.5);
+  tableLookup.addOutputValue(0.7);
+  tableLookup.addOutputValue(0.9);
+
+  TableIndependentVariable independentVariable(m);
+  independentVariable.setInterpolationMethod("Cubic");
+  independentVariable.setExtrapolationMethod("Linear");
+  independentVariable.setMinimumValue(70);
+  independentVariable.setMaximumValue(78);
+  independentVariable.addValue(70);
+  independentVariable.addValue(72);
+  independentVariable.addValue(74);
+  independentVariable.addValue(76);
+  independentVariable.addValue(78);
+  tableLookup.addIndependentVariable(independentVariable);
+
+  ForwardTranslator ft;
+  Workspace workspace = ft.translateModel(m);
+
+  std::vector<WorkspaceObject> tableObjects = workspace.getObjectsByType(IddObjectType::Table_Lookup);
+  ASSERT_EQ(1u, tableObjects.size());
+
+  WorkspaceObject idfTable = tableObjects.front();
+
+  ASSERT_EQ(idfTable.getString(Table_LookupFields::OutputUnitType).get(), "Dimensionless");
+  ASSERT_EQ(5u, idfTable.numExtensibleGroups());
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(0).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 0.1);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(1).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 0.3);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(2).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 0.5);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(3).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 0.7);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(4).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 0.9);
+  ASSERT_EQ(idfTable.getString(Table_LookupFields::NormalizationMethod).get(), "None");
+  ASSERT_EQ(idfTable.getDouble(Table_LookupFields::NormalizationDivisor).get(), 1.0);
+
+  std::vector<WorkspaceObject> independentVariableObjects = workspace.getObjectsByType(IddObjectType::Table_IndependentVariable);
+  ASSERT_EQ(1u, independentVariableObjects.size());
+
+  WorkspaceObject independentVariableObject = independentVariableObjects.front();
+  ASSERT_TRUE(independentVariableObject.getString(Table_IndependentVariableFields::InterpolationMethod));
+  EXPECT_EQ(independentVariableObject.getString(Table_IndependentVariableFields::InterpolationMethod).get(), "Cubic");
+  ASSERT_TRUE(independentVariableObject.getString(Table_IndependentVariableFields::ExtrapolationMethod));
+  EXPECT_EQ(independentVariableObject.getString(Table_IndependentVariableFields::ExtrapolationMethod).get(), "Linear");
+  ASSERT_TRUE(independentVariableObject.getDouble(Table_IndependentVariableFields::MinimumValue));
+  EXPECT_DOUBLE_EQ(independentVariableObject.getDouble(Table_IndependentVariableFields::MinimumValue).get(), 70.0);
+  ASSERT_TRUE(independentVariableObject.getDouble(Table_IndependentVariableFields::MaximumValue));
+  EXPECT_DOUBLE_EQ(independentVariableObject.getDouble(Table_IndependentVariableFields::MaximumValue).get(), 78.0);
+  EXPECT_FALSE(independentVariableObject.getDouble(Table_IndependentVariableFields::NormalizationReferenceValue));
+  ASSERT_TRUE(independentVariableObject.getString(Table_IndependentVariableFields::UnitType));
+  EXPECT_EQ(independentVariableObject.getString(Table_IndependentVariableFields::UnitType).get(), "Dimensionless");
+  ASSERT_EQ(5u, independentVariableObject.numExtensibleGroups());
+
+  std::vector<WorkspaceObject> independentVariableListObjects = workspace.getObjectsByType(IddObjectType::Table_IndependentVariableList);
+  ASSERT_EQ(1u, independentVariableListObjects.size());
+
+  WorkspaceObject independentVariableListObject = independentVariableListObjects.front();
+  ASSERT_EQ(1u, independentVariableListObject.numExtensibleGroups());
+  ASSERT_TRUE(independentVariableListObject.getExtensibleGroup(0).getString(Table_IndependentVariableListExtensibleFields::IndependentVariableName));
+  EXPECT_EQ(
+    independentVariableObject.nameString(),
+    independentVariableListObject.getExtensibleGroup(0).getString(Table_IndependentVariableListExtensibleFields::IndependentVariableName).get());
+}
+
+TEST_F(EnergyPlusFixture, ForwardTranslator_TableLookup_MistmatchedGridSize) {
+  Model m;
+  TableLookup tableLookup(m);
+  tableLookup.addOutputValue(0.1);
+  tableLookup.addOutputValue(0.3);
+  tableLookup.addOutputValue(0.5);
+  tableLookup.addOutputValue(0.7);
+  tableLookup.addOutputValue(0.9);
+
+  TableIndependentVariable independentVariable1(m);
+  independentVariable1.addValue(74);
+  independentVariable1.addValue(76);
+  tableLookup.addIndependentVariable(independentVariable1);
+
+  TableIndependentVariable independentVariable2(m);
+  independentVariable2.addValue(14);
+  independentVariable2.addValue(16);
+  tableLookup.addIndependentVariable(independentVariable2);
+
+  ForwardTranslator ft;
+  Workspace workspace = ft.translateModel(m);
+
+  EXPECT_EQ(0u, workspace.getObjectsByType(IddObjectType::Table_Lookup).size());
+  EXPECT_EQ(0u, workspace.getObjectsByType(IddObjectType::Table_IndependentVariable).size());
+
+  ASSERT_EQ(1, ft.warnings().size());
+  EXPECT_EQ("Not translating Object of type 'OS:Table:Lookup' and named 'Table Lookup 1', it has a mismatch between number of outputValues (=5) and "
+            "the grid size of the TableIndependentVariable(s) (=4).",
+            ft.warnings().front().logMessage());
+}
+
+TEST_F(EnergyPlusFixture, ForwardTranslator_1TableLookup_3independentVariables) {
+
+  Model m;
+
+  // Table with 3 independent variables
+  TableLookup tableLookup(m);
+  tableLookup.addOutputValue(111);
+  tableLookup.addOutputValue(112);
+  tableLookup.addOutputValue(113);
+  tableLookup.addOutputValue(114);
+  tableLookup.addOutputValue(121);
+  tableLookup.addOutputValue(122);
+  tableLookup.addOutputValue(123);
+  tableLookup.addOutputValue(124);
+  tableLookup.addOutputValue(211);
+  tableLookup.addOutputValue(212);
+  tableLookup.addOutputValue(213);
+  tableLookup.addOutputValue(214);
+  tableLookup.addOutputValue(221);
+  tableLookup.addOutputValue(222);
+  tableLookup.addOutputValue(223);
+  tableLookup.addOutputValue(224);
+  tableLookup.addOutputValue(311);
+  tableLookup.addOutputValue(312);
+  tableLookup.addOutputValue(313);
+  tableLookup.addOutputValue(314);
+  tableLookup.addOutputValue(321);
+  tableLookup.addOutputValue(322);
+  tableLookup.addOutputValue(323);
+  tableLookup.addOutputValue(324);
+
+  TableIndependentVariable independentVariable1(m);
+  independentVariable1.setInterpolationMethod("Cubic");
+  independentVariable1.setExtrapolationMethod("Linear");
+  independentVariable1.setMinimumValue(1);
+  independentVariable1.setMaximumValue(3);
+  independentVariable1.addValue(1);
+  independentVariable1.addValue(2);
+  independentVariable1.addValue(3);
+  tableLookup.addIndependentVariable(independentVariable1);
+
+  TableIndependentVariable independentVariable2(m);
+  independentVariable2.setInterpolationMethod("Cubic");
+  independentVariable2.setExtrapolationMethod("Linear");
+  independentVariable2.setMinimumValue(1);
+  independentVariable2.setMaximumValue(2);
+  independentVariable2.addValue(1);
+  independentVariable2.addValue(2);
+  tableLookup.addIndependentVariable(independentVariable2);
+
+  TableIndependentVariable independentVariable3(m);
+  independentVariable3.setInterpolationMethod("Cubic");
+  independentVariable3.setExtrapolationMethod("Linear");
+  independentVariable3.setMinimumValue(1);
+  independentVariable3.setMaximumValue(4);
+  independentVariable3.addValue(1);
+  independentVariable3.addValue(2);
+  independentVariable3.addValue(3);
+  independentVariable3.addValue(4);
+  tableLookup.addIndependentVariable(independentVariable3);
+
+  ForwardTranslator ft;
+  Workspace workspace = ft.translateModel(m);
+
+  std::vector<WorkspaceObject> tableObjects = workspace.getObjectsByType(IddObjectType::Table_Lookup);
+  ASSERT_EQ(1u, tableObjects.size());
+
+  WorkspaceObject idfTable = tableObjects.front();
+
+  ASSERT_EQ(idfTable.getString(Table_LookupFields::OutputUnitType).get(), "Dimensionless");
+  ASSERT_EQ(24u, idfTable.numExtensibleGroups());
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(0).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 111);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(1).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 112);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(2).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 113);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(3).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 114);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(4).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 121);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(5).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 122);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(6).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 123);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(7).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 124);
+
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(8).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 211);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(9).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 212);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(10).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 213);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(11).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 214);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(12).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 221);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(13).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 222);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(14).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 223);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(15).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 224);
+
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(16).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 311);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(17).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 312);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(18).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 313);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(19).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 314);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(20).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 321);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(21).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 322);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(22).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 323);
+  ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(23).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 324);
+
+  std::vector<WorkspaceObject> independentVariableObjects = workspace.getObjectsByType(IddObjectType::Table_IndependentVariable);
+  std::sort(independentVariableObjects.begin(), independentVariableObjects.end(), IdfObjectNameLess());
+  ASSERT_EQ(3u, independentVariableObjects.size());
+
+  ASSERT_TRUE(independentVariableObjects[0].getString(Table_IndependentVariableFields::InterpolationMethod));
+  EXPECT_EQ(independentVariableObjects[0].getString(Table_IndependentVariableFields::InterpolationMethod).get(), "Cubic");
+  ASSERT_TRUE(independentVariableObjects[0].getString(Table_IndependentVariableFields::ExtrapolationMethod));
+  EXPECT_EQ(independentVariableObjects[0].getString(Table_IndependentVariableFields::ExtrapolationMethod).get(), "Linear");
+  ASSERT_TRUE(independentVariableObjects[0].getDouble(Table_IndependentVariableFields::MinimumValue));
+  EXPECT_DOUBLE_EQ(independentVariableObjects[0].getDouble(Table_IndependentVariableFields::MinimumValue).get(), 1.0);
+  ASSERT_TRUE(independentVariableObjects[0].getDouble(Table_IndependentVariableFields::MaximumValue));
+  EXPECT_DOUBLE_EQ(independentVariableObjects[0].getDouble(Table_IndependentVariableFields::MaximumValue).get(), 3.0);
+  EXPECT_FALSE(independentVariableObjects[0].getDouble(Table_IndependentVariableFields::NormalizationReferenceValue));
+  ASSERT_TRUE(independentVariableObjects[0].getString(Table_IndependentVariableFields::UnitType));
+  EXPECT_EQ(independentVariableObjects[0].getString(Table_IndependentVariableFields::UnitType).get(), "Dimensionless");
+  ASSERT_EQ(3u, independentVariableObjects[0].numExtensibleGroups());
+
+  ASSERT_TRUE(independentVariableObjects[1].getString(Table_IndependentVariableFields::InterpolationMethod));
+  EXPECT_EQ(independentVariableObjects[1].getString(Table_IndependentVariableFields::InterpolationMethod).get(), "Cubic");
+  ASSERT_TRUE(independentVariableObjects[1].getString(Table_IndependentVariableFields::ExtrapolationMethod));
+  EXPECT_EQ(independentVariableObjects[1].getString(Table_IndependentVariableFields::ExtrapolationMethod).get(), "Linear");
+  ASSERT_TRUE(independentVariableObjects[1].getDouble(Table_IndependentVariableFields::MinimumValue));
+  EXPECT_DOUBLE_EQ(independentVariableObjects[1].getDouble(Table_IndependentVariableFields::MinimumValue).get(), 1.0);
+  ASSERT_TRUE(independentVariableObjects[1].getDouble(Table_IndependentVariableFields::MaximumValue));
+  EXPECT_DOUBLE_EQ(independentVariableObjects[1].getDouble(Table_IndependentVariableFields::MaximumValue).get(), 2.0);
+  EXPECT_FALSE(independentVariableObjects[1].getDouble(Table_IndependentVariableFields::NormalizationReferenceValue));
+  ASSERT_TRUE(independentVariableObjects[1].getString(Table_IndependentVariableFields::UnitType));
+  EXPECT_EQ(independentVariableObjects[1].getString(Table_IndependentVariableFields::UnitType).get(), "Dimensionless");
+  ASSERT_EQ(2u, independentVariableObjects[1].numExtensibleGroups());
+
+  ASSERT_TRUE(independentVariableObjects[2].getString(Table_IndependentVariableFields::InterpolationMethod));
+  EXPECT_EQ(independentVariableObjects[2].getString(Table_IndependentVariableFields::InterpolationMethod).get(), "Cubic");
+  ASSERT_TRUE(independentVariableObjects[2].getString(Table_IndependentVariableFields::ExtrapolationMethod));
+  EXPECT_EQ(independentVariableObjects[2].getString(Table_IndependentVariableFields::ExtrapolationMethod).get(), "Linear");
+  ASSERT_TRUE(independentVariableObjects[2].getDouble(Table_IndependentVariableFields::MinimumValue));
+  EXPECT_DOUBLE_EQ(independentVariableObjects[2].getDouble(Table_IndependentVariableFields::MinimumValue).get(), 1.0);
+  ASSERT_TRUE(independentVariableObjects[2].getDouble(Table_IndependentVariableFields::MaximumValue));
+  EXPECT_DOUBLE_EQ(independentVariableObjects[2].getDouble(Table_IndependentVariableFields::MaximumValue).get(), 4.0);
+  EXPECT_FALSE(independentVariableObjects[2].getDouble(Table_IndependentVariableFields::NormalizationReferenceValue));
+  ASSERT_TRUE(independentVariableObjects[2].getString(Table_IndependentVariableFields::UnitType));
+  EXPECT_EQ(independentVariableObjects[2].getString(Table_IndependentVariableFields::UnitType).get(), "Dimensionless");
+  ASSERT_EQ(4u, independentVariableObjects[2].numExtensibleGroups());
+
+  std::vector<WorkspaceObject> independentVariableListObjects = workspace.getObjectsByType(IddObjectType::Table_IndependentVariableList);
+  ASSERT_EQ(1u, independentVariableListObjects.size());
+
+  WorkspaceObject independentVariableListObject = independentVariableListObjects.front();
+  ASSERT_EQ(3u, independentVariableListObject.numExtensibleGroups());
+  ASSERT_TRUE(independentVariableListObject.getExtensibleGroup(0).getString(Table_IndependentVariableListExtensibleFields::IndependentVariableName));
+  EXPECT_EQ(
+    independentVariableObjects[0].nameString(),
+    independentVariableListObject.getExtensibleGroup(0).getString(Table_IndependentVariableListExtensibleFields::IndependentVariableName).get());
+  ASSERT_TRUE(independentVariableListObject.getExtensibleGroup(0).getString(Table_IndependentVariableListExtensibleFields::IndependentVariableName));
+  EXPECT_EQ(
+    independentVariableObjects[1].nameString(),
+    independentVariableListObject.getExtensibleGroup(1).getString(Table_IndependentVariableListExtensibleFields::IndependentVariableName).get());
+  ASSERT_TRUE(independentVariableListObject.getExtensibleGroup(0).getString(Table_IndependentVariableListExtensibleFields::IndependentVariableName));
+  EXPECT_EQ(
+    independentVariableObjects[2].nameString(),
+    independentVariableListObject.getExtensibleGroup(2).getString(Table_IndependentVariableListExtensibleFields::IndependentVariableName).get());
+}
+
+TEST_F(EnergyPlusFixture, ForwardTranslator_2TableLookup_SameIndependentVariable) {
+
+  Model m;
+
+  TableIndependentVariable independentVariable1(m);
+  independentVariable1.setName("MyVar");
+  independentVariable1.setInterpolationMethod("Cubic");
+  independentVariable1.setExtrapolationMethod("Linear");
+  independentVariable1.setMinimumValue(1);
+  independentVariable1.setMaximumValue(3);
+  independentVariable1.addValue(1);
+  independentVariable1.addValue(2);
+  independentVariable1.addValue(3);
+
+  TableLookup tableLookup1(m);
+  tableLookup1.addOutputValue(1.0);
+  tableLookup1.addOutputValue(0.9);
+  tableLookup1.addOutputValue(0.8);
+  tableLookup1.addIndependentVariable(independentVariable1);
+
+  TableLookup tableLookup2(m);
+  tableLookup2.addOutputValue(1.0);
+  tableLookup2.addOutputValue(0.9);
+  tableLookup2.addOutputValue(0.8);
+  tableLookup2.addIndependentVariable(independentVariable1);
+
+  ForwardTranslator ft;
+  Workspace workspace = ft.translateModel(m);
+
+  std::vector<WorkspaceObject> tableObjects = workspace.getObjectsByType(IddObjectType::Table_Lookup);
+  ASSERT_EQ(2u, tableObjects.size());
+  EXPECT_EQ(2u, workspace.getObjectsByType(IddObjectType::Table_IndependentVariableList).size());
+  EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::Table_IndependentVariable).size());
+
+  for (const auto& tableObject : tableObjects) {
+    auto varList_ = tableObject.getTarget(Table_LookupFields::IndependentVariableListName);
+    ASSERT_TRUE(varList_);
+    ASSERT_EQ(1, varList_->numExtensibleGroups());
+    EXPECT_EQ(independentVariable1.nameString(),
+              varList_->getExtensibleGroup(0).getString(Table_IndependentVariableListExtensibleFields::IndependentVariableName).get());
+  }
+}
+
+TEST_F(EnergyPlusFixture, ForwardTranslator_TableLookup_NormalizationReference) {
+
+  // One with a normalization reference
+  {
+    Model m;
+    TableLookup tableLookup(m);
+    tableLookup.setNormalizationMethod("DivisorOnly");
+    tableLookup.setNormalizationDivisor(0.9);
+    tableLookup.addOutputValue(0.1);
+    tableLookup.addOutputValue(0.3);
+    tableLookup.addOutputValue(0.5);
+    tableLookup.addOutputValue(0.7);
+    tableLookup.addOutputValue(0.9);
+
+    TableIndependentVariable independentVariable(m);
+    independentVariable.setInterpolationMethod("Cubic");
+    independentVariable.setExtrapolationMethod("Linear");
+    independentVariable.setMinimumValue(70);
+    independentVariable.setMaximumValue(78);
+    independentVariable.addValue(70);
+    independentVariable.addValue(72);
+    independentVariable.addValue(74);
+    independentVariable.addValue(76);
+    independentVariable.addValue(78);
+    tableLookup.addIndependentVariable(independentVariable);
+
+    ForwardTranslator ft;
+    Workspace workspace = ft.translateModel(m);
+
+    std::vector<WorkspaceObject> tableObjects = workspace.getObjectsByType(IddObjectType::Table_Lookup);
+    ASSERT_EQ(1u, tableObjects.size());
+
+    WorkspaceObject idfTable = tableObjects.front();
+
+    ASSERT_EQ(idfTable.getString(Table_LookupFields::OutputUnitType).get(), "Dimensionless");
+    ASSERT_EQ(5u, idfTable.numExtensibleGroups());
+    ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(0).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 0.1);
+    ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(1).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 0.3);
+    ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(2).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 0.5);
+    ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(3).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 0.7);
+    ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(4).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 0.9);
+
+    // Normalization reference should be filled in TableLookup
+    ASSERT_TRUE(idfTable.getString(Table_LookupFields::NormalizationMethod));
+    EXPECT_EQ(idfTable.getString(Table_LookupFields::NormalizationMethod).get(), "DivisorOnly");
+    ASSERT_TRUE(idfTable.getDouble(Table_LookupFields::NormalizationDivisor));
+    EXPECT_EQ(idfTable.getDouble(Table_LookupFields::NormalizationDivisor).get(), 0.9);
+
+    std::vector<WorkspaceObject> independentVariableObjects = workspace.getObjectsByType(IddObjectType::Table_IndependentVariable);
+    ASSERT_EQ(1u, independentVariableObjects.size());
+
+    WorkspaceObject independentVariableObject = independentVariableObjects.front();
+    ASSERT_TRUE(independentVariableObject.getString(Table_IndependentVariableFields::InterpolationMethod));
+    EXPECT_EQ(independentVariableObject.getString(Table_IndependentVariableFields::InterpolationMethod).get(), "Cubic");
+    ASSERT_TRUE(independentVariableObject.getString(Table_IndependentVariableFields::ExtrapolationMethod));
+    EXPECT_EQ(independentVariableObject.getString(Table_IndependentVariableFields::ExtrapolationMethod).get(), "Linear");
+    ASSERT_TRUE(independentVariableObject.getDouble(Table_IndependentVariableFields::MinimumValue));
+    EXPECT_DOUBLE_EQ(independentVariableObject.getDouble(Table_IndependentVariableFields::MinimumValue).get(), 70.0);
+    ASSERT_TRUE(independentVariableObject.getDouble(Table_IndependentVariableFields::MaximumValue));
+    EXPECT_DOUBLE_EQ(independentVariableObject.getDouble(Table_IndependentVariableFields::MaximumValue).get(), 78.0);
+
+    // It's the TableLookup object that gets the normalization reference
+    EXPECT_FALSE(independentVariableObject.getDouble(Table_IndependentVariableFields::NormalizationReferenceValue));
+
+    ASSERT_TRUE(independentVariableObject.getString(Table_IndependentVariableFields::UnitType));
+    EXPECT_EQ(independentVariableObject.getString(Table_IndependentVariableFields::UnitType).get(), "Dimensionless");
+    ASSERT_EQ(5u, independentVariableObject.numExtensibleGroups());
+
+    std::vector<WorkspaceObject> independentVariableListObjects = workspace.getObjectsByType(IddObjectType::Table_IndependentVariableList);
+    ASSERT_EQ(1u, independentVariableListObjects.size());
+
+    WorkspaceObject independentVariableListObject = independentVariableListObjects.front();
+    ASSERT_EQ(1u, independentVariableListObject.numExtensibleGroups());
+    ASSERT_TRUE(
+      independentVariableListObject.getExtensibleGroup(0).getString(Table_IndependentVariableListExtensibleFields::IndependentVariableName));
+    EXPECT_EQ(
+      independentVariableObject.nameString(),
+      independentVariableListObject.getExtensibleGroup(0).getString(Table_IndependentVariableListExtensibleFields::IndependentVariableName).get());
+  }
+}
+
+TEST_F(EnergyPlusFixture, ReverseTranslator_TableLookup) {
+
+  ReverseTranslator rt;
+
+  Workspace w(StrictnessLevel::Minimal, IddFileType::EnergyPlus);
+
+  std::vector<double> y{0.823403, 1.0, 1.1256};
+  std::vector<double> x1{0.714286, 1.0, 1.2857};
+
+  OptionalWorkspaceObject _i_tableLookup = w.addObject(IdfObject(IddObjectType::Table_Lookup));
+  ASSERT_TRUE(_i_tableLookup);
+
+  EXPECT_TRUE(_i_tableLookup->setString(Table_LookupFields::Name, "CoolCapModFuncOfSAFlow"));
+  EXPECT_TRUE(_i_tableLookup->setString(Table_LookupFields::NormalizationMethod, "None"));
+  EXPECT_TRUE(_i_tableLookup->setDouble(Table_LookupFields::NormalizationDivisor, 1.0));
+  EXPECT_TRUE(_i_tableLookup->setDouble(Table_LookupFields::MinimumOutput, 0.8234));
+  EXPECT_TRUE(_i_tableLookup->setDouble(Table_LookupFields::MaximumOutput, 1.1256));
+  EXPECT_TRUE(_i_tableLookup->setString(Table_LookupFields::OutputUnitType, "Dimensionless"));
+  EXPECT_TRUE(_i_tableLookup->setString(Table_LookupFields::ExternalFileName, ""));
+  EXPECT_TRUE(_i_tableLookup->setDouble(Table_LookupFields::ExternalFileColumnNumber, 0));
+  EXPECT_TRUE(_i_tableLookup->setDouble(Table_LookupFields::ExternalFileStartingRowNumber, 0));
+  for (double val : y) {
+    auto eg = _i_tableLookup->pushExtensibleGroup();
+    EXPECT_TRUE(eg.setDouble(Table_LookupExtensibleFields::OutputValue, val));
+  }
+
+  OptionalWorkspaceObject _i_varList = w.addObject(IdfObject(IddObjectType::Table_IndependentVariableList));
+  ASSERT_TRUE(_i_varList);
+  EXPECT_TRUE(_i_tableLookup->setPointer(Table_LookupFields::IndependentVariableListName, _i_varList->handle()));
+
+  OptionalWorkspaceObject _i_var1 = w.addObject(IdfObject(IddObjectType::Table_IndependentVariable));
+  ASSERT_TRUE(_i_var1);
+  {
+    auto weg = _i_varList->pushExtensibleGroup().cast<WorkspaceExtensibleGroup>();
+    EXPECT_TRUE(weg.setPointer(Table_IndependentVariableListExtensibleFields::IndependentVariableName, _i_var1->handle()));
+  }
+
+  EXPECT_TRUE(_i_var1->setString(Table_IndependentVariableFields::Name, "CoolCapModFuncOfWaterFlow_IndependentVariable1"));
+  EXPECT_TRUE(_i_var1->setString(Table_IndependentVariableFields::InterpolationMethod, "Cubic"));
+  EXPECT_TRUE(_i_var1->setString(Table_IndependentVariableFields::ExtrapolationMethod, "Constant"));
+  EXPECT_TRUE(_i_var1->setDouble(Table_IndependentVariableFields::MinimumValue, 0.714));
+  EXPECT_TRUE(_i_var1->setDouble(Table_IndependentVariableFields::MaximumValue, 1.2857));
+  EXPECT_TRUE(_i_var1->setString(Table_IndependentVariableFields::NormalizationReferenceValue, ""));
+  EXPECT_TRUE(_i_var1->setString(Table_IndependentVariableFields::UnitType, "Dimensionless"));
+  EXPECT_TRUE(_i_var1->setString(Table_IndependentVariableFields::ExternalFileName, ""));
+  for (double val : x1) {
+    auto eg = _i_var1->pushExtensibleGroup();
+    EXPECT_TRUE(eg.setDouble(Table_IndependentVariableExtensibleFields::Value, val));
+  }
+
+  // Not there, Model shouldn't have it either
+  Model m = rt.translateWorkspace(w);
+
+  ASSERT_EQ(1, m.getConcreteModelObjects<TableLookup>().size());
+  ASSERT_EQ(1, m.getConcreteModelObjects<TableIndependentVariable>().size());
+
+  TableLookup tableLookup = m.getConcreteModelObjects<TableLookup>()[0];
+
+  EXPECT_EQ("CoolCapModFuncOfSAFlow", tableLookup.nameString());
+  EXPECT_EQ("None", tableLookup.normalizationMethod());
+  EXPECT_EQ(1.0, tableLookup.normalizationDivisor());
+  ASSERT_TRUE(tableLookup.minimumOutput());
+  EXPECT_EQ(0.8234, tableLookup.minimumOutput().get());
+  ASSERT_TRUE(tableLookup.maximumOutput());
+  EXPECT_EQ(1.1256, tableLookup.maximumOutput().get());
+  EXPECT_EQ("Dimensionless", tableLookup.outputUnitType());
+  EXPECT_EQ(y.size(), tableLookup.outputValues().size());
+  EXPECT_EQ(y, tableLookup.outputValues());
+
+  ASSERT_EQ(1, tableLookup.independentVariables().size());
+  TableIndependentVariable var1 = tableLookup.independentVariables()[0];
+
+  EXPECT_EQ("CoolCapModFuncOfWaterFlow_IndependentVariable1", var1.nameString());
+  EXPECT_EQ("Cubic", var1.interpolationMethod());
+  EXPECT_EQ("Constant", var1.extrapolationMethod());
+  ASSERT_TRUE(var1.minimumValue());
+  EXPECT_EQ(0.714, var1.minimumValue().get());
+  ASSERT_TRUE(var1.maximumValue());
+  EXPECT_EQ(1.2857, var1.maximumValue().get());
+  EXPECT_FALSE(var1.normalizationReferenceValue());
+
+  EXPECT_EQ("Dimensionless", var1.unitType());
+  EXPECT_EQ(x1.size(), var1.values().size());
+  EXPECT_EQ(x1, var1.values());
+}
diff --git a/src/energyplus/Test/TableMultiVariableLookup_GTest.cpp b/src/energyplus/Test/TableMultiVariableLookup_GTest.cpp
index 4e905bf5869..962b6dced5e 100644
--- a/src/energyplus/Test/TableMultiVariableLookup_GTest.cpp
+++ b/src/energyplus/Test/TableMultiVariableLookup_GTest.cpp
@@ -76,8 +76,8 @@ TEST_F(EnergyPlusFixture, ForwardTranslator_TableMultiVariableLookup) {
     ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(2).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 0.5);
     ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(3).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 0.7);
     ASSERT_DOUBLE_EQ(idfTable.getExtensibleGroup(4).getDouble(Table_LookupExtensibleFields::OutputValue).get(), 0.9);
-    EXPECT_FALSE(idfTable.getString(Table_LookupFields::NormalizationMethod, false, true));  // Don't return default, return unitialized empty
-    EXPECT_FALSE(idfTable.getDouble(Table_LookupFields::NormalizationDivisor));
+    ASSERT_EQ(idfTable.getString(Table_LookupFields::NormalizationMethod).get(), "None");
+    ASSERT_EQ(idfTable.getDouble(Table_LookupFields::NormalizationDivisor).get(), 1.0);
 
     std::vector<WorkspaceObject> independentVariableObjects = workspace.getObjectsByType(IddObjectType::Table_IndependentVariable);
     ASSERT_EQ(1u, independentVariableObjects.size());
diff --git a/src/model/CMakeLists.txt b/src/model/CMakeLists.txt
index 99c9cae33d4..b57ee8784c9 100644
--- a/src/model/CMakeLists.txt
+++ b/src/model/CMakeLists.txt
@@ -1549,6 +1549,12 @@ set(${target_name}_src
   TableMultiVariableLookup.hpp
   TableMultiVariableLookup_Impl.hpp
   TableMultiVariableLookup.cpp
+  TableLookup.hpp
+  TableLookup_Impl.hpp
+  TableLookup.cpp
+  TableIndependentVariable.hpp
+  TableIndependentVariable_Impl.hpp
+  TableIndependentVariable.cpp
   ThermalStorageChilledWaterStratified.hpp
   ThermalStorageChilledWaterStratified_Impl.hpp
   ThermalStorageChilledWaterStratified.cpp
@@ -2238,6 +2244,8 @@ set(${target_name}_test_src
   test/SurfacePropertySurroundingSurfaces_GTest.cpp
   test/SwimmingPoolIndoor_GTest.cpp
   test/TableMultiVariableLookup_GTest.cpp
+  test/TableLookup_GTest.cpp
+  test/TableIndependentVariable_GTest.cpp
   test/ThermalZone_GTest.cpp
   test/ThermalStorageIceDetailed_GTest.cpp
   test/ThermalStorageChilledWaterStratified_GTest.cpp
diff --git a/src/model/CoilCoolingFourPipeBeam.cpp b/src/model/CoilCoolingFourPipeBeam.cpp
index 12a232ca0f6..8e74d5a136a 100644
--- a/src/model/CoilCoolingFourPipeBeam.cpp
+++ b/src/model/CoilCoolingFourPipeBeam.cpp
@@ -41,7 +41,8 @@
 
 // For Ctor default curves
 #include "CurveLinear.hpp"
-#include "TableMultiVariableLookup.hpp"
+#include "TableLookup.hpp"
+#include "TableIndependentVariable.hpp"
 
 #include <utilities/idd/IddEnums.hxx>
 #include <utilities/idd/OS_Coil_Cooling_FourPipeBeam_FieldEnums.hxx>
@@ -334,22 +335,24 @@ namespace model {
     OS_ASSERT(ok);
 
     // Beam Cooling Capacity Air Flow Modification Factor Curve Name
-    // CoolCapModFuncOfSAFlow: Table:OneIndependentVariable,
-
-    TableMultiVariableLookup coolCapModFuncOfSAFlow(model, 1);
-    coolCapModFuncOfSAFlow.setName("CoolCapModFuncOfSAFlow");
-    coolCapModFuncOfSAFlow.setCurveType("Quadratic");
-    coolCapModFuncOfSAFlow.setInterpolationMethod("EvaluateCurveToLimits");
-    coolCapModFuncOfSAFlow.setMinimumValueofX1(0.714);
-    coolCapModFuncOfSAFlow.setMaximumValueofX1(1.2857);
-    coolCapModFuncOfSAFlow.setMinimumTableOutput(0.8234);
-    coolCapModFuncOfSAFlow.setMaximumTableOutput(1.1256);
-    coolCapModFuncOfSAFlow.setInputUnitTypeforX1("Dimensionless");
-    coolCapModFuncOfSAFlow.setOutputUnitType("Dimensionless");
-
-    coolCapModFuncOfSAFlow.addPoint(0.714286, 0.823403);
-    coolCapModFuncOfSAFlow.addPoint(1.0, 1.0);
-    coolCapModFuncOfSAFlow.addPoint(1.2857, 1.1256);
+    TableLookup coolCapModFuncOfSAFlow(model);
+    {
+      coolCapModFuncOfSAFlow.setName("CoolCapModFuncOfSAFlow");
+      coolCapModFuncOfSAFlow.setMinimumOutput(0.8234);
+      coolCapModFuncOfSAFlow.setMaximumOutput(1.1256);
+      coolCapModFuncOfSAFlow.setOutputUnitType("Dimensionless");
+      coolCapModFuncOfSAFlow.setOutputValues({0.823403, 1.0, 1.1256});
+
+      TableIndependentVariable coolCapModFuncOfSAFlowVar1(model);
+      coolCapModFuncOfSAFlow.addIndependentVariable(coolCapModFuncOfSAFlowVar1);
+      coolCapModFuncOfSAFlowVar1.setName("CoolCapModFuncOfWaterFlow_IndependentVariable1");
+      coolCapModFuncOfSAFlowVar1.setInterpolationMethod("Cubic");
+      coolCapModFuncOfSAFlowVar1.setExtrapolationMethod("Constant");
+      coolCapModFuncOfSAFlowVar1.setMinimumValue(0.714);
+      coolCapModFuncOfSAFlowVar1.setMaximumValue(1.2857);
+      coolCapModFuncOfSAFlowVar1.setUnitType("Dimensionless");
+      coolCapModFuncOfSAFlowVar1.setValues({0.714286, 1.0, 1.2857});
+    }
 
     ok = setBeamCoolingCapacityAirFlowModificationFactorCurve(coolCapModFuncOfSAFlow);
     OS_ASSERT(ok);
@@ -357,25 +360,24 @@ namespace model {
     // Beam Cooling Capacity Chilled Water Flow Modification Factor Curve
     // CapModFuncOfWaterFlow: Table:OneIndependentVariable
 
-    TableMultiVariableLookup capModFuncOfWaterFlow(model, 1);
-    capModFuncOfWaterFlow.setName("CapModFuncOfWaterFlow");
-    capModFuncOfWaterFlow.setCurveType("Quadratic");
-    capModFuncOfWaterFlow.setInterpolationMethod("EvaluateCurveToLimits");
-    capModFuncOfWaterFlow.setMinimumValueofX1(0);
-    capModFuncOfWaterFlow.setMaximumValueofX1(1.33);
-    capModFuncOfWaterFlow.setMinimumTableOutput(0.0);
-    capModFuncOfWaterFlow.setMaximumTableOutput(1.04);
-    capModFuncOfWaterFlow.setInputUnitTypeforX1("Dimensionless");
-    capModFuncOfWaterFlow.setOutputUnitType("Dimensionless");
-
-    capModFuncOfWaterFlow.addPoint(0.0, 0.0);
-    capModFuncOfWaterFlow.addPoint(0.05, 0.001);
-    capModFuncOfWaterFlow.addPoint(0.33333, 0.71);
-    capModFuncOfWaterFlow.addPoint(0.5, 0.85);
-    capModFuncOfWaterFlow.addPoint(0.666667, 0.92);
-    capModFuncOfWaterFlow.addPoint(0.833333, 0.97);
-    capModFuncOfWaterFlow.addPoint(1.0, 1.0);
-    capModFuncOfWaterFlow.addPoint(1.333333, 1.04);
+    TableLookup capModFuncOfWaterFlow(model);
+    {
+      capModFuncOfWaterFlow.setName("CoolCapModFuncOfWaterFlow");
+      capModFuncOfWaterFlow.setMinimumOutput(0.0);
+      capModFuncOfWaterFlow.setMaximumOutput(1.04);
+      capModFuncOfWaterFlow.setOutputUnitType("Dimensionless");
+      capModFuncOfWaterFlow.setOutputValues({0.0, 0.001, 0.71, 0.85, 0.92, 0.97, 1.0, 1.04});
+
+      TableIndependentVariable capModFuncOfWaterFlowVar1(model);
+      capModFuncOfWaterFlow.addIndependentVariable(capModFuncOfWaterFlowVar1);
+      capModFuncOfWaterFlowVar1.setName("CoolCapModFuncOfWaterFlow_IndependentVariable1");
+      capModFuncOfWaterFlowVar1.setInterpolationMethod("Cubic");
+      capModFuncOfWaterFlowVar1.setExtrapolationMethod("Constant");
+      capModFuncOfWaterFlowVar1.setMinimumValue(0.0);
+      capModFuncOfWaterFlowVar1.setMaximumValue(1.33);
+      capModFuncOfWaterFlowVar1.setUnitType("Dimensionless");
+      capModFuncOfWaterFlowVar1.setValues({0.0, 0.05, 0.33333, 0.5, 0.666667, 0.833333, 1.0, 1.333333});
+    }
 
     ok = setBeamCoolingCapacityChilledWaterFlowModificationFactorCurve(capModFuncOfWaterFlow);
     OS_ASSERT(ok);
diff --git a/src/model/CoilHeatingFourPipeBeam.cpp b/src/model/CoilHeatingFourPipeBeam.cpp
index f7dc5e916d6..6872af6bacc 100644
--- a/src/model/CoilHeatingFourPipeBeam.cpp
+++ b/src/model/CoilHeatingFourPipeBeam.cpp
@@ -41,7 +41,8 @@
 
 // For Ctor default curves
 #include "CurveLinear.hpp"
-#include "TableMultiVariableLookup.hpp"
+#include "TableLookup.hpp"
+#include "TableIndependentVariable.hpp"
 
 #include <utilities/idd/IddEnums.hxx>
 #include <utilities/idd/OS_Coil_Heating_FourPipeBeam_FieldEnums.hxx>
@@ -333,20 +334,24 @@ namespace model {
     // Beam Heating Capacity Air Flow Modification Factor Curve Name
     // HeatCapModFuncOfSAFlow: Table:OneIndependentVariable,
 
-    TableMultiVariableLookup heatCapModFuncOfSAFlow(model, 1);
-    heatCapModFuncOfSAFlow.setName("HeatCapModFuncOfSAFlow");
-    heatCapModFuncOfSAFlow.setCurveType("Quadratic");
-    heatCapModFuncOfSAFlow.setInterpolationMethod("EvaluateCurveToLimits");
-    heatCapModFuncOfSAFlow.setMinimumValueofX1(0.714);
-    heatCapModFuncOfSAFlow.setMaximumValueofX1(1.2857);
-    heatCapModFuncOfSAFlow.setMinimumTableOutput(0.8234);
-    heatCapModFuncOfSAFlow.setMaximumTableOutput(1.0778);
-    heatCapModFuncOfSAFlow.setInputUnitTypeforX1("Dimensionless");
-    heatCapModFuncOfSAFlow.setOutputUnitType("Dimensionless");
-
-    heatCapModFuncOfSAFlow.addPoint(0.714286, 0.8554);
-    heatCapModFuncOfSAFlow.addPoint(1.0, 1.0);
-    heatCapModFuncOfSAFlow.addPoint(1.2857, 1.0778);
+    TableLookup heatCapModFuncOfSAFlow(model);
+    {
+      heatCapModFuncOfSAFlow.setName("HeatCapModFuncOfSAFlow");
+      heatCapModFuncOfSAFlow.setMinimumOutput(0.8234);
+      heatCapModFuncOfSAFlow.setMaximumOutput(1.0778);
+      heatCapModFuncOfSAFlow.setOutputUnitType("Dimensionless");
+      heatCapModFuncOfSAFlow.setOutputValues({0.8554, 1.0, 1.0778});
+
+      TableIndependentVariable heatCapModFuncOfSAFlowVar1(model);
+      heatCapModFuncOfSAFlow.addIndependentVariable(heatCapModFuncOfSAFlowVar1);
+      heatCapModFuncOfSAFlowVar1.setName("HeatCapModFuncOfWaterFlow_IndependentVariable1");
+      heatCapModFuncOfSAFlowVar1.setInterpolationMethod("Cubic");
+      heatCapModFuncOfSAFlowVar1.setExtrapolationMethod("Constant");
+      heatCapModFuncOfSAFlowVar1.setMinimumValue(0.714);
+      heatCapModFuncOfSAFlowVar1.setMaximumValue(1.2857);
+      heatCapModFuncOfSAFlowVar1.setUnitType("Dimensionless");
+      heatCapModFuncOfSAFlowVar1.setValues({0.714286, 1.0, 1.2857});
+    }
 
     ok = setBeamHeatingCapacityAirFlowModificationFactorCurve(heatCapModFuncOfSAFlow);
     OS_ASSERT(ok);
@@ -354,27 +359,26 @@ namespace model {
     // Beam Heating Capacity Hot Water Flow Modification Factor Curve
     // CapModFuncOfWaterFlow: Table:OneIndependentVariable
 
-    TableMultiVariableLookup capModFuncOfWaterFlow(model, 1);
-    capModFuncOfWaterFlow.setName("CapModFuncOfWaterFlow");
-    capModFuncOfWaterFlow.setCurveType("Quadratic");
-    capModFuncOfWaterFlow.setInterpolationMethod("EvaluateCurveToLimits");
-    capModFuncOfWaterFlow.setMinimumValueofX1(0);
-    capModFuncOfWaterFlow.setMaximumValueofX1(1.33);
-    capModFuncOfWaterFlow.setMinimumTableOutput(0.0);
-    capModFuncOfWaterFlow.setMaximumTableOutput(1.04);
-    capModFuncOfWaterFlow.setInputUnitTypeforX1("Dimensionless");
-    capModFuncOfWaterFlow.setOutputUnitType("Dimensionless");
-
-    capModFuncOfWaterFlow.addPoint(0.0, 0.0);
-    capModFuncOfWaterFlow.addPoint(0.05, 0.001);
-    capModFuncOfWaterFlow.addPoint(0.33333, 0.71);
-    capModFuncOfWaterFlow.addPoint(0.5, 0.85);
-    capModFuncOfWaterFlow.addPoint(0.666667, 0.92);
-    capModFuncOfWaterFlow.addPoint(0.833333, 0.97);
-    capModFuncOfWaterFlow.addPoint(1.0, 1.0);
-    capModFuncOfWaterFlow.addPoint(1.333333, 1.04);
-
-    ok = setBeamHeatingCapacityHotWaterFlowModificationFactorCurve(capModFuncOfWaterFlow);
+    TableLookup heatCapModFuncOfWaterFlow(model);
+    {
+      heatCapModFuncOfWaterFlow.setName("HeatCapModFuncOfWaterFlow");
+      heatCapModFuncOfWaterFlow.setMinimumOutput(0.0);
+      heatCapModFuncOfWaterFlow.setMaximumOutput(1.04);
+      heatCapModFuncOfWaterFlow.setOutputUnitType("Dimensionless");
+      heatCapModFuncOfWaterFlow.setOutputValues({0.0, 0.001, 0.71, 0.85, 0.92, 0.97, 1.0, 1.04});
+
+      TableIndependentVariable heatCapModFuncOfWaterFlowVar1(model);
+      heatCapModFuncOfWaterFlow.addIndependentVariable(heatCapModFuncOfWaterFlowVar1);
+      heatCapModFuncOfWaterFlowVar1.setName("HeatCapModFuncOfWaterFlow_IndependentVariable1");
+      heatCapModFuncOfWaterFlowVar1.setInterpolationMethod("Cubic");
+      heatCapModFuncOfWaterFlowVar1.setExtrapolationMethod("Constant");
+      heatCapModFuncOfWaterFlowVar1.setMinimumValue(0.0);
+      heatCapModFuncOfWaterFlowVar1.setMaximumValue(1.33);
+      heatCapModFuncOfWaterFlowVar1.setUnitType("Dimensionless");
+      heatCapModFuncOfWaterFlowVar1.setValues({0.0, 0.05, 0.33333, 0.5, 0.666667, 0.833333, 1.0, 1.333333});
+    }
+
+    ok = setBeamHeatingCapacityHotWaterFlowModificationFactorCurve(heatCapModFuncOfWaterFlow);
     OS_ASSERT(ok);
   }
 
diff --git a/src/model/ConcreteModelObjects.hpp b/src/model/ConcreteModelObjects.hpp
index af07ed76718..d0af21b7674 100644
--- a/src/model/ConcreteModelObjects.hpp
+++ b/src/model/ConcreteModelObjects.hpp
@@ -492,6 +492,8 @@
 #include "SurfacePropertySurroundingSurfaces.hpp"
 #include "SwimmingPoolIndoor.hpp"
 #include "TableMultiVariableLookup.hpp"
+#include "TableLookup.hpp"
+#include "TableIndependentVariable.hpp"
 #include "TemperingValve.hpp"
 #include "ThermalZone.hpp"
 #include "ThermalStorageChilledWaterStratified.hpp"
@@ -1022,6 +1024,8 @@
 #include "SurfacePropertySurroundingSurfaces_Impl.hpp"
 #include "SwimmingPoolIndoor_Impl.hpp"
 #include "TableMultiVariableLookup_Impl.hpp"
+#include "TableLookup_Impl.hpp"
+#include "TableIndependentVariable_Impl.hpp"
 #include "TemperingValve_Impl.hpp"
 #include "ThermalZone_Impl.hpp"
 #include "ThermalStorageChilledWaterStratified_Impl.hpp"
diff --git a/src/model/Model.cpp b/src/model/Model.cpp
index 1ca1f7e5fe3..7c45328458f 100644
--- a/src/model/Model.cpp
+++ b/src/model/Model.cpp
@@ -4258,7 +4258,21 @@ namespace model {
     REGISTER_CONSTRUCTOR(SurfacePropertyOtherSideConditionsModel);
     REGISTER_CONSTRUCTOR(SurfacePropertySurroundingSurfaces);
     REGISTER_CONSTRUCTOR(SwimmingPoolIndoor);
+#if defined(_MSC_VER)
+#  pragma warning(push)
+#  pragma warning(disable : 4996)
+#elif (defined(__GNUC__))
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
     REGISTER_CONSTRUCTOR(TableMultiVariableLookup);
+#if defined(_MSC_VER)
+#  pragma warning(pop)
+#elif (defined(__GNUC__))
+#  pragma GCC diagnostic pop
+#endif
+    REGISTER_CONSTRUCTOR(TableLookup);
+    REGISTER_CONSTRUCTOR(TableIndependentVariable);
     REGISTER_CONSTRUCTOR(TemperingValve);
     REGISTER_CONSTRUCTOR(ThermochromicGlazing);
     REGISTER_CONSTRUCTOR(ThermostatSetpointDualSetpoint);
@@ -4796,7 +4810,21 @@ namespace model {
     REGISTER_COPYCONSTRUCTORS(SurfacePropertyOtherSideConditionsModel);
     REGISTER_COPYCONSTRUCTORS(SurfacePropertySurroundingSurfaces);
     REGISTER_COPYCONSTRUCTORS(SwimmingPoolIndoor);
+#if defined(_MSC_VER)
+#  pragma warning(push)
+#  pragma warning(disable : 4996)
+#elif (defined(__GNUC__))
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
     REGISTER_COPYCONSTRUCTORS(TableMultiVariableLookup);
+#if defined(_MSC_VER)
+#  pragma warning(pop)
+#elif (defined(__GNUC__))
+#  pragma GCC diagnostic pop
+#endif
+    REGISTER_COPYCONSTRUCTORS(TableLookup);
+    REGISTER_COPYCONSTRUCTORS(TableIndependentVariable);
     REGISTER_COPYCONSTRUCTORS(TemperingValve);
     REGISTER_COPYCONSTRUCTORS(ThermochromicGlazing);
     REGISTER_COPYCONSTRUCTORS(ThermostatSetpointDualSetpoint);
diff --git a/src/model/ModelResources.i b/src/model/ModelResources.i
index 0e50d0916e5..475039e0940 100644
--- a/src/model/ModelResources.i
+++ b/src/model/ModelResources.i
@@ -173,6 +173,8 @@ MODELOBJECT_TEMPLATES(CurveSigmoid);
 MODELOBJECT_TEMPLATES(CurveTriquadratic);
 MODELOBJECT_TEMPLATES(TableMultiVariableLookupPoint);  // Helper class defined in TableMultiVariableLookup
 MODELOBJECT_TEMPLATES(TableMultiVariableLookup);
+MODELOBJECT_TEMPLATES(TableLookup);
+MODELOBJECT_TEMPLATES(TableIndependentVariable);
 MODELOBJECT_TEMPLATES(SpaceLoadDefinition);
 MODELOBJECT_TEMPLATES(PeopleDefinition);
 MODELOBJECT_TEMPLATES(LightsDefinition);
@@ -271,6 +273,8 @@ SWIG_MODELOBJECT(CurveRectangularHyperbola2, 1);
 SWIG_MODELOBJECT(CurveSigmoid, 1);
 SWIG_MODELOBJECT(CurveTriquadratic, 1);
 SWIG_MODELOBJECT(TableMultiVariableLookup, 1);
+SWIG_MODELOBJECT(TableLookup, 1);
+SWIG_MODELOBJECT(TableIndependentVariable, 1);
 SWIG_MODELOBJECT(SpaceLoadDefinition, 0);
 SWIG_MODELOBJECT(PeopleDefinition, 1);
 SWIG_MODELOBJECT(LightsDefinition, 1);
diff --git a/src/model/TableIndependentVariable.cpp b/src/model/TableIndependentVariable.cpp
new file mode 100644
index 00000000000..4c3e8d9a999
--- /dev/null
+++ b/src/model/TableIndependentVariable.cpp
@@ -0,0 +1,346 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include "TableIndependentVariable.hpp"
+#include "TableIndependentVariable_Impl.hpp"
+
+#include "TableLookup.hpp"
+#include "TableLookup_Impl.hpp"
+#include "Model.hpp"
+#include "Model_Impl.hpp"
+
+#include "../utilities/idf/IdfExtensibleGroup.hpp"
+#include <utilities/idd/IddFactory.hxx>
+
+#include <utilities/idd/OS_Table_IndependentVariable_FieldEnums.hxx>
+#include <utilities/idd/IddEnums.hxx>
+
+#include "../utilities/idf/WorkspaceExtensibleGroup.hpp"
+#include "../utilities/core/Assert.hpp"
+
+namespace openstudio {
+namespace model {
+
+  namespace detail {
+
+    TableIndependentVariable_Impl::TableIndependentVariable_Impl(const IdfObject& idfObject, Model_Impl* model, bool keepHandle)
+      : ResourceObject_Impl(idfObject, model, keepHandle) {
+      OS_ASSERT(idfObject.iddObject().type() == TableIndependentVariable::iddObjectType());
+    }
+
+    TableIndependentVariable_Impl::TableIndependentVariable_Impl(const openstudio::detail::WorkspaceObject_Impl& other, Model_Impl* model,
+                                                                 bool keepHandle)
+      : ResourceObject_Impl(other, model, keepHandle) {
+      OS_ASSERT(other.iddObject().type() == TableIndependentVariable::iddObjectType());
+    }
+
+    TableIndependentVariable_Impl::TableIndependentVariable_Impl(const TableIndependentVariable_Impl& other, Model_Impl* model, bool keepHandle)
+      : ResourceObject_Impl(other, model, keepHandle) {}
+
+    const std::vector<std::string>& TableIndependentVariable_Impl::outputVariableNames() const {
+      static const std::vector<std::string> result;
+      return result;
+    }
+
+    IddObjectType TableIndependentVariable_Impl::iddObjectType() const {
+      return TableIndependentVariable::iddObjectType();
+    }
+
+    std::string TableIndependentVariable_Impl::interpolationMethod() const {
+      boost::optional<std::string> value = getString(OS_Table_IndependentVariableFields::InterpolationMethod, true);
+      OS_ASSERT(value);
+      return value.get();
+    }
+
+    std::string TableIndependentVariable_Impl::extrapolationMethod() const {
+      boost::optional<std::string> value = getString(OS_Table_IndependentVariableFields::ExtrapolationMethod, true);
+      OS_ASSERT(value);
+      return value.get();
+    }
+
+    boost::optional<double> TableIndependentVariable_Impl::minimumValue() const {
+      return getDouble(OS_Table_IndependentVariableFields::MinimumValue, true);
+    }
+
+    boost::optional<double> TableIndependentVariable_Impl::maximumValue() const {
+      return getDouble(OS_Table_IndependentVariableFields::MaximumValue, true);
+    }
+
+    boost::optional<double> TableIndependentVariable_Impl::normalizationReferenceValue() const {
+      return getDouble(OS_Table_IndependentVariableFields::NormalizationReferenceValue, true);
+    }
+
+    std::string TableIndependentVariable_Impl::unitType() const {
+      boost::optional<std::string> value = getString(OS_Table_IndependentVariableFields::UnitType, true);
+      OS_ASSERT(value);
+      return value.get();
+    }
+
+    std::vector<TableLookup> TableIndependentVariable_Impl::tableLookups() const {
+      std::vector<TableLookup> result;
+      for (const TableLookup& tableLookup : this->model().getConcreteModelObjects<TableLookup>()) {
+        for (const TableIndependentVariable& independentVariable : tableLookup.independentVariables()) {
+          if (independentVariable.handle() == this->handle()) {
+            result.push_back(tableLookup);
+          }
+        }
+      }
+      return result;
+    }
+
+    bool TableIndependentVariable_Impl::setInterpolationMethod(const std::string& interpolationMethod) {
+      bool result = setString(OS_Table_IndependentVariableFields::InterpolationMethod, interpolationMethod);
+      return result;
+    }
+
+    bool TableIndependentVariable_Impl::setExtrapolationMethod(const std::string& extrapolationMethod) {
+      bool result = setString(OS_Table_IndependentVariableFields::ExtrapolationMethod, extrapolationMethod);
+      return result;
+    }
+
+    bool TableIndependentVariable_Impl::setMinimumValue(double minimumValue) {
+      bool result = setDouble(OS_Table_IndependentVariableFields::MinimumValue, minimumValue);
+      return result;
+    }
+
+    void TableIndependentVariable_Impl::resetMinimumValue() {
+      bool result = setString(OS_Table_IndependentVariableFields::MinimumValue, "");
+      OS_ASSERT(result);
+    }
+
+    bool TableIndependentVariable_Impl::setMaximumValue(double maximumValue) {
+      bool result = setDouble(OS_Table_IndependentVariableFields::MaximumValue, maximumValue);
+      return result;
+    }
+
+    void TableIndependentVariable_Impl::resetMaximumValue() {
+      bool result = setString(OS_Table_IndependentVariableFields::MaximumValue, "");
+      OS_ASSERT(result);
+    }
+
+    bool TableIndependentVariable_Impl::setNormalizationReferenceValue(double normalizationReferenceValue) {
+      bool result = setDouble(OS_Table_IndependentVariableFields::NormalizationReferenceValue, normalizationReferenceValue);
+      return result;
+    }
+
+    void TableIndependentVariable_Impl::resetNormalizationReferenceValue() {
+      bool result = setString(OS_Table_IndependentVariableFields::NormalizationReferenceValue, "");
+      OS_ASSERT(result);
+    }
+
+    bool TableIndependentVariable_Impl::setUnitType(const std::string& unitType) {
+      bool result = setString(OS_Table_IndependentVariableFields::UnitType, unitType);
+      return result;
+    }
+
+    bool TableIndependentVariable_Impl::addValue(double value) {
+      IdfExtensibleGroup eg = getObject<ModelObject>().pushExtensibleGroup();
+      bool result = eg.setDouble(OS_Table_IndependentVariableExtensibleFields::Value, value);
+      if (!result) {
+        getObject<ModelObject>().eraseExtensibleGroup(eg.groupIndex());
+      }
+
+      return result;
+    }
+
+    bool TableIndependentVariable_Impl::removeValue(unsigned groupIndex) {
+      bool result = false;
+
+      unsigned num = numberofValues();
+      if (groupIndex < num) {
+        getObject<ModelObject>().eraseExtensibleGroup(groupIndex);
+        result = true;
+      }
+      return result;
+    }
+
+    void TableIndependentVariable_Impl::removeAllValues() {
+      getObject<ModelObject>().clearExtensibleGroups();
+    }
+
+    std::vector<double> TableIndependentVariable_Impl::values() const {
+      std::vector<double> result;
+      for (const auto& eg : extensibleGroups()) {
+        auto _d = eg.getDouble(OS_Table_IndependentVariableExtensibleFields::Value);
+        OS_ASSERT(_d);
+        result.push_back(_d.get());
+      }
+      return result;
+    }
+
+    unsigned TableIndependentVariable_Impl::numberofValues() const {
+      return numExtensibleGroups();
+    }
+
+    bool TableIndependentVariable_Impl::setValues(const std::vector<double>& values) {
+      bool result = true;
+
+      this->clearExtensibleGroups();
+      for (const auto& val : values) {
+        result &= addValue(val);
+      }
+      return result;
+    }
+
+  }  // namespace detail
+
+  TableIndependentVariable::TableIndependentVariable(const Model& model) : ResourceObject(TableIndependentVariable::iddObjectType(), model) {
+    OS_ASSERT(getImpl<detail::TableIndependentVariable_Impl>());
+
+    bool ok = true;
+    ok = setInterpolationMethod("Linear");
+    OS_ASSERT(ok);
+    ok = setExtrapolationMethod("Constant");
+    OS_ASSERT(ok);
+    ok = setUnitType("Dimensionless");
+    OS_ASSERT(ok);
+  }
+
+  IddObjectType TableIndependentVariable::iddObjectType() {
+    return {IddObjectType::OS_Table_IndependentVariable};
+  }
+
+  std::vector<std::string> TableIndependentVariable::interpolationMethodValues() {
+    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_Table_IndependentVariableFields::InterpolationMethod);
+  }
+
+  std::vector<std::string> TableIndependentVariable::validInterpolationMethodValues() {
+    return interpolationMethodValues();
+  }
+
+  std::vector<std::string> TableIndependentVariable::extrapolationMethodValues() {
+    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_Table_IndependentVariableFields::ExtrapolationMethod);
+  }
+
+  std::vector<std::string> TableIndependentVariable::validExtrapolationMethodValues() {
+    return extrapolationMethodValues();
+  }
+
+  std::vector<std::string> TableIndependentVariable::unitTypeValues() {
+    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_Table_IndependentVariableFields::UnitType);
+  }
+
+  std::vector<std::string> TableIndependentVariable::validUnitTypeValues() {
+    return unitTypeValues();
+  }
+
+  std::string TableIndependentVariable::interpolationMethod() const {
+    return getImpl<detail::TableIndependentVariable_Impl>()->interpolationMethod();
+  }
+
+  std::string TableIndependentVariable::extrapolationMethod() const {
+    return getImpl<detail::TableIndependentVariable_Impl>()->extrapolationMethod();
+  }
+
+  boost::optional<double> TableIndependentVariable::minimumValue() const {
+    return getImpl<detail::TableIndependentVariable_Impl>()->minimumValue();
+  }
+
+  boost::optional<double> TableIndependentVariable::maximumValue() const {
+    return getImpl<detail::TableIndependentVariable_Impl>()->maximumValue();
+  }
+
+  boost::optional<double> TableIndependentVariable::normalizationReferenceValue() const {
+    return getImpl<detail::TableIndependentVariable_Impl>()->normalizationReferenceValue();
+  }
+
+  std::string TableIndependentVariable::unitType() const {
+    return getImpl<detail::TableIndependentVariable_Impl>()->unitType();
+  }
+
+  std::vector<TableLookup> TableIndependentVariable::tableLookups() const {
+    return getImpl<detail::TableIndependentVariable_Impl>()->tableLookups();
+  }
+
+  bool TableIndependentVariable::setInterpolationMethod(const std::string& interpolationMethod) {
+    return getImpl<detail::TableIndependentVariable_Impl>()->setInterpolationMethod(interpolationMethod);
+  }
+
+  bool TableIndependentVariable::setExtrapolationMethod(const std::string& extrapolationMethod) {
+    return getImpl<detail::TableIndependentVariable_Impl>()->setExtrapolationMethod(extrapolationMethod);
+  }
+
+  bool TableIndependentVariable::setMinimumValue(double minimumValue) {
+    return getImpl<detail::TableIndependentVariable_Impl>()->setMinimumValue(minimumValue);
+  }
+
+  void TableIndependentVariable::resetMinimumValue() {
+    getImpl<detail::TableIndependentVariable_Impl>()->resetMinimumValue();
+  }
+
+  bool TableIndependentVariable::setMaximumValue(double maximumValue) {
+    return getImpl<detail::TableIndependentVariable_Impl>()->setMaximumValue(maximumValue);
+  }
+
+  void TableIndependentVariable::resetMaximumValue() {
+    getImpl<detail::TableIndependentVariable_Impl>()->resetMaximumValue();
+  }
+
+  bool TableIndependentVariable::setNormalizationReferenceValue(double normalizationReferenceValue) {
+    return getImpl<detail::TableIndependentVariable_Impl>()->setNormalizationReferenceValue(normalizationReferenceValue);
+  }
+
+  void TableIndependentVariable::resetNormalizationReferenceValue() {
+    getImpl<detail::TableIndependentVariable_Impl>()->resetNormalizationReferenceValue();
+  }
+
+  bool TableIndependentVariable::setUnitType(const std::string& unitType) {
+    return getImpl<detail::TableIndependentVariable_Impl>()->setUnitType(unitType);
+  }
+
+  bool TableIndependentVariable::addValue(double value) {
+    return getImpl<detail::TableIndependentVariable_Impl>()->addValue(value);
+  }
+
+  bool TableIndependentVariable::removeValue(unsigned groupIndex) {
+    return getImpl<detail::TableIndependentVariable_Impl>()->removeValue(groupIndex);
+  }
+
+  void TableIndependentVariable::removeAllValues() {
+    getImpl<detail::TableIndependentVariable_Impl>()->removeAllValues();
+  }
+
+  std::vector<double> TableIndependentVariable::values() const {
+    return getImpl<detail::TableIndependentVariable_Impl>()->values();
+  }
+
+  unsigned TableIndependentVariable::numberofValues() const {
+    return getImpl<detail::TableIndependentVariable_Impl>()->numberofValues();
+  }
+
+  bool TableIndependentVariable::setValues(const std::vector<double>& values) {
+    return getImpl<detail::TableIndependentVariable_Impl>()->setValues(values);
+  }
+
+  /// @cond
+  TableIndependentVariable::TableIndependentVariable(std::shared_ptr<detail::TableIndependentVariable_Impl> impl) : ResourceObject(std::move(impl)) {}
+  /// @endcond
+
+}  // namespace model
+}  // namespace openstudio
diff --git a/src/model/TableIndependentVariable.hpp b/src/model/TableIndependentVariable.hpp
new file mode 100644
index 00000000000..c56c9b3769a
--- /dev/null
+++ b/src/model/TableIndependentVariable.hpp
@@ -0,0 +1,146 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#ifndef MODEL_TABLEINDEPENDENTVARIABLE_HPP
+#define MODEL_TABLEINDEPENDENTVARIABLE_HPP
+
+#include "ModelAPI.hpp"
+#include "ResourceObject.hpp"
+
+namespace openstudio {
+
+namespace model {
+
+  class TableLookup;
+
+  namespace detail {
+
+    class TableIndependentVariable_Impl;
+
+  }  // namespace detail
+
+  /** TableIndependentVariable is a ResourceObject that wraps the OpenStudio IDD object 'OS:Table:IndependentVariable'. */
+  class MODEL_API TableIndependentVariable : public ResourceObject
+  {
+   public:
+    /** @name Constructors and Destructors */
+    //@{
+
+    explicit TableIndependentVariable(const Model& model);
+
+    virtual ~TableIndependentVariable() = default;
+
+    //@}
+
+    static IddObjectType iddObjectType();
+
+    static std::vector<std::string> interpolationMethodValues();
+    static std::vector<std::string> validInterpolationMethodValues();
+
+    static std::vector<std::string> extrapolationMethodValues();
+    static std::vector<std::string> validExtrapolationMethodValues();
+
+    static std::vector<std::string> unitTypeValues();
+    static std::vector<std::string> validUnitTypeValues();
+
+    /** @name Getters */
+    //@{
+
+    std::string interpolationMethod() const;
+
+    std::string extrapolationMethod() const;
+
+    boost::optional<double> minimumValue() const;
+
+    boost::optional<double> maximumValue() const;
+
+    boost::optional<double> normalizationReferenceValue() const;
+
+    std::string unitType() const;
+
+    //@}
+    /** @name Setters */
+    //@{
+
+    bool setInterpolationMethod(const std::string& interpolationMethod);
+
+    bool setExtrapolationMethod(const std::string& extrapolationMethod);
+
+    bool setMinimumValue(double minimumValue);
+    void resetMinimumValue();
+
+    bool setMaximumValue(double maximumValue);
+    void resetMaximumValue();
+
+    bool setNormalizationReferenceValue(double normalizationReferenceValue);
+    void resetNormalizationReferenceValue();
+
+    bool setUnitType(const std::string& unitType);
+
+    //@}
+    /** @name Other */
+    //@{
+
+    // Convenience method to return TableLookup objects that point to this TableIndependentVariable
+    std::vector<TableLookup> tableLookups() const;
+
+    bool addValue(double value);
+    bool removeValue(unsigned groupIndex);
+    void removeAllValues();
+    std::vector<double> values() const;
+    // Directly set the Values from a vector, will delete any existing Values
+    bool setValues(const std::vector<double>& values);
+    unsigned numberofValues() const;
+
+    //@}
+   protected:
+    /// @cond
+    using ImplType = detail::TableIndependentVariable_Impl;
+
+    explicit TableIndependentVariable(std::shared_ptr<detail::TableIndependentVariable_Impl> impl);
+
+    friend class detail::TableIndependentVariable_Impl;
+    friend class Model;
+    friend class IdfObject;
+    friend class openstudio::detail::IdfObject_Impl;
+    /// @endcond
+   private:
+    REGISTER_LOGGER("openstudio.model.TableIndependentVariable");
+  };
+
+  /** \relates TableIndependentVariable*/
+  using OptionalTableIndependentVariable = boost::optional<TableIndependentVariable>;
+
+  /** \relates TableIndependentVariable*/
+  using TableIndependentVariableVector = std::vector<TableIndependentVariable>;
+
+}  // namespace model
+}  // namespace openstudio
+
+#endif  // MODEL_TABLEINDEPENDENTVARIABLE_HPP
diff --git a/src/model/TableIndependentVariable_Impl.hpp b/src/model/TableIndependentVariable_Impl.hpp
new file mode 100644
index 00000000000..aa1effd44b1
--- /dev/null
+++ b/src/model/TableIndependentVariable_Impl.hpp
@@ -0,0 +1,125 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#ifndef MODEL_TABLEINDEPENDENTVARIABLE_IMPL_HPP
+#define MODEL_TABLEINDEPENDENTVARIABLE_IMPL_HPP
+
+#include "ModelAPI.hpp"
+#include "ResourceObject_Impl.hpp"
+
+namespace openstudio {
+namespace model {
+
+  class TableLookup;
+
+  namespace detail {
+
+    /** TableIndependentVariable_Impl is a ResourceObject_Impl that is the implementation class for TableIndependentVariable.*/
+    class MODEL_API TableIndependentVariable_Impl : public ResourceObject_Impl
+    {
+     public:
+      /** @name Constructors and Destructors */
+      //@{
+
+      TableIndependentVariable_Impl(const IdfObject& idfObject, Model_Impl* model, bool keepHandle);
+
+      TableIndependentVariable_Impl(const openstudio::detail::WorkspaceObject_Impl& other, Model_Impl* model, bool keepHandle);
+
+      TableIndependentVariable_Impl(const TableIndependentVariable_Impl& other, Model_Impl* model, bool keepHandle);
+
+      virtual ~TableIndependentVariable_Impl() = default;
+
+      //@}
+      /** @name Virtual Methods */
+      //@{
+
+      virtual const std::vector<std::string>& outputVariableNames() const override;
+
+      virtual IddObjectType iddObjectType() const override;
+
+      //@}
+      /** @name Getters */
+      //@{
+
+      std::string interpolationMethod() const;
+
+      std::string extrapolationMethod() const;
+
+      boost::optional<double> minimumValue() const;
+
+      boost::optional<double> maximumValue() const;
+
+      boost::optional<double> normalizationReferenceValue() const;
+
+      std::string unitType() const;
+
+      //@}
+      /** @name Setters */
+      //@{
+
+      bool setInterpolationMethod(const std::string& interpolationMethod);
+
+      bool setExtrapolationMethod(const std::string& extrapolationMethod);
+
+      bool setMinimumValue(double minimumValue);
+      void resetMinimumValue();
+
+      bool setMaximumValue(double maximumValue);
+      void resetMaximumValue();
+
+      bool setNormalizationReferenceValue(double normalizationReferenceValue);
+      void resetNormalizationReferenceValue();
+
+      bool setUnitType(const std::string& unitType);
+
+      //@}
+      /** @name Other */
+      //@{
+
+      std::vector<TableLookup> tableLookups() const;
+
+      bool addValue(double value);
+      bool removeValue(unsigned groupIndex);
+      void removeAllValues();
+      std::vector<double> values() const;
+      bool setValues(const std::vector<double>& values);
+      unsigned numberofValues() const;
+
+      //@}
+     protected:
+     private:
+      REGISTER_LOGGER("openstudio.model.TableIndependentVariable");
+    };
+
+  }  // namespace detail
+
+}  // namespace model
+}  // namespace openstudio
+
+#endif  // MODEL_TABLEINDEPENDENTVARIABLE_IMPL_HPP
diff --git a/src/model/TableLookup.cpp b/src/model/TableLookup.cpp
new file mode 100644
index 00000000000..df95a933ed3
--- /dev/null
+++ b/src/model/TableLookup.cpp
@@ -0,0 +1,459 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include "TableLookup.hpp"
+#include "TableLookup_Impl.hpp"
+
+#include "ModelObjectList.hpp"
+#include "ModelObjectList_Impl.hpp"
+#include "TableIndependentVariable.hpp"
+#include "TableIndependentVariable_Impl.hpp"
+#include "Model.hpp"
+#include "Model_Impl.hpp"
+
+#include "../utilities/idf/IdfExtensibleGroup.hpp"
+#include <utilities/idd/IddFactory.hxx>
+
+#include <utilities/idd/OS_Table_Lookup_FieldEnums.hxx>
+#include <utilities/idd/IddEnums.hxx>
+
+#include "../utilities/units/Unit.hpp"
+
+#include "../utilities/idf/WorkspaceExtensibleGroup.hpp"
+#include "../utilities/core/Assert.hpp"
+
+#include <algorithm>
+#include <iomanip>
+
+namespace openstudio {
+namespace model {
+
+  namespace detail {
+
+    TableLookup_Impl::TableLookup_Impl(const IdfObject& idfObject, Model_Impl* model, bool keepHandle) : Curve_Impl(idfObject, model, keepHandle) {
+      OS_ASSERT(idfObject.iddObject().type() == TableLookup::iddObjectType());
+    }
+
+    TableLookup_Impl::TableLookup_Impl(const openstudio::detail::WorkspaceObject_Impl& other, Model_Impl* model, bool keepHandle)
+      : Curve_Impl(other, model, keepHandle) {
+      OS_ASSERT(other.iddObject().type() == TableLookup::iddObjectType());
+    }
+
+    TableLookup_Impl::TableLookup_Impl(const TableLookup_Impl& other, Model_Impl* model, bool keepHandle) : Curve_Impl(other, model, keepHandle) {}
+
+    const std::vector<std::string>& TableLookup_Impl::outputVariableNames() const {
+      // Technically this is dependent on the actual number of independent variables used, but this is a staic one, and it's not worth the hassle
+      // (we could just mutate the static vector (and not make it const), still returning a const reference to it)
+      static const std::vector<std::string> result{
+        "Performance Curve Output Value",           "Performance Curve Input Variable 1 Value", "Performance Curve Input Variable 2 Value",
+        "Performance Curve Input Variable 3 Value", "Performance Curve Input Variable 4 Value", "Performance Curve Input Variable 5 Value",
+      };
+
+      return result;
+    }
+
+    IddObjectType TableLookup_Impl::iddObjectType() const {
+      return TableLookup::iddObjectType();
+    }
+
+    int TableLookup_Impl::numVariables() const {
+      return independentVariables().size();
+    }
+
+    double TableLookup_Impl::evaluate(const std::vector<double>& /*independentVariables*/) const {
+      LOG(Warn, "Curve evaluation isn't implemented for TableLookup");
+      return -9999.0;
+    }
+
+    ModelObject TableLookup_Impl::clone(Model model) const {
+
+      // Call the ModelObject_Impl instead of ParentObject_Impl because we deal with the ModelObjectList ourselves
+      auto modelObjectClone = ModelObject_Impl::clone(model).cast<TableLookup>();  // NOLINT
+
+      // ModelObjectList is kinda dumb, cloning it also clones the underlying objects regardless of whether they are resourceObjects or not
+      // We don't want that, so we make a new one, and populate it like the original
+      auto newVarList = ModelObjectList(model);
+      newVarList.setName(modelObjectClone.name().get() + "  Independent Variable List");
+      bool ok = modelObjectClone.getImpl<detail::TableLookup_Impl>()->setIndependentVariableList(newVarList);
+      OS_ASSERT(ok);
+
+      // Now, the matter is to repopulate it.
+      for (const TableIndependentVariable& var : this->independentVariables()) {
+        if (model != this->model()) {
+          auto varClone = var.clone(model).cast<TableIndependentVariable>();
+          modelObjectClone.addIndependentVariable(varClone);
+        } else {
+          // Add that to the new object
+          modelObjectClone.addIndependentVariable(var);
+        }
+      }
+      return modelObjectClone;
+    }
+
+    std::vector<IdfObject> TableLookup_Impl::remove() {
+      std::vector<IdfObject> result;
+
+      // ModelObjectList is kinda dumb, it removes all the modelObjects it has, which we don't want
+      // Instead we look at the independent variables listed, if they are used only once => delete them
+      // Then clear the modelObjectList, and remove it
+      if (boost::optional<ModelObjectList> varList = this->independentVariableList()) {
+        auto vars = castVector<ResourceObject>(varList->modelObjects());
+
+        for (auto& var : vars) {
+          if (var.directUseCount() == 1) {
+            std::vector<IdfObject> removedModelObject = var.remove();
+            result.insert(result.end(), removedModelObject.begin(), removedModelObject.end());
+          }
+        }
+
+        varList->clearExtensibleGroups();  // Clearer than removeAllModelObjects (which does the same thing, but could mean delete all objects)
+        std::vector<IdfObject> removedVarList = varList->remove();
+        result.insert(result.end(), removedVarList.begin(), removedVarList.end());
+      }
+
+      std::vector<IdfObject> removedObject = Curve_Impl::remove();
+      result.insert(result.end(), removedObject.begin(), removedObject.end());
+
+      return result;
+    }
+
+    boost::optional<ModelObjectList> TableLookup_Impl::independentVariableList() const {
+      return getObject<ModelObject>().getModelObjectTarget<ModelObjectList>(OS_Table_LookupFields::IndependentVariableListName);
+    }
+
+    std::string TableLookup_Impl::normalizationMethod() const {
+      boost::optional<std::string> value = getString(OS_Table_LookupFields::NormalizationMethod, true);
+      OS_ASSERT(value);
+      return value.get();
+    }
+
+    double TableLookup_Impl::normalizationDivisor() const {
+      boost::optional<double> value = getDouble(OS_Table_LookupFields::NormalizationDivisor, true);
+      OS_ASSERT(value);
+      return value.get();
+    }
+
+    boost::optional<double> TableLookup_Impl::minimumOutput() const {
+      return getDouble(OS_Table_LookupFields::MinimumOutput, true);
+    }
+
+    boost::optional<double> TableLookup_Impl::maximumOutput() const {
+      return getDouble(OS_Table_LookupFields::MaximumOutput, true);
+    }
+
+    std::string TableLookup_Impl::outputUnitType() const {
+      boost::optional<std::string> value = getString(OS_Table_LookupFields::OutputUnitType, true);
+      OS_ASSERT(value);
+      return value.get();
+    }
+
+    bool TableLookup_Impl::setNormalizationMethod(const std::string& normalizationMethod) {
+      bool result = setString(OS_Table_LookupFields::NormalizationMethod, normalizationMethod);
+      return result;
+    }
+
+    bool TableLookup_Impl::setNormalizationDivisor(double normalizationDivisior) {
+      bool result = setDouble(OS_Table_LookupFields::NormalizationDivisor, normalizationDivisior);
+      return result;
+    }
+
+    bool TableLookup_Impl::setMinimumOutput(double minimumOutput) {
+      bool result = setDouble(OS_Table_LookupFields::MinimumOutput, minimumOutput);
+      return result;
+    }
+
+    void TableLookup_Impl::resetMinimumOutput() {
+      bool result = setString(OS_Table_LookupFields::MinimumOutput, "");
+      OS_ASSERT(result);
+    }
+
+    bool TableLookup_Impl::setMaximumOutput(double maximumOutput) {
+      bool result = setDouble(OS_Table_LookupFields::MaximumOutput, maximumOutput);
+      return result;
+    }
+
+    void TableLookup_Impl::resetMaximumOutput() {
+      bool result = setString(OS_Table_LookupFields::MaximumOutput, "");
+      OS_ASSERT(result);
+    }
+
+    bool TableLookup_Impl::setOutputUnitType(const std::string& outputUnitType) {
+      bool result = setString(OS_Table_LookupFields::OutputUnitType, outputUnitType);
+      return result;
+    }
+
+    bool TableLookup_Impl::addOutputValue(double outputValue) {
+      WorkspaceExtensibleGroup eg = getObject<ModelObject>().pushExtensibleGroup().cast<WorkspaceExtensibleGroup>();
+      bool result = eg.setDouble(OS_Table_LookupExtensibleFields::OutputValue, outputValue);
+      if (!result) {
+        getObject<ModelObject>().eraseExtensibleGroup(eg.groupIndex());
+      }
+
+      return result;
+    }
+
+    bool TableLookup_Impl::removeOutputValue(unsigned groupIndex) {
+      bool result;
+
+      unsigned num = numberofOutputValues();
+      if (groupIndex < num) {
+        getObject<ModelObject>().eraseExtensibleGroup(groupIndex);
+        result = true;
+      } else {
+        result = false;
+      }
+      return result;
+    }
+
+    void TableLookup_Impl::removeAllOutputValues() {
+      this->clearExtensibleGroups();
+    }
+
+    std::vector<double> TableLookup_Impl::outputValues() const {
+      std::vector<double> result;
+      for (const auto& eg : extensibleGroups()) {
+        auto _d = eg.getDouble(OS_Table_LookupExtensibleFields::OutputValue);
+        OS_ASSERT(_d);
+        result.push_back(_d.get());
+      }
+      return result;
+    }
+
+    unsigned TableLookup_Impl::numberofOutputValues() const {
+      return numExtensibleGroups();
+    }
+
+    bool TableLookup_Impl::setOutputValues(const std::vector<double>& outputValues) {
+      bool result = true;
+
+      this->clearExtensibleGroups();
+      for (const auto& val : outputValues) {
+        result &= addOutputValue(val);
+      }
+      return result;
+    }
+
+    bool TableLookup_Impl::addIndependentVariable(const TableIndependentVariable& tableIndependentVariable) {
+      bool result = false;
+      auto modelObjectList = independentVariableList();
+      if (modelObjectList) {
+        result = modelObjectList->addModelObject(tableIndependentVariable);
+      }
+      return result;
+    }
+
+    bool TableLookup_Impl::removeIndependentVariable(const TableIndependentVariable& tableIndependentVariable) {
+      bool result = false;
+      auto modelObjectList = independentVariableList();
+      if (modelObjectList) {
+        modelObjectList->removeModelObject(tableIndependentVariable);
+        result = true;
+      }
+      return result;
+    }
+
+    void TableLookup_Impl::removeAllIndependentVariables() {
+      auto modelObjectList = independentVariableList();
+      if (modelObjectList) {
+        auto const modelObjects = modelObjectList->modelObjects();
+
+        for (const auto& elem : modelObjects) {
+          auto const modelObject = elem.optionalCast<TableIndependentVariable>();
+          if (modelObject) {
+            modelObjectList->removeModelObject(elem);
+          }
+        }
+      }
+    }
+
+    std::vector<TableIndependentVariable> TableLookup_Impl::independentVariables() const {
+      std::vector<TableIndependentVariable> result;
+      auto const modelObjectList = independentVariableList();
+      if (modelObjectList) {
+        auto const modelObjects = modelObjectList->modelObjects();
+
+        for (const auto& elem : modelObjects) {
+          auto const modelObject = elem.optionalCast<TableIndependentVariable>();
+          if (modelObject) {
+            result.push_back(modelObject.get());
+          }
+        }
+      }
+      return result;
+    }
+
+    bool TableLookup_Impl::setIndependentVariableList(const boost::optional<ModelObjectList>& modelObjectList) {
+      bool result(false);
+      if (modelObjectList) {
+        result = setPointer(OS_Table_LookupFields::IndependentVariableListName, modelObjectList.get().handle());
+      } else {
+        resetIndependentVariableList();
+        result = true;
+      }
+      return result;
+    }
+
+    void TableLookup_Impl::resetIndependentVariableList() {
+      bool result = setString(OS_Table_LookupFields::IndependentVariableListName, "");
+      OS_ASSERT(result);
+    }
+
+  }  // namespace detail
+
+  TableLookup::TableLookup(const Model& model) : Curve(TableLookup::iddObjectType(), model) {
+    OS_ASSERT(getImpl<detail::TableLookup_Impl>());
+
+    bool ok = true;
+    ok = setNormalizationMethod("None");
+    OS_ASSERT(ok);
+    ok = setNormalizationDivisor(1.0);
+    OS_ASSERT(ok);
+    ok = setOutputUnitType("Dimensionless");
+    OS_ASSERT(ok);
+
+    auto tableIndependentVariableList = ModelObjectList(model);
+    tableIndependentVariableList.setName(this->name().get() + " Independent Variable List");
+    ok = getImpl<detail::TableLookup_Impl>()->setIndependentVariableList(tableIndependentVariableList);
+    OS_ASSERT(ok);
+  }
+
+  IddObjectType TableLookup::iddObjectType() {
+    return {IddObjectType::OS_Table_Lookup};
+  }
+
+  std::vector<std::string> TableLookup::normalizationMethodValues() {
+    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_Table_LookupFields::NormalizationMethod);
+  }
+
+  std::vector<std::string> TableLookup::validNormalizationMethodValues() {
+    return normalizationMethodValues();
+  }
+
+  std::vector<std::string> TableLookup::outputUnitTypeValues() {
+    return getIddKeyNames(IddFactory::instance().getObject(iddObjectType()).get(), OS_Table_LookupFields::OutputUnitType);
+  }
+
+  std::vector<std::string> TableLookup::validOutputUnitTypeValues() {
+    return outputUnitTypeValues();
+  }
+
+  std::string TableLookup::normalizationMethod() const {
+    return getImpl<detail::TableLookup_Impl>()->normalizationMethod();
+  }
+
+  double TableLookup::normalizationDivisor() const {
+    return getImpl<detail::TableLookup_Impl>()->normalizationDivisor();
+  }
+
+  boost::optional<double> TableLookup::minimumOutput() const {
+    return getImpl<detail::TableLookup_Impl>()->minimumOutput();
+  }
+
+  boost::optional<double> TableLookup::maximumOutput() const {
+    return getImpl<detail::TableLookup_Impl>()->maximumOutput();
+  }
+
+  std::string TableLookup::outputUnitType() const {
+    return getImpl<detail::TableLookup_Impl>()->outputUnitType();
+  }
+
+  bool TableLookup::setNormalizationMethod(const std::string& normalizationMethod) {
+    return getImpl<detail::TableLookup_Impl>()->setNormalizationMethod(normalizationMethod);
+  }
+
+  bool TableLookup::setNormalizationDivisor(double normalizationDivisior) {
+    return getImpl<detail::TableLookup_Impl>()->setNormalizationDivisor(normalizationDivisior);
+  }
+
+  bool TableLookup::setMinimumOutput(double minimumOutput) {
+    return getImpl<detail::TableLookup_Impl>()->setMinimumOutput(minimumOutput);
+  }
+
+  void TableLookup::resetMinimumOutput() {
+    getImpl<detail::TableLookup_Impl>()->resetMinimumOutput();
+  }
+
+  bool TableLookup::setMaximumOutput(double maximumOutput) {
+    return getImpl<detail::TableLookup_Impl>()->setMaximumOutput(maximumOutput);
+  }
+
+  void TableLookup::resetMaximumOutput() {
+    getImpl<detail::TableLookup_Impl>()->resetMaximumOutput();
+  }
+
+  bool TableLookup::setOutputUnitType(const std::string& outputUnitType) {
+    return getImpl<detail::TableLookup_Impl>()->setOutputUnitType(outputUnitType);
+  }
+
+  bool TableLookup::addOutputValue(double outputValue) {
+    return getImpl<detail::TableLookup_Impl>()->addOutputValue(outputValue);
+  }
+
+  bool TableLookup::removeOutputValue(unsigned groupIndex) {
+    return getImpl<detail::TableLookup_Impl>()->removeOutputValue(groupIndex);
+  }
+
+  void TableLookup::removeAllOutputValues() {
+    getImpl<detail::TableLookup_Impl>()->removeAllOutputValues();
+  }
+
+  std::vector<double> TableLookup::outputValues() const {
+    return getImpl<detail::TableLookup_Impl>()->outputValues();
+  }
+
+  unsigned TableLookup::numberofOutputValues() const {
+    return getImpl<detail::TableLookup_Impl>()->numberofOutputValues();
+  }
+
+  bool TableLookup::setOutputValues(const std::vector<double>& outputValues) {
+    return getImpl<detail::TableLookup_Impl>()->setOutputValues(outputValues);
+  }
+
+  bool TableLookup::addIndependentVariable(const TableIndependentVariable& tableIndependentVariable) {
+    return getImpl<detail::TableLookup_Impl>()->addIndependentVariable(tableIndependentVariable);
+  }
+
+  bool TableLookup::removeIndependentVariable(const TableIndependentVariable& tableIndependentVariable) {
+    return getImpl<detail::TableLookup_Impl>()->removeIndependentVariable(tableIndependentVariable);
+  }
+
+  void TableLookup::removeAllIndependentVariables() {
+    getImpl<detail::TableLookup_Impl>()->removeAllIndependentVariables();
+  }
+
+  std::vector<TableIndependentVariable> TableLookup::independentVariables() const {
+    return getImpl<detail::TableLookup_Impl>()->independentVariables();
+  }
+
+  /// @cond
+  TableLookup::TableLookup(std::shared_ptr<detail::TableLookup_Impl> impl) : Curve(std::dynamic_pointer_cast<detail::Curve_Impl>(std::move(impl))) {}
+  /// @endcond
+
+}  // namespace model
+}  // namespace openstudio
diff --git a/src/model/TableLookup.hpp b/src/model/TableLookup.hpp
new file mode 100644
index 00000000000..7856ffbe84f
--- /dev/null
+++ b/src/model/TableLookup.hpp
@@ -0,0 +1,143 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#ifndef MODEL_TABLELOOKUP_HPP
+#define MODEL_TABLELOOKUP_HPP
+
+#include "ModelAPI.hpp"
+#include "Curve.hpp"
+
+namespace openstudio {
+namespace model {
+
+  class ModelObjectList;
+  class TableIndependentVariable;
+
+  namespace detail {
+
+    class TableLookup_Impl;
+
+  }  // namespace detail
+
+  /** TableLookup is a Curve that wraps the OpenStudio IDD object 'OS:Table:Lookup'. */
+  class MODEL_API TableLookup : public Curve
+  {
+   public:
+    /** @name Constructors and Destructors */
+    //@{
+
+    /** Create a TableLookup object with the specified number
+    * of independent variables.
+    */
+    explicit TableLookup(const Model& model);
+
+    virtual ~TableLookup() = default;
+
+    //@}
+
+    static IddObjectType iddObjectType();
+
+    static std::vector<std::string> normalizationMethodValues();
+    static std::vector<std::string> validNormalizationMethodValues();
+
+    static std::vector<std::string> outputUnitTypeValues();
+    static std::vector<std::string> validOutputUnitTypeValues();
+
+    /** @name Getters */
+    //@{
+
+    std::string normalizationMethod() const;
+
+    double normalizationDivisor() const;
+
+    boost::optional<double> minimumOutput() const;
+
+    boost::optional<double> maximumOutput() const;
+
+    std::string outputUnitType() const;
+
+    //@}
+    /** @name Setters */
+    //@{
+
+    bool setNormalizationMethod(const std::string& normalizationMethod);
+
+    bool setNormalizationDivisor(double normalizationDivisior);
+
+    bool setMinimumOutput(double minimumOutput);
+    void resetMinimumOutput();
+
+    bool setMaximumOutput(double maximumOutput);
+    void resetMaximumOutput();
+
+    bool setOutputUnitType(const std::string& outputUnitType);
+
+    //@}
+    /** @name Other */
+    //@{
+
+    bool addOutputValue(double outputValue);
+    bool removeOutputValue(unsigned groupIndex);
+    void removeAllOutputValues();
+    std::vector<double> outputValues() const;
+    unsigned numberofOutputValues() const;
+    // Directly set the Output Values from a vector, will delete any existing Output Values
+    bool setOutputValues(const std::vector<double>& outputValues);
+
+    bool addIndependentVariable(const TableIndependentVariable& tableIndependentVariable);
+    bool removeIndependentVariable(const TableIndependentVariable& tableIndependentVariable);
+    void removeAllIndependentVariables();
+    std::vector<TableIndependentVariable> independentVariables() const;
+
+    //@}
+   protected:
+    /// @cond
+    using ImplType = detail::TableLookup_Impl;
+
+    explicit TableLookup(std::shared_ptr<detail::TableLookup_Impl> impl);
+
+    friend class detail::TableLookup_Impl;
+    friend class Model;
+    friend class IdfObject;
+    friend class openstudio::detail::IdfObject_Impl;
+    /// @endcond
+   private:
+    REGISTER_LOGGER("openstudio.model.TableLookup");
+  };
+
+  /** \relates TableLookup*/
+  using OptionalTableLookup = boost::optional<TableLookup>;
+
+  /** \relates TableLookup*/
+  using TableLookupVector = std::vector<TableLookup>;
+
+}  // namespace model
+}  // namespace openstudio
+
+#endif  // MODEL_TABLELOOKUP_HPP
diff --git a/src/model/TableLookup_Impl.hpp b/src/model/TableLookup_Impl.hpp
new file mode 100644
index 00000000000..f04d2f8a09c
--- /dev/null
+++ b/src/model/TableLookup_Impl.hpp
@@ -0,0 +1,137 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#ifndef MODEL_TABLELOOKUP_IMPL_HPP
+#define MODEL_TABLELOOKUP_IMPL_HPP
+
+#include "ModelAPI.hpp"
+#include "Curve_Impl.hpp"
+
+namespace openstudio {
+namespace model {
+
+  class ModelObjectList;
+  class TableIndependentVariable;
+
+  namespace detail {
+
+    /** TableLookup_Impl is a Curve_Impl that is the implementation class for TableLookup.*/
+    class MODEL_API TableLookup_Impl : public Curve_Impl
+    {
+     public:
+      /** @name Constructors and Destructors */
+      //@{
+
+      TableLookup_Impl(const IdfObject& idfObject, Model_Impl* model, bool keepHandle);
+
+      TableLookup_Impl(const openstudio::detail::WorkspaceObject_Impl& other, Model_Impl* model, bool keepHandle);
+
+      TableLookup_Impl(const TableLookup_Impl& other, Model_Impl* model, bool keepHandle);
+
+      virtual ~TableLookup_Impl() = default;
+
+      //@}
+      /** @name Virtual Methods */
+      //@{
+
+      virtual const std::vector<std::string>& outputVariableNames() const override;
+
+      virtual IddObjectType iddObjectType() const override;
+
+      virtual int numVariables() const override;
+
+      virtual double evaluate(const std::vector<double>& independentVariables) const override;
+
+      virtual ModelObject clone(Model model) const override;
+
+      virtual std::vector<openstudio::IdfObject> remove() override;
+
+      //@}
+      /** @name Getters */
+      //@{
+
+      boost::optional<ModelObjectList> independentVariableList() const;
+
+      std::string normalizationMethod() const;
+
+      double normalizationDivisor() const;
+
+      boost::optional<double> minimumOutput() const;
+
+      boost::optional<double> maximumOutput() const;
+
+      std::string outputUnitType() const;
+
+      //@}
+      /** @name Setters */
+      //@{
+
+      bool setNormalizationMethod(const std::string& normalizationMethod);
+
+      bool setNormalizationDivisor(double normalizationDivisior);
+
+      bool setMinimumOutput(double minimumOutput);
+      void resetMinimumOutput();
+
+      bool setMaximumOutput(double maximumOutput);
+      void resetMaximumOutput();
+
+      bool setOutputUnitType(const std::string& outputUnitType);
+
+      //@}
+      /** @name Other */
+      //@{
+
+      bool addOutputValue(double outputValue);
+      bool removeOutputValue(unsigned groupIndex);
+      void removeAllOutputValues();
+      std::vector<double> outputValues() const;
+      bool setOutputValues(const std::vector<double>& outputValues);
+      unsigned numberofOutputValues() const;
+
+      bool addIndependentVariable(const TableIndependentVariable& tableIndependentVariable);
+      bool removeIndependentVariable(const TableIndependentVariable& tableIndependentVariable);
+      void removeAllIndependentVariables();
+      std::vector<TableIndependentVariable> independentVariables() const;
+
+      bool setIndependentVariableList(const boost::optional<ModelObjectList>& modelObjectList);
+      void resetIndependentVariableList();
+
+      //@}
+     protected:
+     private:
+      REGISTER_LOGGER("openstudio.model.TableLookup");
+    };
+
+  }  // namespace detail
+
+}  // namespace model
+}  // namespace openstudio
+
+#endif  // MODEL_TABLELOOKUP_IMPL_HPP
diff --git a/src/model/TableMultiVariableLookup.cpp b/src/model/TableMultiVariableLookup.cpp
index 2d957b99fcf..37a046acde0 100644
--- a/src/model/TableMultiVariableLookup.cpp
+++ b/src/model/TableMultiVariableLookup.cpp
@@ -27,6 +27,14 @@
 *  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ***********************************************************************************************************************/
 
+#if defined(_MSC_VER)
+#  pragma warning(push)
+#  pragma warning(disable : 4996)
+#elif (defined(__GNUC__))
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
 #include "TableMultiVariableLookup.hpp"
 #include "TableMultiVariableLookup_Impl.hpp"
 
@@ -906,6 +914,8 @@ namespace model {
     : Curve(TableMultiVariableLookup::iddObjectType(), model) {
     OS_ASSERT(getImpl<detail::TableMultiVariableLookup_Impl>());
 
+    LOG(Warn, "TableMultiVariableLookup is deprecated.  Use TableLookup instead.");
+
     // Check if numberofIndependentVariables between 1 and 5 included, otherwise THROW
     bool ok = getImpl<detail::TableMultiVariableLookup_Impl>()->setNumberofIndependentVariables(numberofIndependentVariables);
     if (!ok) {
@@ -1341,3 +1351,9 @@ namespace model {
 
 }  // namespace model
 }  // namespace openstudio
+
+#if defined(_MSC_VER)
+#  pragma warning(pop)
+#elif (defined(__GNUC__))
+#  pragma GCC diagnostic pop
+#endif
diff --git a/src/model/TableMultiVariableLookup.hpp b/src/model/TableMultiVariableLookup.hpp
index 47e4f5a4745..badd5431ee4 100644
--- a/src/model/TableMultiVariableLookup.hpp
+++ b/src/model/TableMultiVariableLookup.hpp
@@ -34,6 +34,7 @@
 #include "Curve.hpp"
 
 #include "../utilities/math/FloatCompare.hpp"
+#include "../utilities/core/Deprecated.hpp"
 
 namespace openstudio {
 
@@ -71,7 +72,7 @@ namespace model {
   MODEL_API std::ostream& operator<<(std::ostream& out, const openstudio::model::TableMultiVariableLookupPoint& point);
 
   /** TableMultiVariableLookup is a Curve that wraps the OpenStudio IDD object 'OS:Table:MultiVariableLookup'. */
-  class MODEL_API TableMultiVariableLookup : public Curve
+  class OS_DEPRECATED MODEL_API TableMultiVariableLookup : public Curve
   {
    public:
     /** @name Constructors and Destructors */
@@ -326,7 +327,7 @@ namespace model {
     //@}
    protected:
     /// @cond
-    typedef detail::TableMultiVariableLookup_Impl ImplType;
+    using ImplType = detail::TableMultiVariableLookup_Impl;
 
     explicit TableMultiVariableLookup(std::shared_ptr<detail::TableMultiVariableLookup_Impl> impl);
 
@@ -339,14 +340,27 @@ namespace model {
     REGISTER_LOGGER("openstudio.model.TableMultiVariableLookup");
   };
 
+#if defined(_MSC_VER)
+#  pragma warning(push)
+#  pragma warning(disable : 4996)
+#elif (defined(__GNUC__))
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
   /** \relates TableMultiVariableLookup*/
-  typedef boost::optional<TableMultiVariableLookup> OptionalTableMultiVariableLookup;
+  using OptionalTableMultiVariableLookup = boost::optional<TableMultiVariableLookup>;
 
   /** \relates TableMultiVariableLookup*/
-  typedef std::vector<TableMultiVariableLookup> TableMultiVariableLookupVector;
+  using TableMultiVariableLookupVector = std::vector<TableMultiVariableLookup>;
 
   /** \relates TableMultiVariableLookupPoint*/
-  typedef std::vector<TableMultiVariableLookupPoint> TableMultiVariableLookupPointVector;
+  using TableMultiVariableLookupPointVector = std::vector<TableMultiVariableLookupPoint>;
+#if defined(_MSC_VER)
+#  pragma warning(pop)
+#elif (defined(__GNUC__))
+#  pragma GCC diagnostic pop
+#endif
 
 }  // namespace model
 }  // namespace openstudio
diff --git a/src/osversion/VersionTranslator.cpp b/src/osversion/VersionTranslator.cpp
index 78da9b86397..0c46f7445fb 100644
--- a/src/osversion/VersionTranslator.cpp
+++ b/src/osversion/VersionTranslator.cpp
@@ -147,7 +147,7 @@ namespace osversion {
     m_updateMethods[VersionString("3.3.0")] = &VersionTranslator::update_3_2_1_to_3_3_0;
     m_updateMethods[VersionString("3.4.0")] = &VersionTranslator::update_3_3_0_to_3_4_0;
     m_updateMethods[VersionString("3.4.1")] = &VersionTranslator::update_3_4_0_to_3_4_1;
-    //m_updateMethods[VersionString("3.5.0")] = &VersionTranslator::defaultUpdate;
+    // m_updateMethods[VersionString("3.5.0")] = &VersionTranslator::defaultUpdate;
 
     // List of previous versions that may be updated to this one.
     //   - To increment the translator, add an entry for the version just released (branched for
@@ -6969,6 +6969,120 @@ namespace osversion {
       } else if (iddname == "OS:Material:AirWall") {
         m_untranslated.push_back(object);
 
+      } else if (iddname == "OS:Table:MultiVariableLookup") {
+
+        // Stage Data List becomes extensible list (Stage 1, Stage 2, etc.)
+        // ModelObjectList gets removed
+
+        IdfObject tableObject(idd_3_4_1.getObject("OS:Table:LookUp").get());
+
+        // Handle and name are copied
+        for (size_t i = 0; i < 2; ++i) {
+          if ((value = object.getString(i))) {
+            tableObject.setString(i, value.get());
+          }
+        }
+
+        std::string oriInterpMethod = object.getString(2, true).get();
+        std::string newInterpMethod = "Cubic";
+        std::string newExtrapMethod = "Constant";
+        if (openstudio::istringEqual(oriInterpMethod, "LagrangeInterpolationLinearExtrapolation")) {
+          newInterpMethod = "Cubic";
+          newExtrapMethod = "Linear";
+        } else if (istringEqual(oriInterpMethod, "LinearInterpolationOfTable")) {
+          newInterpMethod = "Linear";
+          newExtrapMethod = "Constant";
+        }
+
+        unsigned numIndVars = object.getInt(28).get();
+        unsigned numPoints = object.numExtensibleGroups() / (numIndVars + 1);
+
+        IdfObject varList(idd_3_4_1.getObject("OS:ModelObjectList").get());
+        std::string varListHandle = toString(createUUID());
+        varList.setString(0, varListHandle);
+        varList.setString(1, object.nameString() + "_IndependentVariableList");
+        tableObject.setString(2, varListHandle);
+
+        // If Normalization Reference is supplied
+        if (boost::optional<double> normref_ = object.getDouble(9)) {
+          tableObject.setString(3, "DivisorOnly");
+          tableObject.setDouble(4, normref_.get());
+        } else {
+          // Same as Ctor for these required-fields
+          tableObject.setString(3, "None");
+          tableObject.setDouble(4, 1.0);
+        }
+
+        // Min/Maximum OutputValue
+        if (boost::optional<double> minVal_ = object.getDouble(20)) {
+          tableObject.setDouble(5, minVal_.get());
+        }
+        if (boost::optional<double> minVal_ = object.getDouble(21)) {
+          tableObject.setDouble(6, minVal_.get());
+        }
+
+        // Output Unit Type (will pull "Dimensionless" as default if not set, which matches the Ctor)
+        tableObject.setString(7, object.getString(27, true).get());
+
+        // 8, 9, 10 are External File stuff, left blank
+
+        // Now extensible Output Values
+        for (unsigned k = 0; k < numPoints; ++k) {
+          tableObject.pushExtensibleGroup({object.getExtensibleGroup(numIndVars + (k * (numIndVars + 1))).getString(0).get()});
+        }
+
+        std::vector<IdfObject> varsAdded;
+
+        for (unsigned i = 0; i < numIndVars; ++i) {
+          IdfObject& var = varsAdded.emplace_back(idd_3_4_1.getObject("OS:Table:IndependentVariable").get());
+          std::string varHandle = toString(createUUID());
+          var.setString(0, varHandle);
+          varList.pushExtensibleGroup({varHandle});
+          var.setName(object.nameString() + "_IndependentVariable_" + std::to_string(i));
+
+          var.setString(2, newInterpMethod);
+          var.setString(3, newExtrapMethod);
+
+          // Min Max
+          if (auto minVal_ = object.getDouble(10 + 2 * i)) {
+            var.setDouble(4, minVal_.get());
+          }
+          if (auto maxVal_ = object.getDouble(11 + 2 * i)) {
+            var.setDouble(5, maxVal_.get());
+          }
+
+          // 6 - Normalization Reference Value is left blank
+
+          // 7 - Unit Type
+          var.setString(7, object.getString(22 + i, true).get());
+
+          // 8, 9, 10 are External File stuff, left blank
+
+          // Now extensible values: keep only unique, and sorted
+          std::vector<double> xValues;
+          for (unsigned k = 0; k < numPoints; ++k) {
+            xValues.push_back(object.getExtensibleGroup(i + (k * (numIndVars + 1))).getDouble(0).get());
+          }
+
+          std::sort(xValues.begin(), xValues.end());
+          xValues.erase(std::unique(xValues.begin(), xValues.end()), xValues.end());
+
+          for (auto& val : xValues) {
+            var.pushExtensibleGroup().setDouble(0, val);
+          }
+        }
+
+        m_refactored.push_back(RefactoredObjectData(object, tableObject));
+        m_new.push_back(varList);
+
+        ss << tableObject;
+        ss << varList;
+
+        for (auto&& varAdded : varsAdded) {
+          ss << varAdded;
+          m_new.emplace_back(std::move(varAdded));
+        }
+
         // No-op
       } else {
         ss << object;
@@ -6979,5 +7093,9 @@ namespace osversion {
 
   }  // end update_3_4_0_to_3_4_1
 
+  /*   std::string VersionTranslator::update_3_4_1_to_3_5_0(const IdfFile& idf_3_4_1, const IddFileAndFactoryWrapper& idd_3_5_0) {
+
+  }  // end update_3_4_1_to_3_5_0 */
+
 }  // namespace osversion
 }  // namespace openstudio
diff --git a/src/osversion/VersionTranslator.hpp b/src/osversion/VersionTranslator.hpp
index 3a0fa670989..47b61f44bdb 100644
--- a/src/osversion/VersionTranslator.hpp
+++ b/src/osversion/VersionTranslator.hpp
@@ -252,6 +252,7 @@ namespace osversion {
     std::string update_3_2_1_to_3_3_0(const IdfFile& idf_3_2_1, const IddFileAndFactoryWrapper& idd_3_3_0);
     std::string update_3_3_0_to_3_4_0(const IdfFile& idf_3_3_0, const IddFileAndFactoryWrapper& idd_3_4_0);
     std::string update_3_4_0_to_3_4_1(const IdfFile& idf_3_4_0, const IddFileAndFactoryWrapper& idd_3_4_1);
+    // std::string update_3_4_1_to_3_5_0(const IdfFile& idf_3_4_1, const IddFileAndFactoryWrapper& idd_3_5_0)
 
     IdfObject updateUrlField_0_7_1_to_0_7_2(const IdfObject& object, unsigned index);
 
diff --git a/src/sdd/ForwardTranslator.cpp b/src/sdd/ForwardTranslator.cpp
index f75297d93f3..2a7c6c983b9 100644
--- a/src/sdd/ForwardTranslator.cpp
+++ b/src/sdd/ForwardTranslator.cpp
@@ -81,7 +81,8 @@
 #include "../model/CurveRectangularHyperbola2.hpp"
 #include "../model/CurveSigmoid.hpp"
 #include "../model/CurveTriquadratic.hpp"
-#include "../model/TableMultiVariableLookup.hpp"
+#include "../model/TableLookup.hpp"
+#include "../model/TableIndependentVariable.hpp"
 #include "../model/DefaultConstructionSet.hpp"
 #include "../model/DefaultScheduleSet.hpp"
 #include "../model/DefaultSurfaceConstructions.hpp"
@@ -519,7 +520,8 @@ namespace sdd {
     m_ignoreTypes.push_back(model::SkyTemperature::iddObjectType());
     m_ignoreTypes.push_back(model::StandardsInformationConstruction::iddObjectType());
     m_ignoreTypes.push_back(model::StandardsInformationMaterial::iddObjectType());
-    m_ignoreTypes.push_back(model::TableMultiVariableLookup::iddObjectType());
+    m_ignoreTypes.push_back(model::TableLookup::iddObjectType());
+    m_ignoreTypes.push_back(model::TableIndependentVariable::iddObjectType());
     m_ignoreTypes.push_back(model::Timestep::iddObjectType());
     m_ignoreTypes.push_back(model::UtilityBill::iddObjectType());
     m_ignoreTypes.push_back(model::Version::iddObjectType());
diff --git a/src/sdd/MapHVAC.cpp b/src/sdd/MapHVAC.cpp
index 93e23288bdc..3313efd3b8c 100644
--- a/src/sdd/MapHVAC.cpp
+++ b/src/sdd/MapHVAC.cpp
@@ -80,8 +80,8 @@
 #include "../model/CurveQuadratic_Impl.hpp"
 #include "../model/Curve.hpp"
 #include "../model/Curve_Impl.hpp"
-#include "../model/TableMultiVariableLookup.hpp"
-#include "../model/TableMultiVariableLookup_Impl.hpp"
+#include "../model/TableLookup.hpp"
+#include "../model/TableIndependentVariable.hpp"
 #include "../model/Duct.hpp"
 #include "../model/Duct_Impl.hpp"
 #include "../model/ScheduleRuleset.hpp"
@@ -233,6 +233,7 @@
 #include "../utilities/geometry/Geometry.hpp"
 #include "../utilities/geometry/Point3d.hpp"
 #include "../utilities/geometry/BoundingBox.hpp"
+#include "model/TableIndependentVariable.hpp"
 
 #include <cmath>
 #include <functional>
@@ -8080,7 +8081,7 @@ namespace sdd {
       return boost::none;
     }
 
-    model::TableMultiVariableLookup table(model, 2);
+    model::TableLookup table(model);
 
     auto nameElement = element.child("Name");
     table.setName(nameElement.text().as_string());
@@ -8107,6 +8108,11 @@ namespace sdd {
       return table;
     }
 
+    model::TableIndependentVariable var1(model);
+    model::TableIndependentVariable var2(model);
+    table.addIndependentVariable(var1);
+    table.addIndependentVariable(var2);
+
     for (std::vector<pugi::xml_node>::size_type i = 0; i < arrayVar1Elements.size(); i++) {
 
       boost::optional<double> _arrayVar1 = lexicalCastToDouble(arrayVar1Elements[i]);
@@ -8114,7 +8120,9 @@ namespace sdd {
       boost::optional<double> _arrayOut = lexicalCastToDouble(arrayOutElements[i]);
 
       if (_arrayVar1 && _arrayVar2 && _arrayOut) {
-        table.addPoint(_arrayVar1.get(), _arrayVar2.get(), _arrayOut.get());
+        var1.addValue(_arrayVar1.get());
+        var2.addValue(_arrayVar2.get());
+        table.addOutputValue(_arrayOut.get());
       }
     }
 
@@ -8122,53 +8130,68 @@ namespace sdd {
 
     boost::optional<double> _minOut = lexicalCastToDouble(element.child("MinOut"));
     if (_minOut) {
-      table.setMinimumTableOutput(_minOut.get());
+      table.setMinimumOutput(_minOut.get());
     }
 
     boost::optional<double> _maxOut = lexicalCastToDouble(element.child("MaxOut"));
     if (_maxOut) {
-      table.setMaximumTableOutput(_maxOut.get());
+      table.setMaximumOutput(_maxOut.get());
     }
 
     text = element.child("UnitTypeVar1").text().as_string();
-    table.setInputUnitTypeforX1(text);
+    var1.setUnitType(text);
 
     text = element.child("UnitTypeVar2").text().as_string();
-    table.setInputUnitTypeforX2(text);
+    var2.setUnitType(text);
 
     text = element.child("UnitTypeOut").text().as_string();
     table.setOutputUnitType(text);
 
     boost::optional<double> _minVar1 = lexicalCastToDouble(element.child("MinVar1"));
     if (_minVar1) {
-      table.setMinimumValueofX1(_minVar1.get());
+      var1.setMinimumValue(_minVar1.get());
     }
 
     boost::optional<double> _maxVar1 = lexicalCastToDouble(element.child("MaxVar1"));
     if (_maxVar1) {
-      table.setMaximumValueofX1(_maxVar1.get());
+      var1.setMaximumValue(_maxVar1.get());
     }
 
     boost::optional<double> _minVar2 = lexicalCastToDouble(element.child("MinVar2"));
     if (_minVar2) {
-      table.setMinimumValueofX2(_minVar2.get());
+      var2.setMinimumValue(_minVar2.get());
     }
 
     boost::optional<double> _maxVar2 = lexicalCastToDouble(element.child("MaxVar2"));
     if (_maxVar2) {
-      table.setMaximumValueofX2(_maxVar2.get());
+      var2.setMaximumValue(_maxVar2.get());
     }
 
     text = element.child("InterpMthd").text().as_string();
-    table.setInterpolationMethod(text);
+    std::string newInterpMethod = "Cubic";
+    std::string newExtrapMethod = "Constant";
+    if (openstudio::istringEqual(text, "LagrangeInterpolationLinearExtrapolation")) {
+      newInterpMethod = "Cubic";
+      newExtrapMethod = "Linear";
+    } else if (istringEqual(text, "LinearInterpolationOfTable")) {
+      newInterpMethod = "Linear";
+      newExtrapMethod = "Constant";
+    }
+
+    var1.setInterpolationMethod(newInterpMethod);
+    var1.setExtrapolationMethod(newExtrapMethod);
+    var2.setInterpolationMethod(newInterpMethod);
+    var2.setExtrapolationMethod(newExtrapMethod);
 
     boost::optional<double> _normalizationPt = lexicalCastToDouble(element.child("NormalizationPt"));
     if (_normalizationPt) {
-      table.setNormalizationReference(_normalizationPt.get());
+      table.setNormalizationMethod("DivisorOnly");
+      table.setNormalizationDivisor(_normalizationPt.get());
     }
 
-    text = element.child("Type").text().as_string();
-    table.setCurveType(text);
+    // Now ignored
+    //text = element.child("Type").text().as_string();
+    // table.setCurveType(text);
 
     return table;
   }
@@ -8179,7 +8202,9 @@ namespace sdd {
       return boost::none;
     }
 
-    model::TableMultiVariableLookup table(model, 1);
+    model::TableLookup table(model);
+    model::TableIndependentVariable var1(model);
+    table.addIndependentVariable(var1);
 
     auto nameElement = element.child("Name");
     table.setName(nameElement.text().as_string());
@@ -8204,42 +8229,58 @@ namespace sdd {
       boost::optional<double> _arrayOut = lexicalCastToDouble(arrayOutElements[i]);
 
       if (_arrayVar1 && _arrayOut) {
-        table.addPoint(_arrayVar1.get(), _arrayOut.get());
+        var1.addValue(_arrayVar1.get());
+        table.addOutputValue(_arrayOut.get());
       }
     }
 
     boost::optional<double> _minOut = lexicalCastToDouble(element.child("MinOut"));
     if (_minOut) {
-      table.setMinimumTableOutput(_minOut.get());
+      table.setMinimumOutput(_minOut.get());
     }
 
     boost::optional<double> _maxOut = lexicalCastToDouble(element.child("MaxOut"));
     if (_maxOut) {
-      table.setMaximumTableOutput(_maxOut.get());
+      table.setMaximumOutput(_maxOut.get());
     }
 
-    table.setInputUnitTypeforX1(element.child("UnitTypeVar1").text().as_string());
+    var1.setUnitType(element.child("UnitTypeVar1").text().as_string());
 
     table.setOutputUnitType(element.child("UnitTypeOut").text().as_string());
 
     boost::optional<double> _minVar1 = lexicalCastToDouble(element.child("MinVar1"));
     if (_minVar1) {
-      table.setMinimumValueofX1(_minVar1.get());
+      var1.setMinimumValue(_minVar1.get());
     }
 
     boost::optional<double> _maxVar1 = lexicalCastToDouble(element.child("MaxVar1"));
     if (_maxVar1) {
-      table.setMaximumValueofX1(_maxVar1.get());
+      var1.setMaximumValue(_maxVar1.get());
+    }
+
+    auto text = element.child("InterpMthd").text().as_string();
+    std::string newInterpMethod = "Cubic";
+    std::string newExtrapMethod = "Constant";
+    if (openstudio::istringEqual(text, "LagrangeInterpolationLinearExtrapolation")) {
+      newInterpMethod = "Cubic";
+      newExtrapMethod = "Linear";
+    } else if (istringEqual(text, "LinearInterpolationOfTable")) {
+      newInterpMethod = "Linear";
+      newExtrapMethod = "Constant";
     }
 
-    table.setInterpolationMethod(element.child("InterpMthd").text().as_string());
+    var1.setInterpolationMethod(newInterpMethod);
+    var1.setExtrapolationMethod(newExtrapMethod);
 
     boost::optional<double> _normalizationPt = lexicalCastToDouble(element.child("NormalizationPt"));
     if (_normalizationPt) {
-      table.setNormalizationReference(_normalizationPt.get());
+      table.setNormalizationMethod("DivisorOnly");
+      table.setNormalizationDivisor(_normalizationPt.get());
     }
 
-    table.setCurveType(element.child("Type").text().as_string());
+    // Now ignored
+    //text = element.child("Type").text().as_string();
+    // table.setCurveType(text);
 
     return table;
   }


Test patch:
diff --git a/src/model/test/CoilCoolingFourPipeBeam_GTest.cpp b/src/model/test/CoilCoolingFourPipeBeam_GTest.cpp
index 3ea965f5909..3ff2d0a0bc2 100644
--- a/src/model/test/CoilCoolingFourPipeBeam_GTest.cpp
+++ b/src/model/test/CoilCoolingFourPipeBeam_GTest.cpp
@@ -34,7 +34,7 @@
 #include "../CoilCoolingFourPipeBeam.hpp"
 #include "../CoilCoolingFourPipeBeam_Impl.hpp"
 #include "../CurveLinear.hpp"
-#include "../TableMultiVariableLookup.hpp"
+#include "../TableLookup.hpp"
 #include "../PlantLoop.hpp"
 #include "../Node.hpp"
 
@@ -85,12 +85,12 @@ TEST_F(ModelFixture, CoilCoolingFourPipeBeam_Getters_Setters) {
   EXPECT_EQ(capModFuncOfTempDiff.handle(), coil.beamCoolingCapacityTemperatureDifferenceModificationFactorCurve().handle());
 
   // A6: Beam Cooling Capacity Air Flow Modification Factor Curve Name (UniVariateCurves // UniVariateTables)
-  TableMultiVariableLookup coolCapModFuncOfSAFlow(m, 1);
+  TableLookup coolCapModFuncOfSAFlow(m);
   EXPECT_TRUE(coil.setBeamCoolingCapacityAirFlowModificationFactorCurve(coolCapModFuncOfSAFlow));
   EXPECT_EQ(coolCapModFuncOfSAFlow.handle(), coil.beamCoolingCapacityAirFlowModificationFactorCurve().handle());
 
   // A7: Beam Cooling Capacity Chilled Water Flow Modification Factor Curve Name (UniVariateCurves // UniVariateTables)
-  TableMultiVariableLookup capModFuncOfWaterFlow(m, 1);
+  TableLookup capModFuncOfWaterFlow(m);
   EXPECT_TRUE(coil.setBeamCoolingCapacityChilledWaterFlowModificationFactorCurve(capModFuncOfWaterFlow));
   EXPECT_EQ(capModFuncOfWaterFlow.handle(), coil.beamCoolingCapacityChilledWaterFlowModificationFactorCurve().handle());
 }
diff --git a/src/model/test/CoilHeatingFourPipeBeam_GTest.cpp b/src/model/test/CoilHeatingFourPipeBeam_GTest.cpp
index 7fbe310d927..d27fb2038a6 100644
--- a/src/model/test/CoilHeatingFourPipeBeam_GTest.cpp
+++ b/src/model/test/CoilHeatingFourPipeBeam_GTest.cpp
@@ -34,7 +34,7 @@
 #include "../CoilHeatingFourPipeBeam.hpp"
 #include "../CoilHeatingFourPipeBeam_Impl.hpp"
 #include "../CurveLinear.hpp"
-#include "../TableMultiVariableLookup.hpp"
+#include "../TableLookup.hpp"
 #include "../PlantLoop.hpp"
 #include "../Node.hpp"
 
@@ -85,12 +85,12 @@ TEST_F(ModelFixture, CoilHeatingFourPipeBeam_Getters_Setters) {
   EXPECT_EQ(capModFuncOfTempDiff.handle(), coil.beamHeatingCapacityTemperatureDifferenceModificationFactorCurve().handle());
 
   // A6: Beam Heating Capacity Air Flow Modification Factor Curve Name (UniVariateCurves // UniVariateTables)
-  TableMultiVariableLookup heatCapModFuncOfSAFlow(m, 1);
+  TableLookup heatCapModFuncOfSAFlow(m);
   EXPECT_TRUE(coil.setBeamHeatingCapacityAirFlowModificationFactorCurve(heatCapModFuncOfSAFlow));
   EXPECT_EQ(heatCapModFuncOfSAFlow.handle(), coil.beamHeatingCapacityAirFlowModificationFactorCurve().handle());
 
   // A7: Beam Heating Capacity Hot Water Flow Modification Factor Curve Name (UniVariateCurves // UniVariateTables)
-  TableMultiVariableLookup capModFuncOfWaterFlow(m, 1);
+  TableLookup capModFuncOfWaterFlow(m);
   EXPECT_TRUE(coil.setBeamHeatingCapacityHotWaterFlowModificationFactorCurve(capModFuncOfWaterFlow));
   EXPECT_EQ(capModFuncOfWaterFlow.handle(), coil.beamHeatingCapacityHotWaterFlowModificationFactorCurve().handle());
 }
diff --git a/src/model/test/CoilWaterHeatingAirToWaterHeatPump_GTest.cpp b/src/model/test/CoilWaterHeatingAirToWaterHeatPump_GTest.cpp
index 4aec1474166..a7e95c95876 100644
--- a/src/model/test/CoilWaterHeatingAirToWaterHeatPump_GTest.cpp
+++ b/src/model/test/CoilWaterHeatingAirToWaterHeatPump_GTest.cpp
@@ -32,9 +32,7 @@
 #include "../CoilWaterHeatingAirToWaterHeatPump.hpp"
 #include "../CoilWaterHeatingAirToWaterHeatPump_Impl.hpp"
 #include "../Curve.hpp"
-#include "../Curve_Impl.hpp"
-#include "../TableMultiVariableLookup.hpp"
-#include "../TableMultiVariableLookup_Impl.hpp"
+#include "../TableLookup.hpp"
 
 using namespace openstudio;
 using namespace openstudio::model;
@@ -94,13 +92,13 @@ TEST_F(ModelFixture, CoilWaterHeatingAirToWaterHeatPump) {
   {
     Model m;
 
-    TableMultiVariableLookup heatingCapacityFunctionofTemperatureCurve(m, 1);
-    TableMultiVariableLookup heatingCapacityFunctionofAirFlowFractionCurve(m, 1);
-    TableMultiVariableLookup heatingCapacityFunctionofWaterFlowFractionCurve(m, 1);
-    TableMultiVariableLookup heatingCOPFunctionofTemperatureCurve(m, 1);
-    TableMultiVariableLookup heatingCOPFunctionofAirFlowFractionCurve(m, 1);
-    TableMultiVariableLookup heatingCOPFunctionofWaterFlowFractionCurve(m, 1);
-    TableMultiVariableLookup partLoadFractionCorrelationCurve(m, 1);
+    TableLookup heatingCapacityFunctionofTemperatureCurve(m);
+    TableLookup heatingCapacityFunctionofAirFlowFractionCurve(m);
+    TableLookup heatingCapacityFunctionofWaterFlowFractionCurve(m);
+    TableLookup heatingCOPFunctionofTemperatureCurve(m);
+    TableLookup heatingCOPFunctionofAirFlowFractionCurve(m);
+    TableLookup heatingCOPFunctionofWaterFlowFractionCurve(m);
+    TableLookup partLoadFractionCorrelationCurve(m);
 
     CoilWaterHeatingAirToWaterHeatPump coil(m, heatingCapacityFunctionofTemperatureCurve, heatingCapacityFunctionofAirFlowFractionCurve,
                                             heatingCapacityFunctionofWaterFlowFractionCurve, heatingCOPFunctionofTemperatureCurve,
diff --git a/src/model/test/TableIndependentVariable_GTest.cpp b/src/model/test/TableIndependentVariable_GTest.cpp
new file mode 100644
index 00000000000..987ed383494
--- /dev/null
+++ b/src/model/test/TableIndependentVariable_GTest.cpp
@@ -0,0 +1,148 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include <gtest/gtest.h>
+#include "ModelFixture.hpp"
+#include "../TableIndependentVariable.hpp"
+#include "../TableIndependentVariable_Impl.hpp"
+#include "../TableLookup.hpp"
+#include "../TableLookup_Impl.hpp"
+
+using namespace openstudio;
+using namespace openstudio::model;
+
+TEST_F(ModelFixture, TableIndependentVariable_GettersSetters) {
+
+  Model m;
+  TableIndependentVariable tableIndependentVariable(m);
+
+  // Defaults
+  EXPECT_EQ("Linear", tableIndependentVariable.interpolationMethod());
+  EXPECT_EQ("Constant", tableIndependentVariable.extrapolationMethod());
+  EXPECT_FALSE(tableIndependentVariable.minimumValue());
+  EXPECT_FALSE(tableIndependentVariable.maximumValue());
+  EXPECT_FALSE(tableIndependentVariable.normalizationReferenceValue());
+  EXPECT_EQ("Dimensionless", tableIndependentVariable.unitType());
+  EXPECT_EQ(0u, tableIndependentVariable.tableLookups().size());
+
+  // Interpolation Method: Required String
+  EXPECT_TRUE(tableIndependentVariable.setInterpolationMethod("Cubic"));
+  EXPECT_EQ("Cubic", tableIndependentVariable.interpolationMethod());
+  // Bad Value
+  EXPECT_FALSE(tableIndependentVariable.setInterpolationMethod("BADENUM"));
+  EXPECT_EQ("Cubic", tableIndependentVariable.interpolationMethod());
+
+  // Extrapolation Method: Required String
+  EXPECT_TRUE(tableIndependentVariable.setExtrapolationMethod("Linear"));
+  EXPECT_EQ("Linear", tableIndependentVariable.extrapolationMethod());
+  // Bad Value
+  EXPECT_FALSE(tableIndependentVariable.setExtrapolationMethod("BADENUM"));
+  EXPECT_EQ("Linear", tableIndependentVariable.extrapolationMethod());
+
+  // Minimum Value: Optional Double
+  EXPECT_TRUE(tableIndependentVariable.setMinimumValue(0.5));
+  ASSERT_TRUE(tableIndependentVariable.minimumValue());
+  EXPECT_EQ(0.5, tableIndependentVariable.minimumValue().get());
+  tableIndependentVariable.resetMinimumValue();
+  EXPECT_FALSE(tableIndependentVariable.minimumValue());
+
+  // Maximum Value: Optional Double
+  EXPECT_TRUE(tableIndependentVariable.setMaximumValue(0.6));
+  ASSERT_TRUE(tableIndependentVariable.maximumValue());
+  EXPECT_EQ(0.6, tableIndependentVariable.maximumValue().get());
+  tableIndependentVariable.resetMaximumValue();
+  EXPECT_FALSE(tableIndependentVariable.maximumValue());
+
+  // Normalization Reference Value: Optional Double
+  EXPECT_TRUE(tableIndependentVariable.setNormalizationReferenceValue(0.7));
+  ASSERT_TRUE(tableIndependentVariable.normalizationReferenceValue());
+  EXPECT_EQ(0.7, tableIndependentVariable.normalizationReferenceValue().get());
+  tableIndependentVariable.resetNormalizationReferenceValue();
+  EXPECT_FALSE(tableIndependentVariable.normalizationReferenceValue());
+
+  // Unit Type: Required String
+  EXPECT_TRUE(tableIndependentVariable.setUnitType("Temperature"));
+  EXPECT_EQ("Temperature", tableIndependentVariable.unitType());
+  // Bad Value
+  EXPECT_FALSE(tableIndependentVariable.setUnitType("BADENUM"));
+  EXPECT_EQ("Temperature", tableIndependentVariable.unitType());
+
+  // Values
+  EXPECT_TRUE(tableIndependentVariable.addValue(1.0));
+  EXPECT_EQ(1u, tableIndependentVariable.values().size());
+  EXPECT_EQ(1u, tableIndependentVariable.numberofValues());
+  EXPECT_TRUE(tableIndependentVariable.addValue(2.0));
+  EXPECT_TRUE(tableIndependentVariable.addValue(3.0));
+  EXPECT_EQ(3u, tableIndependentVariable.numberofValues());
+  {
+    std::vector<double> values = tableIndependentVariable.values();
+    ASSERT_EQ(3u, values.size());
+    EXPECT_EQ(1.0, values[0]);
+    EXPECT_EQ(2.0, values[1]);
+    EXPECT_EQ(3.0, values[2]);
+  }
+
+  EXPECT_TRUE(tableIndependentVariable.removeValue(1));
+  {
+    std::vector<double> values = tableIndependentVariable.values();
+    ASSERT_EQ(2, values.size());
+    EXPECT_EQ(1.0, values[0]);
+    EXPECT_EQ(3.0, values[1]);
+  }
+
+  tableIndependentVariable.removeAllValues();
+  EXPECT_EQ(0u, tableIndependentVariable.values().size());
+  EXPECT_EQ(0u, tableIndependentVariable.numberofValues());
+
+  EXPECT_TRUE(tableIndependentVariable.addValue(10.0));
+  EXPECT_EQ(1u, tableIndependentVariable.values().size());
+
+  // Clears any existing values first
+  EXPECT_TRUE(tableIndependentVariable.setValues({1.0, 2.0, 3.0, 4.0}));
+  EXPECT_EQ(4u, tableIndependentVariable.numberofValues());
+  {
+    std::vector<double> values = tableIndependentVariable.values();
+    ASSERT_EQ(4u, values.size());
+    EXPECT_EQ(1.0, values[0]);
+    EXPECT_EQ(2.0, values[1]);
+    EXPECT_EQ(3.0, values[2]);
+    EXPECT_EQ(4.0, values[3]);
+  }
+
+  // Convenience method to get parent TableLookup(s)
+  TableLookup tableLookup1(m);
+  EXPECT_TRUE(tableLookup1.addIndependentVariable(tableIndependentVariable));
+  EXPECT_EQ(1u, tableIndependentVariable.tableLookups().size());
+
+  TableLookup tableLookup2(m);
+  EXPECT_TRUE(tableLookup2.addIndependentVariable(tableIndependentVariable));
+  TableLookup tableLookup3(m);
+  EXPECT_TRUE(tableLookup3.addIndependentVariable(tableIndependentVariable));
+  EXPECT_EQ(3u, tableIndependentVariable.tableLookups().size());
+}
diff --git a/src/model/test/TableLookup_GTest.cpp b/src/model/test/TableLookup_GTest.cpp
new file mode 100644
index 00000000000..68d84ced1a6
--- /dev/null
+++ b/src/model/test/TableLookup_GTest.cpp
@@ -0,0 +1,249 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include <gtest/gtest.h>
+
+#include "ModelFixture.hpp"
+
+#include "../TableLookup.hpp"
+#include "../TableLookup_Impl.hpp"
+#include "../TableIndependentVariable.hpp"
+#include "../TableIndependentVariable_Impl.hpp"
+#include "../ModelObjectList.hpp"
+#include "../ModelObjectList_Impl.hpp"
+
+using namespace openstudio;
+using namespace openstudio::model;
+
+TEST_F(ModelFixture, TableLookup_GettersSetters) {
+  Model m;
+  TableLookup tableLookup(m);
+
+  // Defaults
+  EXPECT_EQ("None", tableLookup.normalizationMethod());
+  EXPECT_EQ(1.0, tableLookup.normalizationDivisor());
+  EXPECT_FALSE(tableLookup.minimumOutput());
+  EXPECT_FALSE(tableLookup.maximumOutput());
+  EXPECT_EQ("Dimensionless", tableLookup.outputUnitType());
+  EXPECT_EQ(0u, tableLookup.outputValues().size());
+  EXPECT_EQ(0u, tableLookup.independentVariables().size());
+
+  // Normalization Method: Required String
+  EXPECT_TRUE(tableLookup.setNormalizationMethod("DivisorOnly"));
+  EXPECT_EQ("DivisorOnly", tableLookup.normalizationMethod());
+  // Bad Value
+  EXPECT_FALSE(tableLookup.setNormalizationMethod("BADENUM"));
+  EXPECT_EQ("DivisorOnly", tableLookup.normalizationMethod());
+
+  // Normalization Divisor: Required Double
+  EXPECT_TRUE(tableLookup.setNormalizationDivisor(0.5));
+  EXPECT_EQ(0.5, tableLookup.normalizationDivisor());
+
+  // Minimum Output: Optional Double
+  EXPECT_TRUE(tableLookup.setMinimumOutput(0.6));
+  ASSERT_TRUE(tableLookup.minimumOutput());
+  EXPECT_EQ(0.6, tableLookup.minimumOutput().get());
+  tableLookup.resetMinimumOutput();
+  EXPECT_FALSE(tableLookup.minimumOutput());
+
+  // Maximum Output: Optional Double
+  EXPECT_TRUE(tableLookup.setMaximumOutput(0.7));
+  ASSERT_TRUE(tableLookup.maximumOutput());
+  EXPECT_EQ(0.7, tableLookup.maximumOutput().get());
+  tableLookup.resetMaximumOutput();
+  EXPECT_FALSE(tableLookup.maximumOutput());
+
+  // Output Unit Type: Required String
+  EXPECT_TRUE(tableLookup.setOutputUnitType("Capacity"));
+  EXPECT_EQ("Capacity", tableLookup.outputUnitType());
+  // Bad Value
+  EXPECT_FALSE(tableLookup.setOutputUnitType("BADENUM"));
+  EXPECT_EQ("Capacity", tableLookup.outputUnitType());
+
+  // OutputValues
+  EXPECT_TRUE(tableLookup.addOutputValue(1.0));
+  EXPECT_EQ(1u, tableLookup.outputValues().size());
+  EXPECT_TRUE(tableLookup.addOutputValue(2.0));
+  EXPECT_TRUE(tableLookup.addOutputValue(3.0));
+  EXPECT_EQ(3u, tableLookup.numberofOutputValues());
+  {
+    std::vector<double> outputValues = tableLookup.outputValues();
+    ASSERT_EQ(3u, outputValues.size());
+    EXPECT_EQ(1.0, outputValues[0]);
+    EXPECT_EQ(2.0, outputValues[1]);
+    EXPECT_EQ(3.0, outputValues[2]);
+  }
+
+  EXPECT_TRUE(tableLookup.removeOutputValue(1));
+  {
+    std::vector<double> outputValues = tableLookup.outputValues();
+    ASSERT_EQ(2, outputValues.size());
+    EXPECT_EQ(1.0, outputValues[0]);
+    EXPECT_EQ(3.0, outputValues[1]);
+  }
+
+  tableLookup.removeAllOutputValues();
+  EXPECT_EQ(0u, tableLookup.outputValues().size());
+  EXPECT_EQ(0u, tableLookup.numberofOutputValues());
+
+  EXPECT_TRUE(tableLookup.addOutputValue(10.0));
+  EXPECT_EQ(1u, tableLookup.outputValues().size());
+
+  // Clears any existing values first
+  EXPECT_TRUE(tableLookup.setOutputValues({1.0, 2.0, 3.0, 4.0}));
+  EXPECT_EQ(4u, tableLookup.numberofOutputValues());
+  {
+    std::vector<double> outputValues = tableLookup.outputValues();
+    ASSERT_EQ(4u, outputValues.size());
+    EXPECT_EQ(1.0, outputValues[0]);
+    EXPECT_EQ(2.0, outputValues[1]);
+    EXPECT_EQ(3.0, outputValues[2]);
+    EXPECT_EQ(4.0, outputValues[3]);
+  }
+
+  // IndependentVariables
+  TableIndependentVariable independentVariable1(m);
+  EXPECT_TRUE(tableLookup.addIndependentVariable(independentVariable1));
+  EXPECT_EQ(1u, tableLookup.independentVariables().size());
+
+  TableIndependentVariable independentVariable2(m);
+  EXPECT_TRUE(tableLookup.addIndependentVariable(independentVariable2));
+  TableIndependentVariable independentVariable3(m);
+  EXPECT_TRUE(tableLookup.addIndependentVariable(independentVariable3));
+  EXPECT_EQ(3u, tableLookup.independentVariables().size());
+
+  EXPECT_TRUE(tableLookup.removeIndependentVariable(independentVariable2));
+  EXPECT_EQ(2u, tableLookup.independentVariables().size());
+  tableLookup.removeAllIndependentVariables();
+  EXPECT_EQ(0u, tableLookup.independentVariables().size());
+}
+
+TEST_F(ModelFixture, TableLookup_Clone) {
+  // Test cloning the TableLookup, make sure the relationship with TableIndependentVariable is sane
+  Model m;
+
+  TableLookup tableLookup(m);
+  TableIndependentVariable independentVariable1(m);
+  EXPECT_TRUE(tableLookup.addIndependentVariable(independentVariable1));
+
+  EXPECT_EQ(1u, m.getModelObjects<TableLookup>().size());
+  EXPECT_EQ(1u, m.getModelObjects<ModelObjectList>().size());
+  EXPECT_EQ(1u, m.getModelObjects<TableIndependentVariable>().size());
+  EXPECT_EQ(1u, tableLookup.independentVariables().size());
+
+  // Clone into the same model
+  {
+    auto tableLookupClone = tableLookup.clone(m).cast<TableLookup>();
+    EXPECT_EQ(2u, m.getModelObjects<TableLookup>().size());
+    EXPECT_EQ(2u, m.getModelObjects<ModelObjectList>().size());
+    EXPECT_EQ(1u, m.getModelObjects<TableIndependentVariable>().size());
+
+    ASSERT_EQ(1u, tableLookup.independentVariables().size());
+    EXPECT_EQ(independentVariable1, tableLookup.independentVariables().front());
+
+    ASSERT_EQ(1u, tableLookupClone.independentVariables().size());
+    EXPECT_EQ(independentVariable1, tableLookupClone.independentVariables().front());
+  }
+
+  // Clone into another model
+  {
+    Model m2;
+    auto tableLookupClone2 = tableLookup.clone(m2).cast<TableLookup>();
+
+    // Check original model is unaffected
+    EXPECT_EQ(2u, m.getModelObjects<TableLookup>().size());
+    EXPECT_EQ(2u, m.getModelObjects<ModelObjectList>().size());
+    EXPECT_EQ(1u, m.getModelObjects<TableIndependentVariable>().size());
+
+    ASSERT_EQ(1u, tableLookup.independentVariables().size());
+    EXPECT_EQ(independentVariable1, tableLookup.independentVariables().front());
+
+    // Check that the TableLookup and TableLookupIndependentVariable are carried into new model
+    EXPECT_EQ(1u, m2.getModelObjects<TableLookup>().size());
+    EXPECT_EQ(1u, m2.getModelObjects<ModelObjectList>().size());
+    EXPECT_EQ(1u, m2.getModelObjects<TableIndependentVariable>().size());
+
+    ASSERT_EQ(1u, tableLookupClone2.independentVariables().size());
+    EXPECT_NE(independentVariable1, tableLookupClone2.independentVariables().front());
+  }
+}
+
+TEST_F(ModelFixture, TableLookup_Remove) {
+  // Make sure the relationship with TableIndependentVariable is sane
+
+  {
+    // Removing a TableLookup that is the sole owner of a TableIndependentVariable: should delete all
+    Model m;
+
+    TableLookup tableLookup(m);
+    TableIndependentVariable independentVariable1(m);
+    EXPECT_TRUE(tableLookup.addIndependentVariable(independentVariable1));
+
+    EXPECT_EQ(1u, m.getModelObjects<TableLookup>().size());
+    EXPECT_EQ(1u, m.getModelObjects<ModelObjectList>().size());
+    EXPECT_EQ(1u, m.getModelObjects<TableIndependentVariable>().size());
+    ASSERT_EQ(1u, tableLookup.independentVariables().size());
+    EXPECT_EQ(independentVariable1, tableLookup.independentVariables().front());
+
+    tableLookup.remove();
+    EXPECT_EQ(0u, m.getModelObjects<TableLookup>().size());
+    EXPECT_EQ(0u, m.getModelObjects<ModelObjectList>().size());
+    EXPECT_EQ(0u, m.getModelObjects<TableIndependentVariable>().size());
+  }
+
+  {
+    // Two TableLookups sharing a single TableIndependentVariable: remove one shouldn't affect the other
+    Model m;
+
+    TableIndependentVariable independentVariable1(m);
+
+    TableLookup tableLookup1(m);
+    EXPECT_TRUE(tableLookup1.addIndependentVariable(independentVariable1));
+
+    TableLookup tableLookup2(m);
+    EXPECT_TRUE(tableLookup2.addIndependentVariable(independentVariable1));
+
+    EXPECT_EQ(2u, m.getModelObjects<TableLookup>().size());
+    EXPECT_EQ(2u, m.getModelObjects<ModelObjectList>().size());
+    EXPECT_EQ(1u, m.getModelObjects<TableIndependentVariable>().size());
+
+    ASSERT_EQ(1u, tableLookup1.independentVariables().size());
+    EXPECT_EQ(independentVariable1, tableLookup1.independentVariables().front());
+    ASSERT_EQ(1u, tableLookup2.independentVariables().size());
+    EXPECT_EQ(independentVariable1, tableLookup2.independentVariables().front());
+
+    tableLookup1.remove();
+    EXPECT_EQ(1u, m.getModelObjects<TableLookup>().size());
+    EXPECT_EQ(1u, m.getModelObjects<ModelObjectList>().size());
+    EXPECT_EQ(1u, m.getModelObjects<TableIndependentVariable>().size());
+
+    ASSERT_EQ(1u, tableLookup2.independentVariables().size());
+    EXPECT_EQ(independentVariable1, tableLookup2.independentVariables().front());
+  }
+}
diff --git a/src/model/test/TableMultiVariableLookup_GTest.cpp b/src/model/test/TableMultiVariableLookup_GTest.cpp
index 6b613ca7543..ee10b3feed6 100644
--- a/src/model/test/TableMultiVariableLookup_GTest.cpp
+++ b/src/model/test/TableMultiVariableLookup_GTest.cpp
@@ -31,6 +31,14 @@
 
 #include "ModelFixture.hpp"
 
+#if defined(_MSC_VER)
+#  pragma warning(push)
+#  pragma warning(disable : 4996)
+#elif (defined(__GNUC__))
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
 #include "../TableMultiVariableLookup.hpp"
 #include "../TableMultiVariableLookup_Impl.hpp"
 
@@ -329,3 +337,9 @@ TEST_F(ModelFixture, TableMultiVariablePointOrder2) {
   EXPECT_EQ(4, points[23].x()[2]);
   EXPECT_EQ(324, points[23].y());
 }
+
+#if defined(_MSC_VER)
+#  pragma warning(pop)
+#elif (defined(__GNUC__))
+#  pragma GCC diagnostic pop
+#endif
diff --git a/src/osversion/test/3_4_1/test_vt_TableMultiVariableLookup.rb b/src/osversion/test/3_4_1/test_vt_TableMultiVariableLookup.rb
new file mode 100644
index 00000000000..1ad7c639e23
--- /dev/null
+++ b/src/osversion/test/3_4_1/test_vt_TableMultiVariableLookup.rb
@@ -0,0 +1,101 @@
+#require '/usr/local/openstudio-3.4.0/Ruby/openstudio'
+
+include OpenStudio::Model
+
+
+def test_vt_TableMultiVariableLookup_oneDim()
+  m = Model.new
+
+  capModFuncOfWaterFlow = OpenStudio::Model::TableMultiVariableLookup.new(m, 1)
+  capModFuncOfWaterFlow.setName('CapModFuncOfWaterFlow')
+
+  capModFuncOfWaterFlow.setCurveType('Quadratic')
+  capModFuncOfWaterFlow.setInterpolationMethod('EvaluateCurveToLimits')
+  capModFuncOfWaterFlow.setMinimumValueofX1(0)
+  capModFuncOfWaterFlow.setMaximumValueofX1(1.33)
+  capModFuncOfWaterFlow.setInputUnitTypeforX1('Dimensionless')
+  capModFuncOfWaterFlow.setOutputUnitType('Dimensionless')
+
+  # The goal being to test the NormalizationReference feature since the E+ 9.2.0
+  # change to TableLookup, we pick one.
+  norm_ref = 2.4
+  capModFuncOfWaterFlow.setNormalizationReference(norm_ref)
+
+  # Quoting I/O ref for 9.1:
+  # > Both the output values and minimum/maximum curve limits
+  # > are normalized as applicable.
+  capModFuncOfWaterFlow.setMinimumTableOutput(0.0 * norm_ref)
+  capModFuncOfWaterFlow.setMaximumTableOutput(1.04 * norm_ref)
+
+  capModFuncOfWaterFlow.addPoint(0.0, 0.0 * norm_ref)
+  capModFuncOfWaterFlow.addPoint(0.05, 0.001 * norm_ref)
+  capModFuncOfWaterFlow.addPoint(0.33333, 0.71 * norm_ref)
+  capModFuncOfWaterFlow.addPoint(0.5, 0.85 * norm_ref)
+  capModFuncOfWaterFlow.addPoint(0.666667, 0.92 * norm_ref)
+  capModFuncOfWaterFlow.addPoint(0.833333, 0.97 * norm_ref)
+  capModFuncOfWaterFlow.addPoint(1.0, 1.0 * norm_ref) # <= RATING POINT
+  capModFuncOfWaterFlow.addPoint(1.333333, 1.04 * norm_ref)
+  m.save('test_vt_TableMultiVariableLookup_oneDim.osm')
+end
+
+
+def test_vt_TableMultiVariableLookup_twoDims()
+# Make one with two independent variables
+  m = Model.new
+  table = OpenStudio::Model::TableMultiVariableLookup.new(m, 2)
+  table.setName("Table With 2 dims")
+  table.setCurveType('Biquadratic')
+  table.setInterpolationMethod('LagrangeInterpolationLinearExtrapolation')
+  table.setNumberofInterpolationPoints(3)
+  table.setMinimumValueofX1(60)
+  table.setMaximumValueofX1(80)
+  table.setMinimumValueofX2(30)
+  table.setMaximumValueofX2(100)
+  table.setInputUnitTypeforX1('Temperature')
+  table.setInputUnitTypeforX2('Temperature')
+  table.setOutputUnitType('Dimensionless')
+  table.setMinimumTableOutput(0.05)
+  table.setMaximumTableOutput(1.25)
+  table.resetNormalizationReference
+
+  table.addPoint(70, 32, 0.1)
+  table.addPoint(72, 32, 0.3)
+  table.addPoint(74, 32, 0.5)
+  table.addPoint(76, 32, 0.7)
+  table.addPoint(78, 32, 0.9)
+
+  table.addPoint(70, 45, 0.2)
+  table.addPoint(72, 45, 0.4)
+  table.addPoint(74, 45, 0.6)
+  table.addPoint(76, 45, 0.8)
+  table.addPoint(78, 45, 1.0)
+
+  table.addPoint(70, 68, 0.3)
+  table.addPoint(72, 68, 0.5)
+  table.addPoint(74, 68, 0.7)
+  table.addPoint(76, 68, 0.9)
+  table.addPoint(78, 68, 1.1)
+
+  table.addPoint(70, 81, 0.4)
+  table.addPoint(72, 81, 0.6)
+  table.addPoint(74, 81, 0.8)
+  table.addPoint(76, 81, 1.0)
+  table.addPoint(78, 81, 1.2)
+
+  table.addPoint(70, 94, 0.5)
+  table.addPoint(72, 94, 0.7)
+  table.addPoint(74, 94, 0.9)
+  table.addPoint(76, 94, 1.1)
+  table.addPoint(78, 94, 1.3)
+
+  table.addPoint(70, 107, 0.6)
+  table.addPoint(72, 107, 0.8)
+  table.addPoint(74, 107, 1.0)
+  table.addPoint(76, 107, 1.2)
+  table.addPoint(78, 107, 1.4)
+
+  m.save('test_vt_TableMultiVariableLookup_twoDims.osm', true)
+end
+
+test_vt_TableMultiVariableLookup_oneDim()
+test_vt_TableMultiVariableLookup_twoDims()
diff --git a/src/osversion/test/3_4_1/test_vt_TableMultiVariableLookup_oneDim.osm b/src/osversion/test/3_4_1/test_vt_TableMultiVariableLookup_oneDim.osm
new file mode 100644
index 00000000000..b4b64416fd0
--- /dev/null
+++ b/src/osversion/test/3_4_1/test_vt_TableMultiVariableLookup_oneDim.osm
@@ -0,0 +1,52 @@
+
+OS:Version,
+  {d58d8bd0-c70c-4043-9c2b-6c92c87ac66f}, !- Handle
+  3.4.0;                                  !- Version Identifier
+
+OS:Table:MultiVariableLookup,
+  {729a4fd9-b42b-4b63-b5fb-82895ea9897f}, !- Handle
+  CapModFuncOfWaterFlow,                  !- Name
+  EvaluateCurveToLimits,                  !- Interpolation Method
+  ,                                       !- Number of Interpolation Points
+  Quadratic,                              !- Curve Type
+  ,                                       !- Table Data Format
+  ,                                       !- External File Name
+  ,                                       !- X1 Sort Order
+  ,                                       !- X2 Sort Order
+  2.4,                                    !- Normalization Reference
+  0,                                      !- Minimum Value of X1
+  1.33,                                   !- Maximum Value of X1
+  ,                                       !- Minimum Value of X2
+  ,                                       !- Maximum Value of X2
+  ,                                       !- Minimum Value of X3
+  ,                                       !- Maximum Value of X3
+  ,                                       !- Minimum Value of X4
+  ,                                       !- Maximum Value of X4
+  ,                                       !- Minimum Value of X5
+  ,                                       !- Maximum Value of X5
+  0,                                      !- Minimum Table Output
+  2.496,                                  !- Maximum Table Output
+  Dimensionless,                          !- Input Unit Type for X1
+  ,                                       !- Input Unit Type for X2
+  ,                                       !- Input Unit Type for X3
+  ,                                       !- Input Unit Type for X4
+  ,                                       !- Input Unit Type for X5
+  Dimensionless,                          !- Output Unit Type
+  1,                                      !- Number of Independent Variables
+  0,                                      !- Data 1
+  0,                                      !- Data 2
+  0.05,                                   !- Data 3
+  0.0024,                                 !- Data 4
+  0.33333,                                !- Data 5
+  1.704,                                  !- Data 6
+  0.5,                                    !- Data 7
+  2.04,                                   !- Data 8
+  0.666667,                               !- Data 9
+  2.208,                                  !- Data 10
+  0.833333,                               !- Data 11
+  2.328,                                  !- Data 12
+  1,                                      !- Data 13
+  2.4,                                    !- Data 14
+  1.333333,                               !- Data 15
+  2.496;                                  !- Data 16
+
diff --git a/src/osversion/test/3_4_1/test_vt_TableMultiVariableLookup_twoDims.osm b/src/osversion/test/3_4_1/test_vt_TableMultiVariableLookup_twoDims.osm
new file mode 100644
index 00000000000..7b0140a7148
--- /dev/null
+++ b/src/osversion/test/3_4_1/test_vt_TableMultiVariableLookup_twoDims.osm
@@ -0,0 +1,126 @@
+
+OS:Version,
+  {19f53ed8-1e1d-4dd6-9e2d-e1048e692620}, !- Handle
+  3.4.0;                                  !- Version Identifier
+
+OS:Table:MultiVariableLookup,
+  {e4cb2274-f39d-45fe-b680-be44bdaaa861}, !- Handle
+  Table With 2 dims,                      !- Name
+  LagrangeInterpolationLinearExtrapolation, !- Interpolation Method
+  3,                                      !- Number of Interpolation Points
+  Biquadratic,                            !- Curve Type
+  ,                                       !- Table Data Format
+  ,                                       !- External File Name
+  ,                                       !- X1 Sort Order
+  ,                                       !- X2 Sort Order
+  ,                                       !- Normalization Reference
+  60,                                     !- Minimum Value of X1
+  80,                                     !- Maximum Value of X1
+  30,                                     !- Minimum Value of X2
+  100,                                    !- Maximum Value of X2
+  ,                                       !- Minimum Value of X3
+  ,                                       !- Maximum Value of X3
+  ,                                       !- Minimum Value of X4
+  ,                                       !- Maximum Value of X4
+  ,                                       !- Minimum Value of X5
+  ,                                       !- Maximum Value of X5
+  0.05,                                   !- Minimum Table Output
+  1.25,                                   !- Maximum Table Output
+  Temperature,                            !- Input Unit Type for X1
+  Temperature,                            !- Input Unit Type for X2
+  ,                                       !- Input Unit Type for X3
+  ,                                       !- Input Unit Type for X4
+  ,                                       !- Input Unit Type for X5
+  Dimensionless,                          !- Output Unit Type
+  2,                                      !- Number of Independent Variables
+  70,                                     !- Data 1
+  32,                                     !- Data 2
+  0.1,                                    !- Data 3
+  72,                                     !- Data 4
+  32,                                     !- Data 5
+  0.3,                                    !- Data 6
+  74,                                     !- Data 7
+  32,                                     !- Data 8
+  0.5,                                    !- Data 9
+  76,                                     !- Data 10
+  32,                                     !- Data 11
+  0.7,                                    !- Data 12
+  78,                                     !- Data 13
+  32,                                     !- Data 14
+  0.9,                                    !- Data 15
+  70,                                     !- Data 16
+  45,                                     !- Data 17
+  0.2,                                    !- Data 18
+  72,                                     !- Data 19
+  45,                                     !- Data 20
+  0.4,                                    !- Data 21
+  74,                                     !- Data 22
+  45,                                     !- Data 23
+  0.6,                                    !- Data 24
+  76,                                     !- Data 25
+  45,                                     !- Data 26
+  0.8,                                    !- Data 27
+  78,                                     !- Data 28
+  45,                                     !- Data 29
+  1,                                      !- Data 30
+  70,                                     !- Data 31
+  68,                                     !- Data 32
+  0.3,                                    !- Data 33
+  72,                                     !- Data 34
+  68,                                     !- Data 35
+  0.5,                                    !- Data 36
+  74,                                     !- Data 37
+  68,                                     !- Data 38
+  0.7,                                    !- Data 39
+  76,                                     !- Data 40
+  68,                                     !- Data 41
+  0.9,                                    !- Data 42
+  78,                                     !- Data 43
+  68,                                     !- Data 44
+  1.1,                                    !- Data 45
+  70,                                     !- Data 46
+  81,                                     !- Data 47
+  0.4,                                    !- Data 48
+  72,                                     !- Data 49
+  81,                                     !- Data 50
+  0.6,                                    !- Data 51
+  74,                                     !- Data 52
+  81,                                     !- Data 53
+  0.8,                                    !- Data 54
+  76,                                     !- Data 55
+  81,                                     !- Data 56
+  1,                                      !- Data 57
+  78,                                     !- Data 58
+  81,                                     !- Data 59
+  1.2,                                    !- Data 60
+  70,                                     !- Data 61
+  94,                                     !- Data 62
+  0.5,                                    !- Data 63
+  72,                                     !- Data 64
+  94,                                     !- Data 65
+  0.7,                                    !- Data 66
+  74,                                     !- Data 67
+  94,                                     !- Data 68
+  0.9,                                    !- Data 69
+  76,                                     !- Data 70
+  94,                                     !- Data 71
+  1.1,                                    !- Data 72
+  78,                                     !- Data 73
+  94,                                     !- Data 74
+  1.3,                                    !- Data 75
+  70,                                     !- Data 76
+  107,                                    !- Data 77
+  0.6,                                    !- Data 78
+  72,                                     !- Data 79
+  107,                                    !- Data 80
+  0.8,                                    !- Data 81
+  74,                                     !- Data 82
+  107,                                    !- Data 83
+  1,                                      !- Data 84
+  76,                                     !- Data 85
+  107,                                    !- Data 86
+  1.2,                                    !- Data 87
+  78,                                     !- Data 88
+  107,                                    !- Data 89
+  1.4;                                    !- Data 90
+
diff --git a/src/osversion/test/VersionTranslator_GTest.cpp b/src/osversion/test/VersionTranslator_GTest.cpp
index f1fb21662db..3367d1bb612 100644
--- a/src/osversion/test/VersionTranslator_GTest.cpp
+++ b/src/osversion/test/VersionTranslator_GTest.cpp
@@ -1855,3 +1855,160 @@ TEST_F(OSVersionFixture, update_3_4_0_to_3_4_1_AirWallMaterial) {
   EXPECT_EQ("OS:Construction:AirBoundary", surface.getTarget(3).get().iddObject().name());
   EXPECT_EQ("Construction 1", surface.getTarget(3).get().nameString());  // Construction Name
 }
+
+TEST_F(OSVersionFixture, update_3_4_0_to_3_4_1_TableMultiVariableLookup_oneDim) {
+  openstudio::path path = resourcesPath() / toPath("osversion/3_4_1/test_vt_TableMultiVariableLookup_oneDim.osm");
+  osversion::VersionTranslator vt;
+  boost::optional<model::Model> model = vt.loadModel(path);
+  ASSERT_TRUE(model) << "Failed to load " << path;
+
+  openstudio::path outPath = resourcesPath() / toPath("osversion/3_4_1/test_vt_TableMultiVariableLookup_oneDim_updated.osm");
+  model->save(outPath, true);
+
+  std::vector<WorkspaceObject> tableLookUps = model->getObjectsByType("OS:Table:Lookup");
+  ASSERT_EQ(1u, tableLookUps.size());
+  ASSERT_EQ(1u, model->getObjectsByType("OS:Table:IndependentVariable").size());
+  ASSERT_EQ(1u, model->getObjectsByType("OS:ModelObjectList").size());
+
+  double norm_ref = 2.4;
+  std::vector<double> x1{0.0, 0.05, 0.33333, 0.5, 0.666667, 0.833333, 1.0, 1.333333};
+  std::vector<double> y{0.0, 0.0024, 1.704, 2.04, 2.208, 2.328, 2.4, 2.496};
+
+  auto& tableLookUp = tableLookUps.front();
+  EXPECT_EQ("CapModFuncOfWaterFlow", tableLookUp.nameString());
+
+  // We have a normalization reference originally, so it's DivisorOnly
+  EXPECT_EQ("DivisorOnly", tableLookUp.getString(3).get());    // Normalization Method
+  EXPECT_EQ(norm_ref, tableLookUp.getDouble(4).get());         // Normalization Divisor
+  EXPECT_EQ(0.0, tableLookUp.getDouble(5).get());              // Minimum Output
+  EXPECT_EQ(1.04 * norm_ref, tableLookUp.getDouble(6).get());  // Maximum Output
+  EXPECT_EQ("Dimensionless", tableLookUp.getString(7).get());  // Output Unit Type
+  EXPECT_TRUE(tableLookUp.isEmpty(8));                         // External File Name
+  EXPECT_TRUE(tableLookUp.isEmpty(9));                         // External File Column Number
+  EXPECT_TRUE(tableLookUp.isEmpty(10));                        // External File Starting Row Number
+  // Output Values
+  ASSERT_EQ(y.size(), tableLookUp.numExtensibleGroups());
+  for (size_t i = 0; auto& eg : tableLookUp.extensibleGroups()) {
+    EXPECT_EQ(y[i], eg.getDouble(0).get());
+    ++i;
+  }
+
+  auto varList_ = tableLookUp.getTarget(2);
+  ASSERT_TRUE(varList_);
+  EXPECT_EQ("CapModFuncOfWaterFlow_IndependentVariableList", varList_->nameString());
+  ASSERT_EQ(1, varList_->numExtensibleGroups());
+  auto var_ = varList_->extensibleGroups().front().cast<WorkspaceExtensibleGroup>().getTarget(0);
+  ASSERT_TRUE(var_);
+  EXPECT_EQ("CapModFuncOfWaterFlow_IndependentVariable_0", var_->nameString());
+
+  // Interpolation = EvaluateCurveToLimits maps to Cubic/Constant
+  EXPECT_EQ("Cubic", var_->getString(2).get());          // Interpolation Method
+  EXPECT_EQ("Constant", var_->getString(3).get());       // Extrapolation Method
+  EXPECT_EQ(0.0, var_->getDouble(4).get());              // Minimum Value
+  EXPECT_EQ(1.33, var_->getDouble(5).get());             // Maximum Value
+  EXPECT_TRUE(var_->isEmpty(6));                         // Normalization Reference Value
+  EXPECT_EQ("Dimensionless", var_->getString(7).get());  // Unit Type
+  EXPECT_TRUE(var_->isEmpty(8));                         // External File Name
+  EXPECT_TRUE(var_->isEmpty(9));                         // External File Column Number
+  EXPECT_TRUE(var_->isEmpty(10));                        // External File Starting Row Number
+
+  // Values
+  ASSERT_EQ(x1.size(), var_->numExtensibleGroups());
+  for (size_t i = 0; auto& eg : var_->extensibleGroups()) {
+    EXPECT_EQ(x1[i], eg.getDouble(0).get());
+    ++i;
+  }
+}
+
+TEST_F(OSVersionFixture, update_3_4_0_to_3_4_1_TableMultiVariableLookup_twoDims) {
+  openstudio::path path = resourcesPath() / toPath("osversion/3_4_1/test_vt_TableMultiVariableLookup_twoDims.osm");
+  osversion::VersionTranslator vt;
+  boost::optional<model::Model> model = vt.loadModel(path);
+  ASSERT_TRUE(model) << "Failed to load " << path;
+
+  openstudio::path outPath = resourcesPath() / toPath("osversion/3_4_1/test_vt_TableMultiVariableLookup_twoDims_updated.osm");
+  model->save(outPath, true);
+
+  std::vector<WorkspaceObject> tableLookUps = model->getObjectsByType("OS:Table:Lookup");
+  ASSERT_EQ(1u, tableLookUps.size());
+  ASSERT_EQ(2u, model->getObjectsByType("OS:Table:IndependentVariable").size());
+  ASSERT_EQ(1u, model->getObjectsByType("OS:ModelObjectList").size());
+
+  std::vector<double> x1{70.0, 72.0, 74.0, 76.0, 78.0};
+  std::vector<double> x2{32.0, 45.0, 68.0, 81.0, 94.0, 107.0};
+
+  std::vector<double> y{0.1, 0.3, 0.5, 0.7, 0.9, 0.2, 0.4, 0.6, 0.8, 1.0, 0.3, 0.5, 0.7, 0.9, 1.1,
+                        0.4, 0.6, 0.8, 1.0, 1.2, 0.5, 0.7, 0.9, 1.1, 1.3, 0.6, 0.8, 1.0, 1.2, 1.4};
+
+  auto& tableLookUp = tableLookUps.front();
+  EXPECT_EQ("Table With 2 dims", tableLookUp.nameString());
+
+  // We don't have a normalization reference originally, so "None" and 1.0 to match the ctor
+
+  EXPECT_EQ("None", tableLookUp.getString(3).get());  // Normalization Method
+  EXPECT_EQ(1.0, tableLookUp.getDouble(4).get());     // Normalization Divisor
+
+  EXPECT_EQ(0.05, tableLookUp.getDouble(5).get());             // Minimum Output
+  EXPECT_EQ(1.25, tableLookUp.getDouble(6).get());             // Maximum Output
+  EXPECT_EQ("Dimensionless", tableLookUp.getString(7).get());  // Output Unit Type
+  EXPECT_TRUE(tableLookUp.isEmpty(8));                         // External File Name
+  EXPECT_TRUE(tableLookUp.isEmpty(9));                         // External File Column Number
+  EXPECT_TRUE(tableLookUp.isEmpty(10));                        // External File Starting Row Number
+  // Output Values
+  ASSERT_EQ(y.size(), tableLookUp.numExtensibleGroups());
+  for (size_t i = 0; auto& eg : tableLookUp.extensibleGroups()) {
+    EXPECT_EQ(y[i], eg.getDouble(0).get());
+    ++i;
+  }
+
+  auto varList_ = tableLookUp.getTarget(2);
+  ASSERT_TRUE(varList_);
+  EXPECT_EQ("Table With 2 dims_IndependentVariableList", varList_->nameString());
+  ASSERT_EQ(2, varList_->numExtensibleGroups());
+  {
+    auto var_ = varList_->extensibleGroups().front().cast<WorkspaceExtensibleGroup>().getTarget(0);
+    ASSERT_TRUE(var_);
+    EXPECT_EQ("Table With 2 dims_IndependentVariable_0", var_->nameString());
+
+    // LagrangeInterpolationLinearExtrapolation = EvaluateCurveToLimits maps to Cubic/Linear
+    EXPECT_EQ("Cubic", var_->getString(2).get());        // Interpolation Method
+    EXPECT_EQ("Linear", var_->getString(3).get());       // Extrapolation Method
+    EXPECT_EQ(60.0, var_->getDouble(4).get());           // Minimum Value
+    EXPECT_EQ(80.0, var_->getDouble(5).get());           // Maximum Value
+    EXPECT_TRUE(var_->isEmpty(6));                       // Normalization Reference Value
+    EXPECT_EQ("Temperature", var_->getString(7).get());  // Unit Type
+    EXPECT_TRUE(var_->isEmpty(8));                       // External File Name
+    EXPECT_TRUE(var_->isEmpty(9));                       // External File Column Number
+    EXPECT_TRUE(var_->isEmpty(10));                      // External File Starting Row Number
+
+    // Values
+    ASSERT_EQ(x1.size(), var_->numExtensibleGroups());
+    for (size_t i = 0; auto& eg : var_->extensibleGroups()) {
+      EXPECT_EQ(x1[i], eg.getDouble(0).get());
+      ++i;
+    }
+  }
+  {
+    auto var_ = varList_->extensibleGroups().back().cast<WorkspaceExtensibleGroup>().getTarget(0);
+    ASSERT_TRUE(var_);
+    EXPECT_EQ("Table With 2 dims_IndependentVariable_1", var_->nameString());
+
+    // LagrangeInterpolationLinearExtrapolation = EvaluateCurveToLimits maps to Cubic/Linear
+    EXPECT_EQ("Cubic", var_->getString(2).get());        // Interpolation Method
+    EXPECT_EQ("Linear", var_->getString(3).get());       // Extrapolation Method
+    EXPECT_EQ(30, var_->getDouble(4).get());             // Minimum Value
+    EXPECT_EQ(100.0, var_->getDouble(5).get());          // Maximum Value
+    EXPECT_TRUE(var_->isEmpty(6));                       // Normalization Reference Value
+    EXPECT_EQ("Temperature", var_->getString(7).get());  // Unit Type
+    EXPECT_TRUE(var_->isEmpty(8));                       // External File Name
+    EXPECT_TRUE(var_->isEmpty(9));                       // External File Column Number
+    EXPECT_TRUE(var_->isEmpty(10));                      // External File Starting Row Number
+
+    // Values
+    ASSERT_EQ(x2.size(), var_->numExtensibleGroups());
+    for (size_t i = 0; auto& eg : var_->extensibleGroups()) {
+      EXPECT_EQ(x2[i], eg.getDouble(0).get());
+      ++i;
+    }
+  }
+}