Skip to content

Commit

Permalink
Merge pull request #65 from DHI/feature/mikenet
Browse files Browse the repository at this point in the history
Initial implementation of the MIKE.NET module
  • Loading branch information
gedaskir authored Dec 11, 2023
2 parents 68837dc + b969dfd commit 1afb7b2
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 5 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

### Added

- mikenet module for easier work with DHI .NET libraries.

## [0.4]

### Added

- DHI.Mike1D.MikeIO C# utility and ResultReaderCopier for more performant reading of result files

### Changed
Expand Down Expand Up @@ -62,7 +68,8 @@
- Reading of res1d and xns11 files into pandas data frames


[unreleased]: https://github.com/DHI/mikeio1d/compare/v0.3...HEAD
[unreleased]: https://github.com/DHI/mikeio1d/compare/v0.4...HEAD
[0.4]: https://github.com/DHI/mikeio1d/releases/tag/v0.4
[0.3]: https://github.com/DHI/mikeio1d/releases/tag/v0.3
[0.2]: https://github.com/DHI/mikeio1d/releases/tag/v0.2
[0.1]: https://github.com/DHI/mikeio1d/releases/tag/v0.1
9 changes: 5 additions & 4 deletions mikeio1d/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import os
from platform import architecture

from .mikepath import MikePath

# PEP0440 compatible formatted version, see:
# https://www.python.org/dev/peps/pep-0440/
#
Expand All @@ -24,16 +26,15 @@
if "64" not in architecture()[0]:
raise Exception("This library has not been tested for a 32 bit system.")

mike_bin_path = os.path.join(os.path.dirname(__file__), "bin")
sys.path.append(mike_bin_path)
MikePath.setup_mike_installation(sys.path)

clr.AddReference("System")
clr.AddReference("System.Runtime")
clr.AddReference("System.Runtime.InteropServices")
clr.AddReference("DHI.Generic.MikeZero.DFS")
clr.AddReference("DHI.Generic.MikeZero.EUM")
# clr.AddReference("DHI.PFS")
# clr.AddReference("DHI.Projections")
# clr.AddReference('DHI.PFS')
# clr.AddReference('DHI.Projections')
clr.AddReference("DHI.Mike1D.Generic")
clr.AddReference("DHI.Mike1D.ResultDataAccess")
clr.AddReference("DHI.Mike1D.CrossSectionModule")
Expand Down
56 changes: 56 additions & 0 deletions mikeio1d/mikenet/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""
MIKE.NET: Module for loading MIKE software .NET libraries
=========================================================
MIKE.NET is a module which creates helper methods for loading and referencing
MIKE software .NET libraries. To use the module on an existing MIKE installation
one needs before loading MIKE IO 1D to define `MIKE_INSTALL_PATH` environment
variable which points to desired installation:
```python
>>> import os
>>> os.environ["MIKE_INSTALL_PATH"] = "C:/Program File (x86)/DHI/MIKE+/2023/"
```
If this variable is not defined the MIKE IO 1D bin folder will be used per
default. This MIKE IO 1D bin folder contains very small subset of MIKE software
libraries.
Usage example
-------------
MIKE.NET can be imported as:
```python
>>> from mikeio1d import mikenet
```
When used within Jupyter `mikenet` has a list of methods starting with `load_`
which can load the relevant MIKE software libraries. For example, we can load a
`DHI.Mike1D.Generic` library as:
```python
>>> mikenet.load_Mike1D_Generic()
```
This makes a `Mike1D_Generic` reference to the `DHI.Mike1D.Generic`, which also
can be auto-completed in Jupyter. As an example, now we can create an instance
of `Quantity` class as:
```python
>>> quantity = mikenet.Mike1D_Generic.Quantity()
```
To load all MIKE software libraries one can use:
```python
>>> mikenet.load_all()
```
"""

import sys

from .library_loaders import LibraryLoaders

mikenet_module = sys.modules[__name__]
library_loaders = LibraryLoaders(mikenet_module)
42 changes: 42 additions & 0 deletions mikeio1d/mikenet/library_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import clr
from pathlib import Path
from warnings import warn


class LibraryLoader:
"""
Helper class which assigns 'load_[LIRABRY_NAME]' to MIKE.NET module.
Parameters
----------
file_name: str
Library file_name to load. Typically this is a full path to the library (dll).
mikenet_module: module
Reference to MIKET.NET module
"""

def __init__(self, file_name, mikenet_module):
self.file_name = file_name
self.mikenet_module = mikenet_module

self.file_path = Path(file_name)
self.library_name = self.file_path.stem
self.library_alias = self.library_name.replace("DHI.", "")
self.library_alias = self.library_alias.replace(".", "_")

load_function_name = "load_" + self.library_alias

setattr(mikenet_module, load_function_name, self.load)

def load(self):
"""
A method which adds a reference of a relevant library to Python.NET (CLR)
and also adds a reference to the MIKE.NET.
"""
clr.AddReference(self.library_name)

try:
mikenet_dict = self.mikenet_module.__dict__
exec(f"import {self.library_name} as {self.library_alias}", mikenet_dict)
except Exception as e:
warn(f"Could not import .NET library {self.library_name}: {e}")
69 changes: 69 additions & 0 deletions mikeio1d/mikenet/library_loaders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import os
import sys
from glob import glob

from ..mikepath import MikePath

from .library_loader import LibraryLoader


class LibraryLoaders:
"""
Helper class which finds all MIKE software .NET libraries
and assigns references to those libraries in MIKE.NET module.
Parameters
----------
mikenet_module: module
Reference to MIKE.NET module
"""

def __init__(self, mikenet_module):
self.mikenet_module = mikenet_module
self.library_loader_list = []
self.library_loader_dict = {}

MikePath.setup_mike_installation(sys.path)
self.create_loaders()

def create_loaders(self):
"""
Creates .NET library loaders and assigns relevant `load` methods
to MIKE.NET module.
"""
mikenet_module = self.mikenet_module

for pattern in MikePath.library_patterns:
file_candidates = glob(os.path.join(MikePath.mike_bin_path, pattern))
for file_name in file_candidates:
library_loader = LibraryLoader(file_name, mikenet_module)
self.library_loader_list.append(library_loader)
self.library_loader_dict[library_loader.library_name] = library_loader
self.library_loader_dict[library_loader.library_alias] = library_loader

setattr(mikenet_module, "load_all", self.load_all)
setattr(mikenet_module, "load", self.load)

def load_all(self):
"""
Loads all libraries present in library_loader_list.
"""
for library_loader in self.library_loader_list:
library_loader.load()

def load(self, libraries=[]):
"""
Loads all libraries specified by the list `libraries`.
Parameters
----------
libraries: list of str
List of strings specifying what .NET libraries to load.
This can also be set as a string having the name of the
library to load.
"""
if isinstance(libraries, str):
libraries = [libraries]

for library in libraries:
self.library_loader_dict[library].load()
65 changes: 65 additions & 0 deletions mikeio1d/mikepath.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import os


class MikePath:
"""
Class for handling MIKE binary paths.
Attributes
----------
mikeio1d_bin_path: string
Path to binaries contained in MIKE IO 1D Python package.
mike_install_path: string
Environment variable MIKE_INSTALL_PATH specifying location of MIKE installation.
skip_bin_x64: string
Environment variable specifying not to append 'bin/x64' for binary location.
bin_x64: string
String which will be appended to mike_install_path for full location of binaries.
mike_bin_path: string
Actual path for MIKE binaries, which is either mikeio1d_bin_path or mike_install_path/bin/x64.
"""

mikeio1d_bin_path = os.path.join(os.path.dirname(__file__), "bin")

mike_install_path = os.environ.get("MIKE_INSTALL_PATH", None)

skip_bin_x64 = os.environ.get("MIKE_SKIP_BIN_X64", None)
bin_x64 = os.path.join("bin", "x64") if skip_bin_x64 is None else ""

mike_bin_path = (
os.path.join(mike_install_path, bin_x64)
if mike_install_path is not None
else mikeio1d_bin_path
)

library_patterns = ["DHI.*.dll"]

@staticmethod
def setup_mike_installation(syspath):
"""
Adds MIKE installation bin path and MIKE IO 1D bin path to the
given PATH variable.
Parameters
----------
syspath: list of str
List of strings defining PATH variable.
"""
mike_bin_path = MikePath.mike_bin_path
mikeio1d_bin_path = MikePath.mikeio1d_bin_path
mike_install_path = MikePath.mike_install_path

syspath.append(mike_bin_path)
if mikeio1d_bin_path != mike_bin_path:
syspath.append(mikeio1d_bin_path)

# Some of the MIKE libraries will need to be resolved by DHI.Mike.Install,
# so set it up here.
import clr

clr.AddReference(
"DHI.Mike.Install, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c513450b5d0bf0bf"
)
from DHI.Mike.Install import MikeImport

MikeImport.SetupInstallDir(mike_install_path)
35 changes: 35 additions & 0 deletions tests/test_mikenet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from mikeio1d import mikenet


def test_load_mike1d_generic():
mikenet.load_Mike1D_Generic()
quantity = mikenet.Mike1D_Generic.Quantity()


def test_load_projections():
mikenet.load("DHI.Projections")
map_projection = mikenet.Projections.MapProjection(
'PROJCS["UTM-33",GEOGCS["Unused",DATUM["UTM Projections",SPHEROID["WGS 1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000],PARAMETER["False_Northing",0],PARAMETER["Central_Meridian",15],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0],UNIT["Meter",1]]'
)


def test_load_all():
mikenet.load_all()
result_data = mikenet.PFS.PFSBuilder()


def test_libraries_loaded_to_clr():
# Libraries already loaded to CLR by mikeio1d.__init__.py
import System
import System.Runtime
import System.Runtime.InteropServices
import DHI.Generic.MikeZero.DFS
import DHI.Generic.MikeZero
import DHI.Mike1D.Generic
import DHI.Mike1D.ResultDataAccess
import DHI.Mike1D.CrossSectionModule
import DHI.Mike1D.MikeIO

# Libraries loaded inside test_mikenet.py
import DHI.PFS
import DHI.Projections

0 comments on commit 1afb7b2

Please sign in to comment.