LinearKnob.java
// @formatter:off
/*******************************************************************************
*
* This file is part of JMad.
*
* Copyright (c) 2008-2011, CERN. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
// @formatter:on
/*
* $Id $
*
* $Date$ $Revision$ $Author$
*
* Copyright CERN ${year}, All Rights Reserved.
*/
package cern.accsoft.steering.jmad.model.knob.custom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cern.accsoft.steering.jmad.domain.ex.JMadModelException;
import cern.accsoft.steering.jmad.domain.knob.KnobType;
import cern.accsoft.steering.jmad.domain.knob.strength.Strength;
import cern.accsoft.steering.jmad.model.JMadModel;
import cern.accsoft.steering.jmad.model.knob.AbstractMultiModelKnob;
import cern.accsoft.steering.jmad.model.knob.StatefulKnob;
/**
* @author ${user}
* @version $Revision$, $Date$, $Author$
*/
public class LinearKnob extends AbstractMultiModelKnob implements StatefulKnob {
/** the logger of the class */
private static final Logger LOGGER = LoggerFactory.getLogger(LinearKnob.class);
/** defines one factor for each strength */
private Map<String, Double> strengthFactors = new HashMap<String, Double>();
/** the actual value of the knob */
private double actualValue = 0.0;
/** the name of this knob */
private String name;
public LinearKnob(String name) {
this.name = name;
}
@Override
public String getDescription() {
return "Knob with linear factors, setting only increments.";
}
@Override
protected synchronized void doSetTotalValue(double value) {
double oldKnobValue = getTotalValue();
double knobDeltaValue = value - oldKnobValue;
/* avoid unnecessary read/write operations to the model */
if (knobDeltaValue == 0.0) {
return;
}
/* we loop over all connected Models */
for (JMadModel model : super.getConnectedModels()) {
this.writeDeltaValueToModel(model, knobDeltaValue);
}
this.actualValue = value;
}
@Override
public void writeCurrentStateToModel(JMadModel model) {
/* this approach assumes that a optics load overwrites all defined strengths!! */
this.writeDeltaValueToModel(model, getTotalValue());
}
/**
* Write a delta knob value to a {@link JMadModel}. For each strength (str) used by the knob, the model will have an
* updated value:
* <p>
* <code>str = old_str + delta_knob * str_factor</code>
*
* @param model the {@link JMadModel} to write the delta value to
* @param knobDeltaValue the amount of delta to write
*/
private synchronized void writeDeltaValueToModel(JMadModel model, double knobDeltaValue) {
for (Entry<String, Double> strengthEntry : this.strengthFactors.entrySet()) {
/*
* we only do incremental change of the parameters. This is necessary to allow several knobs acting on the
* same strengths.
*/
String strengthName = strengthEntry.getKey();
double factor = strengthEntry.getValue();
try {
double oldStrengthValue = getStrengthValue(model, strengthName);
double newStrengthValue = (oldStrengthValue + knobDeltaValue * factor);
setStrengthValue(model, strengthName, newStrengthValue);
} catch (JMadModelException e) {
LOGGER.error("Could not set strength [" + strengthName + "] in model [" + model + "]", e);
}
}
}
/**
* tries to find the strength of the given name in the model and retrieve the value from it. If the strength is not
* available (maybe not parsed, since not in a strength file) it tries to get it directly from the model.
*
* @param model the {@link JMadModel} to get the strength from
* @param strengthName the name of the strength.
* @return the value
* @throws JMadModelException if the retrieval of the value fails
*/
private double getStrengthValue(JMadModel model, String strengthName) throws JMadModelException {
Strength strength = getStrength(model, strengthName);
if (strength != null) {
return strength.getValue();
} else {
return model.getValue(strengthName);
}
}
/**
* Sets the value to the strength of the given name, if the strength exists. This is preferable, because this way
* the strengths stay consistent. If it does not exist, then it calls directly the setValue method of the model.
*
* @param model the {@link JMadModel} to set the strength in
* @param value the value to set for the strength
* @throws JMadModelException if the setting of the value fails
*/
private void setStrengthValue(JMadModel model, String strengthName, double value) throws JMadModelException {
double rdValue = roundToDecimalPlaces(value, MAX_DEC_PLACES);
Strength strength = getStrength(model, strengthName);
if (strength != null) {
strength.setValue(rdValue);
} else {
model.setValue(strengthName, rdValue);
}
}
/**
* searches the strength in the model
*
* @param model the {@link JMadModel} to search the strength in
* @param strengthName the name of the strength
* @return the strength or <code>null</code> if the named strength does not exist in the model
*/
private Strength getStrength(JMadModel model, String strengthName) {
return model.getStrengthsAndVars().getStrength(strengthName);
}
@Override
public String getKey() {
return this.name;
}
@Override
public String getName() {
return this.name;
}
@Override
public double getTotalValue() {
return this.actualValue;
}
/**
* adds a factor for a strength
*
* @param strengthName the name of the strength
* @param factor the scaling factor for this strength
*/
public void addStrengthFactor(String strengthName, double factor) {
this.strengthFactors.put(strengthName, Double.valueOf(factor));
}
/**
* update the knob definition to the given strength name value mapping. Strengths that are not contained in the
* mapping are 'trimmed' back to zero. And new strengths are added to the knob. After this call, the knob is defined
* with exactly the given strength-factor combinations and the strengths involved are trimmed to the value resulting
* from the current knob value.
*
* @param strengthFactorMapping the mapping between strength name and factor which defines the knob.
*/
protected synchronized void updateAllStrengthFactor(Map<String, Double> strengthFactorMapping) {
List<String> processStrengths = new ArrayList<String>();
for (Entry<String, Double> entry : strengthFactorMapping.entrySet()) {
if (!this.strengthFactors.containsKey(entry.getKey())) {
this.strengthFactors.put(entry.getKey(), 0.0);
LOGGER.warn("Added factor [" + entry.getKey() + " = " + entry.getValue() + "] during update of knob ["
+ this.getName() + "]");
}
/* update the strength factor to the model(s) */
this.updateStrengthFactor(entry.getKey(), entry.getValue().doubleValue());
processStrengths.add(entry.getKey());
}
if (processStrengths.size() != this.strengthFactors.size()) {
for (String strengthName : new ArrayList<String>(this.strengthFactors.keySet())) {
if (!processStrengths.contains(strengthName)) {
double value = this.strengthFactors.get(strengthName);
this.removeStrengthFactor(strengthName);
LOGGER.warn("Removed factor [" + strengthName + " = " + value + "] during update of knob ["
+ this.getName() + "]");
}
}
}
}
/**
* Remove the factor for the strength with the given name from the linear knob. This call will ensure, that the
* impact of the strength factor is removed from the underlying models. After this call the strength is not longer
* available in the linear knob.
*
* @param strengthName the name of the strength to remove from the linear knob
*/
protected void removeStrengthFactor(String strengthName) {
if (!this.strengthFactors.containsKey(strengthName)) {
throw new IllegalArgumentException("Knob does not contain a factor for strength [" + strengthName + "]");
}
try {
double oldFactor = this.strengthFactors.get(strengthName).doubleValue();
double totalValue = getTotalValue();
for (JMadModel model : super.getConnectedModels()) {
double actStrength = this.getStrengthValue(model, strengthName);
/* remove the impact of the old strength factor from the strength */
double newStrength = actStrength - (oldFactor * totalValue);
this.setStrengthValue(model, strengthName, newStrength);
}
this.strengthFactors.remove(strengthName);
} catch (JMadModelException e) {
LOGGER.error("Could not remove strength factor for [" + strengthName + "] from knob [" + getName() + "]", e);
}
}
/**
* update one of the knobs strength factors. During update the old strength factors impact on the model is reset and
* the new factors resulting value written to the model. If the old factor and the new factor are equal, nothing is
* done.
*
* @param strengthName the name of the the strength whose factor to update
* @param factor the new value of the scaling factor for this strength
*/
protected synchronized void updateStrengthFactor(String strengthName, double factor) {
if (!this.strengthFactors.containsKey(strengthName)) {
throw new IllegalArgumentException("Knob does not contain a factor for strength [" + strengthName + "]");
}
try {
double oldFactor = this.strengthFactors.get(strengthName).doubleValue();
if (oldFactor == factor) {
/* nothing to do here */
return;
}
double totalValue = getTotalValue();
for (JMadModel model : super.getConnectedModels()) {
double actStrength = this.getStrengthValue(model, strengthName);
/* remove the impact of the old strength factor from the strength */
/* and add the contribution of the new factor to the strength */
double newStrength = actStrength + ((factor - oldFactor) * totalValue);
this.setStrengthValue(model, strengthName, newStrength);
}
/* overwrite the current factor in the factor map */
this.addStrengthFactor(strengthName, factor);
} catch (JMadModelException e) {
LOGGER.error("Could not update strength factor for [" + strengthName + "] in Knob [" + getName() + "]", e);
}
}
@Override
public KnobType getType() {
return KnobType.CUSTOM;
}
public static final int MAX_DEC_PLACES = 14;
public static final double roundToDecimalPlaces(double value, int decPlaces) {
return Math.round(value * Math.pow(10, decPlaces)) / Math.pow(10, decPlaces);
}
}