Repository: NREL/OpenStudio (language: python)

Issue:
E+ 22.2.0 - `ZoneMixing` is now allowed at space level
Allow setting ZoneMixing to a Space in model namespace?
E+ 22.2.0 - `ZoneMixing` is now allowed at space level
Allow setting ZoneMixing to a Space in model namespace?
E+ 22.2.0 - `ZoneMixing` is now allowed at space level
Allow setting ZoneMixing to a Space in model namespace?


Discussion / hints:
I'm running up against an E+ 22.2 error because of this. We assign a ZoneMixing object to a thermal zone (because that's all OpenStudio allows) and the thermal zone has two spaces in it, so E+ automatically reassigns the ZoneMixing object to each Space. Then our code tries to (EMS) actuate the zone-level ZoneMixing object but it no longer exists and we get an E+ simulation error.

If we could assign a ZoneMixing object to a space and it was FT to E+ like that, it would eliminate our error.
this is going to require a (minor) ABI break. this one cannot stay at all (can't deprecate / overload to work around it) https://github.com/NREL/OpenStudio/blob/490d7848e2f0def6186ef4e1f40e7d7309ea1f33/src/model/ZoneMixing.hpp#L68

I'm running up against an E+ 22.2 error because of this. We assign a ZoneMixing object to a thermal zone (because that's all OpenStudio allows) and the thermal zone has two spaces in it, so E+ automatically reassigns the ZoneMixing object to each Space. Then our code tries to (EMS) actuate the zone-level ZoneMixing object but it no longer exists and we get an E+ simulation error.

If we could assign a ZoneMixing object to a space and it was FT to E+ like that, it would eliminate our error.
this is going to require a (minor) ABI break. this one cannot stay at all (can't deprecate / overload to work around it) https://github.com/NREL/OpenStudio/blob/490d7848e2f0def6186ef4e1f40e7d7309ea1f33/src/model/ZoneMixing.hpp#L68

I'm running up against an E+ 22.2 error because of this. We assign a ZoneMixing object to a thermal zone (because that's all OpenStudio allows) and the thermal zone has two spaces in it, so E+ automatically reassigns the ZoneMixing object to each Space. Then our code tries to (EMS) actuate the zone-level ZoneMixing object but it no longer exists and we get an E+ simulation error.

If we could assign a ZoneMixing object to a space and it was FT to E+ like that, it would eliminate our error.
this is going to require a (minor) ABI break. this one cannot stay at all (can't deprecate / overload to work around it) https://github.com/NREL/OpenStudio/blob/490d7848e2f0def6186ef4e1f40e7d7309ea1f33/src/model/ZoneMixing.hpp#L68


Fix patch:
diff --git a/resources/model/OpenStudio.idd b/resources/model/OpenStudio.idd
index 6b0aa46e98f..0f3b1db0657 100644
--- a/resources/model/OpenStudio.idd
+++ b/resources/model/OpenStudio.idd
@@ -28483,8 +28483,8 @@ OS:ZoneHVAC:CoolingPanel:RadiantConvective:Water,
        \maximum 1
 
 OS:ZoneMixing,
-       \memo ZoneMixing is a simple air exchange from one zone to another. Note that this statement
-       \memo only affects the energy balance of the "receiving" zone and will not produce
+       \memo ZoneMixing is a simple air exchange from one zone or space to another. Note that this statement
+       \memo only affects the energy balance of the "receiving" zone or space and will not produce
        \memo any effect on the "source" zone. Mixing statements can be complementary and include
        \memo multiple zones, but the balancing of flows between zones is left to the user's
        \memo discretion.
@@ -28495,11 +28495,12 @@ OS:ZoneMixing,
   A2 , \field Name
        \required-field
        \type alpha
-  A3 , \field Zone Name
-       \note This is the receiving zone
+  A3 , \field Zone or Space Name
+       \note This is the receiving zone or space
        \required-field
        \type object-list
        \object-list ThermalZoneNames
+       \object-list SpaceNames
   A4 , \field Schedule Name
        \required-field
        \type object-list
@@ -28522,7 +28523,7 @@ OS:ZoneMixing,
        \units m3/s
        \type real
        \minimum 0
-  N2 , \field Flow Rate per Zone Floor Area
+  N2 , \field Flow Rate per Floor Area
        \type real
        \minimum 0
        \units m3/s-m2
@@ -28534,38 +28535,43 @@ OS:ZoneMixing,
        \units 1/hr
        \type real
        \minimum 0
-  A6 , \field Source Zone Name
+  A6 , \field Source Zone or Space Name
        \type object-list
        \object-list ThermalZoneNames
+       \object-list SpaceNames
   N5 , \field Delta Temperature
        \units deltaC
        \type real
        \note This field contains the constant temperature differential between source and
-       \note receiving zones below which mixing is shutoff.
+       \note receiving zone or space below which mixing is shutoff. If a source zone is
+       \note specified and it contains more than one space, the average source zone temperature
+       \note will be used for control.
   A7 , \field Delta Temperature Schedule Name
        \type object-list
        \object-list ScheduleNames
-       \note This schedule contains the temperature differential between source and receiving
-       \note zones versus time below which mixing is shutoff.
-  A8 , \field Minimum Zone Temperature Schedule Name
+       \note This schedule contains the temperature differential between source and
+       \note receiving zone or space below which mixing is shutoff. If a source zone is
+       \note specified and it contains more than one space, the average source zone temperature
+       \note will be used for control.
+  A8 , \field Minimum Receiving Temperature Schedule Name
        \type object-list
        \object-list ScheduleNames
-       \note This schedule contains the zone dry-bulb temperature versus time below which
+       \note This schedule contains the receiving zone or space temperature versus time below which
        \note mixing is shutoff.
-  A9 , \field Maximum Zone Temperature Schedule Name
+  A9 , \field Maximum Receiving Temperature Schedule Name
        \type object-list
        \object-list ScheduleNames
-       \note This schedule contains the zone dry-bulb temperature versus time above which
+       \note This schedule contains the receiving zone or space  temperature versus time above which
        \note mixing is shutoff.
-  A10 , \field Minimum Source Zone Temperature Schedule Name
+  A10 , \field Minimum Source Temperature Schedule Name
        \type object-list
        \object-list ScheduleNames
-       \note This schedule contains the source zone dry-bulb temperature versus time below
+       \note This schedule contains the source zone or space temperature versus time below
        \note which mixing is shutoff.
-  A11, \field Maximum Source Zone Temperature Schedule Name
+  A11, \field Maximum Source Temperature Schedule Name
        \type object-list
        \object-list ScheduleNames
-       \note This schedule contains the source zone dry-bulb temperature versus time above
+       \note This schedule contains the source zone or space temperature versus time above
        \note which mixing is shutoff.
   A12, \field Minimum Outdoor Temperature Schedule Name
        \type object-list
diff --git a/src/energyplus/CMakeLists.txt b/src/energyplus/CMakeLists.txt
index 572f1475492..635bed0f8f4 100644
--- a/src/energyplus/CMakeLists.txt
+++ b/src/energyplus/CMakeLists.txt
@@ -800,6 +800,7 @@ set(${target_name}_test_src
   Test/ZoneHVACTerminalUnitVariableRefrigerantFlow_GTest.cpp
   Test/ZoneHVACUnitHeater_GTest.cpp
   Test/ZoneHVACWaterToAirHeatPump_GTest.cpp
+  Test/ZoneMixing_GTest.cpp
   Test/ZonePropertyUserViewFactorsBySurfaceName_GTest.cpp
   Test/ZoneVentilationWindandStackOpenArea_GTest.cpp
 )
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateZoneMixing.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateZoneMixing.cpp
index 286cdf5c942..8666f668e9d 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateZoneMixing.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateZoneMixing.cpp
@@ -37,6 +37,8 @@
 #include "../../model/ZoneMixing_Impl.hpp"
 #include "../../model/ThermalZone.hpp"
 #include "../../model/ThermalZone_Impl.hpp"
+#include "../../model/Space.hpp"
+#include "../../model/Space_Impl.hpp"
 
 #include <utilities/idd/ZoneMixing_FieldEnums.hxx>
 #include "../../utilities/idd/IddEnums.hpp"
@@ -50,6 +52,50 @@ namespace openstudio {
 namespace energyplus {
 
   boost::optional<IdfObject> ForwardTranslator::translateZoneMixing(ZoneMixing& modelObject) {
+
+    ModelObject zoneOrSpace = modelObject.zoneOrSpace();
+    boost::optional<ModelObject> sourceZoneOrSpace = modelObject.sourceZoneOrSpace();
+
+    auto getParentObjectName = [this](const ModelObject& mo) {
+      if (!m_excludeSpaceTranslation) {
+        return mo.nameString();
+      }
+
+      if (auto space_ = mo.optionalCast<Space>()) {
+        if (auto thermalZone_ = space_->thermalZone()) {
+          return thermalZone_->nameString();
+        } else {
+          OS_ASSERT(false);  // This shouldn't happen, since we removed all orphaned spaces earlier in the FT
+        }
+      }
+
+      return mo.nameString();
+    };
+
+    if (!sourceZoneOrSpace) {
+      if (m_excludeSpaceTranslation && modelObject.space()) {
+        LOG(Warn, modelObject.briefDescription()
+                    << " doesn't have a Source Zone or Space, it will not be translated. As you were using Space-Level ZoneMixing, and you are not "
+                       "translating to Spaces, it's possible it was pointing to two spaces inside the same zone");
+      } else {
+        LOG(Warn, modelObject.briefDescription() << " doesn't have a Source Zone or Space, it will not be translated.");
+      }
+      return boost::none;
+    }
+
+    if (zoneOrSpace == sourceZoneOrSpace.get()) {
+      // This isn't going to happen, because zm.setSourceSpace(newSpace) in ThermalZone::combineSpaces will be rejected
+      // Let's play it safe though
+      LOG(Warn, modelObject.briefDescription() << " has the same Receiving and Source Zone or Space, it will not be translated.");
+      if (!m_excludeSpaceTranslation) {
+        // We don't allow this at model time, the only reason we expect this to happen is when m_excludeSpaceTranslation is true, we call
+        // combineSpaces, and if the user has a ZoneMixing pointing to two spaces from the same ThermalZone, you end up with matching Receiving and
+        // Source Spaces
+        OS_ASSERT(false);
+      }
+      return boost::none;
+    }
+
     // Makes sure the modelObject gets put in the map, and that the new idfObject gets put in
     // the final file. Also set's the idfObject's name.
     IdfObject idfObject = createRegisterAndNameIdfObject(IddObjectType::ZoneMixing, modelObject);
@@ -57,14 +103,13 @@ namespace energyplus {
     boost::optional<double> value;
 
     // ZoneorSpaceName
-    ThermalZone zone = modelObject.zone();
-    translateAndMapModelObject(zone);
-    idfObject.setString(ZoneMixingFields::ZoneorSpaceName, zone.name().get());
+    translateAndMapModelObject(zoneOrSpace);
+    idfObject.setString(ZoneMixingFields::ZoneorSpaceName, getParentObjectName(zoneOrSpace));
 
     // ScheduleName
     Schedule schedule = modelObject.schedule();
     translateAndMapModelObject(schedule);
-    idfObject.setString(ZoneMixingFields::ScheduleName, schedule.name().get());
+    idfObject.setString(ZoneMixingFields::ScheduleName, schedule.nameString());
 
     // DesignFlowRateCalculationMethod
     idfObject.setString(ZoneMixingFields::DesignFlowRateCalculationMethod, modelObject.designFlowRateCalculationMethod());
@@ -76,7 +121,7 @@ namespace energyplus {
     }
 
     // FlowRateperFloorArea
-    value = modelObject.flowRateperZoneFloorArea();
+    value = modelObject.flowRateperFloorArea();
     if (value) {
       idfObject.setDouble(ZoneMixingFields::FlowRateperFloorArea, *value);
     }
@@ -94,11 +139,8 @@ namespace energyplus {
     }
 
     // SourceZoneorSpaceName
-    boost::optional<ThermalZone> sourceZone = modelObject.sourceZone();
-    if (sourceZone) {
-      // DLM: do not translate source zone now, it will be translated at the right time
-      idfObject.setString(ZoneMixingFields::SourceZoneorSpaceName, sourceZone->name().get());
-    }
+    // DLM: do not translate source zone now, it will be translated at the right time
+    idfObject.setString(ZoneMixingFields::SourceZoneorSpaceName, getParentObjectName(sourceZoneOrSpace.get()));
 
     // DeltaTemperature
     value = modelObject.deltaTemperature();
@@ -110,49 +152,49 @@ namespace energyplus {
     boost::optional<Schedule> optSchedule = modelObject.deltaTemperatureSchedule();
     if (optSchedule) {
       translateAndMapModelObject(*optSchedule);
-      idfObject.setString(ZoneMixingFields::DeltaTemperatureScheduleName, optSchedule->name().get());
+      idfObject.setString(ZoneMixingFields::DeltaTemperatureScheduleName, optSchedule->nameString());
     }
 
     // MinimumReceivingTemperatureScheduleName
-    optSchedule = modelObject.minimumZoneTemperatureSchedule();
+    optSchedule = modelObject.minimumReceivingTemperatureSchedule();
     if (optSchedule) {
       translateAndMapModelObject(*optSchedule);
-      idfObject.setString(ZoneMixingFields::MinimumReceivingTemperatureScheduleName, optSchedule->name().get());
+      idfObject.setString(ZoneMixingFields::MinimumReceivingTemperatureScheduleName, optSchedule->nameString());
     }
 
     // MaximumReceivingTemperatureScheduleName
-    optSchedule = modelObject.maximumZoneTemperatureSchedule();
+    optSchedule = modelObject.maximumReceivingTemperatureSchedule();
     if (optSchedule) {
       translateAndMapModelObject(*optSchedule);
-      idfObject.setString(ZoneMixingFields::MaximumReceivingTemperatureScheduleName, optSchedule->name().get());
+      idfObject.setString(ZoneMixingFields::MaximumReceivingTemperatureScheduleName, optSchedule->nameString());
     }
 
     // MinimumSourceTemperatureScheduleName
-    optSchedule = modelObject.minimumSourceZoneTemperatureSchedule();
+    optSchedule = modelObject.minimumSourceTemperatureSchedule();
     if (optSchedule) {
       translateAndMapModelObject(*optSchedule);
-      idfObject.setString(ZoneMixingFields::MinimumSourceTemperatureScheduleName, optSchedule->name().get());
+      idfObject.setString(ZoneMixingFields::MinimumSourceTemperatureScheduleName, optSchedule->nameString());
     }
 
     // MaximumSourceTemperatureScheduleName
-    optSchedule = modelObject.maximumSourceZoneTemperatureSchedule();
+    optSchedule = modelObject.maximumSourceTemperatureSchedule();
     if (optSchedule) {
       translateAndMapModelObject(*optSchedule);
-      idfObject.setString(ZoneMixingFields::MaximumSourceTemperatureScheduleName, optSchedule->name().get());
+      idfObject.setString(ZoneMixingFields::MaximumSourceTemperatureScheduleName, optSchedule->nameString());
     }
 
     // MinimumOutdoorTemperatureScheduleName
     optSchedule = modelObject.minimumOutdoorTemperatureSchedule();
     if (optSchedule) {
       translateAndMapModelObject(*optSchedule);
-      idfObject.setString(ZoneMixingFields::MinimumOutdoorTemperatureScheduleName, optSchedule->name().get());
+      idfObject.setString(ZoneMixingFields::MinimumOutdoorTemperatureScheduleName, optSchedule->nameString());
     }
 
     // MaximumOutdoorTemperatureScheduleName
     optSchedule = modelObject.maximumOutdoorTemperatureSchedule();
     if (optSchedule) {
       translateAndMapModelObject(*optSchedule);
-      idfObject.setString(ZoneMixingFields::MaximumOutdoorTemperatureScheduleName, optSchedule->name().get());
+      idfObject.setString(ZoneMixingFields::MaximumOutdoorTemperatureScheduleName, optSchedule->nameString());
     }
 
     return idfObject;
diff --git a/src/energyplus/ReverseTranslator/ReverseTranslateZoneCrossMixing.cpp b/src/energyplus/ReverseTranslator/ReverseTranslateZoneCrossMixing.cpp
index 49e88815abf..b4bba695721 100644
--- a/src/energyplus/ReverseTranslator/ReverseTranslateZoneCrossMixing.cpp
+++ b/src/energyplus/ReverseTranslator/ReverseTranslateZoneCrossMixing.cpp
@@ -134,8 +134,8 @@ namespace energyplus {
     } else if (istringEqual("Flow/Area", *s)) {
       d = workspaceObject.getDouble(openstudio::ZoneCrossMixingFields::FlowRateperFloor);
       if (d) {
-        mixing.setFlowRateperZoneFloorArea(*d);
-        reverseMixing.setFlowRateperZoneFloorArea(*d);
+        mixing.setFlowRateperFloorArea(*d);
+        reverseMixing.setFlowRateperFloorArea(*d);
       } else {
         LOG(Error, "Flow/Area value not found for workspace object " << workspaceObject);
       }
@@ -181,8 +181,8 @@ namespace energyplus {
       OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
       if (modelObject) {
         if (auto s = modelObject->optionalCast<Schedule>()) {
-          mixing.setMinimumZoneTemperatureSchedule(s.get());
-          reverseMixing.setMinimumZoneTemperatureSchedule(s.get());
+          mixing.setMinimumReceivingTemperatureSchedule(s.get());
+          reverseMixing.setMinimumReceivingTemperatureSchedule(s.get());
         }
       }
     }
@@ -192,8 +192,8 @@ namespace energyplus {
       OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
       if (modelObject) {
         if (auto s = modelObject->optionalCast<Schedule>()) {
-          mixing.setMaximumZoneTemperatureSchedule(s.get());
-          reverseMixing.setMaximumZoneTemperatureSchedule(s.get());
+          mixing.setMaximumReceivingTemperatureSchedule(s.get());
+          reverseMixing.setMaximumReceivingTemperatureSchedule(s.get());
         }
       }
     }
@@ -203,8 +203,8 @@ namespace energyplus {
       OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
       if (modelObject) {
         if (auto s = modelObject->optionalCast<Schedule>()) {
-          mixing.setMinimumSourceZoneTemperatureSchedule(s.get());
-          reverseMixing.setMinimumSourceZoneTemperatureSchedule(s.get());
+          mixing.setMinimumSourceTemperatureSchedule(s.get());
+          reverseMixing.setMinimumSourceTemperatureSchedule(s.get());
         }
       }
     }
@@ -214,8 +214,8 @@ namespace energyplus {
       OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
       if (modelObject) {
         if (auto s = modelObject->optionalCast<Schedule>()) {
-          mixing.setMaximumSourceZoneTemperatureSchedule(s.get());
-          reverseMixing.setMaximumSourceZoneTemperatureSchedule(s.get());
+          mixing.setMaximumSourceTemperatureSchedule(s.get());
+          reverseMixing.setMaximumSourceTemperatureSchedule(s.get());
         }
       }
     }
diff --git a/src/energyplus/ReverseTranslator/ReverseTranslateZoneMixing.cpp b/src/energyplus/ReverseTranslator/ReverseTranslateZoneMixing.cpp
index 94467e6a714..272b28722ae 100644
--- a/src/energyplus/ReverseTranslator/ReverseTranslateZoneMixing.cpp
+++ b/src/energyplus/ReverseTranslator/ReverseTranslateZoneMixing.cpp
@@ -125,7 +125,7 @@ namespace energyplus {
     } else if (istringEqual("Flow/Area", *s)) {
       d = workspaceObject.getDouble(openstudio::ZoneMixingFields::FlowRateperFloorArea);
       if (d) {
-        mixing.setFlowRateperZoneFloorArea(*d);
+        mixing.setFlowRateperFloorArea(*d);
       } else {
         LOG(Error, "Flow/Area value not found for workspace object " << workspaceObject);
       }
@@ -167,7 +167,7 @@ namespace energyplus {
       OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
       if (modelObject) {
         if (auto s = modelObject->optionalCast<Schedule>()) {
-          mixing.setMinimumZoneTemperatureSchedule(s.get());
+          mixing.setMinimumReceivingTemperatureSchedule(s.get());
         }
       }
     }
@@ -177,7 +177,7 @@ namespace energyplus {
       OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
       if (modelObject) {
         if (auto s = modelObject->optionalCast<Schedule>()) {
-          mixing.setMaximumZoneTemperatureSchedule(s.get());
+          mixing.setMaximumReceivingTemperatureSchedule(s.get());
         }
       }
     }
@@ -187,7 +187,7 @@ namespace energyplus {
       OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
       if (modelObject) {
         if (auto s = modelObject->optionalCast<Schedule>()) {
-          mixing.setMinimumSourceZoneTemperatureSchedule(s.get());
+          mixing.setMinimumSourceTemperatureSchedule(s.get());
         }
       }
     }
@@ -197,7 +197,7 @@ namespace energyplus {
       OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
       if (modelObject) {
         if (auto s = modelObject->optionalCast<Schedule>()) {
-          mixing.setMaximumSourceZoneTemperatureSchedule(s.get());
+          mixing.setMaximumSourceTemperatureSchedule(s.get());
         }
       }
     }
diff --git a/src/energyplus/Test/ZoneMixing_GTest.cpp b/src/energyplus/Test/ZoneMixing_GTest.cpp
new file mode 100644
index 00000000000..c7fa0b8f36f
--- /dev/null
+++ b/src/energyplus/Test/ZoneMixing_GTest.cpp
@@ -0,0 +1,500 @@
+/***********************************************************************************************************************
+*  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/ZoneMixing.hpp"
+#include "../../model/ZoneMixing_Impl.hpp"
+#include "../../model/ThermalZone.hpp"
+#include "../../model/ScheduleRuleset.hpp"
+#include "../../model/ScheduleTypeLimits.hpp"
+#include "../../model/Model.hpp"
+#include "../../model/Model_Impl.hpp"
+#include "../../model/Space.hpp"
+#include "../../model/Space_Impl.hpp"
+#include "../../model/PeopleDefinition.hpp"
+#include "../../model/People.hpp"
+#include "../../model/SpaceType.hpp"
+#include "../../model/Building.hpp"
+#include "../../model/Building_Impl.hpp"
+#include "../../model/ScheduleConstant.hpp"
+
+#include "../../utilities/idf/WorkspaceObject.hpp"
+#include "../../utilities/idf/IdfExtensibleGroup.hpp"
+#include "../../utilities/idf/WorkspaceExtensibleGroup.hpp"
+
+#include "../../utilities/geometry/Geometry.hpp"
+#include "../../utilities/geometry/Point3d.hpp"
+
+#include <utilities/idd/ZoneMixing_FieldEnums.hxx>
+#include <utilities/idd/IddEnums.hxx>
+
+#include <utility>
+
+using namespace openstudio::energyplus;
+using namespace openstudio::model;
+using namespace openstudio;
+
+TEST_F(EnergyPlusFixture, ForwardTranslatorZoneMixing_DifferentZones) {
+
+  Model m;
+
+  constexpr double width = 10.0;
+  constexpr double height = 3.6;  // It's convenient for ACH, since 3600 s/hr
+  constexpr double spaceFloorArea = width * width;
+  constexpr double spaceVolume = spaceFloorArea * height;
+  constexpr double numPeoplePerSpace = 10.0;
+
+  //            y (=North)
+  //   ▲
+  //   │                  building height = 3m
+  // 10├────────┼────────┼────────┼────────┤
+  //   │        │        │        │        │
+  //   │ Zone 1 │ Zone 1 │ Zone 2 │ Zone 2 │
+  //   │ Space 1│ Space 2│ Space 3│ Space 4│
+  //   │        │        │        │        │
+  //   └────────┴────────┴────────┴────────┴──► x
+  //  0        10       20        30       40
+
+  // Counterclockwise points
+  std::vector<Point3d> floorPointsSpace1{{0.0, 0.0, 0.0}, {0.0, width, 0.0}, {width, width, 0.0}, {width, 0.0, 0.0}};
+
+  auto space1 = Space::fromFloorPrint(floorPointsSpace1, height, m).get();
+  auto space2 = Space::fromFloorPrint(floorPointsSpace1, height, m).get();
+  space2.setXOrigin(width);
+  auto space3 = Space::fromFloorPrint(floorPointsSpace1, height, m).get();
+  space3.setXOrigin(width * 2);
+  auto space4 = Space::fromFloorPrint(floorPointsSpace1, height, m).get();
+  space4.setXOrigin(width * 3);
+
+  ThermalZone z1(m);
+  space1.setThermalZone(z1);
+  space2.setThermalZone(z1);
+
+  ThermalZone z2(m);
+  space3.setThermalZone(z2);
+  space4.setThermalZone(z2);
+
+  // Add 10 People in each space
+  SpaceType buildingSpaceType(m);
+  buildingSpaceType.setName("buildingSpaceType");
+  PeopleDefinition pd(m);
+  pd.setNumberofPeople(numPeoplePerSpace);
+  People p(pd);
+  EXPECT_TRUE(p.setSpaceType(buildingSpaceType));
+  auto building = m.getUniqueModelObject<Building>();
+  EXPECT_TRUE(building.setSpaceType(buildingSpaceType));
+
+  ZoneMixing zmAbs(space3);
+  zmAbs.setName("zmAbs");
+  EXPECT_TRUE(zmAbs.setSourceSpace(space2));
+  constexpr double m3sAbs = 0.6;
+  EXPECT_TRUE(zmAbs.setDesignFlowRate(m3sAbs));
+
+  ZoneMixing zmFloor(space2);
+  zmFloor.setName("zmFloor");
+  EXPECT_TRUE(zmFloor.setSourceSpace(space4));
+  constexpr double m3sPerFlow = 0.001;
+  constexpr double equivm3s_Floor = m3sPerFlow * spaceFloorArea;
+  EXPECT_TRUE(zmFloor.setFlowRateperFloorArea(m3sPerFlow));
+
+  ZoneMixing zmPerson(space4);
+  zmPerson.setName("zmPerson");
+  EXPECT_TRUE(zmPerson.setSourceSpace(space1));
+  constexpr double m3sPerPerson = 0.03;
+  constexpr double equivm3s_Person = m3sPerPerson * numPeoplePerSpace;
+  EXPECT_TRUE(zmPerson.setFlowRateperPerson(m3sPerPerson));
+
+  ZoneMixing zmACH(space1);
+  zmACH.setName("zmACH");
+  EXPECT_TRUE(zmACH.setSourceSpace(space3));
+  constexpr double ach = 0.5;
+  constexpr double equivm3s_ACH = ach * spaceVolume / 3600.0;
+  EXPECT_TRUE(zmACH.setAirChangesperHour(ach));
+
+  ZoneMixing zmZone(z1);
+  zmZone.setName("zmZone");
+  EXPECT_TRUE(zmZone.setSourceZone(z2));
+  EXPECT_TRUE(zmZone.setFlowRateperFloorArea(0.008));
+
+  // |    Zone Mixing   | Receiving |  Source | Flow/Space | Per Floor Area | Per Person |   ACH )  || Calculated Total   |
+  // |       Name       |  Entity   |  Entity |   (m3/s)   |    (m3/s-m2)   |  (m3/s.p)  |  (1/hr)  || For 1 Space (m3/s) |
+  // |------------------|-----------|---------|------------|----------------|------------|----------||--------------------|
+  // | zmAbs            | space3    | space2  | 0.6        |                |            |          ||      0.6           |
+  // | zmFloor          | space2    | space4  |            | 0.001          |            |          ||      0.1           |
+  // | zmPerson         | space4    | space1  |            |                | 0.03       |          ||      0.3           |
+  // | zmACH            | space1    | space3  |            |                |            | 0.5      ||      0.05          |
+  // | zmZone           | **z1**    | **z2**  |            | 0.008          |            | 0.5      ||      0.05          |
+
+  EXPECT_EQ(0.6, m3sAbs);
+  EXPECT_EQ(0.1, equivm3s_Floor);
+  EXPECT_EQ(0.3, equivm3s_Person);
+  EXPECT_EQ(0.05, equivm3s_ACH);
+
+  EXPECT_TRUE(zmAbs.setDeltaTemperature(0.5));
+
+  ScheduleConstant mixingSch(m);
+  mixingSch.setName("mixingSch");
+  EXPECT_TRUE(zmZone.setSchedule(mixingSch));
+
+  ScheduleConstant deltaTempSch(m);
+  deltaTempSch.setName("deltaTempSch");
+  EXPECT_TRUE(zmZone.setDeltaTemperatureSchedule(deltaTempSch));
+
+  ScheduleConstant minimumReceivingTempSch(m);
+  minimumReceivingTempSch.setName("minimumReceivingTempSch");
+  EXPECT_TRUE(zmZone.setMinimumReceivingTemperatureSchedule(minimumReceivingTempSch));
+
+  ScheduleConstant minimumSourceTempSch(m);
+  minimumSourceTempSch.setName("minimumSourceTempSch");
+  EXPECT_TRUE(zmZone.setMinimumSourceTemperatureSchedule(minimumSourceTempSch));
+
+  ScheduleConstant minimumOutdoorTempSch(m);
+  minimumOutdoorTempSch.setName("minimumOutdoorTempSch");
+  EXPECT_TRUE(zmZone.setMinimumOutdoorTemperatureSchedule(minimumOutdoorTempSch));
+
+  ScheduleConstant maximumReceivingTempSch(m);
+  maximumReceivingTempSch.setName("maximumReceivingTempSch");
+  EXPECT_TRUE(zmZone.setMaximumReceivingTemperatureSchedule(maximumReceivingTempSch));
+
+  ScheduleConstant maximumSourceTempSch(m);
+  maximumSourceTempSch.setName("maximumSourceTempSch");
+  EXPECT_TRUE(zmZone.setMaximumSourceTemperatureSchedule(maximumSourceTempSch));
+
+  ScheduleConstant maximumOutdoorTempSch(m);
+  maximumOutdoorTempSch.setName("maximumOutdoorTempSch");
+  EXPECT_TRUE(zmZone.setMaximumOutdoorTemperatureSchedule(maximumOutdoorTempSch));
+
+  auto alwaysOnContinuousSchedule = m.alwaysOnContinuousSchedule();
+
+  ForwardTranslator ft;
+  {
+    ft.setExcludeSpaceTranslation(false);
+
+    Workspace w = ft.translateModel(m);
+
+    auto zones = w.getObjectsByType(IddObjectType::Zone);
+    ASSERT_EQ(2, zones.size());
+    auto zone = zones[0];
+    EXPECT_EQ(4, w.getObjectsByType(IddObjectType::Space).size());
+
+    auto zms = w.getObjectsByType(IddObjectType::ZoneMixing);
+    ASSERT_EQ(5, zms.size());
+
+    for (const auto& zm : zms) {
+      auto name = zm.nameString();
+      auto receiving_ = zm.getTarget(ZoneMixingFields::ZoneorSpaceName);
+      ASSERT_TRUE(receiving_);
+      auto source_ = zm.getTarget(ZoneMixingFields::SourceZoneorSpaceName);
+      ASSERT_TRUE(source_);
+      auto sch_ = zm.getTarget(ZoneMixingFields::ScheduleName);
+      ASSERT_TRUE(sch_);
+
+      if (name == zmZone.nameString()) {
+        EXPECT_EQ(receiving_->iddObject().type(), IddObjectType::Zone);
+        EXPECT_EQ(source_->iddObject().type(), IddObjectType::Zone);
+        EXPECT_EQ(z1.nameString(), receiving_->nameString());
+        EXPECT_EQ(mixingSch.nameString(), sch_->nameString());
+        EXPECT_EQ("Flow/Area", zm.getString(ZoneMixingFields::DesignFlowRateCalculationMethod).get());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DesignFlowRate));
+        EXPECT_EQ(0.008, zm.getDouble(ZoneMixingFields::FlowRateperFloorArea).get());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperPerson));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::AirChangesperHour));
+        EXPECT_EQ(z2.nameString(), source_->nameString());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DeltaTemperature));
+        EXPECT_EQ("deltaTempSch", zm.getString(ZoneMixingFields::DeltaTemperatureScheduleName).get());
+        EXPECT_EQ("minimumReceivingTempSch", zm.getString(ZoneMixingFields::MinimumReceivingTemperatureScheduleName).get());
+        EXPECT_EQ("maximumReceivingTempSch", zm.getString(ZoneMixingFields::MaximumReceivingTemperatureScheduleName).get());
+        EXPECT_EQ("minimumSourceTempSch", zm.getString(ZoneMixingFields::MinimumSourceTemperatureScheduleName).get());
+        EXPECT_EQ("maximumSourceTempSch", zm.getString(ZoneMixingFields::MaximumSourceTemperatureScheduleName).get());
+        EXPECT_EQ("minimumOutdoorTempSch", zm.getString(ZoneMixingFields::MinimumOutdoorTemperatureScheduleName).get());
+        EXPECT_EQ("maximumOutdoorTempSch", zm.getString(ZoneMixingFields::MaximumOutdoorTemperatureScheduleName).get());
+        continue;
+      }
+
+      EXPECT_EQ(receiving_->iddObject().type(), IddObjectType::Space);
+      EXPECT_EQ(source_->iddObject().type(), IddObjectType::Space);
+      EXPECT_EQ(alwaysOnContinuousSchedule.nameString(), sch_->nameString());
+
+      EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DeltaTemperatureScheduleName));
+      EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MinimumReceivingTemperatureScheduleName));
+      EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MaximumReceivingTemperatureScheduleName));
+      EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MinimumSourceTemperatureScheduleName));
+      EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MaximumSourceTemperatureScheduleName));
+      EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MinimumOutdoorTemperatureScheduleName));
+      EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MaximumOutdoorTemperatureScheduleName));
+      if (name == zmAbs.nameString()) {
+        EXPECT_EQ(space3.nameString(), receiving_->nameString());
+        EXPECT_EQ("Flow/Zone", zm.getString(ZoneMixingFields::DesignFlowRateCalculationMethod).get());
+        EXPECT_EQ(m3sAbs, zm.getDouble(ZoneMixingFields::DesignFlowRate).get());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperFloorArea));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperPerson));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::AirChangesperHour));
+        EXPECT_EQ(space2.nameString(), source_->nameString());
+        EXPECT_EQ(0.5, zm.getDouble(ZoneMixingFields::DeltaTemperature).get());
+      } else if (name == zmFloor.nameString()) {
+        EXPECT_EQ(space2.nameString(), receiving_->nameString());
+        EXPECT_EQ("Flow/Area", zm.getString(ZoneMixingFields::DesignFlowRateCalculationMethod).get());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DesignFlowRate));
+        EXPECT_EQ(0.001, zm.getDouble(ZoneMixingFields::FlowRateperFloorArea).get());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperPerson));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::AirChangesperHour));
+        EXPECT_EQ(space4.nameString(), source_->nameString());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DeltaTemperature));
+      } else if (name == zmPerson.nameString()) {
+        EXPECT_EQ(space4.nameString(), receiving_->nameString());
+        EXPECT_EQ("Flow/Person", zm.getString(ZoneMixingFields::DesignFlowRateCalculationMethod).get());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DesignFlowRate));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperFloorArea));
+        EXPECT_EQ(0.03, zm.getDouble(ZoneMixingFields::FlowRateperPerson).get());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::AirChangesperHour));
+        EXPECT_EQ(space1.nameString(), source_->nameString());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DeltaTemperature));
+      } else if (name == zmACH.nameString()) {
+        EXPECT_EQ(space1.nameString(), receiving_->nameString());
+        EXPECT_EQ("AirChanges/Hour", zm.getString(ZoneMixingFields::DesignFlowRateCalculationMethod).get());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DesignFlowRate));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperFloorArea));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperPerson));
+        EXPECT_EQ(0.5, zm.getDouble(ZoneMixingFields::AirChangesperHour).get());
+        EXPECT_EQ(space3.nameString(), source_->nameString());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DeltaTemperature));
+      }
+    }
+  }
+
+  {
+    ft.setExcludeSpaceTranslation(true);
+
+    Workspace w = ft.translateModel(m);
+
+    auto zones = w.getObjectsByType(IddObjectType::Zone);
+    ASSERT_EQ(2, zones.size());
+    auto zone = zones[0];
+    EXPECT_EQ(0, w.getObjectsByType(IddObjectType::Space).size());
+
+    auto zms = w.getObjectsByType(IddObjectType::ZoneMixing);
+    ASSERT_EQ(5, zms.size());
+
+    for (const auto& zm : zms) {
+      auto name = zm.nameString();
+      auto receiving_ = zm.getTarget(ZoneMixingFields::ZoneorSpaceName);
+      ASSERT_TRUE(receiving_);
+      auto source_ = zm.getTarget(ZoneMixingFields::SourceZoneorSpaceName);
+      ASSERT_TRUE(source_);
+      auto sch_ = zm.getTarget(ZoneMixingFields::ScheduleName);
+      ASSERT_TRUE(sch_);
+
+      EXPECT_EQ(receiving_->iddObject().type(), IddObjectType::Zone);
+      EXPECT_EQ(source_->iddObject().type(), IddObjectType::Zone);
+
+      if (name == zmZone.nameString()) {
+        EXPECT_EQ(z1.nameString(), receiving_->nameString());
+        EXPECT_EQ(mixingSch.nameString(), sch_->nameString());
+        EXPECT_EQ("Flow/Area", zm.getString(ZoneMixingFields::DesignFlowRateCalculationMethod).get());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DesignFlowRate));
+        EXPECT_EQ(0.008, zm.getDouble(ZoneMixingFields::FlowRateperFloorArea).get());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperPerson));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::AirChangesperHour));
+        EXPECT_EQ(z2.nameString(), source_->nameString());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DeltaTemperature));
+        EXPECT_EQ("deltaTempSch", zm.getString(ZoneMixingFields::DeltaTemperatureScheduleName).get());
+        EXPECT_EQ("minimumReceivingTempSch", zm.getString(ZoneMixingFields::MinimumReceivingTemperatureScheduleName).get());
+        EXPECT_EQ("maximumReceivingTempSch", zm.getString(ZoneMixingFields::MaximumReceivingTemperatureScheduleName).get());
+        EXPECT_EQ("minimumSourceTempSch", zm.getString(ZoneMixingFields::MinimumSourceTemperatureScheduleName).get());
+        EXPECT_EQ("maximumSourceTempSch", zm.getString(ZoneMixingFields::MaximumSourceTemperatureScheduleName).get());
+        EXPECT_EQ("minimumOutdoorTempSch", zm.getString(ZoneMixingFields::MinimumOutdoorTemperatureScheduleName).get());
+        EXPECT_EQ("maximumOutdoorTempSch", zm.getString(ZoneMixingFields::MaximumOutdoorTemperatureScheduleName).get());
+        continue;
+      }
+
+      EXPECT_EQ(alwaysOnContinuousSchedule.nameString(), sch_->nameString());
+      EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DeltaTemperatureScheduleName));
+      EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MinimumReceivingTemperatureScheduleName));
+      EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MaximumReceivingTemperatureScheduleName));
+      EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MinimumSourceTemperatureScheduleName));
+      EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MaximumSourceTemperatureScheduleName));
+      EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MinimumOutdoorTemperatureScheduleName));
+      EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MaximumOutdoorTemperatureScheduleName));
+      if (name == zmAbs.nameString()) {
+        EXPECT_EQ(z2.nameString(), receiving_->nameString());
+        EXPECT_EQ("Flow/Zone", zm.getString(ZoneMixingFields::DesignFlowRateCalculationMethod).get());
+        EXPECT_EQ(m3sAbs, zm.getDouble(ZoneMixingFields::DesignFlowRate).get());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperFloorArea));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperPerson));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::AirChangesperHour));
+        EXPECT_EQ(z1.nameString(), source_->nameString());
+        EXPECT_EQ(0.5, zm.getDouble(ZoneMixingFields::DeltaTemperature).get());
+      } else if (name == zmFloor.nameString()) {
+        EXPECT_EQ(z1.nameString(), receiving_->nameString());
+        EXPECT_EQ("Flow/Zone", zm.getString(ZoneMixingFields::DesignFlowRateCalculationMethod).get());
+        EXPECT_EQ(equivm3s_Floor, zm.getDouble(ZoneMixingFields::DesignFlowRate).get());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperFloorArea));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperPerson));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::AirChangesperHour));
+        EXPECT_EQ(z2.nameString(), source_->nameString());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DeltaTemperature));
+      } else if (name == zmPerson.nameString()) {
+        EXPECT_EQ(z2.nameString(), receiving_->nameString());
+        EXPECT_EQ("Flow/Zone", zm.getString(ZoneMixingFields::DesignFlowRateCalculationMethod).get());
+        EXPECT_EQ(equivm3s_Person, zm.getDouble(ZoneMixingFields::DesignFlowRate).get());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperFloorArea));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperPerson));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::AirChangesperHour));
+        EXPECT_EQ(z1.nameString(), source_->nameString());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DeltaTemperature));
+      } else if (name == zmACH.nameString()) {
+        EXPECT_EQ(z1.nameString(), receiving_->nameString());
+        EXPECT_EQ("Flow/Zone", zm.getString(ZoneMixingFields::DesignFlowRateCalculationMethod).get());
+        EXPECT_EQ(equivm3s_ACH, zm.getDouble(ZoneMixingFields::DesignFlowRate).get());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperFloorArea));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperPerson));
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::AirChangesperHour));
+        EXPECT_EQ(z2.nameString(), source_->nameString());
+        EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DeltaTemperature));
+      }
+    }
+  }
+}
+
+TEST_F(EnergyPlusFixture, ForwardTranslatorZoneMixing_SameZone) {
+
+  Model m;
+
+  constexpr double width = 10.0;
+  constexpr double height = 3.6;  // It's convenient for ACH, since 3600 s/hr
+
+  //            y (=North)
+  //   ▲
+  //   │                  building height = 3m
+  // 10├────────┼────────┼
+  //   │        │        │
+  //   │ Zone 1 │ Zone 1 │
+  //   │ Space 1│ Space 2│
+  //   │        │        │        │        │
+  //   └────────┴────────┴────────┴────────┴──► x
+  //  0        10       20        30       40
+
+  // Counterclockwise points
+  std::vector<Point3d> floorPointsSpace1{{0.0, 0.0, 0.0}, {0.0, width, 0.0}, {width, width, 0.0}, {width, 0.0, 0.0}};
+
+  auto space1 = Space::fromFloorPrint(floorPointsSpace1, height, m).get();
+  auto space2 = Space::fromFloorPrint(floorPointsSpace1, height, m).get();
+  space2.setXOrigin(width);
+
+  ThermalZone z1(m);
+  space1.setThermalZone(z1);
+  space2.setThermalZone(z1);
+
+  ZoneMixing zmAbs(space1);
+  zmAbs.setName("zmAbs");
+  EXPECT_TRUE(zmAbs.setSourceSpace(space2));
+  constexpr double m3sAbs = 0.6;
+  EXPECT_TRUE(zmAbs.setDesignFlowRate(m3sAbs));
+
+  // |    Zone Mixing   | Receiving |  Source | Flow/Space | Per Floor Area | Per Person |   ACH )  || Calculated Total   |
+  // |       Name       |  Entity   |  Entity |   (m3/s)   |    (m3/s-m2)   |  (m3/s.p)  |  (1/hr)  || For 1 Space (m3/s) |
+  // |------------------|-----------|---------|------------|----------------|------------|----------||--------------------|
+  // | zmAbs            | space1    | space2  | 0.6        |                |            |          ||      0.6           |
+
+  auto alwaysOnContinuousSchedule = m.alwaysOnContinuousSchedule();
+
+  ForwardTranslator ft;
+  {
+    ft.setExcludeSpaceTranslation(false);
+
+    Workspace w = ft.translateModel(m);
+
+    auto zones = w.getObjectsByType(IddObjectType::Zone);
+    ASSERT_EQ(1, zones.size());
+    auto zone = zones[0];
+    EXPECT_EQ(2, w.getObjectsByType(IddObjectType::Space).size());
+
+    auto zms = w.getObjectsByType(IddObjectType::ZoneMixing);
+    ASSERT_EQ(1, zms.size());
+    auto& zm = zms.front();
+
+    auto name = zm.nameString();
+    auto receiving_ = zm.getTarget(ZoneMixingFields::ZoneorSpaceName);
+    ASSERT_TRUE(receiving_);
+    auto source_ = zm.getTarget(ZoneMixingFields::SourceZoneorSpaceName);
+    ASSERT_TRUE(source_);
+    auto sch_ = zm.getTarget(ZoneMixingFields::ScheduleName);
+    ASSERT_TRUE(sch_);
+
+    EXPECT_EQ(receiving_->iddObject().type(), IddObjectType::Space);
+    EXPECT_EQ(source_->iddObject().type(), IddObjectType::Space);
+
+    EXPECT_EQ(name, zmAbs.nameString());
+    EXPECT_EQ(alwaysOnContinuousSchedule.nameString(), sch_->nameString());
+    EXPECT_EQ(space1.nameString(), receiving_->nameString());
+    EXPECT_EQ("Flow/Zone", zm.getString(ZoneMixingFields::DesignFlowRateCalculationMethod).get());
+    EXPECT_EQ(m3sAbs, zm.getDouble(ZoneMixingFields::DesignFlowRate).get());
+    EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperFloorArea));
+    EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::FlowRateperPerson));
+    EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::AirChangesperHour));
+    EXPECT_EQ(space2.nameString(), source_->nameString());
+    EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DeltaTemperature));
+    EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::DeltaTemperatureScheduleName));
+    EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MinimumReceivingTemperatureScheduleName));
+    EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MaximumReceivingTemperatureScheduleName));
+    EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MinimumSourceTemperatureScheduleName));
+    EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MaximumSourceTemperatureScheduleName));
+    EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MinimumOutdoorTemperatureScheduleName));
+    EXPECT_TRUE(zm.isEmpty(ZoneMixingFields::MaximumOutdoorTemperatureScheduleName));
+  }
+
+  {
+    ft.setExcludeSpaceTranslation(true);
+
+    Workspace w = ft.translateModel(m);
+
+    auto zones = w.getObjectsByType(IddObjectType::Zone);
+    ASSERT_EQ(1, zones.size());
+    auto zone = zones[0];
+    EXPECT_EQ(0, w.getObjectsByType(IddObjectType::Space).size());
+
+    auto zms = w.getObjectsByType(IddObjectType::ZoneMixing);
+    ASSERT_EQ(0, zms.size());
+
+    // This is problematic, we actually try to FT that object 4 TIMES
+    // EXPECT_EQ(1, ft.warnings().size());
+    EXPECT_GE(ft.warnings().size(), 1);
+    std::string expectedWarning =
+      "Object of type 'OS:ZoneMixing' and named 'zmAbs' doesn't have a Source Zone or Space, it will not be translated. As you were using "
+      "Space-Level ZoneMixing, and you are not translating to Spaces, it's possible it was pointing to two spaces inside the same zone";
+    auto warnings = ft.warnings();
+    EXPECT_NE(warnings.cend(),
+              std::find_if(warnings.cbegin(), warnings.cend(), [&expectedWarning](auto& l) { return l.logMessage() == expectedWarning; }));
+  }
+}
diff --git a/src/model/ScheduleTypeRegistry.cpp b/src/model/ScheduleTypeRegistry.cpp
index 8c8c97c67c3..728022862c7 100644
--- a/src/model/ScheduleTypeRegistry.cpp
+++ b/src/model/ScheduleTypeRegistry.cpp
@@ -564,12 +564,10 @@ namespace model {
       {"ZoneHVACUnitVentilator", "Supply Air Fan Operating Mode", "supplyAirFanOperatingModeSchedule", false, "ControlMode", 0.0, 1.0},
       {"ZoneMixing", "Zone Mixing", "schedule", true, "Dimensionless", 0.0, 1.0},
       {"ZoneMixing", "Delta Temperature", "deltaTemperatureSchedule", true, "DeltaTemperature", OptionalDouble(), OptionalDouble()},
-      {"ZoneMixing", "Minimum Zone Temperature", "minimumZoneTemperatureSchedule", true, "Temperature", OptionalDouble(), OptionalDouble()},
-      {"ZoneMixing", "Maximum Zone Temperature", "maximumZoneTemperatureSchedule", true, "Temperature", OptionalDouble(), OptionalDouble()},
-      {"ZoneMixing", "Minimum Source Zone Temperature", "minimumSourceZoneTemperatureSchedule", true, "Temperature", OptionalDouble(),
-       OptionalDouble()},
-      {"ZoneMixing", "Maximum Source Zone Temperature", "maximumSourceZoneTemperatureSchedule", true, "Temperature", OptionalDouble(),
-       OptionalDouble()},
+      {"ZoneMixing", "Minimum Receiving Temperature", "minimumReceivingTemperatureSchedule", true, "Temperature", OptionalDouble(), OptionalDouble()},
+      {"ZoneMixing", "Maximum Receiving Temperature", "maximumReceivingTemperatureSchedule", true, "Temperature", OptionalDouble(), OptionalDouble()},
+      {"ZoneMixing", "Minimum Source Temperature", "minimumSourceTemperatureSchedule", true, "Temperature", OptionalDouble(), OptionalDouble()},
+      {"ZoneMixing", "Maximum Source Temperature", "maximumSourceTemperatureSchedule", true, "Temperature", OptionalDouble(), OptionalDouble()},
       {"ZoneMixing", "Minimum Outdoor Temperature", "minimumOutdoorTemperatureSchedule", true, "Temperature", OptionalDouble(), OptionalDouble()},
       {"ZoneMixing", "Maximum Outdoor Temperature", "maximumOutdoorTemperatureSchedule", true, "Temperature", OptionalDouble(), OptionalDouble()},
       {"", "", "", true, "", OptionalDouble(), OptionalDouble()}};
diff --git a/src/model/Space.cpp b/src/model/Space.cpp
index 171fbe57897..45634af4000 100644
--- a/src/model/Space.cpp
+++ b/src/model/Space.cpp
@@ -114,6 +114,8 @@
 #include "DesignSpecificationOutdoorAir_Impl.hpp"
 #include "GlareSensor.hpp"
 #include "GlareSensor_Impl.hpp"
+#include "ZoneMixing.hpp"
+#include "ZoneMixing_Impl.hpp"
 
 #include <utilities/idd/OS_Space_FieldEnums.hxx>
 #include <utilities/idd/OS_Surface_FieldEnums.hxx>
@@ -146,6 +148,7 @@
 #  pragma warning(pop)
 #endif
 
+#include <algorithm>
 #include <cmath>
 
 namespace openstudio {
@@ -259,6 +262,10 @@ namespace model {
       SpaceInfiltrationFlowCoefficientVector spaceInfiltrationFlowCoefficients = this->spaceInfiltrationFlowCoefficients();
       result.insert(result.end(), spaceInfiltrationFlowCoefficients.begin(), spaceInfiltrationFlowCoefficients.end());
 
+      // ZoneMixing
+      auto zoneMixings = this->supplyZoneMixing();
+      result.insert(result.end(), zoneMixings.begin(), zoneMixings.end());
+
       return result;
     }
 
@@ -2909,6 +2916,33 @@ namespace model {
       return boost::make_tuple(point3d.x(), point3d.y());
     }
 
+    std::vector<ZoneMixing> Space_Impl::zoneMixing() const {
+      return getObject<ModelObject>().getModelObjectSources<ZoneMixing>();
+    }
+
+    std::vector<ZoneMixing> Space_Impl::supplyZoneMixing() const {
+      std::vector<ZoneMixing> result = this->zoneMixing();
+
+      Handle handle = this->handle();
+      auto new_end =
+        std::remove_if(result.begin(), result.end(), [&](const ZoneMixing& mixing) { return (mixing.zoneOrSpace().handle() != handle); });
+
+      result.erase(new_end, result.end());
+      return result;
+    }
+
+    std::vector<ZoneMixing> Space_Impl::exhaustZoneMixing() const {
+      std::vector<ZoneMixing> result = this->zoneMixing();
+
+      Handle handle = this->handle();
+      auto new_end = std::remove_if(result.begin(), result.end(), [&](const ZoneMixing& mixing) {
+        return (!mixing.sourceZoneOrSpace() || (mixing.sourceZoneOrSpace()->handle() != handle));
+      });
+
+      result.erase(new_end, result.end());
+      return result;
+    }
+
   }  // namespace detail
 
   Space::Space(const Model& model) : PlanarSurfaceGroup(Space::iddObjectType(), model) {
@@ -3493,6 +3527,18 @@ namespace model {
     return getImpl<detail::Space_Impl>()->isEnclosedVolume();
   }
 
+  std::vector<ZoneMixing> Space::zoneMixing() const {
+    return getImpl<detail::Space_Impl>()->zoneMixing();
+  }
+
+  std::vector<ZoneMixing> Space::supplyZoneMixing() const {
+    return getImpl<detail::Space_Impl>()->supplyZoneMixing();
+  }
+
+  std::vector<ZoneMixing> Space::exhaustZoneMixing() const {
+    return getImpl<detail::Space_Impl>()->exhaustZoneMixing();
+  }
+
   /// @cond
   Space::Space(std::shared_ptr<detail::Space_Impl> impl) : PlanarSurfaceGroup(std::move(impl)) {}
   /// @endcond
diff --git a/src/model/Space.hpp b/src/model/Space.hpp
index b53ef9f1186..fd295fb7713 100644
--- a/src/model/Space.hpp
+++ b/src/model/Space.hpp
@@ -72,6 +72,7 @@ namespace model {
   class SpaceInfiltrationEffectiveLeakageArea;
   class SpaceInfiltrationFlowCoefficient;
   class DesignSpecificationOutdoorAir;
+  class ZoneMixing;
 
   namespace detail {
 
@@ -625,6 +626,15 @@ namespace model {
     Polyhedron polyhedron() const;
     bool isEnclosedVolume() const;
 
+    /** Returns all ZoneMixing objects associated with this space, includes supply and exhaust mixing objects */
+    std::vector<ZoneMixing> zoneMixing() const;
+
+    /** Returns all ZoneMixing objects which supply air to this space */
+    std::vector<ZoneMixing> supplyZoneMixing() const;
+
+    /** Returns all ZoneMixing objects which exhaust air from this space */
+    std::vector<ZoneMixing> exhaustZoneMixing() const;
+
     //@}
    protected:
     /// @cond
diff --git a/src/model/Space_Impl.hpp b/src/model/Space_Impl.hpp
index 0b44459b37a..43de4b5daff 100644
--- a/src/model/Space_Impl.hpp
+++ b/src/model/Space_Impl.hpp
@@ -76,6 +76,7 @@ namespace model {
   class SpaceInfiltrationFlowCoefficient;
   class DesignSpecificationOutdoorAir;
   class Space;
+  class ZoneMixing;
 
   namespace detail {
 
@@ -507,6 +508,10 @@ namespace model {
       Polyhedron polyhedron() const;
       bool isEnclosedVolume() const;
 
+      std::vector<ZoneMixing> zoneMixing() const;
+      std::vector<ZoneMixing> supplyZoneMixing() const;
+      std::vector<ZoneMixing> exhaustZoneMixing() const;
+
      private:
       REGISTER_LOGGER("openstudio.model.Space");
 
diff --git a/src/model/ThermalZone.cpp b/src/model/ThermalZone.cpp
index 519f14eac72..6c43f03de6d 100644
--- a/src/model/ThermalZone.cpp
+++ b/src/model/ThermalZone.cpp
@@ -1459,12 +1459,22 @@ namespace model {
         // first hard size any space loads, do this before removing surfaces as
         // hard sizing may require space geometry
         for (ModelObject child : children) {
-          if (child.optionalCast<SpaceLoad>()) {
-            child.cast<SpaceLoad>().hardSize();
-            child.cast<SpaceLoad>().hardApplySchedules();
+          if (auto sp_ = child.optionalCast<SpaceLoad>()) {
+            sp_->hardSize();
+            sp_->hardApplySchedules();
           }
         }
 
+        for (ZoneMixing& zm : space.supplyZoneMixing()) {
+          zm.hardSize();
+          zm.setParent(newSpace);
+        }
+
+        for (ZoneMixing& zm : space.exhaustZoneMixing()) {
+          // We only need to hardSize when it's the RECEIVING zone
+          zm.setSourceSpace(newSpace);
+        }
+
         // now move costs over to the new space
         for (LifeCycleCost cost : space.lifeCycleCosts()) {
           // new costs are in absolute units as space area is changing in the merge
@@ -2471,7 +2481,8 @@ namespace model {
       std::vector<ZoneMixing> result = this->zoneMixing();
 
       Handle handle = this->handle();
-      auto new_end = std::remove_if(result.begin(), result.end(), [&](const ZoneMixing& mixing) { return (mixing.zone().handle() != handle); });
+      auto new_end =
+        std::remove_if(result.begin(), result.end(), [&](const ZoneMixing& mixing) { return (mixing.zoneOrSpace().handle() != handle); });
 
       result.erase(new_end, result.end());
       return result;
@@ -2481,8 +2492,9 @@ namespace model {
       std::vector<ZoneMixing> result = this->zoneMixing();
 
       Handle handle = this->handle();
-      auto new_end = std::remove_if(result.begin(), result.end(),
-                                    [&](const ZoneMixing& mixing) { return (!mixing.sourceZone() || (mixing.sourceZone()->handle() != handle)); });
+      auto new_end = std::remove_if(result.begin(), result.end(), [&](const ZoneMixing& mixing) {
+        return (!mixing.sourceZoneOrSpace() || (mixing.sourceZoneOrSpace()->handle() != handle));
+      });
 
       result.erase(new_end, result.end());
       return result;
diff --git a/src/model/ZoneMixing.cpp b/src/model/ZoneMixing.cpp
index 3d51c7255a7..6f6f386bf43 100644
--- a/src/model/ZoneMixing.cpp
+++ b/src/model/ZoneMixing.cpp
@@ -32,6 +32,8 @@
 
 #include "ThermalZone.hpp"
 #include "ThermalZone_Impl.hpp"
+#include "Space.hpp"
+#include "Space_Impl.hpp"
 #include "Schedule.hpp"
 #include "Schedule_Impl.hpp"
 #include "ScheduleTypeLimits.hpp"
@@ -88,45 +90,119 @@ namespace model {
     }
 
     std::vector<ScheduleTypeKey> ZoneMixing_Impl::getScheduleTypeKeys(const Schedule& schedule) const {
-      // TODO: Check schedule display names.
       std::vector<ScheduleTypeKey> result;
       UnsignedVector fieldIndices = getSourceIndices(schedule.handle());
-      UnsignedVector::const_iterator b(fieldIndices.begin()), e(fieldIndices.end());
+      UnsignedVector::const_iterator b(fieldIndices.begin());
+      UnsignedVector::const_iterator e(fieldIndices.end());
       if (std::find(b, e, OS_ZoneMixingFields::ScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("ZoneMixing", "Zone Mixing"));
+        result.emplace_back("ZoneMixing", "Zone Mixing");
       }
       if (std::find(b, e, OS_ZoneMixingFields::DeltaTemperatureScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("ZoneMixing", "Delta Temperature"));
+        result.emplace_back("ZoneMixing", "Delta Temperature");
       }
-      if (std::find(b, e, OS_ZoneMixingFields::MinimumZoneTemperatureScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("ZoneMixing", "Minimum Zone Temperature"));
+      if (std::find(b, e, OS_ZoneMixingFields::MinimumReceivingTemperatureScheduleName) != e) {
+        result.emplace_back("ZoneMixing", "Minimum Receiving Temperature");
       }
-      if (std::find(b, e, OS_ZoneMixingFields::MaximumZoneTemperatureScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("ZoneMixing", "Maximum Zone Temperature"));
+      if (std::find(b, e, OS_ZoneMixingFields::MaximumReceivingTemperatureScheduleName) != e) {
+        result.emplace_back("ZoneMixing", "Maximum Receiving Temperature");
       }
-      if (std::find(b, e, OS_ZoneMixingFields::MinimumSourceZoneTemperatureScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("ZoneMixing", "Minimum Source Zone Temperature"));
+      if (std::find(b, e, OS_ZoneMixingFields::MinimumSourceTemperatureScheduleName) != e) {
+        result.emplace_back("ZoneMixing", "Minimum Source Temperature");
       }
-      if (std::find(b, e, OS_ZoneMixingFields::MaximumSourceZoneTemperatureScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("ZoneMixing", "Maximum Source Zone Temperature"));
+      if (std::find(b, e, OS_ZoneMixingFields::MaximumSourceTemperatureScheduleName) != e) {
+        result.emplace_back("ZoneMixing", "Maximum Source Temperature");
       }
       if (std::find(b, e, OS_ZoneMixingFields::MinimumOutdoorTemperatureScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("ZoneMixing", "Minimum Outdoor Temperature"));
+        result.emplace_back("ZoneMixing", "Minimum Outdoor Temperature");
       }
       if (std::find(b, e, OS_ZoneMixingFields::MaximumOutdoorTemperatureScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("ZoneMixing", "Maximum Outdoor Temperature"));
+        result.emplace_back("ZoneMixing", "Maximum Outdoor Temperature");
       }
       return result;
     }
 
-    ThermalZone ZoneMixing_Impl::zone() const {
-      boost::optional<ThermalZone> value = getObject<ModelObject>().getModelObjectTarget<ThermalZone>(OS_ZoneMixingFields::ZoneName);
+    bool ZoneMixing_Impl::setParent(ParentObject& newParent) {
+      bool result = false;
+      if (auto mo_ = newParent.optionalCast<Space>()) {
+        result = this->setSpace(mo_.get());
+        // } else if (auto mo_ = newParent.optionalCast<ThermalZone>()) {
+        //   result = this->setZone(mo_.get());
+      }
+      return result;
+    }
+
+    ModelObject ZoneMixing_Impl::zoneOrSpace() const {
+      boost::optional<ModelObject> value = getObject<ModelObject>().getModelObjectTarget<ModelObject>(OS_ZoneMixingFields::ZoneorSpaceName);
       if (!value) {
-        LOG_AND_THROW(briefDescription() << " does not have a receiving ThermalZone.");
+        LOG_AND_THROW(briefDescription() << " does not have a receiving ThermalZone Or Space.");
       }
       return value.get();
     }
 
+    boost::optional<ThermalZone> ZoneMixing_Impl::zone() const {
+      return zoneOrSpace().optionalCast<ThermalZone>();
+    }
+
+    boost::optional<Space> ZoneMixing_Impl::space() const {
+      return zoneOrSpace().optionalCast<Space>();
+    }
+
+    bool ZoneMixing_Impl::setZone(const ThermalZone& zone) {
+      return setPointer(OS_ZoneMixingFields::ZoneorSpaceName, zone.handle());
+    }
+
+    bool ZoneMixing_Impl::setSpace(const Space& space) {
+      return setPointer(OS_ZoneMixingFields::ZoneorSpaceName, space.handle());
+    }
+
+    boost::optional<ModelObject> ZoneMixing_Impl::sourceZoneOrSpace() const {
+      return getObject<ModelObject>().getModelObjectTarget<ModelObject>(OS_ZoneMixingFields::SourceZoneorSpaceName);
+    }
+
+    boost::optional<ThermalZone> ZoneMixing_Impl::sourceZone() const {
+      return getObject<ModelObject>().getModelObjectTarget<ThermalZone>(OS_ZoneMixingFields::SourceZoneorSpaceName);
+    }
+
+    boost::optional<Space> ZoneMixing_Impl::sourceSpace() const {
+      return getObject<ModelObject>().getModelObjectTarget<Space>(OS_ZoneMixingFields::SourceZoneorSpaceName);
+    }
+
+    bool ZoneMixing_Impl::setSourceZone(const ThermalZone& zone) {
+
+      auto receiving = this->zoneOrSpace();
+      // source zone cannot be the same as this zone
+      if (zone.handle() == receiving.handle()) {
+        LOG(Warn, "For " << briefDescription() << ", Source Zone cannot be the same as the Receiving Zone '" << zone.nameString() << "'.");
+        return false;
+      }
+      if (receiving.iddObjectType() != IddObjectType::OS_ThermalZone) {
+        LOG(Warn, "For " << briefDescription() << ", Receiving is a Space, so you cannot set Source as a Zone.");
+        return false;
+      }
+
+      return setPointer(OS_ZoneMixingFields::SourceZoneorSpaceName, zone.handle());
+    }
+
+    bool ZoneMixing_Impl::setSourceSpace(const Space& space) {
+      auto receiving = this->zoneOrSpace();
+      // source zone cannot be the same as this zone
+      if (space.handle() == receiving.handle()) {
+        LOG(Warn, "For " << briefDescription() << ", Source Space cannot be the same as the Receiving Space '" << space.nameString() << "'.");
+        return false;
+      }
+      if (receiving.iddObjectType() != IddObjectType::OS_Space) {
+        LOG(Warn, "For " << briefDescription() << ", Receiving is a Zone, so you cannot set Source as a Space.");
+        return false;
+      }
+
+      return setPointer(OS_ZoneMixingFields::SourceZoneorSpaceName, space.handle());
+    }
+
+    void ZoneMixing_Impl::resetSourceZoneOrSpace() {
+      bool result = setString(OS_ZoneMixingFields::SourceZoneorSpaceName, "");
+      OS_ASSERT(result);
+    }
+
     Schedule ZoneMixing_Impl::schedule() const {
       boost::optional<Schedule> value = getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_ZoneMixingFields::ScheduleName);
       if (!value) {
@@ -145,8 +221,8 @@ namespace model {
       return getDouble(OS_ZoneMixingFields::DesignFlowRate, true);
     }
 
-    boost::optional<double> ZoneMixing_Impl::flowRateperZoneFloorArea() const {
-      return getDouble(OS_ZoneMixingFields::FlowRateperZoneFloorArea, true);
+    boost::optional<double> ZoneMixing_Impl::flowRateperFloorArea() const {
+      return getDouble(OS_ZoneMixingFields::FlowRateperFloorArea, true);
     }
 
     boost::optional<double> ZoneMixing_Impl::flowRateperPerson() const {
@@ -157,10 +233,6 @@ namespace model {
       return getDouble(OS_ZoneMixingFields::AirChangesperHour, true);
     }
 
-    boost::optional<ThermalZone> ZoneMixing_Impl::sourceZone() const {
-      return getObject<ModelObject>().getModelObjectTarget<ThermalZone>(OS_ZoneMixingFields::SourceZoneName);
-    }
-
     boost::optional<double> ZoneMixing_Impl::deltaTemperature() const {
       return getDouble(OS_ZoneMixingFields::DeltaTemperature);
     }
@@ -169,20 +241,20 @@ namespace model {
       return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_ZoneMixingFields::DeltaTemperatureScheduleName);
     }
 
-    boost::optional<Schedule> ZoneMixing_Impl::minimumZoneTemperatureSchedule() const {
-      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_ZoneMixingFields::MinimumZoneTemperatureScheduleName);
+    boost::optional<Schedule> ZoneMixing_Impl::minimumReceivingTemperatureSchedule() const {
+      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_ZoneMixingFields::MinimumReceivingTemperatureScheduleName);
     }
 
-    boost::optional<Schedule> ZoneMixing_Impl::maximumZoneTemperatureSchedule() const {
-      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_ZoneMixingFields::MaximumZoneTemperatureScheduleName);
+    boost::optional<Schedule> ZoneMixing_Impl::maximumReceivingTemperatureSchedule() const {
+      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_ZoneMixingFields::MaximumReceivingTemperatureScheduleName);
     }
 
-    boost::optional<Schedule> ZoneMixing_Impl::minimumSourceZoneTemperatureSchedule() const {
-      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_ZoneMixingFields::MinimumSourceZoneTemperatureScheduleName);
+    boost::optional<Schedule> ZoneMixing_Impl::minimumSourceTemperatureSchedule() const {
+      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_ZoneMixingFields::MinimumSourceTemperatureScheduleName);
     }
 
-    boost::optional<Schedule> ZoneMixing_Impl::maximumSourceZoneTemperatureSchedule() const {
-      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_ZoneMixingFields::MaximumSourceZoneTemperatureScheduleName);
+    boost::optional<Schedule> ZoneMixing_Impl::maximumSourceTemperatureSchedule() const {
+      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_ZoneMixingFields::MaximumSourceTemperatureScheduleName);
     }
 
     boost::optional<Schedule> ZoneMixing_Impl::minimumOutdoorTemperatureSchedule() const {
@@ -206,7 +278,7 @@ namespace model {
         OS_ASSERT(result);
         //result = setString(OS_ZoneMixingFields::DesignFlowRate, "");
         //OS_ASSERT(result);
-        result = setString(OS_ZoneMixingFields::FlowRateperZoneFloorArea, "");
+        result = setString(OS_ZoneMixingFields::FlowRateperFloorArea, "");
         OS_ASSERT(result);
         result = setString(OS_ZoneMixingFields::FlowRateperPerson, "");
         OS_ASSERT(result);
@@ -216,9 +288,9 @@ namespace model {
       return result;
     }
 
-    bool ZoneMixing_Impl::setFlowRateperZoneFloorArea(double flowRateperZoneFloorArea) {
+    bool ZoneMixing_Impl::setFlowRateperFloorArea(double flowRateperFloorArea) {
       bool result(false);
-      result = setDouble(OS_ZoneMixingFields::FlowRateperZoneFloorArea, flowRateperZoneFloorArea);
+      result = setDouble(OS_ZoneMixingFields::FlowRateperFloorArea, flowRateperFloorArea);
       if (result) {
         result = setString(OS_ZoneMixingFields::DesignFlowRateCalculationMethod, "Flow/Area");
         OS_ASSERT(result);
@@ -242,7 +314,7 @@ namespace model {
         OS_ASSERT(result);
         result = setString(OS_ZoneMixingFields::DesignFlowRate, "");
         OS_ASSERT(result);
-        result = setString(OS_ZoneMixingFields::FlowRateperZoneFloorArea, "");
+        result = setString(OS_ZoneMixingFields::FlowRateperFloorArea, "");
         OS_ASSERT(result);
         //result = setString(OS_ZoneMixingFields::FlowRateperPerson, "");
         //OS_ASSERT(result);
@@ -260,7 +332,7 @@ namespace model {
         OS_ASSERT(result);
         result = setString(OS_ZoneMixingFields::DesignFlowRate, "");
         OS_ASSERT(result);
-        result = setString(OS_ZoneMixingFields::FlowRateperZoneFloorArea, "");
+        result = setString(OS_ZoneMixingFields::FlowRateperFloorArea, "");
         OS_ASSERT(result);
         result = setString(OS_ZoneMixingFields::FlowRateperPerson, "");
         OS_ASSERT(result);
@@ -270,19 +342,29 @@ namespace model {
       return result;
     }
 
-    bool ZoneMixing_Impl::setSourceZone(const ThermalZone& zone) {
-      bool result(false);
+    bool ZoneMixing_Impl::hardSize() {
+      boost::optional<Space> space = this->space();
+      if (!space) {
+        return false;
+      }
 
-      // source zone cannot be the same as this zone
-      if (zone.handle() != this->zone().handle()) {
-        result = setPointer(OS_ZoneMixingFields::SourceZoneName, zone.handle());
+      if (this->designFlowRate()) {
+        return true;
       }
-      return result;
-    }
 
-    void ZoneMixing_Impl::resetSourceZone() {
-      bool result = setString(OS_ZoneMixingFields::SourceZoneName, "");
-      OS_ASSERT(result);
+      if (boost::optional<double> flowRateperFloorArea = this->flowRateperFloorArea()) {
+        return this->setDesignFlowRate(*flowRateperFloorArea * space->floorArea());
+      }
+
+      if (boost::optional<double> flowRateperPerson = this->flowRateperPerson()) {
+        return this->setDesignFlowRate(*flowRateperPerson * space->numberOfPeople());
+      }
+
+      if (boost::optional<double> airChangesperHour = this->airChangesperHour()) {
+        return this->setDesignFlowRate(*airChangesperHour * space->volume() / 3600.0);
+      }
+
+      return false;
     }
 
     bool ZoneMixing_Impl::setDeltaTemperature(double deltaTemperature) {
@@ -310,47 +392,47 @@ namespace model {
       OS_ASSERT(result);
     }
 
-    bool ZoneMixing_Impl::setMinimumZoneTemperatureSchedule(Schedule& schedule) {
-      bool result =
-        ModelObject_Impl::setSchedule(OS_ZoneMixingFields::MinimumZoneTemperatureScheduleName, "ZoneMixing", "Minimum Zone Temperature", schedule);
+    bool ZoneMixing_Impl::setMinimumReceivingTemperatureSchedule(Schedule& schedule) {
+      bool result = ModelObject_Impl::setSchedule(OS_ZoneMixingFields::MinimumReceivingTemperatureScheduleName, "ZoneMixing",
+                                                  "Minimum Receiving Temperature", schedule);
       return result;
     }
 
-    void ZoneMixing_Impl::resetMinimumZoneTemperatureSchedule() {
-      bool result = setString(OS_ZoneMixingFields::MinimumZoneTemperatureScheduleName, "");
+    void ZoneMixing_Impl::resetMinimumReceivingTemperatureSchedule() {
+      bool result = setString(OS_ZoneMixingFields::MinimumReceivingTemperatureScheduleName, "");
       OS_ASSERT(result);
     }
 
-    bool ZoneMixing_Impl::setMaximumZoneTemperatureSchedule(Schedule& schedule) {
-      bool result =
-        ModelObject_Impl::setSchedule(OS_ZoneMixingFields::MaximumZoneTemperatureScheduleName, "ZoneMixing", "Maximum Zone Temperature", schedule);
+    bool ZoneMixing_Impl::setMaximumReceivingTemperatureSchedule(Schedule& schedule) {
+      bool result = ModelObject_Impl::setSchedule(OS_ZoneMixingFields::MaximumReceivingTemperatureScheduleName, "ZoneMixing",
+                                                  "Maximum Receiving Temperature", schedule);
       return result;
     }
 
-    void ZoneMixing_Impl::resetMaximumZoneTemperatureSchedule() {
-      bool result = setString(OS_ZoneMixingFields::MaximumZoneTemperatureScheduleName, "");
+    void ZoneMixing_Impl::resetMaximumReceivingTemperatureSchedule() {
+      bool result = setString(OS_ZoneMixingFields::MaximumReceivingTemperatureScheduleName, "");
       OS_ASSERT(result);
     }
 
-    bool ZoneMixing_Impl::setMinimumSourceZoneTemperatureSchedule(Schedule& schedule) {
-      bool result = ModelObject_Impl::setSchedule(OS_ZoneMixingFields::MinimumSourceZoneTemperatureScheduleName, "ZoneMixing",
-                                                  "Minimum Source Zone Temperature", schedule);
+    bool ZoneMixing_Impl::setMinimumSourceTemperatureSchedule(Schedule& schedule) {
+      bool result = ModelObject_Impl::setSchedule(OS_ZoneMixingFields::MinimumSourceTemperatureScheduleName, "ZoneMixing",
+                                                  "Minimum Source Temperature", schedule);
       return result;
     }
 
-    void ZoneMixing_Impl::resetMinimumSourceZoneTemperatureSchedule() {
-      bool result = setString(OS_ZoneMixingFields::MinimumSourceZoneTemperatureScheduleName, "");
+    void ZoneMixing_Impl::resetMinimumSourceTemperatureSchedule() {
+      bool result = setString(OS_ZoneMixingFields::MinimumSourceTemperatureScheduleName, "");
       OS_ASSERT(result);
     }
 
-    bool ZoneMixing_Impl::setMaximumSourceZoneTemperatureSchedule(Schedule& schedule) {
-      bool result = ModelObject_Impl::setSchedule(OS_ZoneMixingFields::MaximumSourceZoneTemperatureScheduleName, "ZoneMixing",
-                                                  "Maximum Source Zone Temperature", schedule);
+    bool ZoneMixing_Impl::setMaximumSourceTemperatureSchedule(Schedule& schedule) {
+      bool result = ModelObject_Impl::setSchedule(OS_ZoneMixingFields::MaximumSourceTemperatureScheduleName, "ZoneMixing",
+                                                  "Maximum Source Temperature", schedule);
       return result;
     }
 
-    void ZoneMixing_Impl::resetMaximumSourceZoneTemperatureSchedule() {
-      bool result = setString(OS_ZoneMixingFields::MaximumSourceZoneTemperatureScheduleName, "");
+    void ZoneMixing_Impl::resetMaximumSourceTemperatureSchedule() {
+      bool result = setString(OS_ZoneMixingFields::MaximumSourceTemperatureScheduleName, "");
       OS_ASSERT(result);
     }
 
@@ -390,7 +472,7 @@ namespace model {
   ZoneMixing::ZoneMixing(const ThermalZone& zone) : ModelObject(ZoneMixing::iddObjectType(), zone.model()) {
     OS_ASSERT(getImpl<detail::ZoneMixing_Impl>());
 
-    bool ok = setPointer(OS_ZoneMixingFields::ZoneName, zone.handle());
+    bool ok = setPointer(OS_ZoneMixingFields::ZoneorSpaceName, zone.handle());
     OS_ASSERT(ok);
 
     ok = setPointer(OS_ZoneMixingFields::ScheduleName, zone.model().alwaysOnContinuousSchedule().handle());
@@ -400,72 +482,93 @@ namespace model {
     OS_ASSERT(ok);
   }
 
+  ZoneMixing::ZoneMixing(const Space& space) : ModelObject(ZoneMixing::iddObjectType(), space.model()) {
+    OS_ASSERT(getImpl<detail::ZoneMixing_Impl>());
+
+    bool ok = setPointer(OS_ZoneMixingFields::ZoneorSpaceName, space.handle());
+    OS_ASSERT(ok);
+
+    ok = setPointer(OS_ZoneMixingFields::ScheduleName, space.model().alwaysOnContinuousSchedule().handle());
+    OS_ASSERT(ok);
+
+    ok = setDesignFlowRate(0.0);
+    OS_ASSERT(ok);
+  }
+
   IddObjectType ZoneMixing::iddObjectType() {
-    return IddObjectType(IddObjectType::OS_ZoneMixing);
+    return {IddObjectType::OS_ZoneMixing};
+  }
+
+  bool ZoneMixing::hardSize() {
+    return getImpl<detail::ZoneMixing_Impl>()->hardSize();
   }
 
-  ThermalZone ZoneMixing::zone() const {
+  boost::optional<ThermalZone> ZoneMixing::zone() const {
     return getImpl<detail::ZoneMixing_Impl>()->zone();
   }
 
-  Schedule ZoneMixing::schedule() const {
-    return getImpl<detail::ZoneMixing_Impl>()->schedule();
+  boost::optional<Space> ZoneMixing::space() const {
+    return getImpl<detail::ZoneMixing_Impl>()->space();
   }
 
-  std::string ZoneMixing::designFlowRateCalculationMethod() const {
-    return getImpl<detail::ZoneMixing_Impl>()->designFlowRateCalculationMethod();
+  ModelObject ZoneMixing::zoneOrSpace() const {
+    return getImpl<detail::ZoneMixing_Impl>()->zoneOrSpace();
   }
 
-  boost::optional<double> ZoneMixing::designFlowRate() const {
-    return getImpl<detail::ZoneMixing_Impl>()->designFlowRate();
+  boost::optional<ThermalZone> ZoneMixing::sourceZone() const {
+    return getImpl<detail::ZoneMixing_Impl>()->sourceZone();
   }
 
-  boost::optional<double> ZoneMixing::flowRateperZoneFloorArea() const {
-    return getImpl<detail::ZoneMixing_Impl>()->flowRateperZoneFloorArea();
+  boost::optional<Space> ZoneMixing::sourceSpace() const {
+    return getImpl<detail::ZoneMixing_Impl>()->sourceSpace();
   }
 
-  boost::optional<double> ZoneMixing::flowRateperPerson() const {
-    return getImpl<detail::ZoneMixing_Impl>()->flowRateperPerson();
+  boost::optional<ModelObject> ZoneMixing::sourceZoneOrSpace() const {
+    return getImpl<detail::ZoneMixing_Impl>()->sourceZoneOrSpace();
   }
 
-  boost::optional<double> ZoneMixing::airChangesperHour() const {
-    return getImpl<detail::ZoneMixing_Impl>()->airChangesperHour();
+  bool ZoneMixing::setSourceZone(const ThermalZone& zone) {
+    return getImpl<detail::ZoneMixing_Impl>()->setSourceZone(zone);
   }
 
-  boost::optional<ThermalZone> ZoneMixing::sourceZone() const {
-    return getImpl<detail::ZoneMixing_Impl>()->sourceZone();
+  bool ZoneMixing::setSourceSpace(const Space& space) {
+    return getImpl<detail::ZoneMixing_Impl>()->setSourceSpace(space);
   }
 
-  boost::optional<double> ZoneMixing::deltaTemperature() const {
-    return getImpl<detail::ZoneMixing_Impl>()->deltaTemperature();
+  void ZoneMixing::resetSourceZoneOrSpace() {
+    return getImpl<detail::ZoneMixing_Impl>()->resetSourceZoneOrSpace();
   }
 
-  boost::optional<Schedule> ZoneMixing::deltaTemperatureSchedule() const {
-    return getImpl<detail::ZoneMixing_Impl>()->deltaTemperatureSchedule();
+  Schedule ZoneMixing::schedule() const {
+    return getImpl<detail::ZoneMixing_Impl>()->schedule();
   }
 
-  boost::optional<Schedule> ZoneMixing::minimumZoneTemperatureSchedule() const {
-    return getImpl<detail::ZoneMixing_Impl>()->minimumZoneTemperatureSchedule();
+  std::string ZoneMixing::designFlowRateCalculationMethod() const {
+    return getImpl<detail::ZoneMixing_Impl>()->designFlowRateCalculationMethod();
   }
 
-  boost::optional<Schedule> ZoneMixing::maximumZoneTemperatureSchedule() const {
-    return getImpl<detail::ZoneMixing_Impl>()->maximumZoneTemperatureSchedule();
+  boost::optional<double> ZoneMixing::designFlowRate() const {
+    return getImpl<detail::ZoneMixing_Impl>()->designFlowRate();
   }
 
-  boost::optional<Schedule> ZoneMixing::minimumSourceZoneTemperatureSchedule() const {
-    return getImpl<detail::ZoneMixing_Impl>()->minimumSourceZoneTemperatureSchedule();
+  boost::optional<double> ZoneMixing::flowRateperFloorArea() const {
+    return getImpl<detail::ZoneMixing_Impl>()->flowRateperFloorArea();
   }
 
-  boost::optional<Schedule> ZoneMixing::maximumSourceZoneTemperatureSchedule() const {
-    return getImpl<detail::ZoneMixing_Impl>()->maximumSourceZoneTemperatureSchedule();
+  boost::optional<double> ZoneMixing::flowRateperPerson() const {
+    return getImpl<detail::ZoneMixing_Impl>()->flowRateperPerson();
   }
 
-  boost::optional<Schedule> ZoneMixing::minimumOutdoorTemperatureSchedule() const {
-    return getImpl<detail::ZoneMixing_Impl>()->minimumOutdoorTemperatureSchedule();
+  boost::optional<double> ZoneMixing::airChangesperHour() const {
+    return getImpl<detail::ZoneMixing_Impl>()->airChangesperHour();
   }
 
-  boost::optional<Schedule> ZoneMixing::maximumOutdoorTemperatureSchedule() const {
-    return getImpl<detail::ZoneMixing_Impl>()->maximumOutdoorTemperatureSchedule();
+  boost::optional<double> ZoneMixing::deltaTemperature() const {
+    return getImpl<detail::ZoneMixing_Impl>()->deltaTemperature();
+  }
+
+  boost::optional<Schedule> ZoneMixing::deltaTemperatureSchedule() const {
+    return getImpl<detail::ZoneMixing_Impl>()->deltaTemperatureSchedule();
   }
 
   bool ZoneMixing::setSchedule(Schedule& schedule) {
@@ -476,8 +579,8 @@ namespace model {
     return getImpl<detail::ZoneMixing_Impl>()->setDesignFlowRate(designFlowRate);
   }
 
-  bool ZoneMixing::setFlowRateperZoneFloorArea(double flowRateperZoneFloorArea) {
-    return getImpl<detail::ZoneMixing_Impl>()->setFlowRateperZoneFloorArea(flowRateperZoneFloorArea);
+  bool ZoneMixing::setFlowRateperFloorArea(double flowRateperFloorArea) {
+    return getImpl<detail::ZoneMixing_Impl>()->setFlowRateperFloorArea(flowRateperFloorArea);
   }
 
   bool ZoneMixing::setFlowRateperPerson(double flowRateperPerson) {
@@ -488,14 +591,6 @@ namespace model {
     return getImpl<detail::ZoneMixing_Impl>()->setAirChangesperHour(airChangesperHour);
   }
 
-  bool ZoneMixing::setSourceZone(const ThermalZone& zone) {
-    return getImpl<detail::ZoneMixing_Impl>()->setSourceZone(zone);
-  }
-
-  void ZoneMixing::resetSourceZone() {
-    getImpl<detail::ZoneMixing_Impl>()->resetSourceZone();
-  }
-
   bool ZoneMixing::setDeltaTemperature(double deltaTemperature) {
     return getImpl<detail::ZoneMixing_Impl>()->setDeltaTemperature(deltaTemperature);
   }
@@ -512,36 +607,62 @@ namespace model {
     getImpl<detail::ZoneMixing_Impl>()->resetDeltaTemperatureSchedule();
   }
 
-  bool ZoneMixing::setMinimumZoneTemperatureSchedule(Schedule& schedule) {
-    return getImpl<detail::ZoneMixing_Impl>()->setMinimumZoneTemperatureSchedule(schedule);
+  // ReceivingTemperatureSchedule
+
+  boost::optional<Schedule> ZoneMixing::minimumReceivingTemperatureSchedule() const {
+    return getImpl<detail::ZoneMixing_Impl>()->minimumReceivingTemperatureSchedule();
   }
 
-  void ZoneMixing::resetMinimumZoneTemperatureSchedule() {
-    getImpl<detail::ZoneMixing_Impl>()->resetMinimumZoneTemperatureSchedule();
+  bool ZoneMixing::setMinimumReceivingTemperatureSchedule(Schedule& schedule) {
+    return getImpl<detail::ZoneMixing_Impl>()->setMinimumReceivingTemperatureSchedule(schedule);
   }
 
-  bool ZoneMixing::setMaximumZoneTemperatureSchedule(Schedule& schedule) {
-    return getImpl<detail::ZoneMixing_Impl>()->setMaximumZoneTemperatureSchedule(schedule);
+  void ZoneMixing::resetMinimumReceivingTemperatureSchedule() {
+    getImpl<detail::ZoneMixing_Impl>()->resetMinimumReceivingTemperatureSchedule();
   }
 
-  void ZoneMixing::resetMaximumZoneTemperatureSchedule() {
-    getImpl<detail::ZoneMixing_Impl>()->resetMaximumZoneTemperatureSchedule();
+  boost::optional<Schedule> ZoneMixing::maximumReceivingTemperatureSchedule() const {
+    return getImpl<detail::ZoneMixing_Impl>()->maximumReceivingTemperatureSchedule();
   }
 
-  bool ZoneMixing::setMinimumSourceZoneTemperatureSchedule(Schedule& schedule) {
-    return getImpl<detail::ZoneMixing_Impl>()->setMinimumSourceZoneTemperatureSchedule(schedule);
+  bool ZoneMixing::setMaximumReceivingTemperatureSchedule(Schedule& schedule) {
+    return getImpl<detail::ZoneMixing_Impl>()->setMaximumReceivingTemperatureSchedule(schedule);
   }
 
-  void ZoneMixing::resetMinimumSourceZoneTemperatureSchedule() {
-    getImpl<detail::ZoneMixing_Impl>()->resetMinimumSourceZoneTemperatureSchedule();
+  void ZoneMixing::resetMaximumReceivingTemperatureSchedule() {
+    getImpl<detail::ZoneMixing_Impl>()->resetMaximumReceivingTemperatureSchedule();
   }
 
-  bool ZoneMixing::setMaximumSourceZoneTemperatureSchedule(Schedule& schedule) {
-    return getImpl<detail::ZoneMixing_Impl>()->setMaximumSourceZoneTemperatureSchedule(schedule);
+  // SourceTemperatureSchedule
+
+  boost::optional<Schedule> ZoneMixing::minimumSourceTemperatureSchedule() const {
+    return getImpl<detail::ZoneMixing_Impl>()->minimumSourceTemperatureSchedule();
   }
 
-  void ZoneMixing::resetMaximumSourceZoneTemperatureSchedule() {
-    getImpl<detail::ZoneMixing_Impl>()->resetMaximumSourceZoneTemperatureSchedule();
+  bool ZoneMixing::setMinimumSourceTemperatureSchedule(Schedule& schedule) {
+    return getImpl<detail::ZoneMixing_Impl>()->setMinimumSourceTemperatureSchedule(schedule);
+  }
+
+  void ZoneMixing::resetMinimumSourceTemperatureSchedule() {
+    getImpl<detail::ZoneMixing_Impl>()->resetMinimumSourceTemperatureSchedule();
+  }
+
+  boost::optional<Schedule> ZoneMixing::maximumSourceTemperatureSchedule() const {
+    return getImpl<detail::ZoneMixing_Impl>()->maximumSourceTemperatureSchedule();
+  }
+
+  bool ZoneMixing::setMaximumSourceTemperatureSchedule(Schedule& schedule) {
+    return getImpl<detail::ZoneMixing_Impl>()->setMaximumSourceTemperatureSchedule(schedule);
+  }
+
+  void ZoneMixing::resetMaximumSourceTemperatureSchedule() {
+    getImpl<detail::ZoneMixing_Impl>()->resetMaximumSourceTemperatureSchedule();
+  }
+
+  // Outdoor Temperature Schedule
+
+  boost::optional<Schedule> ZoneMixing::minimumOutdoorTemperatureSchedule() const {
+    return getImpl<detail::ZoneMixing_Impl>()->minimumOutdoorTemperatureSchedule();
   }
 
   bool ZoneMixing::setMinimumOutdoorTemperatureSchedule(Schedule& schedule) {
@@ -552,6 +673,10 @@ namespace model {
     getImpl<detail::ZoneMixing_Impl>()->resetMinimumOutdoorTemperatureSchedule();
   }
 
+  boost::optional<Schedule> ZoneMixing::maximumOutdoorTemperatureSchedule() const {
+    return getImpl<detail::ZoneMixing_Impl>()->maximumOutdoorTemperatureSchedule();
+  }
+
   bool ZoneMixing::setMaximumOutdoorTemperatureSchedule(Schedule& schedule) {
     return getImpl<detail::ZoneMixing_Impl>()->setMaximumOutdoorTemperatureSchedule(schedule);
   }
@@ -562,6 +687,101 @@ namespace model {
 
   /// @cond
   ZoneMixing::ZoneMixing(std::shared_ptr<detail::ZoneMixing_Impl> impl) : ModelObject(std::move(impl)) {}
+
+  // DEPRECATED
+
+  void ZoneMixing::resetSourceZone() {
+    LOG(Warn, "As of 3.5.0, resetSourceZone is deprecated. Use resetSourceZoneOrSpace instead. It will be "
+              "removed within three releases.");
+    getImpl<detail::ZoneMixing_Impl>()->resetSourceZoneOrSpace();
+  }
+
+  boost::optional<double> ZoneMixing::flowRateperZoneFloorArea() const {
+    LOG(Warn, "As of 3.5.0, flowRateperZoneFloorArea is deprecated. Use flowRateperFloorArea instead. It will be "
+              "removed within three releases.");
+    return getImpl<detail::ZoneMixing_Impl>()->flowRateperFloorArea();
+  }
+
+  bool ZoneMixing::setFlowRateperZoneFloorArea(double flowRateperZoneFloorArea) {
+    LOG(Warn, "As of 3.5.0, setFlowRateperZoneFloorArea is deprecated. Use setFlowRateperFloorArea instead. It will be "
+              "removed within three releases.");
+    return getImpl<detail::ZoneMixing_Impl>()->setFlowRateperFloorArea(flowRateperZoneFloorArea);
+  }
+
+  // ReceivingTemperatureSchedule
+  boost::optional<Schedule> ZoneMixing::minimumZoneTemperatureSchedule() const {
+    LOG(Warn, "As of 3.5.0, minimumZoneTemperatureSchedule is deprecated. Use minimumReceivingTemperatureSchedule instead. It will be "
+              "removed within three releases.");
+    return getImpl<detail::ZoneMixing_Impl>()->minimumReceivingTemperatureSchedule();
+  }
+
+  bool ZoneMixing::setMinimumZoneTemperatureSchedule(Schedule& schedule) {
+    LOG(Warn, "As of 3.5.0, setMinimumZoneTemperatureSchedule is deprecated. Use setMinimumReceivingTemperatureSchedule instead. It will be "
+              "removed within three releases.");
+    return getImpl<detail::ZoneMixing_Impl>()->setMinimumReceivingTemperatureSchedule(schedule);
+  }
+
+  void ZoneMixing::resetMinimumZoneTemperatureSchedule() {
+    LOG(Warn, "As of 3.5.0, resetMinimumZoneTemperatureSchedule is deprecated. Use resetMinimumReceivingTemperatureSchedule instead. It will be "
+              "removed within three releases.");
+    getImpl<detail::ZoneMixing_Impl>()->resetMinimumReceivingTemperatureSchedule();
+  }
+
+  boost::optional<Schedule> ZoneMixing::maximumZoneTemperatureSchedule() const {
+    LOG(Warn, "As of 3.5.0, maximumZoneTemperatureSchedule is deprecated. Use maximumReceivingTemperatureSchedule instead. It will be "
+              "removed within three releases.");
+    return getImpl<detail::ZoneMixing_Impl>()->maximumReceivingTemperatureSchedule();
+  }
+
+  bool ZoneMixing::setMaximumZoneTemperatureSchedule(Schedule& schedule) {
+    LOG(Warn, "As of 3.5.0, setMaximumZoneTemperatureSchedule is deprecated. Use setMaximumReceivingTemperatureSchedule instead. It will be "
+              "removed within three releases.");
+    return getImpl<detail::ZoneMixing_Impl>()->setMaximumReceivingTemperatureSchedule(schedule);
+  }
+
+  void ZoneMixing::resetMaximumZoneTemperatureSchedule() {
+    LOG(Warn, "As of 3.5.0, resetMaximumZoneTemperatureSchedule is deprecated. Use resetMaximumReceivingTemperatureSchedule instead. It will be "
+              "removed within three releases.");
+    getImpl<detail::ZoneMixing_Impl>()->resetMaximumReceivingTemperatureSchedule();
+  }
+
+  // SourceTemperatureSchedule
+  boost::optional<Schedule> ZoneMixing::minimumSourceZoneTemperatureSchedule() const {
+    LOG(Warn, "As of 3.5.0, minimumSourceZoneTemperatureSchedule is deprecated. Use minimumSourceTemperatureSchedule instead. It will be "
+              "removed within three releases.");
+    return getImpl<detail::ZoneMixing_Impl>()->minimumSourceTemperatureSchedule();
+  }
+
+  bool ZoneMixing::setMinimumSourceZoneTemperatureSchedule(Schedule& schedule) {
+    LOG(Warn, "As of 3.5.0, setMinimumSourceZoneTemperatureSchedule is deprecated. Use setMinimumSourceTemperatureSchedule instead. It will be "
+              "removed within three releases.");
+    return getImpl<detail::ZoneMixing_Impl>()->setMinimumSourceTemperatureSchedule(schedule);
+  }
+
+  void ZoneMixing::resetMinimumSourceZoneTemperatureSchedule() {
+    LOG(Warn, "As of 3.5.0, resetMinimumSourceZoneTemperatureSchedule is deprecated. Use resetMinimumSourceTemperatureSchedule instead. It will be "
+              "removed within three releases.");
+    getImpl<detail::ZoneMixing_Impl>()->resetMinimumSourceTemperatureSchedule();
+  }
+
+  boost::optional<Schedule> ZoneMixing::maximumSourceZoneTemperatureSchedule() const {
+    LOG(Warn, "As of 3.5.0, maximumSourceZoneTemperatureSchedule is deprecated. Use maximumSourceTemperatureSchedule instead. It will be "
+              "removed within three releases.");
+    return getImpl<detail::ZoneMixing_Impl>()->maximumSourceTemperatureSchedule();
+  }
+
+  bool ZoneMixing::setMaximumSourceZoneTemperatureSchedule(Schedule& schedule) {
+    LOG(Warn, "As of 3.5.0, setMaximumSourceZoneTemperatureSchedule is deprecated. Use setMaximumSourceTemperatureSchedule instead. It will be "
+              "removed within three releases.");
+    return getImpl<detail::ZoneMixing_Impl>()->setMaximumSourceTemperatureSchedule(schedule);
+  }
+
+  void ZoneMixing::resetMaximumSourceZoneTemperatureSchedule() {
+    LOG(Warn, "As of 3.5.0, resetMaximumSourceZoneTemperatureSchedule is deprecated. Use resetMaximumSourceTemperatureSchedule instead. It will be "
+              "removed within three releases.");
+    getImpl<detail::ZoneMixing_Impl>()->resetMaximumSourceTemperatureSchedule();
+  }
+
   /// @endcond
 
 }  // namespace model
diff --git a/src/model/ZoneMixing.hpp b/src/model/ZoneMixing.hpp
index ee18aa0dd65..ca1b7990a09 100644
--- a/src/model/ZoneMixing.hpp
+++ b/src/model/ZoneMixing.hpp
@@ -33,12 +33,15 @@
 #include "ModelAPI.hpp"
 #include "ModelObject.hpp"
 
+#include "../utilities/core/Deprecated.hpp"
+
 namespace openstudio {
 
 namespace model {
 
-  class ThermalZone;
   class Schedule;
+  class Space;
+  class ThermalZone;
 
   namespace detail {
 
@@ -54,18 +57,23 @@ namespace model {
     //@{
 
     explicit ZoneMixing(const ThermalZone& thermalZone);
+    explicit ZoneMixing(const Space& space);
 
-    virtual ~ZoneMixing() {}
+    virtual ~ZoneMixing() = default;
 
     //@}
 
     static IddObjectType iddObjectType();
 
+    bool hardSize();
+
     /** @name Getters */
     //@{
 
     /// Returns the ThermalZone which receives air from this mixing object.
-    ThermalZone zone() const;
+    boost::optional<ThermalZone> zone() const;
+    boost::optional<Space> space() const;
+    ModelObject zoneOrSpace() const;
 
     /// Returns fractional schedule which moderates amount of air flow from the design level.
     Schedule schedule() const;
@@ -74,7 +82,8 @@ namespace model {
 
     boost::optional<double> designFlowRate() const;
 
-    boost::optional<double> flowRateperZoneFloorArea() const;
+    OS_DEPRECATED boost::optional<double> flowRateperZoneFloorArea() const;
+    boost::optional<double> flowRateperFloorArea() const;
 
     boost::optional<double> flowRateperPerson() const;
 
@@ -82,6 +91,8 @@ namespace model {
 
     /// Returns the ThermalZone which supplies air to this mixing object.
     boost::optional<ThermalZone> sourceZone() const;
+    boost::optional<Space> sourceSpace() const;
+    boost::optional<ModelObject> sourceZoneOrSpace() const;
 
     /// Returns the constant temperature differential between source and receiving zones below which mixing is shutoff.
     // DLM: is this a signed difference or absolute?
@@ -90,17 +101,21 @@ namespace model {
     /// Returns the temperature schedule containing the differential between source and receiving zones versus time below which mixing is shutoff.
     boost::optional<Schedule> deltaTemperatureSchedule() const;
 
-    /// Returns the temperature schedule containing the receiving zone dry-bulb temperature below which mixing is shutoff.
-    boost::optional<Schedule> minimumZoneTemperatureSchedule() const;
+    /// Returns the temperature schedule containing the receiving zone or space dry-bulb temperature below which mixing is shutoff.
+    boost::optional<Schedule> minimumReceivingTemperatureSchedule() const;
+    OS_DEPRECATED boost::optional<Schedule> minimumZoneTemperatureSchedule() const;
 
-    /// Returns the temperature schedule containing the receiving zone dry-bulb temperature above which mixing is shutoff.
-    boost::optional<Schedule> maximumZoneTemperatureSchedule() const;
+    /// Returns the temperature schedule containing the receiving zone or space dry-bulb temperature above which mixing is shutoff.
+    boost::optional<Schedule> maximumReceivingTemperatureSchedule() const;
+    OS_DEPRECATED boost::optional<Schedule> maximumZoneTemperatureSchedule() const;
 
-    /// Returns the temperature schedule containing the source zone dry-bulb temperature below which mixing is shutoff.
-    boost::optional<Schedule> minimumSourceZoneTemperatureSchedule() const;
+    /// Returns the temperature schedule containing the source zone or space dry-bulb temperature below which mixing is shutoff.
+    boost::optional<Schedule> minimumSourceTemperatureSchedule() const;
+    OS_DEPRECATED boost::optional<Schedule> minimumSourceZoneTemperatureSchedule() const;
 
-    /// Returns the temperature schedule containing the source zone dry-bulb temperature above which mixing is shutoff.
-    boost::optional<Schedule> maximumSourceZoneTemperatureSchedule() const;
+    /// Returns the temperature schedule containing the source zone or space dry-bulb temperature above which mixing is shutoff.
+    boost::optional<Schedule> maximumSourceTemperatureSchedule() const;
+    OS_DEPRECATED boost::optional<Schedule> maximumSourceZoneTemperatureSchedule() const;
 
     /// Returns the temperature schedule containing the outdoor temperature below which mixing is shutoff.
     boost::optional<Schedule> minimumOutdoorTemperatureSchedule() const;
@@ -117,7 +132,8 @@ namespace model {
 
     bool setDesignFlowRate(double designFlowRate);
 
-    bool setFlowRateperZoneFloorArea(double flowRateperZoneFloorArea);
+    bool setFlowRateperFloorArea(double flowRateperFloorArea);
+    OS_DEPRECATED bool setFlowRateperZoneFloorArea(double flowRateperZoneFloorArea);
 
     bool setFlowRateperPerson(double flowRateperPerson);
 
@@ -125,8 +141,10 @@ namespace model {
 
     /// Sets the ThermalZone which supplies air to this mixing object.
     bool setSourceZone(const ThermalZone& zone);
+    OS_DEPRECATED void resetSourceZone();
 
-    void resetSourceZone();
+    bool setSourceSpace(const Space& space);
+    void resetSourceZoneOrSpace();
 
     /// Sets the constant temperature differential between source and receiving zones below which mixing is shutoff.
     bool setDeltaTemperature(double deltaTemperature);
@@ -139,24 +157,32 @@ namespace model {
     void resetDeltaTemperatureSchedule();
 
     /// Sets the temperature schedule containing the receiving zone dry-bulb temperature below which mixing is shutoff.
-    bool setMinimumZoneTemperatureSchedule(Schedule& schedule);
+    bool setMinimumReceivingTemperatureSchedule(Schedule& schedule);
+    void resetMinimumReceivingTemperatureSchedule();
 
-    void resetMinimumZoneTemperatureSchedule();
+    OS_DEPRECATED bool setMinimumZoneTemperatureSchedule(Schedule& schedule);
+    OS_DEPRECATED void resetMinimumZoneTemperatureSchedule();
 
     /// Sets the temperature schedule containing the receiving zone dry-bulb temperature above which mixing is shutoff.
-    bool setMaximumZoneTemperatureSchedule(Schedule& schedule);
+    bool setMaximumReceivingTemperatureSchedule(Schedule& schedule);
+    void resetMaximumReceivingTemperatureSchedule();
 
-    void resetMaximumZoneTemperatureSchedule();
+    OS_DEPRECATED bool setMaximumZoneTemperatureSchedule(Schedule& schedule);
+    OS_DEPRECATED void resetMaximumZoneTemperatureSchedule();
 
     /// Sets the temperature schedule containing the source zone dry-bulb temperature below which mixing is shutoff.
-    bool setMinimumSourceZoneTemperatureSchedule(Schedule& schedule);
+    bool setMinimumSourceTemperatureSchedule(Schedule& schedule);
+    void resetMinimumSourceTemperatureSchedule();
 
-    void resetMinimumSourceZoneTemperatureSchedule();
+    OS_DEPRECATED bool setMinimumSourceZoneTemperatureSchedule(Schedule& schedule);
+    OS_DEPRECATED void resetMinimumSourceZoneTemperatureSchedule();
 
     /// Sets the temperature schedule containing the source zone dry-bulb temperature above which mixing is shutoff.
-    bool setMaximumSourceZoneTemperatureSchedule(Schedule& schedule);
+    bool setMaximumSourceTemperatureSchedule(Schedule& schedule);
+    void resetMaximumSourceTemperatureSchedule();
 
-    void resetMaximumSourceZoneTemperatureSchedule();
+    OS_DEPRECATED bool setMaximumSourceZoneTemperatureSchedule(Schedule& schedule);
+    OS_DEPRECATED void resetMaximumSourceZoneTemperatureSchedule();
 
     /// Returns the temperature schedule containing the outdoor temperature below which mixing is shutoff.
     bool setMinimumOutdoorTemperatureSchedule(Schedule& schedule);
@@ -176,7 +202,7 @@ namespace model {
 
    protected:
     /// @cond
-    typedef detail::ZoneMixing_Impl ImplType;
+    using ImplType = detail::ZoneMixing_Impl;
 
     explicit ZoneMixing(std::shared_ptr<detail::ZoneMixing_Impl> impl);
 
@@ -190,10 +216,10 @@ namespace model {
   };
 
   /** \relates ZoneMixing*/
-  typedef boost::optional<ZoneMixing> OptionalZoneMixing;
+  using OptionalZoneMixing = boost::optional<ZoneMixing>;
 
   /** \relates ZoneMixing*/
-  typedef std::vector<ZoneMixing> ZoneMixingVector;
+  using ZoneMixingVector = std::vector<ZoneMixing>;
 
 }  // namespace model
 }  // namespace openstudio
diff --git a/src/model/ZoneMixing_Impl.hpp b/src/model/ZoneMixing_Impl.hpp
index a07a223f22a..5d2de2836f4 100644
--- a/src/model/ZoneMixing_Impl.hpp
+++ b/src/model/ZoneMixing_Impl.hpp
@@ -36,8 +36,9 @@
 namespace openstudio {
 namespace model {
 
-  class ThermalZone;
   class Schedule;
+  class Space;
+  class ThermalZone;
 
   namespace detail {
 
@@ -55,7 +56,7 @@ namespace model {
 
       ZoneMixing_Impl(const ZoneMixing_Impl& other, Model_Impl* model, bool keepHandle);
 
-      virtual ~ZoneMixing_Impl() {}
+      virtual ~ZoneMixing_Impl() = default;
 
       //@}
       /** @name Virtual Methods */
@@ -67,11 +68,17 @@ namespace model {
 
       virtual std::vector<ScheduleTypeKey> getScheduleTypeKeys(const Schedule& schedule) const override;
 
+      virtual bool setParent(ParentObject& newParent) override;
+
+      bool hardSize();
+
       //@}
       /** @name Getters */
       //@{
 
-      ThermalZone zone() const;
+      boost::optional<ThermalZone> zone() const;
+      boost::optional<Space> space() const;
+      ModelObject zoneOrSpace() const;
 
       Schedule schedule() const;
 
@@ -79,25 +86,27 @@ namespace model {
 
       boost::optional<double> designFlowRate() const;
 
-      boost::optional<double> flowRateperZoneFloorArea() const;
+      boost::optional<double> flowRateperFloorArea() const;
 
       boost::optional<double> flowRateperPerson() const;
 
       boost::optional<double> airChangesperHour() const;
 
       boost::optional<ThermalZone> sourceZone() const;
+      boost::optional<Space> sourceSpace() const;
+      boost::optional<ModelObject> sourceZoneOrSpace() const;
 
       boost::optional<double> deltaTemperature() const;
 
       boost::optional<Schedule> deltaTemperatureSchedule() const;
 
-      boost::optional<Schedule> minimumZoneTemperatureSchedule() const;
+      boost::optional<Schedule> minimumReceivingTemperatureSchedule() const;
 
-      boost::optional<Schedule> maximumZoneTemperatureSchedule() const;
+      boost::optional<Schedule> maximumReceivingTemperatureSchedule() const;
 
-      boost::optional<Schedule> minimumSourceZoneTemperatureSchedule() const;
+      boost::optional<Schedule> minimumSourceTemperatureSchedule() const;
 
-      boost::optional<Schedule> maximumSourceZoneTemperatureSchedule() const;
+      boost::optional<Schedule> maximumSourceTemperatureSchedule() const;
 
       boost::optional<Schedule> minimumOutdoorTemperatureSchedule() const;
 
@@ -111,19 +120,22 @@ namespace model {
       /** @name Setters */
       //@{
 
+      bool setZone(const ThermalZone& zone);
+      bool setSpace(const Space& space);
+
       bool setSchedule(Schedule& schedule);
 
       bool setDesignFlowRate(double designFlowRate);
 
-      bool setFlowRateperZoneFloorArea(double flowRateperZoneFloorArea);
+      bool setFlowRateperFloorArea(double flowRateperFloorArea);
 
       bool setFlowRateperPerson(double flowRateperPerson);
 
       bool setAirChangesperHour(double airChangesperHour);
 
       bool setSourceZone(const ThermalZone& zone);
-
-      void resetSourceZone();
+      bool setSourceSpace(const Space& space);
+      void resetSourceZoneOrSpace();
 
       bool setDeltaTemperature(double deltaTemperature);
 
@@ -133,21 +145,21 @@ namespace model {
 
       void resetDeltaTemperatureSchedule();
 
-      bool setMinimumZoneTemperatureSchedule(Schedule& schedule);
+      bool setMinimumReceivingTemperatureSchedule(Schedule& schedule);
 
-      void resetMinimumZoneTemperatureSchedule();
+      void resetMinimumReceivingTemperatureSchedule();
 
-      bool setMaximumZoneTemperatureSchedule(Schedule& schedule);
+      bool setMaximumReceivingTemperatureSchedule(Schedule& schedule);
 
-      void resetMaximumZoneTemperatureSchedule();
+      void resetMaximumReceivingTemperatureSchedule();
 
-      bool setMinimumSourceZoneTemperatureSchedule(Schedule& schedule);
+      bool setMinimumSourceTemperatureSchedule(Schedule& schedule);
 
-      void resetMinimumSourceZoneTemperatureSchedule();
+      void resetMinimumSourceTemperatureSchedule();
 
-      bool setMaximumSourceZoneTemperatureSchedule(Schedule& schedule);
+      bool setMaximumSourceTemperatureSchedule(Schedule& schedule);
 
-      void resetMaximumSourceZoneTemperatureSchedule();
+      void resetMaximumSourceTemperatureSchedule();
 
       bool setMinimumOutdoorTemperatureSchedule(Schedule& schedule);
 


Test patch:
diff --git a/src/model/test/ZoneMixing_GTest.cpp b/src/model/test/ZoneMixing_GTest.cpp
index ad0d42f6b56..01a227b6847 100644
--- a/src/model/test/ZoneMixing_GTest.cpp
+++ b/src/model/test/ZoneMixing_GTest.cpp
@@ -38,6 +38,15 @@
 #include "../ScheduleTypeLimits.hpp"
 #include "../Model.hpp"
 #include "../Model_Impl.hpp"
+#include "../Space.hpp"
+#include "../Space_Impl.hpp"
+#include "../PeopleDefinition.hpp"
+#include "../People.hpp"
+#include "../SpaceType.hpp"
+#include "../Building.hpp"
+#include "../Building_Impl.hpp"
+
+#include "../../utilities/geometry/Point3d.hpp"
 
 using namespace openstudio;
 using namespace openstudio::model;
@@ -49,7 +58,7 @@ TEST_F(ModelFixture, ZoneMixing) {
   ThermalZone zone1(model);
   ThermalZone zone2(model);
   ZoneMixing mixing(zone1);
-  EXPECT_EQ(zone1.handle(), mixing.zone().handle());
+  EXPECT_EQ(zone1.handle(), mixing.zoneOrSpace().handle());
   EXPECT_EQ(model.alwaysOnContinuousSchedule().handle(), mixing.schedule().handle());
   EXPECT_FALSE(mixing.sourceZone());
 
@@ -63,7 +72,7 @@ TEST_F(ModelFixture, ZoneMixing) {
   EXPECT_EQ("Flow/Zone", mixing.designFlowRateCalculationMethod());
   ASSERT_TRUE(mixing.designFlowRate());
   EXPECT_EQ(0.0, mixing.designFlowRate().get());
-  EXPECT_FALSE(mixing.flowRateperZoneFloorArea());
+  EXPECT_FALSE(mixing.flowRateperFloorArea());
   EXPECT_FALSE(mixing.flowRateperPerson());
   EXPECT_FALSE(mixing.airChangesperHour());
 
@@ -71,22 +80,22 @@ TEST_F(ModelFixture, ZoneMixing) {
   EXPECT_EQ("Flow/Zone", mixing.designFlowRateCalculationMethod());
   ASSERT_TRUE(mixing.designFlowRate());
   EXPECT_EQ(100.0, mixing.designFlowRate().get());
-  EXPECT_FALSE(mixing.flowRateperZoneFloorArea());
+  EXPECT_FALSE(mixing.flowRateperFloorArea());
   EXPECT_FALSE(mixing.flowRateperPerson());
   EXPECT_FALSE(mixing.airChangesperHour());
 
-  EXPECT_TRUE(mixing.setFlowRateperZoneFloorArea(100.0));
+  EXPECT_TRUE(mixing.setFlowRateperFloorArea(100.0));
   EXPECT_EQ("Flow/Area", mixing.designFlowRateCalculationMethod());
   EXPECT_FALSE(mixing.designFlowRate());
-  ASSERT_TRUE(mixing.flowRateperZoneFloorArea());
-  EXPECT_EQ(100.0, mixing.flowRateperZoneFloorArea().get());
+  ASSERT_TRUE(mixing.flowRateperFloorArea());
+  EXPECT_EQ(100.0, mixing.flowRateperFloorArea().get());
   EXPECT_FALSE(mixing.flowRateperPerson());
   EXPECT_FALSE(mixing.airChangesperHour());
 
   EXPECT_TRUE(mixing.setFlowRateperPerson(100.0));
   EXPECT_EQ("Flow/Person", mixing.designFlowRateCalculationMethod());
   EXPECT_FALSE(mixing.designFlowRate());
-  EXPECT_FALSE(mixing.flowRateperZoneFloorArea());
+  EXPECT_FALSE(mixing.flowRateperFloorArea());
   ASSERT_TRUE(mixing.flowRateperPerson());
   EXPECT_EQ(100.0, mixing.flowRateperPerson().get());
   EXPECT_FALSE(mixing.airChangesperHour());
@@ -94,7 +103,7 @@ TEST_F(ModelFixture, ZoneMixing) {
   EXPECT_TRUE(mixing.setAirChangesperHour(100.0));
   EXPECT_EQ("AirChanges/Hour", mixing.designFlowRateCalculationMethod());
   EXPECT_FALSE(mixing.designFlowRate());
-  EXPECT_FALSE(mixing.flowRateperZoneFloorArea());
+  EXPECT_FALSE(mixing.flowRateperFloorArea());
   EXPECT_FALSE(mixing.flowRateperPerson());
   ASSERT_TRUE(mixing.airChangesperHour());
   EXPECT_EQ(100.0, mixing.airChangesperHour().get());
@@ -112,10 +121,10 @@ TEST_F(ModelFixture, ZoneMixing) {
 
   EXPECT_FALSE(mixing.deltaTemperature());
   EXPECT_FALSE(mixing.deltaTemperatureSchedule());
-  EXPECT_FALSE(mixing.minimumZoneTemperatureSchedule());
-  EXPECT_FALSE(mixing.maximumZoneTemperatureSchedule());
-  EXPECT_FALSE(mixing.minimumSourceZoneTemperatureSchedule());
-  EXPECT_FALSE(mixing.maximumSourceZoneTemperatureSchedule());
+  EXPECT_FALSE(mixing.minimumReceivingTemperatureSchedule());
+  EXPECT_FALSE(mixing.maximumReceivingTemperatureSchedule());
+  EXPECT_FALSE(mixing.minimumSourceTemperatureSchedule());
+  EXPECT_FALSE(mixing.maximumSourceTemperatureSchedule());
   EXPECT_FALSE(mixing.minimumOutdoorTemperatureSchedule());
   EXPECT_FALSE(mixing.maximumOutdoorTemperatureSchedule());
 
@@ -157,12 +166,12 @@ TEST_F(ModelFixture, ZoneMixing) {
   EXPECT_EQ(-10.0, mixing.deltaTemperature().get());
   EXPECT_FALSE(mixing.deltaTemperatureSchedule());
 
-  EXPECT_FALSE(mixing.maximumZoneTemperatureSchedule());
-  EXPECT_FALSE(mixing.setMaximumZoneTemperatureSchedule(fractionalSchedule));
-  EXPECT_FALSE(mixing.setMaximumZoneTemperatureSchedule(deltaTemperatureSchedule));
-  EXPECT_TRUE(mixing.setMaximumZoneTemperatureSchedule(temperatureSchedule));
-  ASSERT_TRUE(mixing.maximumZoneTemperatureSchedule());
-  EXPECT_EQ(temperatureSchedule.handle(), mixing.maximumZoneTemperatureSchedule().get().handle());
+  EXPECT_FALSE(mixing.maximumReceivingTemperatureSchedule());
+  EXPECT_FALSE(mixing.setMaximumReceivingTemperatureSchedule(fractionalSchedule));
+  EXPECT_FALSE(mixing.setMaximumReceivingTemperatureSchedule(deltaTemperatureSchedule));
+  EXPECT_TRUE(mixing.setMaximumReceivingTemperatureSchedule(temperatureSchedule));
+  ASSERT_TRUE(mixing.maximumReceivingTemperatureSchedule());
+  EXPECT_EQ(temperatureSchedule.handle(), mixing.maximumReceivingTemperatureSchedule().get().handle());
 }
 
 TEST_F(ModelFixture, ZoneMixing_SameZone) {
@@ -205,3 +214,268 @@ TEST_F(ModelFixture, ZoneMixing_ZoneRemove) {
     EXPECT_EQ(0u, model.getModelObjects<ZoneMixing>().size());
   }
 }
+
+TEST_F(ModelFixture, ZoneMixing_CantMixSpacesAndZones) {
+
+  Model m;
+
+  constexpr double width = 10.0;
+  constexpr double height = 3.6;  // It's convenient for ACH, since 3600 s/hr
+  // constexpr double spaceFloorArea = width * width;
+  // constexpr double spaceVolume = spaceFloorArea * height;
+  // constexpr double oneWallArea = width * height;
+
+  //            y (=North)
+  //   ▲
+  //   │                  building height = 3m
+  // 10├────────┼────────┼────────┤
+  //   │        │        │        │
+  //   │ Zone 1 │ Zone 1 │ Zone 2 │
+  //   │ Space 1│ Space 2│ Space 3│
+  //   │        │        │        │
+  //   └────────┴────────┴────────┴─────► x
+  //  0        10       20        30
+
+  // Counterclockwise points
+  std::vector<Point3d> floorPointsSpace1{{0.0, 0.0, 0.0}, {0.0, width, 0.0}, {width, width, 0.0}, {width, 0.0, 0.0}};
+
+  auto space1 = Space::fromFloorPrint(floorPointsSpace1, height, m).get();
+  auto space2 = Space::fromFloorPrint(floorPointsSpace1, height, m).get();
+  space2.setXOrigin(width);
+  auto space3 = Space::fromFloorPrint(floorPointsSpace1, height, m).get();
+  space3.setXOrigin(width * 2);
+
+  ThermalZone z1(m);
+  space1.setThermalZone(z1);
+  space2.setThermalZone(z1);
+
+  ThermalZone z2(m);
+  space3.setThermalZone(z2);
+
+  {
+    ZoneMixing zm(space1);
+    // Can't mix Spaces and Zones
+    EXPECT_FALSE(zm.setSourceZone(z1));
+    EXPECT_FALSE(zm.sourceZoneOrSpace());
+    EXPECT_FALSE(zm.sourceZone());
+    EXPECT_FALSE(zm.sourceSpace());
+
+    EXPECT_TRUE(zm.setSourceSpace(space3));
+    ASSERT_TRUE(zm.sourceZoneOrSpace());
+    EXPECT_EQ(space3, zm.sourceZoneOrSpace().get());
+    ASSERT_TRUE(zm.sourceSpace());
+    EXPECT_EQ(space3, zm.sourceSpace().get());
+    EXPECT_FALSE(zm.sourceZone());
+  }
+
+  {
+    ZoneMixing zm(z1);
+    // Can't mix Spaces and Zones
+    EXPECT_FALSE(zm.setSourceSpace(space1));
+    EXPECT_FALSE(zm.sourceZoneOrSpace());
+    EXPECT_FALSE(zm.sourceZone());
+    EXPECT_FALSE(zm.sourceSpace());
+
+    EXPECT_TRUE(zm.setSourceZone(z2));
+    ASSERT_TRUE(zm.sourceZoneOrSpace());
+    EXPECT_EQ(z2, zm.sourceZoneOrSpace().get());
+    ASSERT_TRUE(zm.sourceZone());
+    EXPECT_EQ(z2, zm.sourceZone().get());
+    EXPECT_FALSE(zm.sourceSpace());
+  }
+}
+
+TEST_F(ModelFixture, ZoneMixing_CombineSpacesDifferentZones) {
+
+  Model m;
+
+  constexpr double width = 10.0;
+  constexpr double height = 3.6;  // It's convenient for ACH, since 3600 s/hr
+  constexpr double spaceFloorArea = width * width;
+  constexpr double spaceVolume = spaceFloorArea * height;
+  constexpr double numPeoplePerSpace = 10.0;
+
+  //            y (=North)
+  //   ▲
+  //   │                  building height = 3m
+  // 10├────────┼────────┼────────┼────────┤
+  //   │        │        │        │        │
+  //   │ Zone 1 │ Zone 1 │ Zone 2 │ Zone 2 │
+  //   │ Space 1│ Space 2│ Space 3│ Space 4│
+  //   │        │        │        │        │
+  //   └────────┴────────┴────────┴────────┴──► x
+  //  0        10       20        30       40
+
+  // Counterclockwise points
+  std::vector<Point3d> floorPointsSpace1{{0.0, 0.0, 0.0}, {0.0, width, 0.0}, {width, width, 0.0}, {width, 0.0, 0.0}};
+
+  auto space1 = Space::fromFloorPrint(floorPointsSpace1, height, m).get();
+  auto space2 = Space::fromFloorPrint(floorPointsSpace1, height, m).get();
+  space2.setXOrigin(width);
+  auto space3 = Space::fromFloorPrint(floorPointsSpace1, height, m).get();
+  space3.setXOrigin(width * 2);
+  auto space4 = Space::fromFloorPrint(floorPointsSpace1, height, m).get();
+  space4.setXOrigin(width * 3);
+
+  ThermalZone z1(m);
+  space1.setThermalZone(z1);
+  space2.setThermalZone(z1);
+
+  ThermalZone z2(m);
+  space3.setThermalZone(z2);
+  space4.setThermalZone(z2);
+
+  // Add 10 People in each space
+  SpaceType buildingSpaceType(m);
+  buildingSpaceType.setName("buildingSpaceType");
+  PeopleDefinition pd(m);
+  pd.setNumberofPeople(numPeoplePerSpace);
+  People p(pd);
+  EXPECT_TRUE(p.setSpaceType(buildingSpaceType));
+  auto building = m.getUniqueModelObject<Building>();
+  EXPECT_TRUE(building.setSpaceType(buildingSpaceType));
+
+  ZoneMixing zmAbs(space3);
+  EXPECT_TRUE(zmAbs.setSourceSpace(space2));
+  constexpr double m3sAbs = 0.6;
+  EXPECT_TRUE(zmAbs.setDesignFlowRate(m3sAbs));
+
+  ZoneMixing zmFloor(space2);
+  EXPECT_TRUE(zmFloor.setSourceSpace(space4));
+  constexpr double m3sPerFlow = 0.001;
+  constexpr double equivm3s_Floor = m3sPerFlow * spaceFloorArea;
+  EXPECT_TRUE(zmFloor.setFlowRateperFloorArea(m3sPerFlow));
+
+  ZoneMixing zmPerson(space4);
+  EXPECT_TRUE(zmPerson.setSourceSpace(space1));
+  constexpr double m3sPerPerson = 0.03;
+  constexpr double equivm3s_Person = m3sPerPerson * numPeoplePerSpace;
+  EXPECT_TRUE(zmPerson.setFlowRateperPerson(m3sPerPerson));
+
+  ZoneMixing zmACH(space1);
+  EXPECT_TRUE(zmACH.setSourceSpace(space3));
+  constexpr double ach = 0.5;
+  constexpr double equivm3s_ACH = ach * spaceVolume / 3600.0;
+  EXPECT_TRUE(zmACH.setAirChangesperHour(ach));
+
+  // |    Zone Mixing   | Receiving | Source  | Flow/Space | Per Floor Area | Per Person |   ACH )  || Calculated Total   |
+  // |       Name       |   Space   |  Space  |   (m3/s)   |    (m3/s-m2)   |  (m3/s.p)  |  (1/hr)  || For 1 Space (m3/s) |
+  // |------------------|-----------|---------|------------|----------------|------------|----------||--------------------|
+  // | zmAbs            | space3    | space2  | 0.6        |                |            |          ||      0.6           |
+  // | zmFloor          | space2    | space4  |            | 0.001          |            |          ||      0.1           |
+  // | zmPerson         | space4    | space1  |            |                | 0.03       |          ||      0.3           |
+  // | zmACH            | space1    | space3  |            |                |            | 0.5      ||      0.05          |
+
+  EXPECT_EQ(0.6, m3sAbs);
+  EXPECT_EQ(0.1, equivm3s_Floor);
+  EXPECT_EQ(0.3, equivm3s_Person);
+  EXPECT_EQ(0.05, equivm3s_ACH);
+
+  // Combine Zone 1's space
+  // Only receiving spaces trigger a hardsize, so zmFloor and zmACH should be hardsized
+  auto space12_ = z1.combineSpaces();
+  ASSERT_TRUE(space12_);
+
+  EXPECT_EQ(3, m.getConcreteModelObjects<Space>().size());
+
+  ASSERT_TRUE(zmAbs.designFlowRate());
+  EXPECT_EQ(m3sAbs, zmAbs.designFlowRate().get());
+  EXPECT_FALSE(zmAbs.flowRateperFloorArea());
+  EXPECT_FALSE(zmAbs.flowRateperPerson());
+  EXPECT_FALSE(zmAbs.airChangesperHour());
+  ASSERT_TRUE(zmAbs.space());
+  EXPECT_EQ(space3, zmAbs.space().get());
+  EXPECT_FALSE(zmAbs.zone());
+  ASSERT_TRUE(zmAbs.sourceSpace());
+  EXPECT_EQ(space12_.get(), zmAbs.sourceSpace().get());
+  EXPECT_FALSE(zmAbs.sourceZone());
+
+  ASSERT_TRUE(zmFloor.designFlowRate());
+  EXPECT_EQ(equivm3s_Floor, zmFloor.designFlowRate().get());
+  EXPECT_FALSE(zmFloor.flowRateperFloorArea());
+  EXPECT_FALSE(zmFloor.flowRateperPerson());
+  EXPECT_FALSE(zmFloor.airChangesperHour());
+  ASSERT_TRUE(zmFloor.space());
+  EXPECT_EQ(space12_.get(), zmFloor.space().get());
+  EXPECT_FALSE(zmFloor.zone());
+  ASSERT_TRUE(zmFloor.sourceSpace());
+  EXPECT_EQ(space4, zmFloor.sourceSpace().get());
+  EXPECT_FALSE(zmFloor.sourceZone());
+
+  EXPECT_FALSE(zmPerson.designFlowRate());
+  EXPECT_FALSE(zmPerson.flowRateperFloorArea());
+  ASSERT_TRUE(zmPerson.flowRateperPerson());
+  EXPECT_EQ(m3sPerPerson, zmPerson.flowRateperPerson().get());
+  EXPECT_FALSE(zmPerson.airChangesperHour());
+  ASSERT_TRUE(zmPerson.space());
+  EXPECT_EQ(space4, zmPerson.space().get());
+  EXPECT_FALSE(zmPerson.zone());
+  ASSERT_TRUE(zmPerson.sourceSpace());
+  EXPECT_EQ(space12_.get(), zmPerson.sourceSpace().get());
+  EXPECT_FALSE(zmPerson.sourceZone());
+
+  ASSERT_TRUE(zmACH.designFlowRate());
+  EXPECT_EQ(equivm3s_ACH, zmACH.designFlowRate().get());
+  EXPECT_FALSE(zmACH.flowRateperFloorArea());
+  EXPECT_FALSE(zmACH.flowRateperPerson());
+  EXPECT_FALSE(zmACH.airChangesperHour());
+  ASSERT_TRUE(zmACH.space());
+  EXPECT_EQ(space12_.get(), zmACH.space().get());
+  EXPECT_FALSE(zmACH.zone());
+  ASSERT_TRUE(zmACH.sourceSpace());
+  EXPECT_EQ(space3, zmACH.sourceSpace().get());
+  EXPECT_FALSE(zmACH.sourceZone());
+
+  auto space34_ = z2.combineSpaces();
+  ASSERT_TRUE(space34_);
+
+  EXPECT_EQ(2, m.getConcreteModelObjects<Space>().size());
+
+  ASSERT_TRUE(zmAbs.designFlowRate());
+  EXPECT_EQ(m3sAbs, zmAbs.designFlowRate().get());
+  EXPECT_FALSE(zmAbs.flowRateperFloorArea());
+  EXPECT_FALSE(zmAbs.flowRateperPerson());
+  EXPECT_FALSE(zmAbs.airChangesperHour());
+  ASSERT_TRUE(zmAbs.space());
+  EXPECT_EQ(space34_.get(), zmAbs.space().get());
+  EXPECT_FALSE(zmAbs.zone());
+  ASSERT_TRUE(zmAbs.sourceSpace());
+  EXPECT_EQ(space12_.get(), zmAbs.sourceSpace().get());
+  EXPECT_FALSE(zmAbs.sourceZone());
+
+  ASSERT_TRUE(zmFloor.designFlowRate());
+  EXPECT_EQ(equivm3s_Floor, zmFloor.designFlowRate().get());
+  EXPECT_FALSE(zmFloor.flowRateperFloorArea());
+  EXPECT_FALSE(zmFloor.flowRateperPerson());
+  EXPECT_FALSE(zmFloor.airChangesperHour());
+  ASSERT_TRUE(zmFloor.space());
+  EXPECT_EQ(space12_.get(), zmFloor.space().get());
+  EXPECT_FALSE(zmFloor.zone());
+  ASSERT_TRUE(zmFloor.sourceSpace());
+  EXPECT_EQ(space34_.get(), zmFloor.sourceSpace().get());
+  EXPECT_FALSE(zmFloor.sourceZone());
+
+  ASSERT_TRUE(zmPerson.designFlowRate());
+  EXPECT_EQ(equivm3s_Person, zmPerson.designFlowRate().get());
+  EXPECT_FALSE(zmPerson.flowRateperFloorArea());
+  EXPECT_FALSE(zmPerson.flowRateperPerson());
+  EXPECT_FALSE(zmPerson.airChangesperHour());
+  ASSERT_TRUE(zmPerson.space());
+  EXPECT_EQ(space34_.get(), zmPerson.space().get());
+  EXPECT_FALSE(zmPerson.zone());
+  ASSERT_TRUE(zmPerson.sourceSpace());
+  EXPECT_EQ(space12_.get(), zmPerson.sourceSpace().get());
+  EXPECT_FALSE(zmPerson.sourceZone());
+
+  ASSERT_TRUE(zmACH.designFlowRate());
+  EXPECT_EQ(equivm3s_ACH, zmACH.designFlowRate().get());
+  EXPECT_FALSE(zmACH.flowRateperFloorArea());
+  EXPECT_FALSE(zmACH.flowRateperPerson());
+  EXPECT_FALSE(zmACH.airChangesperHour());
+  ASSERT_TRUE(zmACH.space());
+  EXPECT_EQ(space12_.get(), zmACH.space().get());
+  EXPECT_FALSE(zmACH.zone());
+  ASSERT_TRUE(zmACH.sourceSpace());
+  EXPECT_EQ(space34_.get(), zmACH.sourceSpace().get());
+  EXPECT_FALSE(zmACH.sourceZone());
+}