cellml/libcellml

invalid reference to a local units

Closed this issue · 4 comments

libcellml==0.4.0

I encountered some units definition issues. I have attached the Python script reproducing the errors:

  • Units reference 'fmol' in units 'per_fmol' is not a valid reference to a local units or a standard unit type.
  • Units reference 'fmol' in units 'per_fmol' is not a valid reference to a local units or a standard unit type.

This could be due to misuse of the libcellml. Could you please help me to fix the issues? Thanks.

from libcellml import Component, Generator, GeneratorProfile, Model, Units,  Variable, ImportSource, Printer, Analyser,Importer
from pathlib import PurePath,Path
import os
BUILTIN_UNITS = {'ampere':Units.StandardUnit.AMPERE, 'becquerel':Units.StandardUnit.BECQUEREL, 'candela':Units.StandardUnit.CANDELA, 'coulomb':Units.StandardUnit.COULOMB, 'dimensionless':Units.StandardUnit.DIMENSIONLESS, 
                 'farad':Units.StandardUnit.FARAD, 'gram':Units.StandardUnit.GRAM, 'gray':Units.StandardUnit.GRAY, 'henry':Units.StandardUnit.HENRY, 'hertz':Units.StandardUnit.HERTZ, 'joule':Units.StandardUnit.JOULE,
                   'katal':Units.StandardUnit.KATAL, 'kelvin':Units.StandardUnit.KELVIN, 'kilogram':Units.StandardUnit.KILOGRAM, 'liter':Units.StandardUnit.LITRE, 'litre':Units.StandardUnit.LITRE, 
                   'lumen':Units.StandardUnit.LUMEN, 'lux':Units.StandardUnit.LUX, 'metre':Units.StandardUnit.METRE, 'meter':Units.StandardUnit.METRE, 'mole':Units.StandardUnit.MOLE, 'newton':Units.StandardUnit.NEWTON, 
                   'ohm':Units.StandardUnit.OHM, 'pascal':Units.StandardUnit.PASCAL, 'radian':Units.StandardUnit.RADIAN, 'second':Units.StandardUnit.SECOND, 'siemens':Units.StandardUnit.SIEMENS, 'sievert':Units.StandardUnit.SIEVERT, 
                   'steradian':Units.StandardUnit.STERADIAN, 'tesla':Units.StandardUnit.TESLA, 'volt':Units.StandardUnit.VOLT, 'watt':Units.StandardUnit.WATT, 'weber':Units.StandardUnit.WEBER}

# Write a model to cellml file, input: directory, model, output: cellml file
def writeCellML(full_path, model):   
    printer = Printer()
    serialised_model = printer.printModel(model)    
    write_file = open(full_path, "w")
    write_file.write(serialised_model)
    write_file.close()
def _dump_issues(source_method_name, logger):
    if logger.issueCount() > 0:
        print('The method "{}" found {} issues:'.format(source_method_name, logger.issueCount()))
        for i in range(0, logger.issueCount()):
            print('    - {}'.format(logger.issue(i).description()))
def analyse_model(model):
    analyser = Analyser()
    analyser.analyseModel(model)
    a = analyser.model()
    _dump_issues("analyse_model", analyser)
    return a

def resolve_imports(model, base_dir, strict_mode):
    importer = Importer(strict_mode)
    importer.resolveImports(model, base_dir)
    _dump_issues("resolve_imports", importer)
    if model.hasUnresolvedImports():
        print("unresolved imports?")
    else:
        print("no unresolved imports.")
    return importer

def writePythonCode(full_path, model,strict_mode=True):
    base_dir = PurePath(full_path).parent.as_posix()
    importer = resolve_imports(model, base_dir, strict_mode)
    flatModel = importer.flattenModel(model)
    a = analyse_model(flatModel)              
    generator = Generator()
    generator.setModel(a)
    profile = GeneratorProfile(GeneratorProfile.Profile.PYTHON)
    generator.setProfile(profile)
    implementation_code_python = generator.implementationCode()                   
    # Save the python file in the same directory as the CellML file
    with open(full_path, "w") as f:
        f.write(implementation_code_python)

def defineUnits(iunitsName,unitName, prefix, exponent, multiplier):
    iunits = Units(iunitsName) 
    if exponent != '':
        exponent = float(exponent)
    else:
        exponent = 1.0
    if multiplier != '':
        multiplier = float(multiplier)
    else:
        multiplier = 1.0   
    if prefix == '':
        prefix = 1   
    if unitName in BUILTIN_UNITS:
        iunits.addUnit(BUILTIN_UNITS[unitName], prefix, exponent, multiplier)
    else:
        iunits.addUnit(unitName,prefix, exponent, multiplier)                
    return iunits
# Define the units and write to cellml file
units_model = Model('units_def')
iunits = defineUnits('per_sec', 'second', '', '-1', '')
units_model.addUnits(iunits)
iunits = defineUnits('fmol', 'mole', 'femto', '', '')
units_model.addUnits(iunits)
iunits = defineUnits('per_fmol', 'fmol', '', '-1', '')
units_model.addUnits(iunits)
u=Units('per_sec_fmol')
u.addUnit('per_sec', '1')
u.addUnit('per_fmol', '1')
units_model.addUnits(u)
full_path = units_model.name()+'.cellml'
writeCellML('units_def.cellml', units_model)
# Define the model to use the units and write to cellml file
model= Model('model_test1')
c=Component(model.name())
model.addComponent(c)
var1=Variable('var1')
var1.setUnits('per_sec_fmol')
c.addVariable(var1)
var2=Variable('var2')
var2.setUnits('per_sec')
c.addVariable(var2)
var3=Variable('var3')
var3.setUnits('per_fmol')
c.addVariable(var3)
model_path=Path.cwd()
units_to_import=['per_sec_fmol', 'per_sec', 'per_fmol']
model_path = PurePath(full_path).parent
relative_path_os = os.path.relpath(full_path, model_path)
relative_path=PurePath(relative_path_os).as_posix()
importSource = ImportSource()
importSource.setUrl(relative_path)
importSource.setModel(units_model)

for unit in units_to_import:
    u = Units(unit) 
    u.setImportSource(importSource)
    u.setImportReference(unit)
    model.addUnits(u)

model_full_path = model.name()+'.cellml'
writeCellML(model_full_path, model)
py_full_path = model.name()+'.py'
writePythonCode(py_full_path, model)
hsorby commented

When I run this with the latest development codebase I get the following:

no unresolved imports.
The method "analyse_model" found 3 issues:
    - Variable 'var1' in component 'model_test1' is unused.
    - Variable 'var2' in component 'model_test1' is unused.
    - Variable 'var3' in component 'model_test1' is unused.

What does your output look like?

hsorby commented

I think recent changes not currently available in a release will fix this issue.

I got the following:

no unresolved imports.
The method "analyse_model" found 2 issues:
    - Units reference 'fmol' in units 'per_fmol' is not a valid reference to a local units or a standard unit type.
    - Units reference 'fmol' in units 'per_fmol' is not a valid reference to a local units or a standard unit type.

I have cloned the libcellml repository and built the library on my PC. Hugh pointed out I used prefix by mistake so I fixed the prefix error and modified the Python code. Using the new built library, the invalid reference issue remains.
The following Python code can reproduce the issues and the output would be:

no unresolved imports.
The method "analyse_model" found 2 issues:
    - Units reference 'fmol' in units 'per_fmol' is not a valid reference to a local units or a standard unit type.
    - Units reference 'fmol' in units 'per_fmol' is not a valid reference to a local units or a standard unit type.
from libcellml import Component, Generator, GeneratorProfile, Model, Units,  Variable, ImportSource, Printer, Analyser,Importer
from pathlib import PurePath,Path
import os
BUILTIN_UNITS = {'ampere':Units.StandardUnit.AMPERE, 'becquerel':Units.StandardUnit.BECQUEREL, 'candela':Units.StandardUnit.CANDELA, 'coulomb':Units.StandardUnit.COULOMB, 'dimensionless':Units.StandardUnit.DIMENSIONLESS, 
                 'farad':Units.StandardUnit.FARAD, 'gram':Units.StandardUnit.GRAM, 'gray':Units.StandardUnit.GRAY, 'henry':Units.StandardUnit.HENRY, 'hertz':Units.StandardUnit.HERTZ, 'joule':Units.StandardUnit.JOULE,
                   'katal':Units.StandardUnit.KATAL, 'kelvin':Units.StandardUnit.KELVIN, 'kilogram':Units.StandardUnit.KILOGRAM, 'liter':Units.StandardUnit.LITRE, 'litre':Units.StandardUnit.LITRE, 
                   'lumen':Units.StandardUnit.LUMEN, 'lux':Units.StandardUnit.LUX, 'metre':Units.StandardUnit.METRE, 'meter':Units.StandardUnit.METRE, 'mole':Units.StandardUnit.MOLE, 'newton':Units.StandardUnit.NEWTON, 
                   'ohm':Units.StandardUnit.OHM, 'pascal':Units.StandardUnit.PASCAL, 'radian':Units.StandardUnit.RADIAN, 'second':Units.StandardUnit.SECOND, 'siemens':Units.StandardUnit.SIEMENS, 'sievert':Units.StandardUnit.SIEVERT, 
                   'steradian':Units.StandardUnit.STERADIAN, 'tesla':Units.StandardUnit.TESLA, 'volt':Units.StandardUnit.VOLT, 'watt':Units.StandardUnit.WATT, 'weber':Units.StandardUnit.WEBER}

# Write a model to cellml file, input: directory, model, output: cellml file
def writeCellML(full_path, model):   
    printer = Printer()
    serialised_model = printer.printModel(model)    
    write_file = open(full_path, "w")
    write_file.write(serialised_model)
    write_file.close()
def _dump_issues(source_method_name, logger):
    if logger.issueCount() > 0:
        print('The method "{}" found {} issues:'.format(source_method_name, logger.issueCount()))
        for i in range(0, logger.issueCount()):
            print('    - {}'.format(logger.issue(i).description()))
def analyse_model(model):
    analyser = Analyser()
    analyser.analyseModel(model)
    a = analyser.model()
    _dump_issues("analyse_model", analyser)
    return a

def resolve_imports(model, base_dir, strict_mode):
    importer = Importer(strict_mode)
    importer.resolveImports(model, base_dir)
    _dump_issues("resolve_imports", importer)
    if model.hasUnresolvedImports():
        print("unresolved imports?")
    else:
        print("no unresolved imports.")
    return importer

def writePythonCode(full_path, model,strict_mode=True):
    base_dir = PurePath(full_path).parent.as_posix()
    importer = resolve_imports(model, base_dir, strict_mode)
    flatModel = importer.flattenModel(model)
    a = analyse_model(flatModel)              
    generator = Generator()
    generator.setModel(a)
    profile = GeneratorProfile(GeneratorProfile.Profile.PYTHON)
    generator.setProfile(profile)
    implementation_code_python = generator.implementationCode()                   
    # Save the python file in the same directory as the CellML file
    with open(full_path, "w") as f:
        f.write(implementation_code_python)

def defineUnits(iunitsName,unitName, prefix, exponent, multiplier):
    iunits = Units(iunitsName) 
    if exponent != '':
        exponent = float(exponent)
    else:
        exponent = 1.0
    if multiplier != '':
        multiplier = float(multiplier)
    else:
        multiplier = 1.0   
    if prefix == '':
        prefix = 0   
    if unitName in BUILTIN_UNITS:
        iunits.addUnit(BUILTIN_UNITS[unitName], prefix, exponent, multiplier)
    else:
        iunits.addUnit(unitName,prefix, exponent, multiplier)                
    return iunits
# Define the units and write to cellml file
units_model = Model('units_def')
iunits = defineUnits('per_sec', 'second', '', '-1', '')
units_model.addUnits(iunits)
iunits = defineUnits('fmol', 'mole', 'femto', '', '')
units_model.addUnits(iunits)
iunits = defineUnits('per_fmol', 'fmol', '', '-1', '')
units_model.addUnits(iunits)
u=Units('per_sec_fmol')
u.addUnit('per_sec', '0')
u.addUnit('per_fmol', '0')
units_model.addUnits(u)
full_path = units_model.name()+'.cellml'
writeCellML('units_def.cellml', units_model)
# Define the model to use the units and write to cellml file
model= Model('model_test1')
c=Component(model.name())
model.addComponent(c)
var1=Variable('var1')
var1.setUnits('per_sec_fmol')
var1.setInitialValue('1.0')
c.addVariable(var1)

model_path=Path.cwd()
units_to_import=['per_sec_fmol']
model_path = PurePath(full_path).parent
relative_path_os = os.path.relpath(full_path, model_path)
relative_path=PurePath(relative_path_os).as_posix()
importSource = ImportSource()
importSource.setUrl(relative_path)
importSource.setModel(units_model)

for unit in units_to_import:
    u = Units(unit) 
    u.setImportSource(importSource)
    u.setImportReference(unit)
    model.addUnits(u)
    
cellmlfile_name=model.name()+'.cellml'
model_full_path = str(PurePath(model_path).joinpath(cellmlfile_name))
writeCellML(model_full_path, model)
python_file_name = model.name() + '.py'
python_file_path = str(PurePath(model_path).joinpath(python_file_name))
writePythonCode(python_file_path, model)