diff --git a/opm/parser/eclipse/EclipseState/EclipseState.cpp b/opm/parser/eclipse/EclipseState/EclipseState.cpp index 4e64af8f2..c3eb62a20 100644 --- a/opm/parser/eclipse/EclipseState/EclipseState.cpp +++ b/opm/parser/eclipse/EclipseState/EclipseState.cpp @@ -32,6 +32,8 @@ namespace Opm { EclipseState::EclipseState(DeckConstPtr deck) { + m_unitSystem = deck->getActiveUnitSystem(); + initPhases(deck); initEclipseGrid(deck); initSchedule(deck); @@ -134,20 +136,22 @@ namespace Opm { void EclipseState::initProperties(DeckConstPtr deck) { BoxManager boxManager(m_eclipseGrid->getNX( ) , m_eclipseGrid->getNY() , m_eclipseGrid->getNZ()); - std::vector > supportedIntKeywords = {{ "SATNUM" , 0 }, - { "PVTNUM" , 0 }, - { "EQLNUM" , 0 }, - { "FIPNUM" , 0 }}; - - std::vector > supportedDoubleKeywords = {{ "PORO" , 0 }, - { "PERMX" , 0 }, - { "PERMY" , 0 }, - { "PERMZ" , 0 }}; - - + typedef GridProperties::SupportedKeywordInfo SupportedIntKeywordInfo; + static std::vector supportedIntKeywords = + {SupportedIntKeywordInfo( "SATNUM" , 0.0, "1" ), + SupportedIntKeywordInfo( "PVTNUM" , 0.0, "1" ), + SupportedIntKeywordInfo( "EQLNUM" , 0.0, "1" ), + SupportedIntKeywordInfo( "FIPNUM" , 0.0, "1" )}; + + typedef GridProperties::SupportedKeywordInfo SupportedDoubleKeywordInfo; + static std::vector supportedDoubleKeywords = + {SupportedDoubleKeywordInfo( "PORO" , 0.0, "1" ), + SupportedDoubleKeywordInfo( "PERMX" , 0.0, "Permeability" ), + SupportedDoubleKeywordInfo( "PERMY" , 0.0, "Permeability" ), + SupportedDoubleKeywordInfo( "PERMZ" , 0.0, "Permeability" )}; m_intGridProperties = std::make_shared >(m_eclipseGrid->getNX() , m_eclipseGrid->getNY() , m_eclipseGrid->getNZ() , supportedIntKeywords); - m_doubleGridProperties = std::make_shared >(m_eclipseGrid->getNX() , m_eclipseGrid->getNY() , m_eclipseGrid->getNZ() , supportedDoubleKeywords); + m_doubleGridProperties.reset(new GridProperties(m_eclipseGrid->getNX() , m_eclipseGrid->getNY() , m_eclipseGrid->getNZ() , supportedDoubleKeywords)); if (Section::hasGRID(deck)) { @@ -176,10 +180,12 @@ namespace Opm { scanSection( solutionSection , boxManager ); } } - - - + double EclipseState::getSIScaling(const std::string &dimensionString) const + { + return m_unitSystem->getDimension(dimensionString)->getSIScaling(); + } + void EclipseState::scanSection(std::shared_ptr section , BoxManager& boxManager) { for (auto iter = section->begin(); iter != section->end(); ++iter) { DeckKeywordConstPtr deckKeyword = *iter; @@ -276,8 +282,9 @@ namespace Opm { property->add( intShift , boxManager.getActiveBox() ); } else if (m_doubleGridProperties->hasKeyword( field )) { std::shared_ptr > property = m_doubleGridProperties->getKeyword( field ); - - property->add( shiftValue , boxManager.getActiveBox() ); + + double siShiftValue = shiftValue * getSIScaling(property->getDimensionString()); + property->add(siShiftValue , boxManager.getActiveBox() ); } else throw std::invalid_argument("Fatal error processing ADD keyword. Tried to shift not defined keyword " + field); @@ -301,7 +308,8 @@ namespace Opm { } else if (m_doubleGridProperties->supportsKeyword( field )) { std::shared_ptr > property = m_doubleGridProperties->getKeyword( field ); - property->setScalar( value , boxManager.getActiveBox() ); + double siValue = value * getSIScaling(property->getDimensionString()); + property->setScalar( siValue , boxManager.getActiveBox() ); } else throw std::invalid_argument("Fatal error processing EQUALS keyword. Tried to set not defined keyword " + field); diff --git a/opm/parser/eclipse/EclipseState/EclipseState.hpp b/opm/parser/eclipse/EclipseState/EclipseState.hpp index 38ae80b48..ee63acf81 100644 --- a/opm/parser/eclipse/EclipseState/EclipseState.hpp +++ b/opm/parser/eclipse/EclipseState/EclipseState.hpp @@ -56,6 +56,8 @@ namespace Opm { void initTitle(DeckConstPtr deck); void initProperties(DeckConstPtr deck); + double getSIScaling(const std::string &dimensionString) const; + void scanSection(std::shared_ptr section , BoxManager& boxManager); void handleADDKeyword(DeckKeywordConstPtr deckKeyword , BoxManager& boxManager); void handleBOXKeyword(DeckKeywordConstPtr deckKeyword , BoxManager& boxManager); @@ -73,6 +75,7 @@ namespace Opm { ScheduleConstPtr schedule; std::set phases; std::string m_title; + std::shared_ptr m_unitSystem; std::shared_ptr > m_intGridProperties; std::shared_ptr > m_doubleGridProperties; }; diff --git a/opm/parser/eclipse/EclipseState/Grid/EclipseGrid.cpp b/opm/parser/eclipse/EclipseState/Grid/EclipseGrid.cpp index 7c10ae179..61fc3856b 100644 --- a/opm/parser/eclipse/EclipseState/Grid/EclipseGrid.cpp +++ b/opm/parser/eclipse/EclipseState/Grid/EclipseGrid.cpp @@ -301,7 +301,7 @@ namespace Opm { } void EclipseGrid::exportACTNUM( std::vector& actnum) const { - int volume = getNX() * getNY() * getNZ(); + size_t volume = getNX() * getNY() * getNZ(); if (getNumActive() == volume) actnum.resize(0); else { diff --git a/opm/parser/eclipse/EclipseState/Grid/GridProperties.hpp b/opm/parser/eclipse/EclipseState/Grid/GridProperties.hpp index 56b64bf40..7b14b34c4 100644 --- a/opm/parser/eclipse/EclipseState/Grid/GridProperties.hpp +++ b/opm/parser/eclipse/EclipseState/Grid/GridProperties.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -48,53 +49,47 @@ namespace Opm { template class GridProperties { public: + typedef typename GridProperty::SupportedKeywordInfo SupportedKeywordInfo; - GridProperties(size_t nx , size_t ny , size_t nz , const std::vector > & supportedKeywords) { + GridProperties(size_t nx , size_t ny , size_t nz , + const std::vector & supportedKeywords) { m_nx = nx; m_ny = ny; m_nz = nz; for (auto iter = supportedKeywords.begin(); iter != supportedKeywords.end(); ++iter) - m_supportedKeywords.insert( std::pair(*iter)); + m_supportedKeywords[std::get<0>(*iter)] = *iter; } bool supportsKeyword(const std::string& keyword) { - if (m_supportedKeywords.find( keyword ) == m_supportedKeywords.end()) - return false; - else - return true; + return m_supportedKeywords.count( keyword ) > 0; } bool hasKeyword(const std::string& keyword) { - if (m_properties.find( keyword ) == m_properties.end()) - return false; - else - return true; + return m_properties.count( keyword ) > 0; } std::shared_ptr > getKeyword(const std::string& keyword) { if (!hasKeyword(keyword)) addKeyword(keyword); - - { - auto pair = m_properties.find( keyword ); - return (*pair).second; - } + + return m_properties.at( keyword ); } - bool addKeyword(const std::string& keyword) { - if (!supportsKeyword( keyword )) - throw std::invalid_argument("The keyword: " + keyword + " is not supported in this container"); + bool addKeyword(const std::string& keywordName) { + if (!supportsKeyword( keywordName )) + throw std::invalid_argument("The keyword: " + keywordName + " is not supported in this container"); - if (hasKeyword(keyword)) + if (hasKeyword(keywordName)) return false; else { - auto supportedKeyword = m_supportedKeywords.find( keyword ); - std::shared_ptr > newProperty(new GridProperty(m_nx , m_ny , m_nz , keyword , (*supportedKeyword).second)); - m_properties.insert( std::pair > > ( keyword , newProperty )); + auto supportedKeyword = m_supportedKeywords.at( keywordName ); + std::shared_ptr > newProperty(new GridProperty(m_nx , m_ny , m_nz , supportedKeyword)); + + m_properties.insert( std::pair > > ( keywordName , newProperty )); return true; } } @@ -102,7 +97,7 @@ public: private: size_t m_nx, m_ny, m_nz; - std::unordered_map m_supportedKeywords; + std::unordered_map m_supportedKeywords; std::map > > m_properties; }; diff --git a/opm/parser/eclipse/EclipseState/Grid/GridProperty.hpp b/opm/parser/eclipse/EclipseState/Grid/GridProperty.hpp index 160e24b6b..85962d6ff 100644 --- a/opm/parser/eclipse/EclipseState/Grid/GridProperty.hpp +++ b/opm/parser/eclipse/EclipseState/Grid/GridProperty.hpp @@ -46,16 +46,17 @@ namespace Opm { template class GridProperty { public: + typedef std::tuple SupportedKeywordInfo; - GridProperty(size_t nx , size_t ny , size_t nz , const std::string& keyword , T defaultValue = 0) { + GridProperty(size_t nx , size_t ny , size_t nz , const SupportedKeywordInfo& kwInfo) { m_nx = nx; m_ny = ny; m_nz = nz; - m_keyword = keyword; + m_kwInfo = kwInfo; m_data.resize( nx * ny * nz ); - std::fill( m_data.begin() , m_data.end() , defaultValue ); + std::fill( m_data.begin() , m_data.end() , std::get<1>(m_kwInfo)); } - + size_t size() const { return m_data.size(); } @@ -87,7 +88,8 @@ public: void copyFrom(const GridProperty& src, std::shared_ptr inputBox) { if (inputBox->isGlobal()) { - std::copy( src.m_data.begin() , src.m_data.end() , m_data.begin() ); + for (size_t i = 0; i < src.size(); ++i) + m_data[i] = src.m_data[i]; } else { const std::vector& indexList = inputBox->getIndexList(); for (size_t i = 0; i < indexList.size(); i++) { @@ -99,9 +101,8 @@ public: void scale(T scaleFactor , std::shared_ptr inputBox) { if (inputBox->isGlobal()) { - std::transform(m_data.begin(), m_data.end(), m_data.begin(), - std::bind1st(std::multiplies() , scaleFactor)); - + for (size_t i = 0; i < m_data.size(); ++i) + m_data[i] *= scaleFactor; } else { const std::vector& indexList = inputBox->getIndexList(); for (size_t i = 0; i < indexList.size(); i++) { @@ -114,9 +115,8 @@ public: void add(T shiftValue , std::shared_ptr inputBox) { if (inputBox->isGlobal()) { - std::transform(m_data.begin(), m_data.end(), m_data.begin(), - std::bind1st(std::plus() , shiftValue)); - + for (size_t i = 0; i < m_data.size(); ++i) + m_data[i] += shiftValue; } else { const std::vector& indexList = inputBox->getIndexList(); for (size_t i = 0; i < indexList.size(); i++) { @@ -142,7 +142,15 @@ public: } - + const std::string& getKeywordName() const + { + return std::get<0>(m_kwInfo); + } + + const std::string& getDimensionString() const + { + return std::get<2>(m_kwInfo); + } private: @@ -151,7 +159,7 @@ private: for (size_t i = 0; i < data.size(); i++) m_data[i] = data[i]; } else - throw std::invalid_argument("Size mismatch when setting data for:" + m_keyword + " keyword size: " + boost::lexical_cast(m_data.size()) + " input size: " + boost::lexical_cast(data.size())); + throw std::invalid_argument("Size mismatch when setting data for:" + getKeywordName() + " keyword size: " + boost::lexical_cast(m_data.size()) + " input size: " + boost::lexical_cast(data.size())); } @@ -166,12 +174,12 @@ private: m_data[targetIndex] = data[i]; } } else - throw std::invalid_argument("Size mismatch when setting data for:" + m_keyword + " box size: " + boost::lexical_cast(inputBox->size()) + " input size: " + boost::lexical_cast(data.size())); + throw std::invalid_argument("Size mismatch when setting data for:" + getKeywordName() + " box size: " + boost::lexical_cast(inputBox->size()) + " input size: " + boost::lexical_cast(data.size())); } } size_t m_nx,m_ny,m_nz; - std::string m_keyword; + SupportedKeywordInfo m_kwInfo; std::vector m_data; }; diff --git a/opm/parser/eclipse/EclipseState/Grid/tests/GridPropertiesTests.cpp b/opm/parser/eclipse/EclipseState/Grid/tests/GridPropertiesTests.cpp index 5c72a9c17..b1fc0f6c4 100644 --- a/opm/parser/eclipse/EclipseState/Grid/tests/GridPropertiesTests.cpp +++ b/opm/parser/eclipse/EclipseState/Grid/tests/GridPropertiesTests.cpp @@ -36,7 +36,11 @@ BOOST_AUTO_TEST_CASE(Empty) { - Opm::GridProperties gridProperties( 10, 10, 100 , std::vector > { {"SATNUM" , 0} , {"FIPNUM" , 2 } }); + typedef typename Opm::GridProperties::SupportedKeywordInfo SupportedKeywordInfo; + std::vector supportedKeywords = + { SupportedKeywordInfo("SATNUM" , 0, "1"), + SupportedKeywordInfo("FIPNUM" , 2, "1") }; + Opm::GridProperties gridProperties( 10, 10, 100 , supportedKeywords); BOOST_CHECK( gridProperties.supportsKeyword("SATNUM") ); BOOST_CHECK( gridProperties.supportsKeyword("FIPNUM") ); @@ -48,7 +52,10 @@ BOOST_AUTO_TEST_CASE(Empty) { BOOST_AUTO_TEST_CASE(addKeyword) { - Opm::GridProperties gridProperties( 100, 10 , 10 , std::vector > { {"SATNUM" , 0} }); + typedef Opm::GridProperties::SupportedKeywordInfo SupportedKeywordInfo; + std::vector supportedKeywords = + { SupportedKeywordInfo("SATNUM" , 0, "1") }; + Opm::GridProperties gridProperties( 100, 10 , 10 , supportedKeywords); BOOST_CHECK_THROW( gridProperties.addKeyword("NOT-SUPPORTED") , std::invalid_argument); @@ -61,7 +68,10 @@ BOOST_AUTO_TEST_CASE(addKeyword) { BOOST_AUTO_TEST_CASE(getKeyword) { - Opm::GridProperties gridProperties( 100,25,4 , std::vector > { {"SATNUM" , 0} }); + typedef Opm::GridProperties::SupportedKeywordInfo SupportedKeywordInfo; + std::vector supportedKeywords = + { SupportedKeywordInfo("SATNUM" , 0, "1") }; + Opm::GridProperties gridProperties( 100,25,4 , supportedKeywords); std::shared_ptr > satnum1 = gridProperties.getKeyword("SATNUM"); std::shared_ptr > satnum2 = gridProperties.getKeyword("SATNUM"); diff --git a/opm/parser/eclipse/EclipseState/Grid/tests/GridPropertyTests.cpp b/opm/parser/eclipse/EclipseState/Grid/tests/GridPropertyTests.cpp index ef6d628a2..9ff0574ba 100644 --- a/opm/parser/eclipse/EclipseState/Grid/tests/GridPropertyTests.cpp +++ b/opm/parser/eclipse/EclipseState/Grid/tests/GridPropertyTests.cpp @@ -38,17 +38,19 @@ Opm::DeckKeywordConstPtr createSATNUMKeyword(); Opm::DeckKeywordConstPtr createTABDIMSKeyword(); BOOST_AUTO_TEST_CASE(Empty) { - Opm::GridProperty gridProperties( 5 , 5 , 4 , "SATNUM" , 77); - const std::vector& data = gridProperties.getData(); + typedef typename Opm::GridProperty::SupportedKeywordInfo SupportedKeywordInfo; + SupportedKeywordInfo keywordInfo("SATNUM" , 77, "1"); + Opm::GridProperty gridProperty( 5 , 5 , 4 , keywordInfo); + const std::vector& data = gridProperty.getData(); BOOST_CHECK_EQUAL( 100U , data.size()); - BOOST_CHECK_EQUAL( 100U , gridProperties.size()); + BOOST_CHECK_EQUAL( 100U , gridProperty.size()); for (size_t k=0; k < 4; k++) { for (size_t j=0; j < 5; j++) { for (size_t i=0; i < 5; i++) { size_t g = i + j*5 + k*25; BOOST_CHECK_EQUAL( 77 , data[g] ); - BOOST_CHECK_EQUAL( 77 , gridProperties.iget( g )); - BOOST_CHECK_EQUAL( 77 , gridProperties.iget( i,j,k )); + BOOST_CHECK_EQUAL( 77 , gridProperty.iget( g )); + BOOST_CHECK_EQUAL( 77 , gridProperty.iget( i,j,k )); } } } @@ -56,8 +58,13 @@ BOOST_AUTO_TEST_CASE(Empty) { BOOST_AUTO_TEST_CASE(EmptyDefault) { - Opm::GridProperty gridProperties( 10,10,1 , "SATNUM"); - const std::vector& data = gridProperties.getData(); + typedef Opm::GridProperty::SupportedKeywordInfo SupportedKeywordInfo; + SupportedKeywordInfo keywordInfo("SATNUM" , 0, "1"); + Opm::GridProperty gridProperty( /*nx=*/10, + /*ny=*/10, + /*nz=*/1 , + keywordInfo); + const std::vector& data = gridProperty.getData(); BOOST_CHECK_EQUAL( 100U , data.size()); for (size_t i=0; i < data.size(); i++) BOOST_CHECK_EQUAL( 0 , data[i] ); @@ -89,14 +96,18 @@ Opm::DeckKeywordConstPtr createTABDIMSKeyword( ) { BOOST_AUTO_TEST_CASE(SetFromDeckKeyword_notData_Throws) { Opm::DeckKeywordConstPtr tabdimsKw = createTABDIMSKeyword(); - Opm::GridProperty gridProperty( 6 ,1,1 , "TABDIMS" , 100); + typedef Opm::GridProperty::SupportedKeywordInfo SupportedKeywordInfo; + SupportedKeywordInfo keywordInfo("TABDIMS" , 100, "1"); + Opm::GridProperty gridProperty( 6 ,1,1 , keywordInfo); BOOST_CHECK_THROW( gridProperty.loadFromDeckKeyword( tabdimsKw ) , std::invalid_argument ); } BOOST_AUTO_TEST_CASE(SetFromDeckKeyword_wrong_size_throws) { Opm::DeckKeywordConstPtr satnumKw = createSATNUMKeyword(); - Opm::GridProperty gridProperty( 15 ,1,1, "SATNUM",66); + typedef Opm::GridProperty::SupportedKeywordInfo SupportedKeywordInfo; + SupportedKeywordInfo keywordInfo("SATNUM" , 66, "1"); + Opm::GridProperty gridProperty( 15 ,1,1, keywordInfo); BOOST_CHECK_THROW( gridProperty.loadFromDeckKeyword( satnumKw ) , std::invalid_argument ); } @@ -104,7 +115,9 @@ BOOST_AUTO_TEST_CASE(SetFromDeckKeyword_wrong_size_throws) { BOOST_AUTO_TEST_CASE(SetFromDeckKeyword) { Opm::DeckKeywordConstPtr satnumKw = createSATNUMKeyword(); - Opm::GridProperty gridProperty( 4 , 4 , 2 , "SATNUM" , 99); + typedef Opm::GridProperty::SupportedKeywordInfo SupportedKeywordInfo; + SupportedKeywordInfo keywordInfo("SATNUM" , 99, "1"); + Opm::GridProperty gridProperty( 4 , 4 , 2 , keywordInfo); gridProperty.loadFromDeckKeyword( satnumKw ); const std::vector& data = gridProperty.getData(); for (size_t k=0; k < 2; k++) { @@ -123,12 +136,15 @@ BOOST_AUTO_TEST_CASE(SetFromDeckKeyword) { BOOST_AUTO_TEST_CASE(copy) { - Opm::GridProperty prop1( 4 , 4 , 2 , "P1" , 0); - Opm::GridProperty prop2( 4 , 4 , 2 , "P2" , 9); + typedef Opm::GridProperty::SupportedKeywordInfo SupportedKeywordInfo; + SupportedKeywordInfo keywordInfo1("P1" , 0, "1"); + SupportedKeywordInfo keywordInfo2("P2" , 9, "1"); + Opm::GridProperty prop1( 4 , 4 , 2 , keywordInfo1); + Opm::GridProperty prop2( 4 , 4 , 2 , keywordInfo2); Opm::Box global(4,4,2); std::shared_ptr layer0 = std::make_shared(global , 0,3,0,3,0,0); - + prop2.copyFrom(prop1 , layer0); for (size_t j=0; j < 4; j++) { @@ -142,8 +158,12 @@ BOOST_AUTO_TEST_CASE(copy) { BOOST_AUTO_TEST_CASE(SCALE) { - Opm::GridProperty prop1( 4 , 4 , 2 , "P1" , 1); - Opm::GridProperty prop2( 4 , 4 , 2 , "P2" , 9); + typedef Opm::GridProperty::SupportedKeywordInfo SupportedKeywordInfo; + SupportedKeywordInfo keywordInfo1("P1" , 1, "1"); + SupportedKeywordInfo keywordInfo2("P2" , 9, "1"); + + Opm::GridProperty prop1( 4 , 4 , 2 , keywordInfo1); + Opm::GridProperty prop2( 4 , 4 , 2 , keywordInfo2); std::shared_ptr global = std::make_shared(4,4,2); std::shared_ptr layer0 = std::make_shared(*global , 0,3,0,3,0,0); @@ -163,7 +183,9 @@ BOOST_AUTO_TEST_CASE(SCALE) { BOOST_AUTO_TEST_CASE(SET) { - Opm::GridProperty prop( 4 , 4 , 2 , "P1" , 1); + typedef Opm::GridProperty::SupportedKeywordInfo SupportedKeywordInfo; + SupportedKeywordInfo keywordInfo("P1" , 1, "1"); + Opm::GridProperty prop( 4 , 4 , 2 , keywordInfo); std::shared_ptr global = std::make_shared(4,4,2); std::shared_ptr layer0 = std::make_shared(*global , 0,3,0,3,0,0); @@ -182,8 +204,11 @@ BOOST_AUTO_TEST_CASE(SET) { BOOST_AUTO_TEST_CASE(ADD) { - Opm::GridProperty prop1( 4 , 4 , 2 , "P1" , 1); - Opm::GridProperty prop2( 4 , 4 , 2 , "P2" , 9); + typedef Opm::GridProperty::SupportedKeywordInfo SupportedKeywordInfo; + SupportedKeywordInfo keywordInfo1("P1" , 1, "1"); + SupportedKeywordInfo keywordInfo2("P2" , 9, "1"); + Opm::GridProperty prop1( 4 , 4 , 2 , keywordInfo1); + Opm::GridProperty prop2( 4 , 4 , 2 , keywordInfo2); std::shared_ptr global = std::make_shared(4,4,2); std::shared_ptr layer0 = std::make_shared(*global , 0,3,0,3,0,0); diff --git a/opm/parser/eclipse/IntegrationTests/BoxTest.cpp b/opm/parser/eclipse/IntegrationTests/BoxTest.cpp index bcccb0564..8660e0dfe 100644 --- a/opm/parser/eclipse/IntegrationTests/BoxTest.cpp +++ b/opm/parser/eclipse/IntegrationTests/BoxTest.cpp @@ -124,7 +124,7 @@ BOOST_AUTO_TEST_CASE( PERMX ) { for (i = 0; i < grid->getNX(); i++) { BOOST_CHECK_CLOSE( permx->iget(i,j,k) * 0.25 , permz->iget(i,j,k) , 0.001); - BOOST_CHECK_EQUAL( permx->iget(i,j,k) , permy->iget(i,j,k)); + BOOST_CHECK_EQUAL( permx->iget(i,j,k) * 2 , permy->iget(i,j,k)); } } diff --git a/testdata/integration_tests/BOX/BOXTEST1 b/testdata/integration_tests/BOX/BOXTEST1 index 9ed5a58e2..1f8c0aed9 100644 --- a/testdata/integration_tests/BOX/BOXTEST1 +++ b/testdata/integration_tests/BOX/BOXTEST1 @@ -18,13 +18,17 @@ TOPS 1000*0.25 / PERMX - 1000*100 / + 1000*1 / COPY PERMX PERMZ / PERMX PERMY / / +ADD + PERMY 1 / +/ + MULTIPLY PERMZ 0.25 / /